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