Bug 723251 - onSaveInstanceState() never saves screenshots to instance state bundle. r=bnicholson
☠☠ backed out by bd09e7a515b7 ☠ ☠
authorJosh Dhaliwal <jdhaliwal@mozilla.com>
Wed, 14 Mar 2012 18:00:29 -0400
changeset 89407 11a5cc8bb70a5804bd2df8e21b54636a3d5e0bbd
parent 89406 41d14a8398a208ca8cd725bf20c0297036094047
child 89408 30a16e96d2f9ce527516df0f154a39ff87b6e9d5
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbnicholson
bugs723251
milestone14.0a1
Bug 723251 - onSaveInstanceState() never saves screenshots to instance state bundle. r=bnicholson
mobile/android/base/GeckoApp.java
mobile/android/base/Tabs.java
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -106,21 +106,22 @@ abstract public class GeckoApp
     public static final String ACTION_ALERT_CLICK   = "org.mozilla.gecko.ACTION_ALERT_CLICK";
     public static final String ACTION_ALERT_CLEAR   = "org.mozilla.gecko.ACTION_ALERT_CLEAR";
     public static final String ACTION_WEBAPP        = "org.mozilla.gecko.WEBAPP";
     public static final String ACTION_DEBUG         = "org.mozilla.gecko.DEBUG";
     public static final String ACTION_BOOKMARK      = "org.mozilla.gecko.BOOKMARK";
     public static final String ACTION_LOAD          = "org.mozilla.gecko.LOAD";
     public static final String ACTION_UPDATE        = "org.mozilla.gecko.UPDATE";
     public static final String ACTION_INIT_PW       = "org.mozilla.gecko.INIT_PW";
-    public static final String SAVED_STATE_URI      = "uri";
-    public static final String SAVED_STATE_TITLE    = "title";
-    public static final String SAVED_STATE_VIEWPORT = "viewport";
-    public static final String SAVED_STATE_SCREEN   = "screen";
-    public static final String SAVED_STATE_SESSION  = "session";
+    private static final String SAVED_STATE_URI      = "uri";
+    private static final String SAVED_STATE_TITLE    = "title";
+    private static final String SAVED_STATE_VIEWPORT = "viewport";
+    private static final String SAVED_STATE_SCREEN   = "/sdcard/lastscreen.png";
+    private static final String SAVED_STATE_SESSION  = "session";
+    private static final String SAVED_STATE_FILESIZE  = "filesize";
 
     StartupMode mStartupMode = null;
     private LinearLayout mMainLayout;
     private RelativeLayout mGeckoLayout;
     public static SurfaceView cameraView;
     public static GeckoApp mAppContext;
     public static boolean mDOMFullScreen = false;
     public static Menu sMenu;
@@ -136,25 +137,26 @@ abstract public class GeckoApp
     private BroadcastReceiver mBatteryReceiver;
 
     public static BrowserToolbar mBrowserToolbar;
     public static DoorHangerPopup mDoorHangerPopup;
     public static FormAssistPopup mFormAssistPopup;
     public Favicons mFavicons;
 
     private static LayerController mLayerController;
-    private static PlaceholderLayerClient mPlaceholderLayerClient;
+    private static PlaceholderLayerClient mPlaceholderLayerClient = null;
     private static GeckoLayerClient mLayerClient;
     private AboutHomeContent mAboutHomeContent;
     private static AbsoluteLayout mPluginContainer;
 
-    public String mLastTitle;
-    public String mLastSnapshotUri;
-    public String mLastViewport;
-    public byte[] mLastScreen;
+    public String mLastTitle = null;
+    public String mLastSnapshotUri = null;
+    public String mLastViewport = null;
+    public byte[] mLastScreen = null;
+    private int mLastScreenFilesize;
     public 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 {
@@ -539,97 +541,101 @@ abstract public class GeckoApp
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         if (mOwnActivityDepth > 0)
             return; // we're showing one of our own activities and likely won't get paged out
 
         if (outState == null)
             outState = new Bundle();
 
-        new SessionSnapshotRunnable(null).run();
-
-        outState.putString(SAVED_STATE_TITLE, mLastTitle);
-        outState.putString(SAVED_STATE_VIEWPORT, mLastViewport);
-        outState.putByteArray(SAVED_STATE_SCREEN, mLastScreen);
-        outState.putBoolean(SAVED_STATE_SESSION, true);
+        getSessionSnapshot(outState);
     }
 
