Bug 739747: Add application level onPause and onResume. [r=mfinkle, r=blassey]
authorSriram Ramasubramanian <sriram@mozilla.com>
Tue, 03 Apr 2012 11:58:01 -0700
changeset 94233 5c941f58dd5e64acb58344e6231908ccccb940f8
parent 94232 d0aa1570eb2119583a84e9b016c72cc8e3333bee
child 94234 ccb232b6b97ad2efb99951154ad1ecc2e960dcd0
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle, blassey
bugs739747
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 739747: Add application level onPause and onResume. [r=mfinkle, r=blassey]
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/AwesomeBar.java
mobile/android/base/GeckoActivity.java.in
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoApplication.java
mobile/android/base/GeckoEvent.java
mobile/android/base/Makefile.in
mobile/android/base/RemoteTabs.java
mobile/android/base/TabsTray.java
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -44,16 +44,17 @@
     <uses-feature android:name="android.hardware.camera" android:required="false"/>
     <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
 
     <!-- App requires OpenGL ES 2.0 -->
     <uses-feature android:glEsVersion="0x00020000" android:required="true" />
  
     <application android:label="@MOZ_APP_DISPLAYNAME@"
 		 android:icon="@drawable/icon"
+                 android:name="org.mozilla.gecko.GeckoApplication"
 #if MOZILLA_OFFICIAL
 		 android:debuggable="false">
 #else
 		 android:debuggable="true">
 #endif
 
         <activity android:name="App"
                   android:label="@MOZ_APP_DISPLAYNAME@"
--- a/mobile/android/base/AwesomeBar.java
+++ b/mobile/android/base/AwesomeBar.java
@@ -81,17 +81,17 @@ import java.net.URLEncoder;
 import java.util.Map;
 
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
 import org.mozilla.gecko.db.BrowserDB;
 
 import org.json.JSONObject;
 
