Rework activity lifecycle so onSaveInstanceState() is after onPause().

The goal is to fix a bunch of fragment-related bugs caused by various
things trying to do fragment transactions after onPause()...  which
currently throws an exception, since this is after the activity's state
has been saved so the new fragment state can be lost.

The basic change is relatively simple -- we now consider processes
hosting paused or stopping activities to be unkillable, and the client
code now does the onSaveInstanceState() as part of stopping the
activity.

For compatibility, if an app's targetSdkVersion is < HONEYCOMB, the
client side will still call onSaveInstanceState() prior to onPause()
and just hold on to that state until it needs to report it in once
being stopped.

Also included here is a change to generate thumbnails by taking
screenshots.  The code for generating thumbnails by re-rendering
the view hierarchy is thus removed.

Change-Id: Iac1191646bd3cadbfe65779297795f22edf7e74a
diff --git a/services/java/com/android/server/ScreenRotationAnimation.java b/services/java/com/android/server/ScreenRotationAnimation.java
index a95a6c7..ced7c7b 100644
--- a/services/java/com/android/server/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/ScreenRotationAnimation.java
@@ -152,6 +152,27 @@
         }
     }
 
+    public static void createRotationMatrix(int rotation, int width, int height,
+            Matrix outMatrix) {
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                outMatrix.reset();
+                break;
+            case Surface.ROTATION_90:
+                outMatrix.setRotate(90, 0, 0);
+                outMatrix.postTranslate(height, 0);
+                break;
+            case Surface.ROTATION_180:
+                outMatrix.setRotate(180, 0, 0);
+                outMatrix.postTranslate(width, height);
+                break;
+            case Surface.ROTATION_270:
+                outMatrix.setRotate(270, 0, 0);
+                outMatrix.postTranslate(0, width);
+                break;
+        }
+    }
+
     // Must be called while in a transaction.
     public void setRotation(int rotation) {
         mCurRotation = rotation;
@@ -160,23 +181,7 @@
         // to the snapshot to make it stay in the same original position
         // with the current screen rotation.
         int delta = deltaRotation(rotation, mSnapshotRotation);
-        switch (delta) {
-            case Surface.ROTATION_0:
-                mSnapshotInitialMatrix.reset();
-                break;
-            case Surface.ROTATION_90:
-                mSnapshotInitialMatrix.setRotate(90, 0, 0);
-                mSnapshotInitialMatrix.postTranslate(mHeight, 0);
-                break;
-            case Surface.ROTATION_180:
-                mSnapshotInitialMatrix.setRotate(180, 0, 0);
-                mSnapshotInitialMatrix.postTranslate(mWidth, mHeight);
-                break;
-            case Surface.ROTATION_270:
-                mSnapshotInitialMatrix.setRotate(270, 0, 0);
-                mSnapshotInitialMatrix.postTranslate(0, mWidth);
-                break;
-        }
+        createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
 
         if (DEBUG) Slog.v(TAG, "**** ROTATION: " + delta);
         setSnapshotTransform(mSnapshotInitialMatrix, 1.0f);
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index f78ebb9..7a2c0c6 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -60,6 +60,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -4922,6 +4923,90 @@
         SystemProperties.set(StrictMode.VISUAL_PROPERTY, value);
     }
 
