Detecting whether an Android app is stopping (or starting)

I am writing an Android app (called Rabbit Escape), and I want it to start playing music when the user enters the app, and stop when the user leaves.

Not as easy as it sounds because Android largely doesn’t think in apps, but Activities.

Update: real-life code for this is here: Code for detecting when you leave an Android app.

In my previous post Order of Android Activity lifecycle events I tracked the methods that get called on activities on my two test devices when the user does various actions. There was some variation between devices, but we can determine some useful rules.

First, let’s enumerate what we need to detect.

The user leaves by:

  • pressing “home” at any time
  • pressing the power button when the app is running
  • pressing “back” when in the first activity
  • launching an external activity (e.g. web browser)

The user enters by:

  • starting the app (e.g. from the home page, or the “running apps” panel)
  • pressing the power button when the app has been suspended
  • pressing “back” when in an external activity launched by us

From the previous post we can work out these rules:

  • When the user is entering, onResume is always called (on the activity that will be current)
  • When the user is leaving, onStop is always called (on the current activity), except when pressing the power button (to turn off) where you may only see an onPause call.
  • When the user is moving between activities, onResume is always called on the new activity, and onStop is always called on the old one. The onResume call is always before onStop

Thus, if we are OK with leaving ourselves running when the power button is pressed, we can detect leaving and entering the app using the code below.

If we need to stop something when the power button is pressed too, it seems we must be prepared to stop it when we receive onPause, and immediately restart it if we receive onResume, hoping that doesn’t cause a problem (e.g. a break in the music).

// All activities have these two methods:
@Override
public void onResume()
{
    super.onResume();
    LeavingOrEntering.activityResumed( this );
}

@Override
public void onStop()
{
    super.onStop();
    LeavingOrEntering.activityStopped( this );
}

// And we detect leaving and entering like this:
class LeavingOrEntering
{
    private static Activity currentActivity = null;

    public static void activityResumed( Activity activity )
    {
        if ( currentActivity == null )
        {
            // We were resumed and no-one else was running.
            notifyEntering() // Start the music!
        }
        currentActivity = activity;
    }

    public static void activityStopped( Activity activity )
    {
        assert currentActivity != null;

        if ( currentActivity == activity )  
        {
            // We were stopped and no-one else has been started.
            notifyLeaving(); // Stop the music!
        }
        currentActivity == null;
    }
}

Order of Android Activity lifecycle events

I noticed some variation between devices so I tested various user actions on various devices and recorded the Android Activity lifecycle methods (e.g. onResume, onPause, onCreate, onStop) that got called. My results are below.

[Why? I want to detect whether the user is leaving the app or just transitioning from one activity to another. See my next blog posts Detecting whether an Android app is stopping (or starting) and Code for detecting when you leave an Android app for how to do it.]

Press the home button

  • Ensure the app process is stopped.
  • Start the app. It starts an Activity.
  • Start recording events.
  • Press “home” on the device.
HTC Wildfire S
Android 2.3.5 (API 10)
onSaveInstanceState
onPause
onStop
  
Nexus One Emulator (x86)
Android L (API 20)
onPause
onSaveInstanceState
onStop
  

When home was pressed, onPause and onStop were called in that order, and onSaveInstanceState was always called before onStop. The app was still running after the test.

Note that onSaveInstanceState can happen either before or after onPause.

This is consistent with the information given on the Android Activity API docs:

Starting with Honeycomb, an application is not in the killable state until its onStop() has returned. This impacts when onSaveInstanceState(Bundle) may be called (it may be safely called after onPause() and allows and application to safely wait until onStop() to save persistent state.

Press the home button in a second activity

  • Ensure the app process is stopped.
  • Start the app. It starts Activity1.
  • Press a button in the activity that launches Activity2.
  • Start recording events.
  • Press “home” on the device.

Results are identical to “Press the home button” above, with Activity2 receiving all the calls, and Activity1 not receiving any.

Press the back button to exit

  • Ensure the app process is stopped.
  • Start the app. It starts an Activity.
  • Start recording events.
  • Press “back” on the device.
HTC Wildfire S
Android 2.3.5 (API 10)
onPause
onStop
onDestroy
  
Nexus One Emulator (x86)
Android L (API 20)
(same as above)

When back was pressed, onPause, onStop and onDestroy were called. The app process was still running after the test.

onSaveInstanceState was not called, because there is no current state to resume – when you re-enter you will be back at the beginning.

Start the app

  • Ensure the app process is stopped.
  • Start recording events.
  • Start the app.
HTC Wildfire S
Android 2.3.5 (API 10)
onCreate
onStart
onResume
  
Nexus One Emulator (x86)
Android L (API 20)
(same as above)

When the app was started, onCreate, onStart and onResume were called in that order.

Turn off with the power button

  • Start the app.
  • Start recording events.
  • Press the power button (turn off).
HTC Wildfire S
Android 2.3.5 (API 10)
onSaveInstanceState
onPause
  
Nexus One Emulator (x86)
Android L (API 20)
onPause
onSaveInstanceState
onStop

When the phone was suspended, onSaveInstanceState and onPause were called in different orders, and onStop was called in some cases.

Turn on with the power button

  • Start the app.
  • Press the power button (turn off).
  • Start recording events.
  • Press the power button (turn on).
  • Unlock the device.
HTC Wildfire S
Android 2.3.5 (API 10)
onResume
  
Nexus One Emulator (x86)
Android L (API 20)
onRestart
onStart
onResume
  

When the phone was resumed, onResume was always called, and onRestart and onStart were sometimes called.

Restart the app after pressing home

  • Ensure the app process is stopped.
  • Start the app.
  • Press “home” on the device.
  • Start recording events.
  • Start the app.
HTC Wildfire S
Android 2.3.5 (API 10)
onRestart
onStart
onResume
  
Nexus One Emulator (x86)
Android L (API 20)
(same as above)

When the app was restarted after pressing home, onCreate was not called, but onRestart, onStart and onResume were (in that order).

Restart the app after pressing back

  • Ensure the app process is stopped.
  • Start the app.
  • Press “back” on the device.
  • Start recording events.
  • Start the app.
HTC Wildfire S
Android 2.3.5 (API 10)
onCreate
onStart
onResume
  
Nexus One Emulator (x86)
Android L (API 20)
(same as above)

When the app was restarted after pressing back, onRestart was not called, but onCreate, onStart and onResume were (in that order).

This is consistent with the idea that pressing back effectively ends the app, but pressing home effectively pauses it.

Launch a second activity

  • Ensure the app process is stopped.
  • Start the app. It starts Activity1.
  • Start recording events.
  • Press a button in the activity that launches Activity2.
HTC Wildfire S
Android 2.3.5 (API 10)
Activity1.onSaveInstanceState
Activity1.onPause
Activity2.onCreate
Activity2.onStart
Activity2.onResume
Activity1.onStop
  
Nexus One Emulator (x86)
Android L (API 20)
Activity1.onPause
Activity2.onCreate
Activity2.onStart
Activity2.onResume
Activity1.onSaveInstanceState
Activity1.onStop
  

The later version of Android called onSaveInstanceState much later in the process. This is consistent with the documentation for the Activity Lifecycle.

In both cases Activity1’s onPause was called before any methods were called on Activity2.

In both cases Activity1’s onStop was called after all methods were called on Activity2.

Activity2’s methods were called just as if it were the starting activity of a freshly-launched app (see “Start the app” above).

Activity1’s methods were called just as if the home button had been pressed.

Launch an external activity

  • Ensure the app process is stopped.
  • Start the app. It starts an Activity.
  • Start recording events (in our activity only).
  • Press a button in the activity that launches the Android web browser.

Results are identical to “Press the home button” above.

Go back from one activity to another

  • Ensure the app process is stopped.
  • Start the app. It starts Activity1.
  • Press a button in the activity that launches Activity2.
  • Start recording events.
  • Press “back” on the device.
HTC Wildfire S
Android 2.3.5 (API 10)
Activity2.onPause
Activity1.onRestart
Activity1.onStart
Activity1.onResume
Activity2.onStop
Activity2.onDestroy
  
Nexus One Emulator (x86)
Android L (API 20)
(same as above)

Activity2 sees the same calls as in “Press the back button to exit” (i.e. it is destroyed).

Activity1 see the same calls as in “Restart the app after pressing home” (i.e. it is resumed).

Go back from an external activity to ours

  • Ensure the app process is stopped.
  • Start the app. It starts an Activity.
  • Press a button in the activity that launches the Android web browser.
  • Start recording events (in our activity only).
  • Press “back” on the device.

Results are identical to “Restart the app after pressing home” above.

See my next blog post for how to detect whether we are leaving the app, or just transitioning between activities: Detecting whether an Android app is stopping (or starting).

Snake in Dart

Series: Groovy, Ruby, BASIC, Dart, Elm, Python3+Qt5

I’m writing the game Snake in lots of programming languages, for fun, and to try out new languages. This time, Dart, which is for people who love Java and wish they didn’t have to do JavaScript.

Slides: Snake in Dart

If you want to, you can Support me on Patreon.

Preventing Kupfer switching to existing windows

I am enjoying using Kupfer but I don’t like the way it switches to an existing window of an application, instead of launching a new instance.

I should write a patch for a config option or similar, but for now, here are my notes on how I disabled the behaviour in the source code:

sudo sensible-editor /usr/share/kupfer/kupfer/launch.py

I just commented out 3 lines inside the launch_application method (lines 70-72 for me):

#       if activate and svc.application_is_running(app_id):
#               svc.application_to_front(app_id)
#               return True

This prevents Kupfer looking for open windows before launching.

Using Kupfer on MATE

I am trying out MATE desktop and really liking it.

I like to use a (GNOME-Do style) keyboard-driven application launcher, and have found Kupfer really good.

Kupfer as packaged for Ubuntu MATE does not support MATE desktop, so the file manager (“Caja”) does not show up by default, along with other things like the preferences dialogs, and there is no support for actions like Log Out or Shut Down.

To get MATE applications to show up in Kupfer, all I had to do was make up a config value that is not presented in the preferences, by editing the config file:

pluma ~/.config/kupfer/kupfer.cfg

I found the line that started “desktop_type = ” and changed it to say:

desktop_type = MATE

Update [Thanks to Kristian Nygaard Jensen and Igor Santos]: if you can’t find that line in the file, add this at the bottom of the file:

[plugin_applications]
desktop_type = MATE

To get MATE session commands in Kupfer, I copied the GNOME session management plugin and made a similar one for MATE.

cd /usr/share/kupfer/kupfer/plugin
sudo cp session_gnome.py session_mate.py
sudo pluma session_mate.py

I just changed the 3 lines after “# sequences of argument lists”, and made them look like this:

LOGOUT_CMD = (["mate-session-save", "--logout-dialog"],)
SHUTDOWN_CMD = (["mate-session-save", "--shutdown-dialog"],)
LOCKSCREEN_CMD = (["mate-screensaver-command", "--lock"],)

When I restarted Kupfer I was able to open MATE applications and control the MATE session using Kupfer.

There is an open pull request from 2013 for Kupfer to add MATE session management: github.com/engla/kupfer/pull/15 – thanks to its author labero, since this showed me what I needed to do for that part.