Tags

, , , , , , , ,

There are times when we want to know if our entire application went to background or not, say, to switch off GPS listeners. Android doesn’t support an Application level onApplicationPause() and onApplicationResume(). The Application.ActivityLifecycleCallbacks are available only starting SDK 14. How can we achieve this in previous versions?

An application can go to background when an activity goes to background and the new foreground activity doesn’t belong to the application. To trap this, we need to have all our activities to be subclasses of our own Activity, say SmurfActivity. Android provides an option to extend Application too, to have global code if needed. Here is how our SmurfActivity will look like:

public class SmurfActivity extends Activity {
    private boolean hasStarted = false;
    private boolean mSmurfActivityOpened = false;

    @Override
    public void onPause() {
        super.onPause();

        // Avoid pause notifications in destroy path.
        if (!isFinishing() && (getApplication() instanceof SmurfApplication))
            ((SmurfApplication) getApplication()).onActivityPause(this);
    }

    @Override
    public void onResume() {
        super.onResume();

        // Avoid resume notifications in startup path.
        if (hasStarted && (getApplication() instanceof SmurfApplication)) {
            ((SmurfApplication) getApplication()).onActivityResume(this);
            mSmurfActivityOpened = false;
        } else {
            hasStarted = true;
        }
    }

    @Override
    public void startActivity(Intent intent) {
        checkIfSmurfActivity(intent);
        super.startActivity(intent);
    }

    @Override
    public void startActivityForResult(Intent intent, int request) {
        checkIfSmurfActivity(intent);
        super.startActivityForResult(intent, request);
    }

    private void checkIfSmurfActivity(Intent intent) {
        // Whenever we call our own activity, the component and it's package name is set.
        // If we call an activity from another package, or an open intent (leaving android to resolve)
        // component has a different package name or it is null.
        ComponentName component = intent.getComponent();
        mSmurfActivityOpened = false;
        if (component != null &&
            component.getPackageName() != null &&
            component.getPackageName().equals(_appliction's_package_name_)) {
            mSmurfActivityOpened = true;
        }
    }

    public boolean isSmurfActivityOpened() {
        return mSmurfActivityOpened;
    }

    public boolean isApplicationInBackground() {
        return ((SmurfApplication) getApplication()).isApplicationInBackground();
    } 
}

When a new activity is opened using startActivity() or startActivityForResult(), we check if the new activity is our own activity. If not, we make a note of it. Android triggers onPause() on old activity when a new activity is opened, or home button is pressed. At this point, we know if the new activity is ours, and we use this to inform our Application to inform its listeners about pausing. When the activity resumes, we inform our Application to inform its listeners about resuming.

And the SmurfApplication looks like:

public class SmurfApplication extends Application {

    private boolean mInBackground = false;
    private ArrayList<ApplicationLifecycleCallbacks> mListeners;

    // Anyone listening for Application level pause resume should implement this.
    public interface ApplicationLifecycleCallbacks {
        public void onApplicationPause();
        public void onApplicationResume();
    }

    public void addApplicationLifecycleCallbacks(ApplicationLifecycleCallbacks callback) {
        if (mListeners == null)
            mListeners = new ArrayList<ApplicationLifecycleCallbacks>();

        mListeners.add(callback);
    }

    public void removeApplicationLifecycleCallbacks(ApplicationLifecycleCallbacks callback) {
        if (mListeners == null)
            return;

        mListeners.remove(callback);
    }

    // When any activity pauses with the new activity not being ours.
    public void onActivityPause(GeckoActivity activity) {
        if (activity.isSmurfActivityOpened() || mListeners == null)
            return;

        mInBackground = true;

        for (ApplicationLifecycleCallbacks listener: mListeners)
            listener.onApplicationPause();
    }

    // When any activity resumes with a previous activity not being ours.
    public void onActivityResume(GeckoActivity activity) {
        // This is a misnomer. Should have been "wasGeckoActivityOpened".
        if (activity.isSmurfActivityOpened() || mListeners == null)
            return;

        for (ApplicationLifecycleCallbacks listener: mListeners)
            listener.onApplicationResume();

        mInBackground = false;
    }

    // Helper to find if the Application is in background from any activity.
    public boolean isApplicationInBackground() {
        return mInBackground;
    }
}

And the way to inform Android to use this SmurfApplication instead of its standard application is:

<!-- In AndroidManifest.xml -->
<application android:label="Application Name"
             android:name="com.companyname.projectname.SmurfApplication">
...
</application>
Advertisements