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