1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
20 
21 import android.view.animation.AlphaAnimation;
22 import android.view.animation.Animation;
23 import android.view.animation.AnimationUtils;
24 
25 import com.android.internal.R;
26 
27 import java.util.ArrayList;
28 
29 /**
30  * Controller to fade out and in windows when the display is changing rotation. It can be used for
31  * both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
32  * the windows until they are drawn with the new rotation.
33  */
34 public class FadeRotationAnimationController extends FadeAnimationController {
35 
36     private final ArrayList<WindowToken> mTargetWindowTokens = new ArrayList<>();
37     private final WindowManagerService mService;
38     /** If non-null, it usually indicates that there will be a screen rotation animation. */
39     private final Runnable mFrozenTimeoutRunnable;
40     private final WindowToken mNavBarToken;
41 
42     /** A runnable which gets called when the {@link #show()} is called. */
43     private Runnable mOnShowRunnable;
44 
45     /** Whether to use constant zero alpha animation. */
46     private boolean mHideImmediately;
47 
FadeRotationAnimationController(DisplayContent displayContent)48     public FadeRotationAnimationController(DisplayContent displayContent) {
49         super(displayContent);
50         mService = displayContent.mWmService;
51         mFrozenTimeoutRunnable = mService.mDisplayFrozen ? () -> {
52             synchronized (mService.mGlobalLock) {
53                 displayContent.finishFadeRotationAnimationIfPossible();
54                 mService.mWindowPlacerLocked.performSurfacePlacement();
55             }
56         } : null;
57         if (mFrozenTimeoutRunnable != null) {
58             // Hide the windows immediately because screen should have been covered by screenshot.
59             mHideImmediately = true;
60         }
61         final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
62         final WindowState navigationBar = displayPolicy.getNavigationBar();
63         if (navigationBar != null) {
64             mNavBarToken = navigationBar.mToken;
65             final RecentsAnimationController controller = mService.getRecentsAnimationController();
66             final boolean navBarControlledByRecents =
67                     controller != null && controller.isNavigationBarAttachedToApp();
68             // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation
69             // bar is currently controlled by recents animation.
70             if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) {
71                 mTargetWindowTokens.add(mNavBarToken);
72             }
73         } else {
74             mNavBarToken = null;
75         }
76         // Collect the target windows to fade out. The display won't wait for them to unfreeze.
77         final WindowState notificationShade = displayPolicy.getNotificationShade();
78         displayContent.forAllWindows(w -> {
79             if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate
80                     && !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar
81                     && w != notificationShade) {
82                 mTargetWindowTokens.add(w.mToken);
83             }
84         }, true /* traverseTopToBottom */);
85     }
86 
87     /** Applies show animation on the previously hidden window tokens. */
show()88     void show() {
89         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
90             final WindowToken windowToken = mTargetWindowTokens.get(i);
91             fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
92         }
93         mTargetWindowTokens.clear();
94         if (mFrozenTimeoutRunnable != null) {
95             mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
96         }
97         if (mOnShowRunnable != null) {
98             mOnShowRunnable.run();
99             mOnShowRunnable = null;
100         }
101     }
102 
103     /**
104      * Returns {@code true} if all target windows are shown. It only takes effects if this
105      * controller is created for normal rotation.
106      */
show(WindowToken token)107     boolean show(WindowToken token) {
108         if (mFrozenTimeoutRunnable != null && mTargetWindowTokens.remove(token)) {
109             fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
110             if (mTargetWindowTokens.isEmpty()) {
111                 mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
112                 return true;
113             }
114         }
115         return false;
116     }
117 
118     /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
hide()119     void hide() {
120         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
121             final WindowToken windowToken = mTargetWindowTokens.get(i);
122             fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
123         }
124         if (mFrozenTimeoutRunnable != null) {
125             mService.mH.postDelayed(mFrozenTimeoutRunnable,
126                     WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
127         }
128     }
129 
130     /** Hides the window immediately until it is drawn in new rotation. */
hideImmediately(WindowToken windowToken)131     void hideImmediately(WindowToken windowToken) {
132         final boolean original = mHideImmediately;
133         mHideImmediately = true;
134         mTargetWindowTokens.add(windowToken);
135         fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
136         mHideImmediately = original;
137     }
138 
139     /** Returns {@code true} if the window is handled by this controller. */
isHandledToken(WindowToken token)140     boolean isHandledToken(WindowToken token) {
141         return token == mNavBarToken || isTargetToken(token);
142     }
143 
144     /** Returns {@code true} if the controller will run fade animations on the window. */
isTargetToken(WindowToken token)145     boolean isTargetToken(WindowToken token) {
146         return mTargetWindowTokens.contains(token);
147     }
148 
setOnShowRunnable(Runnable onShowRunnable)149     void setOnShowRunnable(Runnable onShowRunnable) {
150         mOnShowRunnable = onShowRunnable;
151     }
152 
153     @Override
getFadeInAnimation()154     public Animation getFadeInAnimation() {
155         if (mFrozenTimeoutRunnable != null) {
156             // Use a shorter animation so it is easier to align with screen rotation animation.
157             return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
158         }
159         return super.getFadeInAnimation();
160     }
161 
162     @Override
getFadeOutAnimation()163     public Animation getFadeOutAnimation() {
164         if (mHideImmediately) {
165             return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */);
166         }
167         return super.getFadeOutAnimation();
168     }
169 }
170