-public class AwesomeBar extends Activity implements GeckoEventListener {
+public class AwesomeBar extends GeckoActivity implements GeckoEventListener {
     private static final String LOGTAG = "GeckoAwesomeBar";
 
     static final String URL_KEY = "url";
     static final String CURRENT_URL_KEY = "currenturl";
     static final String TYPE_KEY = "type";
     static final String SEARCH_KEY = "search";
     static final String USER_ENTERED_KEY = "user_entered";
     static enum Type { ADD, EDIT };
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/GeckoActivity.java.in
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#filter substitution
+package org.mozilla.gecko;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+import android.content.Intent;
+import android.content.ComponentName;
+
+public class GeckoActivity extends Activity {
+    private boolean hasStarted = false;
+    private boolean isGeckoActivityOpened = false;
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        // Avoid pause notifications in destroy path.
+        if (!isFinishing() && (getApplication() instanceof GeckoApplication))
+            ((GeckoApplication) getApplication()).onActivityPause(this);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        // Avoid resume notifications in startup path.
+        if (hasStarted && (getApplication() instanceof GeckoApplication)) {
+            ((GeckoApplication) getApplication()).onActivityResume(this);
+            isGeckoActivityOpened = false;
+        } else {
+            hasStarted = true;
+        }
+    }
+
+    @Override
+    public void startActivity(Intent intent) {
+        checkIfGeckoActivity(intent);
+        super.startActivity(intent);
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int request) {
+        checkIfGeckoActivity(intent);
+        super.startActivityForResult(intent, request);
+    }
+
+    private void checkIfGeckoActivity(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();
+        if (component == null)
+            isGeckoActivityOpened = false;
+
+        if (component.getPackageName().equals("@ANDROID_PACKAGE_NAME@"))
+            isGeckoActivityOpened = true;
+        else
+            isGeckoActivityOpened = false;
+    }
+
+    public boolean isApplicationInBackground() {
+        return !isGeckoActivityOpened;
+    } 
+}
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -88,18 +88,19 @@ import android.net.*;
 import android.database.*;
 import android.database.sqlite.*;
 import android.provider.*;
 import android.content.pm.*;
 import android.content.pm.PackageManager.*;
 import dalvik.system.*;
 
 abstract public class GeckoApp
-    extends Activity implements GeckoEventListener, SensorEventListener, LocationListener
-{
+                extends GeckoActivity 
+                implements GeckoEventListener, SensorEventListener, LocationListener,
+                           GeckoApplication.ApplicationLifecycleCallbacks {
     private static final String LOGTAG = "GeckoApp";
 
     public static enum StartupMode {
         NORMAL,
         NEW_VERSION,
         NEW_PROFILE
     }
 
@@ -136,17 +137,16 @@ abstract public class GeckoApp
     public Favicons mFavicons;
 
     private static LayerController mLayerController;
     private static GeckoLayerClient mLayerClient;
     private AboutHomeContent mAboutHomeContent;
     private static AbsoluteLayout mPluginContainer;
 
     public String mLastTitle;
-    private int mOwnActivityDepth = 0;
     private boolean mRestoreSession = false;
     private boolean mInitialized = false;
 
     private static final String HANDLER_MSG_TYPE = "type";
     private static final int HANDLER_MSG_TYPE_INITIALIZE = 1;
 
     static class ExtraMenuItem implements MenuItem.OnMenuItemClickListener {
         String label;
@@ -525,17 +525,17 @@ abstract public class GeckoApp
                 return true;
             default:
                 return super.onOptionsItemSelected(item);
         }
     }
 
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        if (mOwnActivityDepth > 0)
+        if (!isApplicationInBackground())
             return; // we're showing one of our own activities and likely won't get paged out
 
         if (outState == null)
             outState = new Bundle();
 
         outState.putString(SAVED_STATE_TITLE, mLastTitle);
         outState.putBoolean(SAVED_STATE_SESSION, true);
     }
@@ -1579,16 +1579,18 @@ abstract public class GeckoApp
         mBrowserToolbar = new BrowserToolbar(mAppContext);
         mBrowserToolbar.from(actionBar);
 
         // setup gecko layout
         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
         mMainLayout = (LinearLayout) findViewById(R.id.main_layout);
 
         mConnectivityReceiver = new GeckoConnectivityReceiver();
+
+        ((GeckoApplication) getApplication()).addApplicationLifecycleCallbacks(this);
     }
 
     private void initialize() {
         mInitialized = true;
 
         Intent intent = getIntent();
         String action = intent.getAction();
         String args = intent.getStringExtra("args");
@@ -1966,17 +1968,16 @@ abstract public class GeckoApp
         return uri;
     }
 
     @Override
     public void onPause()
     {
         Log.i(LOGTAG, "pause");
 
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createPauseEvent(mOwnActivityDepth));
         // The user is navigating away from this activity, but nothing
         // has come to the foreground yet; for Gecko, we may want to
         // stop repainting, for example.
 
         // Whatever we do here should be fast, because we're blocking
         // the next activity from showing up until we finish.
 
         // onPause will be followed by either onResume or onStop.
@@ -1986,18 +1987,16 @@ abstract public class GeckoApp
         GeckoNetworkManager.getInstance().stop();
         GeckoScreenOrientationListener.getInstance().stop();
     }
 
     @Override
     public void onResume()
     {
         Log.i(LOGTAG, "resume");
-        if (checkLaunchState(LaunchState.GeckoRunning))
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createResumeEvent(mOwnActivityDepth));
 
         // After an onPause, the activity is back in the foreground.
         // Undo whatever we did in onPause.
         super.onResume();
 
         /* We load the initial UI and wait until it is shown to the user
            to continue other initializations and loading about:home (if needed) */
         if (!mInitialized) {
@@ -2018,19 +2017,16 @@ abstract public class GeckoApp
 
         mMainHandler.post(new Runnable() {
             public void run() {
                 mConnectivityReceiver.registerFor(mAppContext);
                 GeckoNetworkManager.getInstance().start();
                 GeckoScreenOrientationListener.getInstance().start();
             }
         });
-
-        if (mOwnActivityDepth > 0)
-            mOwnActivityDepth--;
     }
 
     @Override
     public void onStop()
     {
         Log.i(LOGTAG, "stop");
         // We're about to be stopped, potentially in preparation for
         // being destroyed.  We're killable after this point -- as I
@@ -2038,34 +2034,34 @@ abstract public class GeckoApp
         // without going through onDestroy.
         //
         // We might also get an onRestart after this; not sure what
         // that would mean for Gecko if we were to kill it here.
         // Instead, what we should do here is save prefs, session,
         // etc., and generally mark the profile as 'clean', and then
         // dirty it again if we get an onResume.
 
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createStoppingEvent(mOwnActivityDepth));
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createStoppingEvent(isApplicationInBackground()));
         super.onStop();
     }
 
     @Override
     public void onRestart()
     {
         Log.i(LOGTAG, "restart");
         super.onRestart();
     }
 
     @Override
     public void onStart()
     {
         Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - onStart");
 
         Log.i(LOGTAG, "start");
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createStartEvent(mOwnActivityDepth));
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createStartEvent(isApplicationInBackground()));
         super.onStart();
     }
 
     @Override
     public void onDestroy()
     {
         Log.i(LOGTAG, "destroy");
 
@@ -2110,16 +2106,18 @@ abstract public class GeckoApp
 
         super.onDestroy();
 
         unregisterReceiver(mBatteryReceiver);
 
         if (mAboutHomeContent != null) {
             mAboutHomeContent.onDestroy();
         }
+
+        ((GeckoApplication) getApplication()).removeApplicationLifecycleCallbacks(this);
     }
 
     @Override
     public void onContentChanged() {
         super.onContentChanged();
         if (mAboutHomeContent != null)
             mAboutHomeContent.onActivityContentChanged(this);
     }
