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