• 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 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 
22 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
24 
25 import android.app.PictureInPictureParams;
26 import android.app.RemoteAction;
27 import android.content.ComponentName;
28 import android.content.pm.ParceledListSlice;
29 import android.content.res.Resources;
30 import android.graphics.Insets;
31 import android.graphics.Matrix;
32 import android.graphics.Rect;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.util.Log;
36 import android.util.RotationUtils;
37 import android.util.Slog;
38 import android.view.IPinnedTaskListener;
39 import android.view.Surface;
40 import android.view.SurfaceControl;
41 import android.window.PictureInPictureSurfaceTransaction;
42 
43 import java.io.PrintWriter;
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /**
48  * Holds the common state of the pinned task between the system and SystemUI. If SystemUI ever
49  * needs to be restarted, it will be notified with the last known state.
50  *
51  * Changes to the pinned task also flow through this controller, and generally, the system only
52  * changes the pinned task bounds through this controller in two ways:
53  *
54  * 1) When first entering PiP: the controller returns the valid bounds given, taking aspect ratio
55  *    and IME state into account.
56  * 2) When rotating the device: the controller calculates the new bounds in the new orientation,
57  *    taking the IME state into account. In this case, we currently ignore the
58  *    SystemUI adjustments (ie. expanded for menu, interaction, etc).
59  *
60  * Other changes in the system, including adjustment of IME, configuration change, and more are
61  * handled by SystemUI (similar to the docked task divider).
62  */
63 class PinnedTaskController {
64 
65     private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedTaskController" : TAG_WM;
66     private static final int DEFER_ORIENTATION_CHANGE_TIMEOUT_MS = 1000;
67 
68     private final WindowManagerService mService;
69     private final DisplayContent mDisplayContent;
70 
71     private IPinnedTaskListener mPinnedTaskListener;
72     private final PinnedTaskListenerDeathHandler mPinnedTaskListenerDeathHandler =
73             new PinnedTaskListenerDeathHandler();
74 
75     /**
76      * Non-null if the entering PiP task will cause display rotation to change. The bounds are
77      * based on the new rotation.
78      */
79     private Rect mDestRotatedBounds;
80     /**
81      * Non-null if the entering PiP task from recents animation will cause display rotation to
82      * change. The transaction is based on the old rotation.
83      */
84     private PictureInPictureSurfaceTransaction mPipTransaction;
85     /** Whether to skip task configuration change once. */
86     private boolean mFreezingTaskConfig;
87     /** Defer display orientation change if the PiP task is animating across orientations. */
88     private boolean mDeferOrientationChanging;
89     private final Runnable mDeferOrientationTimeoutRunnable;
90 
91     private boolean mIsImeShowing;
92     private int mImeHeight;
93 
94     // The set of actions and aspect-ratio for the that are currently allowed on the PiP activity
95     private ArrayList<RemoteAction> mActions = new ArrayList<>();
96     private float mAspectRatio = -1f;
97 
98     // The aspect ratio bounds of the PIP.
99     private float mMinAspectRatio;
100     private float mMaxAspectRatio;
101 
102     /**
103      * Handler for the case where the listener dies.
104      */
105     private class PinnedTaskListenerDeathHandler implements IBinder.DeathRecipient {
106 
107         @Override
binderDied()108         public void binderDied() {
109             synchronized (mService.mGlobalLock) {
110                 mPinnedTaskListener = null;
111                 mFreezingTaskConfig = false;
112                 mDeferOrientationTimeoutRunnable.run();
113             }
114         }
115     }
116 
PinnedTaskController(WindowManagerService service, DisplayContent displayContent)117     PinnedTaskController(WindowManagerService service, DisplayContent displayContent) {
118         mService = service;
119         mDisplayContent = displayContent;
120         mDeferOrientationTimeoutRunnable = () -> {
121             synchronized (mService.mGlobalLock) {
122                 if (mDeferOrientationChanging) {
123                     continueOrientationChange();
124                     mService.mWindowPlacerLocked.requestTraversal();
125                 }
126             }
127         };
128         reloadResources();
129     }
130 
131     /** Updates the resources used by pinned controllers.  */
onPostDisplayConfigurationChanged()132     void onPostDisplayConfigurationChanged() {
133         reloadResources();
134         mFreezingTaskConfig = false;
135     }
136 
137     /**
138      * Reloads all the resources for the current configuration.
139      */
reloadResources()140     private void reloadResources() {
141         final Resources res = mService.mContext.getResources();
142         mMinAspectRatio = res.getFloat(
143                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
144         mMaxAspectRatio = res.getFloat(
145                 com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
146     }
147 
148     /**
149      * Registers a pinned task listener.
150      */
registerPinnedTaskListener(IPinnedTaskListener listener)151     void registerPinnedTaskListener(IPinnedTaskListener listener) {
152         try {
153             listener.asBinder().linkToDeath(mPinnedTaskListenerDeathHandler, 0);
154             mPinnedTaskListener = listener;
155             notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
156             notifyMovementBoundsChanged(false /* fromImeAdjustment */);
157             notifyActionsChanged(mActions);
158         } catch (RemoteException e) {
159             Log.e(TAG, "Failed to register pinned task listener", e);
160         }
161     }
162 
163     /**
164      * @return whether the given {@param aspectRatio} is valid.
165      */
isValidPictureInPictureAspectRatio(float aspectRatio)166     public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
167         return Float.compare(mMinAspectRatio, aspectRatio) <= 0
168                 && Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
169     }
170 
171     /**
172      * Called when a fullscreen task is entering PiP with display orientation change. This is used
173      * to avoid flickering when running PiP animation across different orientations.
174      */
deferOrientationChangeForEnteringPipFromFullScreenIfNeeded()175     void deferOrientationChangeForEnteringPipFromFullScreenIfNeeded() {
176         final Task topFullscreenTask = mDisplayContent.getDefaultTaskDisplayArea()
177                 .getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
178         final ActivityRecord topFullscreen = topFullscreenTask != null
179                 ? topFullscreenTask.topRunningActivity() : null;
180         if (topFullscreen == null || topFullscreen.hasFixedRotationTransform()) {
181             return;
182         }
183         final int rotation = mDisplayContent.rotationForActivityInDifferentOrientation(
184                 topFullscreen);
185         if (rotation == ROTATION_UNDEFINED) {
186             return;
187         }
188         // If the next top activity will change the orientation of display, start fixed rotation to
189         // notify PipTaskOrganizer before it receives task appeared. And defer display orientation
190         // update until the new PiP bounds are set.
191         mDisplayContent.setFixedRotationLaunchingApp(topFullscreen, rotation);
192         mDeferOrientationChanging = true;
193         mService.mH.removeCallbacks(mDeferOrientationTimeoutRunnable);
194         final float animatorScale = Math.max(1, mService.getCurrentAnimatorScale());
195         mService.mH.postDelayed(mDeferOrientationTimeoutRunnable,
196                 (int) (animatorScale * DEFER_ORIENTATION_CHANGE_TIMEOUT_MS));
197     }
198 
199     /** Defers orientation change while there is a top fixed rotation activity. */
shouldDeferOrientationChange()200     boolean shouldDeferOrientationChange() {
201         return mDeferOrientationChanging;
202     }
203 
204     /**
205      * Sets the bounds for {@link #startSeamlessRotationIfNeeded} if the orientation of display
206      * will be changed.
207      */
setEnterPipBounds(Rect bounds)208     void setEnterPipBounds(Rect bounds) {
209         if (!mDeferOrientationChanging) {
210             return;
211         }
212         mFreezingTaskConfig = true;
213         mDestRotatedBounds = new Rect(bounds);
214         continueOrientationChange();
215     }
216 
217     /**
218      * Sets the transaction for {@link #startSeamlessRotationIfNeeded} if the orientation of display
219      * will be changed. This is only called when finishing recents animation with pending
220      * orientation change that will be handled by
221      * {@link DisplayContent.FixedRotationTransitionListener#onFinishRecentsAnimation}.
222      */
setEnterPipTransaction(PictureInPictureSurfaceTransaction tx)223     void setEnterPipTransaction(PictureInPictureSurfaceTransaction tx) {
224         mFreezingTaskConfig = true;
225         mPipTransaction = tx;
226     }
227 
228     /** Called when the activity in PiP task has PiP windowing mode (at the end of animation). */
continueOrientationChange()229     private void continueOrientationChange() {
230         mDeferOrientationChanging = false;
231         mService.mH.removeCallbacks(mDeferOrientationTimeoutRunnable);
232         final WindowContainer<?> orientationSource = mDisplayContent.getLastOrientationSource();
233         if (orientationSource != null && !orientationSource.isAppTransitioning()) {
234             mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
235         }
236     }
237 
238     /**
239      * Resets rotation and applies scale and position to PiP task surface to match the current
240      * rotation of display. The final surface matrix will be replaced by PiPTaskOrganizer after it
241      * receives the callback of fixed rotation completion.
242      */
startSeamlessRotationIfNeeded(SurfaceControl.Transaction t, int oldRotation, int newRotation)243     void startSeamlessRotationIfNeeded(SurfaceControl.Transaction t,
244             int oldRotation, int newRotation) {
245         final Rect bounds = mDestRotatedBounds;
246         final PictureInPictureSurfaceTransaction pipTx = mPipTransaction;
247         if (bounds == null && pipTx == null) {
248             return;
249         }
250         final TaskDisplayArea taskArea = mDisplayContent.getDefaultTaskDisplayArea();
251         final Task pinnedTask = taskArea.getRootPinnedTask();
252         if (pinnedTask == null) {
253             return;
254         }
255 
256         mDestRotatedBounds = null;
257         mPipTransaction = null;
258         final Rect areaBounds = taskArea.getBounds();
259         if (pipTx != null) {
260             // The transaction from recents animation is in old rotation. So the position needs to
261             // be rotated.
262             float dx = pipTx.mPositionX;
263             float dy = pipTx.mPositionY;
264             final Matrix matrix = pipTx.getMatrix();
265             if (pipTx.mRotation == 90) {
266                 dx = pipTx.mPositionY;
267                 dy = areaBounds.right - pipTx.mPositionX;
268                 matrix.postRotate(-90);
269             } else if (pipTx.mRotation == -90) {
270                 dx = areaBounds.bottom - pipTx.mPositionY;
271                 dy = pipTx.mPositionX;
272                 matrix.postRotate(90);
273             }
274             matrix.postTranslate(dx, dy);
275             final SurfaceControl leash = pinnedTask.getSurfaceControl();
276             t.setMatrix(leash, matrix, new float[9])
277                     .setCornerRadius(leash, pipTx.mCornerRadius);
278             Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy);
279             return;
280         }
281 
282         final PictureInPictureParams params = pinnedTask.getPictureInPictureParams();
283         final Rect sourceHintRect = params != null && params.hasSourceBoundsHint()
284                 ? params.getSourceRectHint()
285                 : null;
286         Slog.i(TAG, "Seamless rotation PiP bounds=" + bounds + " hintRect=" + sourceHintRect);
287         final int rotationDelta = RotationUtils.deltaRotation(oldRotation, newRotation);
288         // Adjust for display cutout if applicable.
289         if (sourceHintRect != null && rotationDelta == Surface.ROTATION_270) {
290             if (pinnedTask.getDisplayCutoutInsets() != null) {
291                 final int rotationBackDelta = RotationUtils.deltaRotation(newRotation, oldRotation);
292                 final Rect displayCutoutInsets = RotationUtils.rotateInsets(
293                         Insets.of(pinnedTask.getDisplayCutoutInsets()), rotationBackDelta).toRect();
294                 sourceHintRect.offset(displayCutoutInsets.left, displayCutoutInsets.top);
295             }
296         }
297         final Rect contentBounds = sourceHintRect != null && areaBounds.contains(sourceHintRect)
298                 ? sourceHintRect : areaBounds;
299         final int w = contentBounds.width();
300         final int h = contentBounds.height();
301         final float scale = w <= h ? (float) bounds.width() / w : (float) bounds.height() / h;
302         final int insetLeft = (int) ((contentBounds.left - areaBounds.left) * scale + .5f);
303         final int insetTop = (int) ((contentBounds.top - areaBounds.top) * scale + .5f);
304         final Matrix matrix = new Matrix();
305         matrix.setScale(scale, scale);
306         matrix.postTranslate(bounds.left - insetLeft, bounds.top - insetTop);
307         t.setMatrix(pinnedTask.getSurfaceControl(), matrix, new float[9]);
308     }
309 
310     /**
311      * Returns {@code true} to skip {@link Task#onConfigurationChanged} because it is expected that
312      * there will be a orientation change and a PiP configuration change.
313      */
isFreezingTaskConfig(Task task)314     boolean isFreezingTaskConfig(Task task) {
315         return mFreezingTaskConfig
316                 && task == mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask();
317     }
318 
319     /** Resets the states which were used to perform fixed rotation with PiP task. */
onCancelFixedRotationTransform(Task task)320     void onCancelFixedRotationTransform(Task task) {
321         mFreezingTaskConfig = false;
322         mDeferOrientationChanging = false;
323         mDestRotatedBounds = null;
324         mPipTransaction = null;
325         if (!task.isOrganized()) {
326             // Force clearing Task#mForceNotOrganized because the display didn't rotate.
327             task.onConfigurationChanged(task.getParent().getConfiguration());
328         }
329     }
330 
331     /**
332      * Activity is hidden (either stopped or removed), resets the last saved snap fraction
333      * so that the default bounds will be returned for the next session.
334      */
onActivityHidden(ComponentName componentName)335     void onActivityHidden(ComponentName componentName) {
336         if (mPinnedTaskListener == null) return;
337         try {
338             mPinnedTaskListener.onActivityHidden(componentName);
339         } catch (RemoteException e) {
340             Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e);
341         }
342     }
343 
344     /**
345      * Sets the Ime state and height.
346      */
setAdjustedForIme(boolean adjustedForIme, int imeHeight)347     void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
348         // Due to the order of callbacks from the system, we may receive an ime height even when
349         // {@param adjustedForIme} is false, and also a zero height when {@param adjustedForIme}
350         // is true.  Instead, ensure that the ime state changes with the height and if the ime is
351         // showing, then the height is non-zero.
352         final boolean imeShowing = adjustedForIme && imeHeight > 0;
353         imeHeight = imeShowing ? imeHeight : 0;
354         if (imeShowing == mIsImeShowing && imeHeight == mImeHeight) {
355             return;
356         }
357 
358         mIsImeShowing = imeShowing;
359         mImeHeight = imeHeight;
360         notifyImeVisibilityChanged(imeShowing, imeHeight);
361         notifyMovementBoundsChanged(true /* fromImeAdjustment */);
362     }
363 
364     /**
365      * Sets the current aspect ratio.
366      */
setAspectRatio(float aspectRatio)367     void setAspectRatio(float aspectRatio) {
368         if (Float.compare(mAspectRatio, aspectRatio) != 0) {
369             mAspectRatio = aspectRatio;
370             notifyAspectRatioChanged(aspectRatio);
371             notifyMovementBoundsChanged(false /* fromImeAdjustment */);
372         }
373     }
374 
375     /**
376      * @return the current aspect ratio.
377      */
getAspectRatio()378     float getAspectRatio() {
379         return mAspectRatio;
380     }
381 
382     /**
383      * Sets the current set of actions.
384      */
setActions(List<RemoteAction> actions)385     void setActions(List<RemoteAction> actions) {
386         mActions.clear();
387         if (actions != null) {
388             mActions.addAll(actions);
389         }
390         notifyActionsChanged(mActions);
391     }
392 
393     /**
394      * Notifies listeners that the PIP needs to be adjusted for the IME.
395      */
notifyImeVisibilityChanged(boolean imeVisible, int imeHeight)396     private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) {
397         if (mPinnedTaskListener != null) {
398             try {
399                 mPinnedTaskListener.onImeVisibilityChanged(imeVisible, imeHeight);
400             } catch (RemoteException e) {
401                 Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
402             }
403         }
404     }
405 
notifyAspectRatioChanged(float aspectRatio)406     private void notifyAspectRatioChanged(float aspectRatio) {
407         if (mPinnedTaskListener == null) return;
408         try {
409             mPinnedTaskListener.onAspectRatioChanged(aspectRatio);
410         } catch (RemoteException e) {
411             Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e);
412         }
413     }
414 
415     /**
416      * Notifies listeners that the PIP actions have changed.
417      */
notifyActionsChanged(List<RemoteAction> actions)418     private void notifyActionsChanged(List<RemoteAction> actions) {
419         if (mPinnedTaskListener != null) {
420             try {
421                 mPinnedTaskListener.onActionsChanged(new ParceledListSlice(actions));
422             } catch (RemoteException e) {
423                 Slog.e(TAG_WM, "Error delivering actions changed event.", e);
424             }
425         }
426     }
427 
428     /**
429      * Notifies listeners that the PIP movement bounds have changed.
430      */
notifyMovementBoundsChanged(boolean fromImeAdjustment)431     private void notifyMovementBoundsChanged(boolean fromImeAdjustment) {
432         synchronized (mService.mGlobalLock) {
433             if (mPinnedTaskListener == null) {
434                 return;
435             }
436             try {
437                 mPinnedTaskListener.onMovementBoundsChanged(fromImeAdjustment);
438             } catch (RemoteException e) {
439                 Slog.e(TAG_WM, "Error delivering actions changed event.", e);
440             }
441         }
442     }
443 
dump(String prefix, PrintWriter pw)444     void dump(String prefix, PrintWriter pw) {
445         pw.println(prefix + "PinnedTaskController");
446         if (mDeferOrientationChanging) pw.println(prefix + "  mDeferOrientationChanging=true");
447         if (mFreezingTaskConfig) pw.println(prefix + "  mFreezingTaskConfig=true");
448         if (mDestRotatedBounds != null) {
449             pw.println(prefix + "  mPendingBounds=" + mDestRotatedBounds);
450         }
451         if (mPipTransaction != null) {
452             pw.println(prefix + "  mPipTransaction=" + mPipTransaction);
453         }
454         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
455         pw.println(prefix + "  mImeHeight=" + mImeHeight);
456         pw.println(prefix + "  mAspectRatio=" + mAspectRatio);
457         pw.println(prefix + "  mMinAspectRatio=" + mMinAspectRatio);
458         pw.println(prefix + "  mMaxAspectRatio=" + mMaxAspectRatio);
459         if (mActions.isEmpty()) {
460             pw.println(prefix + "  mActions=[]");
461         } else {
462             pw.println(prefix + "  mActions=[");
463             for (int i = 0; i < mActions.size(); i++) {
464                 RemoteAction action = mActions.get(i);
465                 pw.print(prefix + "    Action[" + i + "]: ");
466                 action.dump("", pw);
467             }
468             pw.println(prefix + "  ]");
469         }
470     }
471 }
472