@@ -2143,16 +2141,29 @@ abstract public class GeckoApp
     public void onLowMemory()
     {
         Log.e(LOGTAG, "low memory");
         if (checkLaunchState(LaunchState.GeckoRunning))
             GeckoAppShell.onLowMemory();
         super.onLowMemory();
     }
 
+    @Override
+    public void onApplicationPause() {
+        Log.i(LOGTAG, "application paused");
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createPauseEvent(true));
+    }
+
+    @Override
+    public void onApplicationResume() {
+        Log.i(LOGTAG, "application resumed");
+        if (checkLaunchState(LaunchState.GeckoRunning))
+            GeckoAppShell.sendEventToGecko(GeckoEvent.createResumeEvent(true));
+    }
+
     abstract public String getPackageName();
     abstract public String getContentProcessName();
 
     public void addEnvToIntent(Intent intent) {
         Map<String,String> envMap = System.getenv();
         Set<Map.Entry<String,String>> envSet = envMap.entrySet();
         Iterator<Map.Entry<String,String>> envIter = envSet.iterator();
         int c = 0;
@@ -2766,28 +2777,16 @@ abstract public class GeckoApp
             switch (type) {
                 case HANDLER_MSG_TYPE_INITIALIZE:
                     initialize();
                     break;
 
             }
         }
     } 