+    public Bitmap screenshotApplications(int maxWidth, int maxHeight) {
+        if (!checkCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
+                "screenshotApplications()")) {
+            throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+        }
+
+        Bitmap rawss;
+
+        final Rect frame = new Rect();
+
+        float scale;
+        int sw, sh, dw, dh;
+        int rot;
+
+        synchronized(mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+
+            int aboveAppLayer = mPolicy.windowTypeToLayerLw(
+                    WindowManager.LayoutParams.TYPE_APPLICATION) * TYPE_LAYER_MULTIPLIER
+                    + TYPE_LAYER_OFFSET;
+            aboveAppLayer += TYPE_LAYER_MULTIPLIER;
+
+            // Figure out the part of the screen that is actually the app.
+            for (int i=0; i<mWindows.size(); i++) {
+                WindowState ws = mWindows.get(i);
+                if (ws.mSurface == null) {
+                    continue;
+                }
+                if (ws.mLayer >= aboveAppLayer) {
+                    break;
+                }
+                final Rect wf = ws.mFrame;
+                final Rect cr = ws.mContentInsets;
+                int left = wf.left + cr.left;
+                int top = wf.top + cr.top;
+                int right = wf.right - cr.right;
+                int bottom = wf.bottom - cr.bottom;
+                frame.union(left, top, right, bottom);
+            }
+            Binder.restoreCallingIdentity(ident);
+
+            if (frame.isEmpty()) {
+                return null;
+            }
+
+            // The screenshot API does not apply the current screen rotation.
+            rot = mDisplay.getRotation();
+            int fw = frame.width();
+            int fh = frame.height();
+
+            // First try reducing to fit in x dimension.
+            scale = maxWidth/(float)fw;
+            sw = maxWidth;
+            sh = (int)(fh*scale);
+            if (sh > maxHeight) {
+                // y dimension became too long; constrain by that.
+                scale = maxHeight/(float)fh;
+                sw = (int)(fw*scale);
+                sh = maxHeight;
+            }
+
+            // The screen shot will contain the entire screen.
+            dw = (int)(mDisplay.getWidth()*scale);
+            dh = (int)(mDisplay.getHeight()*scale);
+            if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+                int tmp = dw;
+                dw = dh;
+                dh = tmp;
+                rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90;
+            }
+            rawss = Surface.screenshot(dw, dh);
+        }
+
+        Bitmap bm = Bitmap.createBitmap(sw, sh, rawss.getConfig());
+        Matrix matrix = new Matrix();
+        ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix);
+        matrix.postTranslate(-(int)(frame.left*scale), -(int)(frame.top*scale));
+        Canvas canvas = new Canvas(bm);
+        canvas.drawBitmap(rawss, matrix, null);
+
+        rawss.recycle();
+        return bm;
+    }
+
     public void freezeRotation() {
         if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
                 "setRotation()")) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 1a10cff..1bfdcae 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -3780,22 +3780,22 @@
         }
     }
     
