Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0aae2d4e authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

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
parent 0b38aa0f
Loading
Loading
Loading
Loading
+13 −41
Original line number Diff line number Diff line
@@ -297,7 +297,7 @@ import java.util.List;
 *             <p>Followed by either <code>onResume()</code> if the activity
 *             returns back to the front, or <code>onStop()</code> if it becomes
 *             invisible to the user.</td>
 *         <td align="center"><font color="#800000"><strong>Yes</strong></font></td>
 *         <td align="center"><font color="#800000"><strong>Pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB}</strong></font></td>
 *         <td align="center"><code>onResume()</code> or<br>
 *                 <code>onStop()</code></td>
 *     </tr>
@@ -347,6 +347,14 @@ import java.util.List;
 * because the later is not part of the lifecycle callbacks, so will not
 * be called in every situation as described in its documentation.</p>
 *
 * <p class="note">Be aware that these semantics will change slightly between
 * applications targeting platforms starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}
 * vs. those targeting prior platforms.  Starting with Honeycomb, an application
 * is not in the killable state until its {@link #onStop} has returned.  This
 * impacts when {@link #onSaveInstanceState(Bundle)} may be called (it may be
 * safely called after {@link #onPause()} and allows and application to safely
 * wait until {@link #onStop()} to save persistent state.</p>
 *
 * <p>For those methods that are not marked as being killable, the activity's
 * process will not be killed by the system starting from the time the method
 * is called and continuing after it returns.  Thus an activity is in the killable
@@ -489,7 +497,7 @@ import java.util.List;
 * paused.  Note this implies
 * that the user pressing BACK from your activity does <em>not</em>
 * mean "cancel" -- it means to leave the activity with its current contents
 * saved away.  Cancelling edits in an activity must be provided through
 * saved away.  Canceling edits in an activity must be provided through
 * some other mechanism, such as an explicit "revert" or "undo" option.</p>
 *
 * <p>See the {@linkplain android.content.ContentProvider content package} for
@@ -1255,11 +1263,8 @@ public class Activity extends ContextThemeWrapper
     * can use the given <var>canvas</var>, which is configured to draw into the
     * bitmap, for rendering if desired.
     * 
     * <p>The default implementation renders the Screen's current view
     * hierarchy into the canvas to generate a thumbnail.
     * 
     * <p>If you return false, the bitmap will be filled with a default
     * thumbnail.
     * <p>The default implementation returns fails and does not draw a thumbnail;
     * this will result in the platform creating its own thumbnail if needed.
     * 
     * @param outBitmap The bitmap to contain the thumbnail.
     * @param canvas Can be used to render into the bitmap.
@@ -1272,42 +1277,9 @@ public class Activity extends ContextThemeWrapper
     * @see #onPause
     */
    public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
        if (mDecor == null) {
        return false;
    }

        int paddingLeft = 0;
        int paddingRight = 0;
        int paddingTop = 0;
        int paddingBottom = 0;

        // Find System window and use padding so we ignore space reserved for decorations
        // like the status bar and such.
        final FrameLayout top = (FrameLayout) mDecor;
        for (int i = 0; i < top.getChildCount(); i++) {
            View child = top.getChildAt(i);
            if (child.isFitsSystemWindowsFlagSet()) {
                paddingLeft = child.getPaddingLeft();
                paddingRight = child.getPaddingRight();
                paddingTop = child.getPaddingTop();
                paddingBottom = child.getPaddingBottom();
                break;
            }
        }
        
        final int visibleWidth = mDecor.getWidth() - paddingLeft - paddingRight;
        final int visibleHeight = mDecor.getHeight() - paddingTop - paddingBottom;

        canvas.save();
        canvas.scale( (float) outBitmap.getWidth() / visibleWidth,
                (float) outBitmap.getHeight() / visibleHeight);
        canvas.translate(-paddingLeft, -paddingTop);
        mDecor.draw(canvas);
        canvas.restore();

        return true;
    }

    /**
     * Generate a new description for this activity.  This method is called
     * before pausing the activity and can, if desired, return some textual
+7 −7
Original line number Diff line number Diff line
@@ -359,8 +359,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
        case ACTIVITY_PAUSED_TRANSACTION: {
            data.enforceInterface(IActivityManager.descriptor);
            IBinder token = data.readStrongBinder();
            Bundle map = data.readBundle();
            activityPaused(token, map);
            activityPaused(token);
            reply.writeNoException();
            return true;
        }
@@ -368,10 +367,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
        case ACTIVITY_STOPPED_TRANSACTION: {
            data.enforceInterface(IActivityManager.descriptor);
            IBinder token = data.readStrongBinder();
            Bundle map = data.readBundle();
            Bitmap thumbnail = data.readInt() != 0
                ? Bitmap.CREATOR.createFromParcel(data) : null;
            CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data);
            activityStopped(token, thumbnail, description);
            activityStopped(token, map, thumbnail, description);
            reply.writeNoException();
            return true;
        }
@@ -1688,25 +1688,25 @@ class ActivityManagerProxy implements IActivityManager
        data.recycle();
        reply.recycle();
    }
    public void activityPaused(IBinder token, Bundle state) throws RemoteException
    public void activityPaused(IBinder token) throws RemoteException
    {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        data.writeStrongBinder(token);
        data.writeBundle(state);
        mRemote.transact(ACTIVITY_PAUSED_TRANSACTION, data, reply, 0);
        reply.readException();
        data.recycle();
        reply.recycle();
    }
    public void activityStopped(IBinder token,
    public void activityStopped(IBinder token, Bundle state,
            Bitmap thumbnail, CharSequence description) throws RemoteException
    {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        data.writeStrongBinder(token);
        data.writeBundle(state);
        if (thumbnail != null) {
            data.writeInt(1);
            thumbnail.writeToParcel(data, 0);
+70 −38
Original line number Diff line number Diff line
@@ -239,6 +239,14 @@ public final class ActivityThread {
            nextIdle = null;
        }

        public boolean isPreHoneycomb() {
            if (activity != null) {
                return activity.getApplicationInfo().targetSdkVersion
                        < android.os.Build.VERSION_CODES.HONEYCOMB;
            }
            return false;
        }

        public String toString() {
            ComponentName componentName = intent.getComponent();
            return "ActivityRecord{"
@@ -2299,10 +2307,13 @@ public final class ActivityThread {

    private int mThumbnailWidth = -1;
    private int mThumbnailHeight = -1;
    private Bitmap mAvailThumbnailBitmap = null;
    private Canvas mThumbnailCanvas = null;

    private final Bitmap createThumbnailBitmap(ActivityClientRecord r) {
        Bitmap thumbnail = null;
        Bitmap thumbnail = mAvailThumbnailBitmap;
        try {
            if (thumbnail == null) {
                int w = mThumbnailWidth;
                int h;
                if (w < 0) {
@@ -2318,21 +2329,21 @@ public final class ActivityThread {

                // On platforms where we don't want thumbnails, set dims to (0,0)
                if ((w > 0) && (h > 0)) {
                View topView = r.activity.getWindow().getDecorView();

                // Maximize bitmap by capturing in native aspect.
                if (topView.getWidth() >= topView.getHeight()) {
                    thumbnail = Bitmap.createBitmap(w, h, THUMBNAIL_FORMAT);
                } else {
                    thumbnail = Bitmap.createBitmap(h, w, THUMBNAIL_FORMAT);
                    thumbnail.eraseColor(0);
                }
            }

                thumbnail.eraseColor(0);
                Canvas cv = new Canvas(thumbnail);
            Canvas cv = mThumbnailCanvas;
            if (cv == null) {
                mThumbnailCanvas = cv = new Canvas();
            }

            cv.setBitmap(thumbnail);
            if (!r.activity.onCreateThumbnail(thumbnail, cv)) {
                mAvailThumbnailBitmap = thumbnail;
                thumbnail = null;
            }
            }

        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
@@ -2357,14 +2368,14 @@ public final class ActivityThread {
            }

            r.activity.mConfigChangeFlags |= configChanges;
            Bundle state = performPauseActivity(token, finished, true);
            performPauseActivity(token, finished, r.isPreHoneycomb());

            // Make sure any pending writes are now committed.
            QueuedWork.waitToFinish();
            
            // Tell the activity manager we have paused.
            try {
                ActivityManagerNative.getDefault().activityPaused(token, state);
                ActivityManagerNative.getDefault().activityPaused(token);
            } catch (RemoteException ex) {
            }
        }
@@ -2404,6 +2415,8 @@ public final class ActivityThread {
                state = new Bundle();
                mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
                r.state = state;
            } else {
                r.state = null;
            }
            // Now we are idle.
            r.activity.mCalled = false;
@@ -2430,9 +2443,9 @@ public final class ActivityThread {
        return state;
    }

    final void performStopActivity(IBinder token) {
    final void performStopActivity(IBinder token, boolean saveState) {
        ActivityClientRecord r = mActivities.get(token);
        performStopActivityInner(r, null, false);
        performStopActivityInner(r, null, false, saveState);
    }

    private static class StopInfo {
@@ -2447,9 +2460,18 @@ public final class ActivityThread {
        }
    }

    /**
     * Core implementation of stopping an activity.  Note this is a little
     * tricky because the server's meaning of stop is slightly different
     * than our client -- for the server, stop means to save state and give
     * it the result when it is done, but the window may still be visible.
     * For the client, we want to call onStop()/onStart() to indicate when
     * the activity's UI visibillity changes.
     */
    private final void performStopActivityInner(ActivityClientRecord r,
            StopInfo info, boolean keepShown) {
            StopInfo info, boolean keepShown, boolean saveState) {
        if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
        Bundle state = null;
        if (r != null) {
            if (!keepShown && r.stopped) {
                if (r.activity.mFinished) {
@@ -2479,6 +2501,17 @@ public final class ActivityThread {
                }
            }

            // Next have the activity save its current state and managed dialogs...
            if (!r.activity.mFinished && saveState) {
                if (r.state == null) {
                    state = new Bundle();
                    mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
                    r.state = state;
                } else {
                    state = r.state;
                }
            }

            if (!keepShown) {
                try {
                    // Now we are idle.
@@ -2530,7 +2563,7 @@ public final class ActivityThread {
        r.activity.mConfigChangeFlags |= configChanges;

        StopInfo info = new StopInfo();
        performStopActivityInner(r, info, show);
        performStopActivityInner(r, info, show, true);

        if (localLOGV) Slog.v(
            TAG, "Finishing stop of " + r + ": show=" + show
@@ -2541,7 +2574,7 @@ public final class ActivityThread {
        // Tell activity manager we have been stopped.
        try {
            ActivityManagerNative.getDefault().activityStopped(
                r.token, info.thumbnail, info.description);
                r.token, r.state, info.thumbnail, info.description);
        } catch (RemoteException ex) {
        }
    }
@@ -2557,7 +2590,7 @@ public final class ActivityThread {
    private final void handleWindowVisibility(IBinder token, boolean show) {
        ActivityClientRecord r = mActivities.get(token);
        if (!show && !r.stopped) {
            performStopActivityInner(r, null, show);
            performStopActivityInner(r, null, show, false);
        } else if (show && r.stopped) {
            // If we are getting ready to gc after going to the background, well
            // we are back active so skip it.
@@ -2651,9 +2684,6 @@ public final class ActivityThread {
            if (finishing) {
                r.activity.mFinished = true;
            }
            if (getNonConfigInstance) {
                r.activity.mChangingConfigurations = true;
            }
            if (!r.paused) {
                try {
                    r.activity.mCalled = false;
@@ -2924,9 +2954,11 @@ public final class ActivityThread {
        r.onlyLocalRequest = tmp.onlyLocalRequest;
        Intent currentIntent = r.activity.mIntent;

        r.activity.mChangingConfigurations = true;

        Bundle savedState = null;
        if (!r.paused) {
            savedState = performPauseActivity(r.token, false, true);
            savedState = performPauseActivity(r.token, false, r.isPreHoneycomb());
        }

        handleDestroyActivity(r.token, false, configChanges, true);
+3 −3
Original line number Diff line number Diff line
@@ -119,9 +119,9 @@ public interface IActivityManager extends IInterface {
    public void attachApplication(IApplicationThread app) throws RemoteException;
    /* oneway */
    public void activityIdle(IBinder token, Configuration config) throws RemoteException;
    public void activityPaused(IBinder token, Bundle state) throws RemoteException;
    public void activityPaused(IBinder token) throws RemoteException;
    /* oneway */
    public void activityStopped(IBinder token,
    public void activityStopped(IBinder token, Bundle state,
            Bitmap thumbnail, CharSequence description) throws RemoteException;
    /* oneway */
    public void activityDestroyed(IBinder token) throws RemoteException;
+2 −2
Original line number Diff line number Diff line
@@ -176,7 +176,7 @@ public class LocalActivityManager {
                }
                if (desiredState == CREATED) {
                    if (localLOGV) Log.v(TAG, r.id + ": stopping");
                    mActivityThread.performStopActivity(r);
                    mActivityThread.performStopActivity(r, false);
                    r.curState = CREATED;
                }
                return;
@@ -191,7 +191,7 @@ public class LocalActivityManager {
                    if (localLOGV) Log.v(TAG, r.id + ": pausing");
                    performPause(r, mFinishing);
                    if (localLOGV) Log.v(TAG, r.id + ": stopping");
                    mActivityThread.performStopActivity(r);
                    mActivityThread.performStopActivity(r, false);
                    r.curState = CREATED;
                }
                return;
Loading