-    public class SessionSnapshotRunnable implements Runnable {
-        Tab mThumbnailTab;
-        SessionSnapshotRunnable(Tab thumbnailTab) {
-            mThumbnailTab = thumbnailTab;
+    /* This function saves the state of the current browsing session, which includes:
+     *     - a screenshot of currently selected tab
+     *     - the title of the last history entry for the currently selected tab
+     *     - a boolean to indicate a saved state
+     */
+    private void getSessionSnapshot(final Bundle outState) {
+        final Tab tab = Tabs.getInstance().getSelectedTab();
+        if (tab == null)
+            return;
+
+        outState.putInt(SAVED_STATE_FILESIZE, mLastScreen.length);
+        if (mLastScreen != null) {
+            try {
+                FileOutputStream fos = new FileOutputStream(SAVED_STATE_SCREEN);
+                fos.write(mLastScreen);
+                fos.close();
+            } catch (IOException e) {
+                Log.e(LOGTAG, "Failed to write last screenshot to file!");
+            }
+        }
+
+        HistoryEntry lastHistoryEntry = tab.getLastHistoryEntry();
+        if (lastHistoryEntry != null) {
+            outState.putString(SAVED_STATE_TITLE, lastHistoryEntry.mTitle);
+            outState.putString(SAVED_STATE_URI, lastHistoryEntry.mUri);
         }
 
-        public void run() {
-            if (mLayerClient == null)
-                return;
-
-            synchronized (mLayerClient) {
-                if (!Tabs.getInstance().isSelectedTab(mThumbnailTab))
-                    return;
-
-                HistoryEntry lastHistoryEntry = mThumbnailTab.getLastHistoryEntry();
-                if (lastHistoryEntry == null)
-                    return;
-
-                ViewportMetrics viewportMetrics = mLayerClient.getGeckoViewportMetrics();
-                // If we don't have viewport metrics, the screenshot won't be right so bail
-                if (viewportMetrics == null)
-                    return;
-                
+        if (getLayerController().getLayerClient() == mSoftwareLayerClient) {
+            ViewportMetrics viewportMetrics = mSoftwareLayerClient.getGeckoViewportMetrics();
+            if (viewportMetrics != null) {
+                outState.putString(SAVED_STATE_VIEWPORT, viewportMetrics.toJSON());
+            }
+        }
+
+        outState.putBoolean(SAVED_STATE_SESSION, true); 
+    }
+
+    void getAsyncThumbnailForTab(final Tab tab, boolean forceBigScreenshot) {
+        // This function captures and processes a screenshot asynchronously for any tab, and can specify the screenshot to be thumbnail sized or not  
+        if (!tab.hasLoaded()) {
+            if (!forceBigScreenshot) {
+                byte[] thumbnail = BrowserDB.getThumbnailForUrl(getContentResolver(), tab.getURL());
+                if (thumbnail != null) {
+                    processThumbnail(tab, null, thumbnail);
+                }
+            }
+            return;
+        }
+
+        HistoryEntry lastHistoryEntry = tab.getLastHistoryEntry();
+        if (lastHistoryEntry != null) {
+            ViewportMetrics viewportMetrics = mSoftwareLayerClient.getGeckoViewportMetrics();
+            // If we don't have viewport metrics, the screenshot won't be right so bail
+            if (viewportMetrics != null && mLastTitle != null && mLastSnapshotUri != null) {                                
                 String viewportJSON = viewportMetrics.toJSON();
                 // If the title, uri and viewport haven't changed, the old screenshot is probably valid
                 // Ordering of .equals() below is important since mLast* variables may be null
                 if (viewportJSON.equals(mLastViewport) &&
                     lastHistoryEntry.mTitle.equals(mLastTitle) &&
                     lastHistoryEntry.mUri.equals(mLastSnapshotUri))
-                    return; 
-
-                mLastViewport = viewportJSON;
-                mLastTitle = lastHistoryEntry.mTitle;
-                mLastSnapshotUri = lastHistoryEntry.mUri;
-                getAndProcessThumbnailForTab(mThumbnailTab, true);
+                    return;
             }
         }
-    }
-
-    void getAndProcessThumbnailForTab(final Tab tab, boolean forceBigSceenshot) {
-        boolean isSelectedTab = Tabs.getInstance().isSelectedTab(tab);
-        final Bitmap bitmap = isSelectedTab ? mLayerClient.getBitmap() : null;
-        
-        if (bitmap != null) {
-            ByteArrayOutputStream bos = new ByteArrayOutputStream();
-            bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
-            processThumbnail(tab, bitmap, bos.toByteArray());
-        } else {
-            if (tab.getState() == Tab.STATE_DELAYED) {
-                byte[] thumbnail = BrowserDB.getThumbnailForUrl(getContentResolver(), tab.getURL());
-                if (thumbnail != null)
-                    processThumbnail(tab, null, thumbnail);
-                return;
-            }
-
-            mLastScreen = null;
-            View view = mLayerController.getView();
-            int sw = forceBigSceenshot ? view.getWidth() : tab.getMinScreenshotWidth();
-            int sh = forceBigSceenshot ? view.getHeight(): tab.getMinScreenshotHeight();
-            int dw = forceBigSceenshot ? sw : tab.getThumbnailWidth();
-            int dh = forceBigSceenshot ? sh : tab.getThumbnailHeight();
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), sw, sh, dw, dh));
-        }
+
+        int sw = forceBigScreenshot ? mSoftwareLayerClient.getWidth() : tab.getMinScreenshotWidth();
+        int sh = forceBigScreenshot ? mSoftwareLayerClient.getHeight(): tab.getMinScreenshotHeight();
+        int dw = forceBigScreenshot ? sw : tab.getThumbnailWidth();
+        int dh = forceBigScreenshot ? sh : tab.getThumbnailHeight();
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), sw, sh, dw, dh));
     }
     
     void processThumbnail(Tab thumbnailTab, Bitmap bitmap, byte[] compressed) {
-        if (Tabs.getInstance().isSelectedTab(thumbnailTab)) {
+        if (Tabs.getInstance().isSelectedTab(thumbnailTab)
+            && bitmap.getWidth() == mSoftwareLayerClient.getWidth()
+            && bitmap.getHeight() == mSoftwareLayerClient.getHeight()) {
             if (compressed == null) {
                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
                 compressed = bos.toByteArray();
             }
             mLastScreen = compressed;
+            return;
         }
 
         if ("about:home".equals(thumbnailTab.getURL())) {
             thumbnailTab.updateThumbnail(null);
             return;
         }
         try {
             if (bitmap == null)
@@ -954,17 +960,16 @@ abstract public class GeckoApp
                 mMainHandler.post(new Runnable() {
                     public void run() {
                         if (sMenu != null)
                             sMenu.findItem(R.id.settings).setEnabled(true);
                     }
                 });
                 setLaunchState(GeckoApp.LaunchState.GeckoRunning);
                 GeckoAppShell.sendPendingEventsToGecko();
-                connectGeckoLayerClient();
             } else if (event.equals("ToggleChrome:Hide")) {
                 mMainHandler.post(new Runnable() {
                     public void run() {
                         mBrowserToolbar.hide();
                     }
                 });
             } else if (event.equals("ToggleChrome:Show")) {
                 mMainHandler.post(new Runnable() {
@@ -1248,23 +1253,31 @@ abstract public class GeckoApp
 
         tab.setState(success ? Tab.STATE_SUCCESS : Tab.STATE_ERROR);
 
         mMainHandler.post(new Runnable() {
             public void run() {
                 if (Tabs.getInstance().isSelectedTab(tab))
                     mBrowserToolbar.setProgressVisibility(false);
                 Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.STOP);
+                               
+                if (mPlaceholderLayerClient != null) {
+                    connectGeckoLayerClient();
+                }
+                
+                if (Tabs.getInstance().isSelectedTab(tab) 
+                    && !tab.getURL().equals("about:home")) {
+                    GeckoAppShell.getHandler().postDelayed(new Runnable() {
+                        public void run() {
+                            getAsyncThumbnailForTab(tab,true);
+                        }
+                    }, 2000);
+                }
             }
         });
-
-        if (Tabs.getInstance().isSelectedTab(tab)) {
-            Runnable r = new SessionSnapshotRunnable(tab);
-            GeckoAppShell.getHandler().postDelayed(r, 500);
-        }
     }
 
     void handleShowToast(final String message, final String duration) {
         mMainHandler.post(new Runnable() {
             public void run() {
                 Toast toast;
                 if (duration.equals("long"))
                     toast = Toast.makeText(mAppContext, message, Toast.LENGTH_LONG);
@@ -1619,18 +1632,19 @@ abstract public class GeckoApp
         }
 
         GeckoAppShell.loadMozGlue();
         mMainHandler = new GeckoAppHandler();
         Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - onCreate");
         if (savedInstanceState != null) {
             mLastTitle = savedInstanceState.getString(SAVED_STATE_TITLE);
             mLastViewport = savedInstanceState.getString(SAVED_STATE_VIEWPORT);
-            mLastScreen = savedInstanceState.getByteArray(SAVED_STATE_SCREEN);
+            mLastScreenFilesize = savedInstanceState.getInt(SAVED_STATE_FILESIZE);
             mRestoreSession = savedInstanceState.getBoolean(SAVED_STATE_SESSION);
+            mLastSnapshotUri = savedInstanceState.getString(SAVED_STATE_URI);
         }
 
         super.onCreate(savedInstanceState);
 
         mOrientation = getResources().getConfiguration().orientation;
 
         setContentView(R.layout.gecko_app);
 
@@ -1647,16 +1661,29 @@ abstract public class GeckoApp
         mConnectivityFilter = new IntentFilter();
         mConnectivityFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         mConnectivityReceiver = new GeckoConnectivityReceiver();
     }
 
     private void initialize() {
         mInitialized = true;
 
+        byte[] lastScreen = null;
+
+        if (mLastScreenFilesize > 0) {
+            try {
+                FileInputStream fis = new FileInputStream(SAVED_STATE_SCREEN);
+                lastScreen = new byte[mLastScreenFilesize];
+                fis.read(lastScreen, 0, mLastScreenFilesize);
+                fis.close();
+            } catch (IOException e) {
+                Log.e(LOGTAG, "Failed to read last screenshot from file!");
+            }
+        }
+
         Intent intent = getIntent();
         String action = intent.getAction();
         String args = intent.getStringExtra("args");
         if (args != null && args.contains("-profile")) {
             Pattern p = Pattern.compile("(?:-profile\\s*)(\\w*)(\\s*)");
             Matcher m = p.matcher(args);
             if (m.find()) {
                 mProfile = GeckoProfile.get(this, m.group(1));
@@ -1739,17 +1766,23 @@ abstract public class GeckoApp
              * and zoom a cached screenshot of the previous page. This call will return null if
              * there is no cached screenshot; in that case, we have no choice but to display a
              * checkerboard.
              *
              * TODO: Fall back to a built-in screenshot of the Fennec Start page for a nice first-
              * run experience, perhaps?
              */
             mLayerController = new LayerController(this);
-            mPlaceholderLayerClient = new PlaceholderLayerClient(mLayerController, mLastViewport);
+            if (lastScreen != null) {
+                mLastScreen = lastScreen;
+                mPlaceholderLayerClient = PlaceholderLayerClient.createInstance(this);
+                mLayerController.setLayerClient(mPlaceholderLayerClient);
+            } else {
+                mLayerController.setLayerClient(mSoftwareLayerClient);
+            }
 
             mGeckoLayout.addView(mLayerController.getView(), 0);
         }
 
         mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
 
         mDoorHangerPopup = new DoorHangerPopup(this);
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
@@ -2028,19 +2061,16 @@ abstract public class GeckoApp
         return uri;
     }
 
     @Override
     public void onPause()
     {
         Log.i(LOGTAG, "pause");
 
-        Runnable r = new SessionSnapshotRunnable(null);
-        GeckoAppShell.getHandler().post(r);
-
         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.
 
@@ -2562,16 +2592,23 @@ abstract public class GeckoApp
             Log.e(LOGTAG, "error building JSON arguments");
         }
         if (type == AwesomeBar.Type.ADD) {
             Log.d(LOGTAG, "Sending message to Gecko: " + SystemClock.uptimeMillis() + " - Tab:Add");
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Add", args.toString()));
         } else {
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Load", args.toString()));
         }
+
+        // If placeholder snapshot screenshot is being shown and we're trying to load a request
+        // (switching tab, loading new url, etc.) that is different from the snapshot's url, then
+        // switch to showing the GeckoSoftwareLayerClient to load this new url 
+        if (mPlaceholderLayerClient != null && mLastSnapshotUri != null && !mLastSnapshotUri.equals(url)) {
+            connectGeckoLayerClient();
+        }
     }
 
     public void loadUrl(String url, AwesomeBar.Type type) {
         loadRequest(url, type, null, false);
     }
 
     /**
      * Open the url as a new tab, and mark the selected tab as its "parent".
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -144,16 +144,17 @@ public class Tabs implements GeckoEventL
                     if (oldTab != null)
                         GeckoApp.mAppContext.hidePlugins(oldTab, true);
                 }
             }
         });
 
         // Pass a message to Gecko to update tab state in BrowserApp
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Selected", String.valueOf(tab.getId())));
+        GeckoApp.mAppContext.getAsyncThumbnailForTab(tab, true);
         return selectedTab = tab;
     }
 
     public int getIndexOf(Tab tab) {
         return order.lastIndexOf(tab);
     }
 
     public Tab getTabAt(int index) {
@@ -305,17 +306,17 @@ public class Tabs implements GeckoEventL
     }
 
     public void refreshThumbnails() {
         Iterator<Tab> iterator = tabs.values().iterator();
         while (iterator.hasNext()) {
             final Tab tab = iterator.next();
             GeckoAppShell.getHandler().post(new Runnable() {
                 public void run() {
-                    GeckoApp.mAppContext.getAndProcessThumbnailForTab(tab, false);
+                    GeckoApp.mAppContext.getAsyncThumbnailForTab(tab, false);
                 }
             });
         }
     }
 
     public interface OnTabsChangedListener {
         public void onTabChanged(Tab tab, TabEvents msg);
     }