-
-    @Override
-    public void startActivity(Intent intent) {
-        mOwnActivityDepth++;
-        super.startActivity(intent);
-    }
-
-    @Override
-    public void startActivityForResult(Intent intent, int request) {
-        mOwnActivityDepth++;
-        super.startActivityForResult(intent, request);
-    }
 }
 
 class PluginLayoutParams extends AbsoluteLayout.LayoutParams
 {
     private static final int MAX_DIMENSION = 2048;
     private static final String LOGTAG = "GeckoApp.PluginLayoutParams";
 
     private int mOriginalX;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/GeckoApplication.java
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.Application;
+
+public class GeckoApplication extends Application {
+
+    private ArrayList<ApplicationLifecycleCallbacks> mListeners;
+
+    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);
+    }
+
+    public void onActivityPause(GeckoActivity activity) {
+        if (!activity.isApplicationInBackground())
+            return;
+
+        if (mListeners == null)
+            return;
+
+        for (ApplicationLifecycleCallbacks listener: mListeners)
+            listener.onApplicationPause();
+    }
+
+    public void onActivityResume(GeckoActivity activity) {
+        // This is a misnomer. Should have been "wasApplicationInBackground".
+        if (!activity.isApplicationInBackground())
+            return;
+
+        if (mListeners == null)
+            return;
+
+        for (ApplicationLifecycleCallbacks listener: mListeners)
+            listener.onApplicationResume();
+    }
+}
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -145,37 +145,37 @@ public class GeckoEvent {
     public int mNativeWindow;
 
     public short mScreenOrientation;
 
     private GeckoEvent(int evType) {
         mType = evType;
     }
 
-    public static GeckoEvent createPauseEvent(int activityDepth) {
+    public static GeckoEvent createPauseEvent(boolean isApplicationInBackground) {
         GeckoEvent event = new GeckoEvent(ACTIVITY_PAUSING);
-        event.mFlags = activityDepth > 0 ? 1 : 0;
+        event.mFlags = isApplicationInBackground ? 0 : 1;
         return event;
     }
 
-    public static GeckoEvent createResumeEvent(int activityDepth) {
+    public static GeckoEvent createResumeEvent(boolean isApplicationInBackground) {
         GeckoEvent event = new GeckoEvent(ACTIVITY_RESUMING);
-        event.mFlags = activityDepth > 0 ? 1 : 0;
+        event.mFlags = isApplicationInBackground ? 0 : 1;
         return event;
     }
 
-    public static GeckoEvent createStoppingEvent(int activityDepth) {
+    public static GeckoEvent createStoppingEvent(boolean isApplicationInBackground) {
         GeckoEvent event = new GeckoEvent(ACTIVITY_STOPPING);
-        event.mFlags = activityDepth > 0 ? 1 : 0;
+        event.mFlags = isApplicationInBackground ? 0 : 1;
         return event;
     }
 
-    public static GeckoEvent createStartEvent(int activityDepth) {
+    public static GeckoEvent createStartEvent(boolean isApplicationInBackground) {
         GeckoEvent event = new GeckoEvent(ACTIVITY_START);
-        event.mFlags = activityDepth > 0 ? 1 : 0;
+        event.mFlags = isApplicationInBackground ? 0 : 1;
         return event;
     }
 
     public static GeckoEvent createShutdownEvent() {
         return new GeckoEvent(ACTIVITY_SHUTDOWN);
     }
 
     public static GeckoEvent createSyncEvent() {
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -74,16 +74,17 @@ FENNEC_JAVA_FILES = \
   db/LocalBrowserDB.java \
   db/DBUtils.java \
   DoorHanger.java \
   DoorHangerPopup.java \
   Favicons.java \
   FloatUtils.java \
   FormAssistPopup.java \
   GeckoActionBar.java \
+  GeckoApplication.java \
   GeckoApp.java \
   GeckoAppShell.java \
   GeckoAsyncTask.java \
   GeckoBatteryManager.java \
   GeckoBackgroundThread.java \
   GeckoConnectivityReceiver.java \
   GeckoEvent.java \
   GeckoEventListener.java \
@@ -156,16 +157,17 @@ FENNEC_JAVA_FILES = \
   $(NULL)
 
 ifdef MOZ_WEBSMS_BACKEND
 FENNEC_JAVA_FILES += GeckoSmsManager.java
 endif
 
 FENNEC_PP_JAVA_FILES = \
   App.java \
+  GeckoActivity.java \
   LauncherShortcuts.java \
   NotificationHandler.java \
   Restarter.java \
   db/BrowserContract.java \
   db/BrowserProvider.java \
   db/PasswordsProvider.java \
   db/FormHistoryProvider.java \
   db/TabsProvider.java \
--- a/mobile/android/base/RemoteTabs.java
+++ b/mobile/android/base/RemoteTabs.java
@@ -22,17 +22,17 @@ import android.util.DisplayMetrics;
 import android.view.View;
 import android.widget.Button;
 import android.widget.LinearLayout;
 import android.widget.ExpandableListView;
 import android.widget.SimpleExpandableListAdapter;
 import android.text.TextUtils;
 import android.util.Log;
 
-public class RemoteTabs extends Activity
+public class RemoteTabs extends GeckoActivity
        implements ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener, 
                   TabsAccessor.OnQueryTabsCompleteListener {
     private static final String LOGTAG = "GeckoRemoteTabs";
 
     private static int sPreferredHeight;
     private static int sChildItemHeight;
     private static int sGroupItemHeight;
     private static ExpandableListView mList;
--- a/mobile/android/base/TabsTray.java
+++ b/mobile/android/base/TabsTray.java
@@ -26,17 +26,17 @@ import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 
-public class TabsTray extends Activity implements Tabs.OnTabsChangedListener {
+public class TabsTray extends GeckoActivity implements Tabs.OnTabsChangedListener {
 
     private static int sPreferredHeight;
     private static int sMaxHeight;
     private static int sListItemHeight;
     private static int sAddTabHeight;
     private static ListView mList;
     private static TabsListContainer mListContainer;
     private static LinkTextView mRemoteTabs;