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