-    public final void activityPaused(IBinder token, Bundle icicle) {
+    public final void activityPaused(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        mMainStack.activityPaused(token, false);
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    public final void activityStopped(IBinder token, Bundle icicle, Bitmap thumbnail,
+            CharSequence description) {
+        if (localLOGV) Slog.v(
+            TAG, "Activity stopped: token=" + token);
+
         // Refuse possible leaked file descriptors
         if (icicle != null && icicle.hasFileDescriptors()) {
             throw new IllegalArgumentException("File descriptors passed in Bundle");
         }
 
-        final long origId = Binder.clearCallingIdentity();
-        mMainStack.activityPaused(token, icicle, false);
-        Binder.restoreCallingIdentity(origId);
-    }
-
-    public final void activityStopped(IBinder token, Bitmap thumbnail,
-            CharSequence description) {
-        if (localLOGV) Slog.v(
-            TAG, "Activity stopped: token=" + token);
-
         ActivityRecord r = null;
 
         final long origId = Binder.clearCallingIdentity();
@@ -3804,7 +3804,11 @@
             int index = mMainStack.indexOfTokenLocked(token);
             if (index >= 0) {
                 r = (ActivityRecord)mMainStack.mHistory.get(index);
-                r.thumbnail = thumbnail;
+                r.icicle = icicle;
+                r.haveState = true;
+                if (thumbnail != null) {
+                    r.thumbnail = thumbnail;
+                }
                 r.description = description;
                 r.stopped = true;
                 r.state = ActivityState.STOPPED;
@@ -4822,6 +4826,10 @@
                 throw new SecurityException(msg);
             }
 
+            final boolean canReadFb = checkCallingPermission(
+                    android.Manifest.permission.READ_FRAME_BUFFER)
+                    == PackageManager.PERMISSION_GRANTED;
+
             int pos = mMainStack.mHistory.size()-1;
             ActivityRecord next =
                 pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null;
@@ -4866,7 +4874,13 @@
                     ci.id = curTask.taskId;
                     ci.baseActivity = r.intent.getComponent();
                     ci.topActivity = top.intent.getComponent();
-                    ci.thumbnail = top.thumbnail;
+                    if (canReadFb) {
+                        if (top.thumbnail != null) {
+                            ci.thumbnail = top.thumbnail;
+                        } else if (top.state == ActivityState.RESUMED) {
+                            ci.thumbnail = top.stack.screenshotActivities();
+                        }
+                    }
                     ci.description = topDescription;
                     ci.numActivities = numActivities;
                     ci.numRunning = numRunning;
@@ -11806,7 +11820,7 @@
         } else if (app == mHeavyWeightProcess) {
             // We don't want to kill the current heavy-weight process.
             adj = HEAVY_WEIGHT_APP_ADJ;
-            schedGroup = Process.THREAD_GROUP_DEFAULT;
+            schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
             app.adjType = "heavy";
         } else if (app == mHomeProcess) {
             // This process is hosting what we currently consider to be the
@@ -11822,13 +11836,19 @@
             app.adjType = "bg-activities";
             N = app.activities.size();
             for (int j=0; j<N; j++) {
-                if (app.activities.get(j).visible) {
+                ActivityRecord r = app.activities.get(j);
+                if (r.visible) {
                     // This app has a visible activity!
                     app.hidden = false;
                     adj = VISIBLE_APP_ADJ;
                     schedGroup = Process.THREAD_GROUP_DEFAULT;
                     app.adjType = "visible";
                     break;
+                } else if (r.state == ActivityState.PAUSING
+                        || r.state == ActivityState.PAUSED
+                        || r.state == ActivityState.STOPPING) {
+                    adj = PERCEPTIBLE_APP_ADJ;
+                    app.adjType = "stopping";
                 }
             }
         } else {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index b4ea036..aa4cd66 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -46,6 +46,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -237,6 +239,9 @@
     
     long mInitialStartTime = 0;
     
+    int mThumbnailWidth = -1;
+    int mThumbnailHeight = -1;
+
     static final int PAUSE_TIMEOUT_MSG = 9;
     static final int IDLE_TIMEOUT_MSG = 10;
     static final int IDLE_NOW_MSG = 11;
@@ -256,7 +261,7 @@
                     // We don't at this point know if the activity is fullscreen,
                     // so we need to be conservative and assume it isn't.
                     Slog.w(TAG, "Activity pause timeout for " + token);
-                    activityPaused(token, null, true);
+                    activityPaused(token, true);
                 } break;
                 case IDLE_TIMEOUT_MSG: {
                     if (mService.mDidDexOpt) {
@@ -564,8 +569,6 @@
             // As part of the process of launching, ActivityThread also performs
             // a resume.
             r.state = ActivityState.RESUMED;
-            r.icicle = null;
-            r.haveState = false;
             r.stopped = false;
             mResumedActivity = r;
             r.task.touchActiveTime();
@@ -580,6 +583,9 @@
             r.stopped = true;
         }
 
+        r.icicle = null;
+        r.haveState = false;
+
         // Launch the new version setup screen if needed.  We do this -after-
         // launching the initial activity (that is, home), so that it can have
         // a chance to initialize itself while in the background, making the
@@ -644,6 +650,23 @@
         }
     }
     
+    public final Bitmap screenshotActivities() {
+        Resources res = mService.mContext.getResources();
+        int w = mThumbnailWidth;
+        int h = mThumbnailHeight;
+        if (w < 0) {
+            mThumbnailWidth = w =
+                res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+            mThumbnailHeight = h =
+                res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+        }
+
+        if (w > 0) {
+            return mService.mWindowManager.screenshotApplications(w, h);
+        }
+        return null;
+    }
+
     private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) {
         if (mPausingActivity != null) {
             RuntimeException e = new RuntimeException();
@@ -663,6 +686,7 @@
         mLastPausedActivity = prev;
         prev.state = ActivityState.PAUSING;
         prev.task.touchActiveTime();
+        prev.thumbnail = screenshotActivities();
 
         mService.updateCpuStats();
         
@@ -726,10 +750,9 @@
         }
     }
     
-    final void activityPaused(IBinder token, Bundle icicle, boolean timeout) {
+    final void activityPaused(IBinder token, boolean timeout) {
         if (DEBUG_PAUSE) Slog.v(
-            TAG, "Activity paused: token=" + token + ", icicle=" + icicle
-            + ", timeout=" + timeout);
+            TAG, "Activity paused: token=" + token + ", timeout=" + timeout);
 
         ActivityRecord r = null;
 
@@ -737,10 +760,6 @@
             int index = indexOfTokenLocked(token);
             if (index >= 0) {
                 r = (ActivityRecord)mHistory.get(index);
-                if (!timeout) {
-                    r.icicle = icicle;
-                    r.haveState = true;
-                }
                 mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
                 if (mPausingActivity == r) {
                     r.state = ActivityState.PAUSED;