• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 android.app.WindowConfiguration.ROTATION_UNDEFINED;
20 
21 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
22 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
23 
24 import android.content.res.Resources;
25 import android.graphics.Rect;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.util.Log;
29 import android.util.Slog;
30 import android.view.IPinnedTaskListener;
31 
32 import java.io.PrintWriter;
33 
34 /**
35  * Holds the common state of the pinned task between the system and SystemUI. If SystemUI ever
36  * needs to be restarted, it will be notified with the last known state.
37  *
38  * Changes to the pinned task also flow through this controller, and generally, the system only
39  * changes the pinned task bounds through this controller in two ways:
40  *
41  * 1) When first entering PiP: the controller returns the valid bounds given, taking aspect ratio
42  *    and IME state into account.
43  * 2) When rotating the device: the controller calculates the new bounds in the new orientation,
44  *    taking the IME state into account. In this case, we currently ignore the
45  *    SystemUI adjustments (ie. expanded for menu, interaction, etc).
46  *
47  * Other changes in the system, including adjustment of IME, configuration change, and more are
48  * handled by SystemUI (similar to the docked task divider).
49  */
50 class PinnedTaskController {
51 
52     private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedTaskController" : TAG_WM;
53     private static final int DEFER_ORIENTATION_CHANGE_TIMEOUT_MS = 1000;
54 
55     private final WindowManagerService mService;
56     private final DisplayContent mDisplayContent;
57 
58     private IPinnedTaskListener mPinnedTaskListener;
59     private final PinnedTaskListenerDeathHandler mPinnedTaskListenerDeathHandler =
60             new PinnedTaskListenerDeathHandler();
61 
62     /**
63      * Non-null if the entering PiP task will cause display rotation to change. The bounds are
64      * based on the new rotation.
65      */
66     private Rect mDestRotatedBounds;
67 
68     /** Whether to skip task configuration change once. */
69     private boolean mFreezingTaskConfig;
70     /** Defer display orientation change if the PiP task is animating across orientations. */
71     private boolean mDeferOrientationChanging;
72     private final Runnable mDeferOrientationTimeoutRunnable;
73 
74     private boolean mIsImeShowing;
75     private int mImeHeight;
76 
77     // The aspect ratio bounds of the PIP.
78     private float mMinAspectRatio;
79     private float mMaxAspectRatio;
80 
81     /**
82      * Handler for the case where the listener dies.
83      */
84     private class PinnedTaskListenerDeathHandler implements IBinder.DeathRecipient {
85 
86         @Override
binderDied()87         public void binderDied() {
88             synchronized (mService.mGlobalLock) {
89                 mPinnedTaskListener = null;
90                 mFreezingTaskConfig = false;
91                 mDeferOrientationTimeoutRunnable.run();
92             }
93         }
94     }
95 
PinnedTaskController(WindowManagerService service, DisplayContent displayContent)96     PinnedTaskController(WindowManagerService service, DisplayContent displayContent) {
97         mService = service;
98         mDisplayContent = displayContent;
99         mDeferOrientationTimeoutRunnable = () -> {
100             synchronized (mService.mGlobalLock) {
101                 if (mDeferOrientationChanging) {
102                     continueOrientationChange();
103                     mService.mWindowPlacerLocked.requestTraversal();
104                 }
105             }
106         };
107         reloadResources();
108     }
109 
110     /** Updates the resources used by pinned controllers.  */
onPostDisplayConfigurationChanged()111     void onPostDisplayConfigurationChanged() {
112         reloadResources();
113         mFreezingTaskConfig = false;
114     }
115 
116     /**
117      * Reloads all the resources for the current configuration.
118      */
reloadResources()119     private void reloadResources() {
120         final Resources res = mService.mContext.getResources();
121         mMinAspectRatio = res.getFloat(
122                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
123         mMaxAspectRatio = res.getFloat(
124                 com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
125     }
126 
127     /**
128      * Registers a pinned task listener.
129      */
registerPinnedTaskListener(IPinnedTaskListener listener)130     void registerPinnedTaskListener(IPinnedTaskListener listener) {
131         try {
132             listener.asBinder().linkToDeath(mPinnedTaskListenerDeathHandler, 0);
133             mPinnedTaskListener = listener;
134             notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
135             notifyMovementBoundsChanged(false /* fromImeAdjustment */);
136         } catch (RemoteException e) {
137             Log.e(TAG, "Failed to register pinned task listener", e);
138         }
139     }
140 
141     /**
142      * @return whether the given {@param aspectRatio} is valid, i.e. min <= ratio <= max.
143      */
isValidPictureInPictureAspectRatio(float aspectRatio)144     public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
145         return Float.compare(mMinAspectRatio, aspectRatio) <= 0
146                 && Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
147     }
148 
149     /**
150      * @return whether the given {@param aspectRatio} is valid, i.e. ratio < min or ratio > max.
151      */
isValidExpandedPictureInPictureAspectRatio(float aspectRatio)152     public boolean isValidExpandedPictureInPictureAspectRatio(float aspectRatio) {
153         return Float.compare(mMinAspectRatio, aspectRatio) > 0
154                 || Float.compare(aspectRatio, mMaxAspectRatio) > 0;
155     }
156 
157     /**
158      * Called when a fullscreen task is entering PiP with display orientation change. This is used
159      * to avoid flickering when running PiP animation across different orientations.
160      */
deferOrientationChangeForEnteringPipFromFullScreenIfNeeded()161     void deferOrientationChangeForEnteringPipFromFullScreenIfNeeded() {
162         final ActivityRecord topFullscreen = mDisplayContent.getActivity(
163                 a -> a.providesOrientation() && !a.getTask().inMultiWindowMode());
164         if (topFullscreen == null || topFullscreen.hasFixedRotationTransform()) {
165             return;
166         }
167         final int rotation = mDisplayContent.rotationForActivityInDifferentOrientation(
168                 topFullscreen);
169         if (rotation == ROTATION_UNDEFINED) {
170             return;
171         }
172         // If the next top activity will change the orientation of display, start fixed rotation to
173         // notify PipTaskOrganizer before it receives task appeared. And defer display orientation
174         // update until the new PiP bounds are set.
175         mDisplayContent.setFixedRotationLaunchingApp(topFullscreen, rotation);
176         mDeferOrientationChanging = true;
177         mService.mH.removeCallbacks(mDeferOrientationTimeoutRunnable);
178         final float animatorScale = Math.max(1, mService.getCurrentAnimatorScale());
179         mService.mH.postDelayed(mDeferOrientationTimeoutRunnable,
180                 (int) (animatorScale * DEFER_ORIENTATION_CHANGE_TIMEOUT_MS));
181     }
182 
183     /** Defers orientation change while there is a top fixed rotation activity. */
shouldDeferOrientationChange()184     boolean shouldDeferOrientationChange() {
185         return mDeferOrientationChanging;
186     }
187 
188     /**
189      * Sets the bounds for {@link #startSeamlessRotationIfNeeded} if the orientation of display
190      * will be changed.
191      */
setEnterPipBounds(Rect bounds)192     void setEnterPipBounds(Rect bounds) {
193         if (!mDeferOrientationChanging) {
194             return;
195         }
196         mFreezingTaskConfig = true;
197         mDestRotatedBounds = new Rect(bounds);
198         if (!mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
199             continueOrientationChange();
200         }
201     }
202 
203     /**
204      * Sets a hint if the orientation of display will be changed. This is only called when
205      * finishing recents animation with pending orientation change that will be handled by
206      * {@link DisplayContent.FixedRotationTransitionListener}.
207      */
setEnterPipWithRotatedTransientLaunch()208     void setEnterPipWithRotatedTransientLaunch() {
209         mFreezingTaskConfig = true;
210     }
211 
212     /** Called when the activity in PiP task has PiP windowing mode (at the end of animation). */
continueOrientationChange()213     private void continueOrientationChange() {
214         mDeferOrientationChanging = false;
215         mService.mH.removeCallbacks(mDeferOrientationTimeoutRunnable);
216         final WindowContainer<?> orientationSource = mDisplayContent.getLastOrientationSource();
217         if (orientationSource != null && !orientationSource.isAppTransitioning()) {
218             mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
219         }
220     }
221 
222     /**
223      * Returns {@code true} to skip {@link Task#onConfigurationChanged} because it is expected that
224      * there will be a orientation change and a PiP configuration change.
225      */
isFreezingTaskConfig(Task task)226     boolean isFreezingTaskConfig(Task task) {
227         return mFreezingTaskConfig
228                 && task == mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask();
229     }
230 
231     /** Resets the states which were used to perform fixed rotation with PiP task. */
onCancelFixedRotationTransform()232     void onCancelFixedRotationTransform() {
233         mFreezingTaskConfig = false;
234         mDeferOrientationChanging = false;
235         mDestRotatedBounds = null;
236     }
237 
238     /**
239      * Sets the Ime state and height.
240      */
setAdjustedForIme(boolean adjustedForIme, int imeHeight)241     void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
242         // Due to the order of callbacks from the system, we may receive an ime height even when
243         // {@param adjustedForIme} is false, and also a zero height when {@param adjustedForIme}
244         // is true.  Instead, ensure that the ime state changes with the height and if the ime is
245         // showing, then the height is non-zero.
246         final boolean imeShowing = adjustedForIme && imeHeight > 0;
247         imeHeight = imeShowing ? imeHeight : 0;
248         if (imeShowing == mIsImeShowing && imeHeight == mImeHeight) {
249             return;
250         }
251 
252         mIsImeShowing = imeShowing;
253         mImeHeight = imeHeight;
254         notifyImeVisibilityChanged(imeShowing, imeHeight);
255         notifyMovementBoundsChanged(true /* fromImeAdjustment */);
256     }
257 
258     /**
259      * Notifies listeners that the PIP needs to be adjusted for the IME.
260      */
notifyImeVisibilityChanged(boolean imeVisible, int imeHeight)261     private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) {
262         if (mPinnedTaskListener == null) {
263             return;
264         }
265 
266         try {
267             mPinnedTaskListener.onImeVisibilityChanged(imeVisible, imeHeight);
268         } catch (RemoteException e) {
269             Slog.e(TAG_WM, "Error delivering ime visibility changed event.", e);
270         }
271     }
272 
273     /**
274      * Notifies listeners that the PIP movement bounds have changed.
275      */
notifyMovementBoundsChanged(boolean fromImeAdjustment)276     private void notifyMovementBoundsChanged(boolean fromImeAdjustment) {
277         if (mPinnedTaskListener == null) {
278             return;
279         }
280 
281         try {
282             mPinnedTaskListener.onMovementBoundsChanged(fromImeAdjustment);
283         } catch (RemoteException e) {
284             Slog.e(TAG_WM, "Error delivering movement bounds changed event.", e);
285         }
286     }
287 
dump(String prefix, PrintWriter pw)288     void dump(String prefix, PrintWriter pw) {
289         pw.println(prefix + "PinnedTaskController");
290         if (mDeferOrientationChanging) pw.println(prefix + "  mDeferOrientationChanging=true");
291         if (mFreezingTaskConfig) pw.println(prefix + "  mFreezingTaskConfig=true");
292         if (mDestRotatedBounds != null) {
293             pw.println(prefix + "  mPendingBounds=" + mDestRotatedBounds);
294         }
295         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
296         pw.println(prefix + "  mImeHeight=" + mImeHeight);
297         pw.println(prefix + "  mMinAspectRatio=" + mMinAspectRatio);
298         pw.println(prefix + "  mMaxAspectRatio=" + mMaxAspectRatio);
299     }
300 }
301