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

Commit 62ab9b78 authored by George Mount's avatar George Mount
Browse files

Support Activity Transitions when activity stopped.

Bug 14459812
Bug 14454812
Bug 14450295

A new transport was supported for Activity Transitions
in which convertToTranslucent can now take a Bundle
to transport to the calling Activity. This also allows
waking Activities to regain the communication between
the exiting Activity and the calling Activity.

Change-Id: I29aee14b7e56d9b8b9fb656f382b2c4172b02e14
parent a6464b38
Loading
Loading
Loading
Loading
+12 −18
Original line number Diff line number Diff line
@@ -3251,6 +3251,7 @@ package android.app {
    method public boolean navigateUpToFromChild(android.app.Activity, android.content.Intent);
    method public void onActionModeFinished(android.view.ActionMode);
    method public void onActionModeStarted(android.view.ActionMode);
    method protected void onActivityReenter(int, android.content.Intent);
    method protected void onActivityResult(int, int, android.content.Intent);
    method public void onAttachFragment(android.app.Fragment);
    method public void onAttachedToWindow();
@@ -3329,7 +3330,6 @@ package android.app {
    method public final boolean requestWindowFeature(int);
    method public final void runOnUiThread(java.lang.Runnable);
    method public void setActionBar(android.widget.Toolbar);
    method public void setActivityTransitionListener(android.app.ActivityOptions.ActivityTransitionListener);
    method public void setContentTransitionManager(android.transition.TransitionManager);
    method public void setContentView(int);
    method public void setContentView(android.view.View);
@@ -3351,6 +3351,7 @@ package android.app {
    method public final void setResult(int);
    method public final void setResult(int, android.content.Intent);
    method public final void setSecondaryProgress(int);
    method public void setSharedElementListener(android.app.SharedElementListener);
    method public void setTitle(java.lang.CharSequence);
    method public void setTitle(int);
    method public deprecated void setTitleColor(int);
@@ -3564,28 +3565,13 @@ package android.app {
  public class ActivityOptions {
    method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
    method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
    method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.view.Window, android.view.View, java.lang.String);
    method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.view.Window, android.app.ActivityOptions.ActivityTransitionListener);
    method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, java.lang.String);
    method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View, java.lang.String>...);
    method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
    method public android.os.Bundle toBundle();
    method public void update(android.app.ActivityOptions);
  }
  public static class ActivityOptions.ActivityTransitionListener {
    ctor public ActivityOptions.ActivityTransitionListener();
    method public android.util.Pair<android.view.View, java.lang.String>[] getSharedElementsMapping();
    method public boolean handleRejectedSharedElements(java.util.List<android.view.View>);
    method public void onCaptureSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
    method public void onCaptureSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
    method public void onEnterReady();
    method public void onExitTransitionComplete();
    method public void onRemoteExitComplete();
    method public void onSharedElementExitTransitionComplete();
    method public void onSharedElementTransferred(java.util.List<java.lang.String>, java.util.List<android.view.View>);
    method public void onStartEnterTransition(java.util.List<java.lang.String>, java.util.List<android.view.View>);
    method public void onStartExitTransition(java.util.List<java.lang.String>, java.util.List<android.view.View>);
  }
  public class AlarmManager {
    method public void cancel(android.app.PendingIntent);
    method public void set(int, long, android.app.PendingIntent);
@@ -4809,6 +4795,14 @@ package android.app {
    field public static final int START_STICKY_COMPATIBILITY = 0; // 0x0
  }
  public class SharedElementListener {
    ctor public SharedElementListener();
    method public void handleRejectedSharedElements(java.util.List<android.view.View>);
    method public void remapSharedElements(java.util.List<java.lang.String>, java.util.Map<java.lang.String, android.view.View>);
    method public void setSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
    method public void setSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
  }
  public deprecated class TabActivity extends android.app.ActivityGroup {
    ctor public TabActivity();
    method public android.widget.TabHost getTabHost();
+46 −35
Original line number Diff line number Diff line
@@ -777,8 +777,9 @@ public class Activity extends ContextThemeWrapper

    private Thread mUiThread;
    final Handler mHandler = new Handler();
    private ActivityOptions mCalledActivityOptions;
    private EnterTransitionCoordinator mEnterTransitionCoordinator;

    private ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
    SharedElementListener mTransitionListener = new SharedElementListener();

    /** Return the intent that started this activity. */
    public Intent getIntent() {
@@ -1100,9 +1101,6 @@ public class Activity extends ContextThemeWrapper
            mTitleReady = true;
            onTitleChanged(getTitle(), getTitleColor());
        }
        if (mEnterTransitionCoordinator != null) {
            mEnterTransitionCoordinator.readyToEnter();
        }
        mCalled = true;
    }

@@ -1149,12 +1147,6 @@ public class Activity extends ContextThemeWrapper
        }

        getApplication().dispatchActivityStarted(this);

        final ActivityOptions activityOptions = getActivityOptions();
        if (activityOptions != null &&
                activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
            mEnterTransitionCoordinator = activityOptions.createEnterActivityTransition(this);
        }
    }

    /**
@@ -1204,7 +1196,6 @@ public class Activity extends ContextThemeWrapper
    protected void onResume() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
        getApplication().dispatchActivityResumed(this);
        mCalledActivityOptions = null;
        mCalled = true;
    }

@@ -1279,6 +1270,7 @@ public class Activity extends ContextThemeWrapper
    final void performSaveInstanceState(Bundle outState) {
        onSaveInstanceState(outState);
        saveManagedDialogs(outState);
        mActivityTransitionState.saveState(outState);
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
    }

@@ -1549,10 +1541,7 @@ public class Activity extends ContextThemeWrapper
    protected void onStop() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
        if (mCalledActivityOptions != null) {
            mCalledActivityOptions.dispatchActivityStopped();
            mCalledActivityOptions = null;
        }
        mActivityTransitionState.onStop();
        getApplication().dispatchActivityStopped(this);
        mTranslucentCallback = null;
        mCalled = true;
@@ -3650,7 +3639,7 @@ public class Activity extends ContextThemeWrapper
    public void startActivityForResult(Intent intent, int requestCode) {
        Bundle options = null;
        if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
            options = ActivityOptions.makeSceneTransitionAnimation(mWindow, null).toBundle();
            options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
        }
        startActivityForResult(intent, requestCode, options);
    }
@@ -3691,9 +3680,7 @@ public class Activity extends ContextThemeWrapper
     */
    public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
        if (options != null) {
            ActivityOptions activityOptions = new ActivityOptions(options);
            activityOptions.dispatchStartExit();
            mCalledActivityOptions = activityOptions;
            mActivityTransitionState.startExitOutTransition(this, options);
        }
        if (mParent == null) {
            Instrumentation.ActivityResult ar =
@@ -4559,13 +4546,10 @@ public class Activity extends ContextThemeWrapper
     * to reverse its exit Transition. When the exit Transition completes,
     * {@link #finish()} is called. If no entry Transition was used, finish() is called
     * immediately and the Activity exit Transition is run.
     * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
     * android.app.ActivityOptions.ActivityTransitionListener)
     * @see android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, android.util.Pair[])
     */
    public void finishWithTransition() {
        if (mEnterTransitionCoordinator != null) {
            mEnterTransitionCoordinator.startExit();
        } else {
        if (!mActivityTransitionState.startExitBackTransition(this)) {
            finish();
        }
    }
@@ -4642,6 +4626,27 @@ public class Activity extends ContextThemeWrapper
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    }

    /**
     * Called when an activity you launched with an activity transition exposes this
     * Activity through a returning activity transition, giving you the resultCode
     * and any additional data from it. This method will only be called if the activity
     * set a result code other than {@link #RESULT_CANCELED} and it supports activity
     * transitions with {@link Window#FEATURE_CONTENT_TRANSITIONS}.
     *
     * <p>The purpose of this function is to let the called Activity send a hint about
     * its state so that this underlying Activity can prepare to be exposed. A call to
     * this method does not guarantee that the called Activity has or will be exiting soon.
     * It only indicates that it will expose this Activity's Window and it has
     * some data to pass to prepare it.</p>
     *
     * @param resultCode The integer result code returned by the child activity
     *                   through its setResult().
     * @param data An Intent, which can return result data to the caller
     *               (various data can be attached to Intent "extras").
     */
    protected void onActivityReenter(int resultCode, Intent data) {
    }

    /**
     * Create a new PendingIntent object which you can hand to others 
     * for them to use to send result data back to your 
@@ -5246,7 +5251,8 @@ public class Activity extends ContextThemeWrapper
     * This call has no effect on non-translucent activities or on activities with the
     * {@link android.R.attr#windowIsFloating} attribute.
     *
     * @see #convertToTranslucent(TranslucentConversionListener)
     * @see #convertToTranslucent(android.app.Activity.TranslucentConversionListener,
     * ActivityOptions)
     * @see TranslucentConversionListener
     *
     * @hide
@@ -5544,18 +5550,18 @@ public class Activity extends ContextThemeWrapper
    }

    /**
     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.Window,
     * android.app.ActivityOptions.ActivityTransitionListener)} was used to start an Activity,
     * the Window will be triggered to enter with a Transition. <code>listener</code> allows
     * The Activity to listen to events of the entering transition and control the mapping of
     * shared elements. This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
     * android.view.View, String)} was used to start an Activity, <var>listener</var>
     * will be called to handle shared elements. This requires
     * {@link Window#FEATURE_CONTENT_TRANSITIONS}.
     *
     * @param listener Used to listen to events in the entering transition.
     * @param listener Used to manipulate how shared element transitions function.
     */
    public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) {
        if (mEnterTransitionCoordinator != null) {
            mEnterTransitionCoordinator.setActivityTransitionListener(listener);
    public void setSharedElementListener(SharedElementListener listener) {
        if (listener == null) {
            listener = new SharedElementListener();
        }
        mTransitionListener = listener;
    }

    // ------------------ Internal API ------------------
@@ -5621,19 +5627,23 @@ public class Activity extends ContextThemeWrapper
        mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
                com.android.internal.R.styleable.Window_windowNoDisplay, false);
        mFragments.dispatchActivityCreated();
        mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
    }

    final void performCreate(Bundle icicle) {
        onCreate(icicle);
        mActivityTransitionState.readState(icicle);
        performCreateCommon();
    }

    final void performCreate(Bundle icicle, PersistableBundle persistentState) {
        onCreate(icicle, persistentState);
        mActivityTransitionState.readState(icicle);
        performCreateCommon();
    }

    final void performStart() {
        mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
        mFragments.noteStateNotSaved();
        mCalled = false;
        mFragments.execPendingActions();
@@ -5656,6 +5666,7 @@ public class Activity extends ContextThemeWrapper
                lm.doReportStart();
            }
        }
        mActivityTransitionState.enterReady(this);
    }
    
    final void performRestart() {
+113 −156

File changed.

Preview size limit exceeded, changes collapsed.

+122 −702

File changed.

Preview size limit exceeded, changes collapsed.

+204 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.app;

import android.os.Bundle;
import android.os.ResultReceiver;
import android.util.ArrayMap;
import android.view.View;
import android.view.Window;

import java.util.ArrayList;

/**
 * This class contains all persistence-related functionality for Activity Transitions.
 * Activities start exit and enter Activity Transitions through this class.
 */
class ActivityTransitionState {

    private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements";

    private static final String ENTERING_MAPPED_FROM = "android:enteringMappedFrom";

    private static final String ENTERING_MAPPED_TO = "android:enteringMappedTo";

    private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";

    private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";

    /**
     * The shared elements that the calling Activity has said that they transferred to this
     * Activity.
     */
    private ArrayList<String> mEnteringNames;

    /**
     * The shared elements that this Activity as accepted and mapped to local Views.
     */
    private ArrayList<String> mEnteringFrom;

    /**
     * The names of local Views that are mapped to those elements in mEnteringFrom.
     */
    private ArrayList<String> mEnteringTo;

    /**
     * The names of shared elements that were shared to the called Activity.
     */
    private ArrayList<String> mExitingFrom;

    /**
     * The names of local Views that were shared out, mapped to those elements in mExitingFrom.
     */
    private ArrayList<String> mExitingTo;

    /**
     * The ActivityOptions used to call an Activity. Used to make the elements restore
     * Visibility of exited Views.
     */
    private ActivityOptions mCalledActivityOptions;

    /**
     * We must be able to cancel entering transitions to stop changing the Window to
     * opaque when we exit before making the Window opaque.
     */
    private EnterTransitionCoordinator mEnterTransitionCoordinator;

    /**
     * ActivityOptions used on entering this Activity.
     */
    private ActivityOptions mEnterActivityOptions;

    /**
     * Has an exit transition been started? If so, we don't want to double-exit.
     */
    private boolean mHasExited;

    public ActivityTransitionState() {
    }

    public void readState(Bundle bundle) {
        if (bundle != null) {
            if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
                mEnteringNames = bundle.getStringArrayList(ENTERING_SHARED_ELEMENTS);
                mEnteringFrom = bundle.getStringArrayList(ENTERING_MAPPED_FROM);
                mEnteringTo = bundle.getStringArrayList(ENTERING_MAPPED_TO);
            }
            if (mEnterTransitionCoordinator == null) {
                mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
                mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO);
            }
        }
    }

    public void saveState(Bundle bundle) {
        if (mEnteringNames != null) {
            bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames);
            bundle.putStringArrayList(ENTERING_MAPPED_FROM, mEnteringFrom);
            bundle.putStringArrayList(ENTERING_MAPPED_TO, mEnteringTo);
        }
        if (mExitingFrom != null) {
            bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
            bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo);
        }
    }

    public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
        if (activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)
                && options != null && mEnterActivityOptions == null
                && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
            mEnterActivityOptions = options;
            if (mEnterActivityOptions.isReturning()) {
                int result = mEnterActivityOptions.getResultCode();
                if (result != 0) {
                    activity.onActivityReenter(result, mEnterActivityOptions.getResultData());
                }
            }
        }
    }

    public void enterReady(Activity activity) {
        if (mEnterActivityOptions == null) {
            return;
        }
        mHasExited = false;
        ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
        ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
        if (mEnterActivityOptions.isReturning()) {
            if (mCalledActivityOptions != null) {
                mCalledActivityOptions.dispatchActivityStopped();
                mCalledActivityOptions = null;
            }
            activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
            mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
                    resultReceiver, sharedElementNames, mExitingFrom, mExitingTo);
        } else {
            mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
                    resultReceiver, sharedElementNames, null, null);
            mEnteringNames = sharedElementNames;
            mEnteringFrom = mEnterTransitionCoordinator.getAcceptedNames();
            mEnteringTo = mEnterTransitionCoordinator.getMappedNames();
        }
        mExitingFrom = null;
        mExitingTo = null;
        mEnterActivityOptions = null;
    }

    public void onStop() {
        if (mCalledActivityOptions != null) {
            mCalledActivityOptions.dispatchActivityStopped();
            mCalledActivityOptions = null;
        }
        if (mEnterTransitionCoordinator != null) {
            mEnterTransitionCoordinator.stop();
            mEnterTransitionCoordinator = null;
        }
    }

    public boolean startExitBackTransition(Activity activity) {
        if (mEnteringNames == null) {
            return false;
        } else {
            if (!mHasExited) {
                mHasExited = true;
                if (mEnterTransitionCoordinator != null) {
                    mEnterTransitionCoordinator.stop();
                    mEnterTransitionCoordinator = null;
                }
                ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
                activity.getWindow().getDecorView().findNamedViews(sharedElements);

                ExitTransitionCoordinator exitCoordinator =
                        new ExitTransitionCoordinator(activity, mEnteringNames, mEnteringFrom,
                                mEnteringTo, true);
                exitCoordinator.startExit(activity.mResultCode, activity.mResultData);
            }
            return true;
        }
    }

    public void startExitOutTransition(Activity activity, Bundle options) {
        if (!activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
            return;
        }
        mCalledActivityOptions = new ActivityOptions(options);
        if (mCalledActivityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
            mExitingFrom = mCalledActivityOptions.getSharedElementNames();
            mExitingTo = mCalledActivityOptions.getLocalSharedElementNames();
            mCalledActivityOptions.dispatchStartExit();
        }
    }
}
Loading