diff options
| author | 2015-11-15 10:58:57 -0800 | |
|---|---|---|
| committer | 2015-11-17 10:03:19 -0800 | |
| commit | 1a4dfe593aafda057ac9cb3086b84588d88cd09f (patch) | |
| tree | 11ce6d626fc1db80508babe4c109ce0f1628fbd0 | |
| parent | ff814adfdda376ceeeff5765cd50195b88e567ad (diff) | |
Synchronize recents to freeform transition.
Recents to freeform animation must hang on the first frame and inform
Recents to hide its views. This mirrors the transition from freeform
to Recents, where the animation needs to hang on the last frame.
We need a special window flag for recents to force a redraw after the
animation launches. At this point Recents will become not visible
from the perspective of the activity manager, which would prevent
further drawing. We make recents ignore that and instead depend on
window visibility which will change after recents exit animation
finishes.
Bug: 24913782
Change-Id: Ief743b7e6fcebb3d8789d4745fb122ac607c1cf0
9 files changed, 117 insertions, 50 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fdea1ba157f4..cd9dd97468b3 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; + import android.Manifest; import android.animation.LayoutTransition; import android.app.ActivityManagerNative; @@ -37,7 +39,6 @@ import android.graphics.Region; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; -import android.hardware.input.InputManager; import android.media.AudioManager; import android.os.Binder; import android.os.Build; @@ -74,7 +75,6 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.WindowCallbacks; import android.widget.Scroller; import com.android.internal.R; @@ -176,6 +176,11 @@ public final class ViewRootImpl implements ViewParent, int mViewVisibility; boolean mAppVisible = true; + // For recents to freeform transition we need to keep drawing after the app receives information + // that it became invisible. This will ignore that information and depend on the decor view + // visibility to control drawing. The decor view visibility will get adjusted when the app get + // stopped and that's when the app will stop drawing further frames. + private boolean mForceDecorViewVisibility = false; int mOrigWindowType = -1; /** Whether the window had focus during the most recent traversal. */ @@ -561,6 +566,8 @@ public final class ViewRootImpl implements ViewParent, & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } + mForceDecorViewVisibility = (mWindowAttributes.privateFlags + & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0; try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; @@ -1063,7 +1070,7 @@ public final class ViewRootImpl implements ViewParent, } int getHostVisibility() { - return mAppVisible ? mView.getVisibility() : View.GONE; + return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE; } /** @@ -1568,7 +1575,7 @@ public final class ViewRootImpl implements ViewParent, boolean insetsPending = false; int relayoutResult = 0; - boolean isViewVisible = viewVisibility == View.VISIBLE; + final boolean isViewVisible = viewVisibility == View.VISIBLE; if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null) { @@ -2471,8 +2478,7 @@ public final class ViewRootImpl implements ViewParent, if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { if (c instanceof SurfaceHolder.Callback2) { - ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( - mSurfaceHolder); + ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(mSurfaceHolder); } } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index edf4297afdea..1521f2ed07fe 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -118,8 +118,7 @@ public interface WindowManager extends ViewManager { */ public void removeViewImmediate(View view); - public static class LayoutParams extends ViewGroup.LayoutParams - implements Parcelable { + public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { /** * X position for this window. With the default gravity it is ignored. * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or @@ -1149,13 +1148,21 @@ public interface WindowManager extends ViewManager { /** * Flag indicating that the x, y, width, and height members should be - * ignored (and thus their previous value preserved). For example + * ignored (and thus their previous value preserved). For example * because they are being managed externally through repositionChild. * * {@hide} */ public static final int PRIVATE_FLAG_PRESERVE_GEOMETRY = 0x00002000; + /** + * Flag that will make window ignore app visibility and instead depend purely on the decor + * view visibility for determining window visibility. This is used by recents to keep + * drawing after it launches an app. + * @hide + */ + public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000; + /** * Control flags that are private to the platform. diff --git a/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java new file mode 100644 index 000000000000..8ae8c53a9197 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 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 com.android.systemui.recents; + +import com.android.systemui.recents.events.EventBus; + +/** + * Event sent when the exit animation is started. + * + * This is sent so parts of UI can synchronize on this event and adjust their appearance. An example + * of that is hiding the tasks when the launched application window becomes visible. + */ +public class ExitRecentsWindowFirstAnimationFrameEvent extends EventBus.Event { +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 3ae8827b3fc6..1e46e6f54245 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -37,6 +37,7 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewStub; import android.view.ViewTreeObserver; +import android.view.WindowManager; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.recents.events.EventBus; @@ -352,6 +353,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub); mScrimViews = new SystemBarScrimViews(this); + getWindow().getAttributes().privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; // Create the home intent runnable Intent homeIntent = new Intent(Intent.ACTION_MAIN, null); @@ -648,6 +651,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); } + public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) { + mRecentsView.setStackViewVisibility(View.INVISIBLE); + mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); + } + public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) { RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); int launchToTaskId = launchState.launchedToTaskId; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 50aa2f746f23..b491f943734d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -68,8 +68,8 @@ import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; * An implementation of the Recents component for the current user. For secondary users, this can * be called remotely from the system user. */ -public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub - implements ActivityOptions.OnAnimationFinishedListener { +public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements + ActivityOptions.OnAnimationFinishedListener { private final static String TAG = "RecentsImpl"; private final static boolean DEBUG = false; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java index 85b8fcfb4a7f..b2f716d2add5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java @@ -27,12 +27,12 @@ import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; import android.util.Log; -import android.util.SparseArray; import android.view.AppTransitionAnimationSpec; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.ExitRecentsWindowFirstAnimationFrameEvent; import com.android.systemui.recents.Recents; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; @@ -106,6 +106,7 @@ public class RecentsTransitionHelper { // If we are launching into another task, cancel the previous task's // window transition EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task)); + EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); if (lockToTask) { // Request screen pinning after the animation runs @@ -116,7 +117,12 @@ public class RecentsTransitionHelper { } else { // This is only the case if the task is not on screen (scrolled offscreen for example) transitionFuture = null; - animStartedListener = null; + animStartedListener = new ActivityOptions.OnAnimationStartedListener() { + @Override + public void onAnimationStarted() { + EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); + } + }; } if (taskView == null) { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 89f565816a62..943c9ed39262 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -139,7 +139,7 @@ public class AppTransition implements Dump { private static final long APP_TRANSITION_TIMEOUT_MS = 5000; private final Context mContext; - private final Handler mH; + private final WindowManagerService mService; private int mNextAppTransition = TRANSIT_UNSET; @@ -208,15 +208,10 @@ public class AppTransition implements Dump { private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>(); private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor(); - private final Object mServiceLock; - private final WindowSurfacePlacer mWindowSurfacePlacer; - AppTransition(Context context, Handler h, Object serviceLock, - WindowSurfacePlacer windowSurfacePlacer) { + AppTransition(Context context, WindowManagerService service) { mContext = context; - mH = h; - mServiceLock = serviceLock; - mWindowSurfacePlacer = windowSurfacePlacer; + mService = service; mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.linear_out_slow_in); mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, @@ -971,7 +966,7 @@ public class AppTransition implements Dump { @Override public void onAnimationEnd(Animation animation) { - mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget(); + mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget(); } @Override @@ -1326,7 +1321,8 @@ public class AppTransition implements Dump { void postAnimationCallback() { if (mNextAppTransitionCallback != null) { - mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, mNextAppTransitionCallback)); + mService.mH.sendMessage(mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, + mNextAppTransitionCallback)); mNextAppTransitionCallback = null; } } @@ -1478,14 +1474,15 @@ public class AppTransition implements Dump { } catch (RemoteException e) { Slog.w(TAG, "Failed to fetch app transition specs: " + e); } - synchronized (mServiceLock) { + synchronized (mService.mWindowMap) { mNextAppTransitionAnimationsSpecsPending = false; overridePendingAppTransitionMultiThumb(specs, mNextAppTransitionFutureCallback, null /* finishedCallback */, mNextAppTransitionScaleUp); mNextAppTransitionFutureCallback = null; - mWindowSurfacePlacer.requestTraversal(); + mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp); } + mService.requestTraversal(); } }); } @@ -1672,8 +1669,8 @@ public class AppTransition implements Dump { } boolean prepared = prepare(); if (isTransitionSet()) { - mH.removeMessages(H.APP_TRANSITION_TIMEOUT); - mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS); + mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT); + mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS); } return prepared; } diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index 2905269eb21c..dfd01efe36d0 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -37,6 +37,10 @@ import java.util.ArrayList; public class AppWindowAnimator { static final String TAG = "AppWindowAnimator"; + private static final int PROLONG_ANIMATION_DISABLED = 0; + static final int PROLONG_ANIMATION_AT_END = 1; + static final int PROLONG_ANIMATION_AT_START = 2; + final AppWindowToken mAppToken; final WindowManagerService mService; final WindowAnimator mAnimator; @@ -85,7 +89,7 @@ public class AppWindowAnimator { // If true when the animation hits the last frame, it will keep running on that last frame. // This is used to synchronize animation with Recents and we wait for Recents to tell us to // finish or for a new animation be set as fail-safe mechanism. - private boolean mProlongAnimation; + private int mProlongAnimation; // Whether the prolong animation can be removed when animation is set. The purpose of this is // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it // when new animation is set. @@ -142,7 +146,7 @@ public class AppWindowAnimator { anim.setBackgroundColor(0); } if (mClearProlongedAnimation) { - mProlongAnimation = false; + mProlongAnimation = PROLONG_ANIMATION_DISABLED; } else { mClearProlongedAnimation = true; } @@ -266,6 +270,10 @@ public class AppWindowAnimator { return false; } transformation.clear(); + if (mProlongAnimation == PROLONG_ANIMATION_AT_START) { + animation.setStartTime(currentTime); + currentTime += 1; + } boolean hasMoreFrames = animation.getTransformation(currentTime, transformation); if (!hasMoreFrames) { if (deferThumbnailDestruction && !deferFinalFrameCleanup) { @@ -278,7 +286,7 @@ public class AppWindowAnimator { "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames + ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation); deferFinalFrameCleanup = false; - if (mProlongAnimation) { + if (mProlongAnimation == PROLONG_ANIMATION_AT_END) { hasMoreFrames = true; } else { animation = null; @@ -434,13 +442,13 @@ public class AppWindowAnimator { } } - void startProlongAnimation() { - mProlongAnimation = true; + void startProlongAnimation(int prolongType) { + mProlongAnimation = prolongType; mClearProlongedAnimation = false; } void endProlongedAnimation() { - mProlongAnimation = false; + mProlongAnimation = PROLONG_ANIMATION_DISABLED; } // This is an animation that does nothing: it just immediately finishes diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c359c5306b4c..a9bd71ff703b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -50,6 +50,9 @@ import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END; +import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START; + import android.Manifest; import android.animation.ValueAnimator; import android.annotation.Nullable; @@ -909,7 +912,7 @@ public class WindowManagerService extends IWindowManager.Stub PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN"); mScreenFrozenLock.setReferenceCounted(false); - mAppTransition = new AppTransition(context, mH, mWindowMap, mWindowPlacerLocked); + mAppTransition = new AppTransition(context, this); mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier); mActivityManager = ActivityManagerNative.getDefault(); @@ -3692,22 +3695,26 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback, onAnimationFinishedCallback, scaleUp); - if (!scaleUp) { - // This is used by freeform to recents windows transition. We need to synchronize - // the animation with the appearance of the content of recents, so we will make - // animation stay on the last frame a little longer. - mTmpTaskIds.clear(); - for (int i = specs.length - 1; i >= 0; i--) { - mTmpTaskIds.put(specs[i].taskId, 0); - } - for (final WindowState win : mWindowMap.values()) { - final Task task = win.getTask(); - if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) { - final AppWindowToken appToken = win.mAppToken; - if (appToken != null && appToken.mAppAnimator != null) { - appToken.mAppAnimator.startProlongAnimation(); - } - } + prolongAnimationsFromSpecs(specs, scaleUp); + + } + } + + void prolongAnimationsFromSpecs(AppTransitionAnimationSpec[] specs, boolean scaleUp) { + // This is used by freeform <-> recents windows transition. We need to synchronize + // the animation with the appearance of the content of recents, so we will make + // animation stay on the first or last frame a little longer. + mTmpTaskIds.clear(); + for (int i = specs.length - 1; i >= 0; i--) { + mTmpTaskIds.put(specs[i].taskId, 0); + } + for (final WindowState win : mWindowMap.values()) { + final Task task = win.getTask(); + if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) { + final AppWindowToken appToken = win.mAppToken; + if (appToken != null && appToken.mAppAnimator != null) { + appToken.mAppAnimator.startProlongAnimation(scaleUp ? + PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END); } } } |