1 /* 2 * Copyright (C) 2021 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 com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.WindowConfiguration; 24 import android.content.ComponentName; 25 import android.graphics.Point; 26 import android.graphics.Rect; 27 import android.hardware.HardwareBuffer; 28 import android.os.Bundle; 29 import android.os.IBinder; 30 import android.os.RemoteCallback; 31 import android.os.RemoteException; 32 import android.os.SystemProperties; 33 import android.util.Slog; 34 import android.view.IWindowFocusObserver; 35 import android.view.RemoteAnimationTarget; 36 import android.view.SurfaceControl; 37 import android.window.BackAnimationAdaptor; 38 import android.window.BackNavigationInfo; 39 import android.window.OnBackInvokedCallbackInfo; 40 import android.window.TaskSnapshot; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.protolog.common.ProtoLog; 44 import com.android.server.LocalServices; 45 46 /** 47 * Controller to handle actions related to the back gesture on the server side. 48 */ 49 class BackNavigationController { 50 static final String TAG = "BackNavigationController"; 51 WindowManagerService mWindowManagerService; 52 private IWindowFocusObserver mFocusObserver; 53 // TODO (b/241808055) Find a appropriate time to remove during refactor 54 // Execute back animation with legacy transition system. Temporary flag for easier debugging. 55 static final boolean USE_TRANSITION = 56 SystemProperties.getInt("persist.wm.debug.predictive_back_ani_trans", 1) != 0; 57 58 BackNaviAnimationController mBackNaviAnimationController; 59 60 /** 61 * Returns true if the back predictability feature is enabled 62 */ isEnabled()63 static boolean isEnabled() { 64 return SystemProperties.getInt("persist.wm.debug.predictive_back", 1) != 0; 65 } 66 isScreenshotEnabled()67 static boolean isScreenshotEnabled() { 68 return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0; 69 } 70 71 /** 72 * Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming 73 * back gesture animation. 74 * 75 * @return a {@link BackNavigationInfo} instance containing the required leashes and metadata 76 * for the animation, or null if we don't know how to animate the current window and need to 77 * fallback on dispatching the key event. 78 */ 79 @VisibleForTesting 80 @Nullable startBackNavigation(boolean requestAnimation, IWindowFocusObserver observer, BackAnimationAdaptor backAnimationAdaptor)81 BackNavigationInfo startBackNavigation(boolean requestAnimation, 82 IWindowFocusObserver observer, BackAnimationAdaptor backAnimationAdaptor) { 83 final WindowManagerService wmService = mWindowManagerService; 84 final SurfaceControl.Transaction tx = wmService.mTransactionFactory.get(); 85 mFocusObserver = observer; 86 87 int backType = BackNavigationInfo.TYPE_UNDEFINED; 88 89 // The currently visible activity (if any). 90 ActivityRecord currentActivity = null; 91 92 // The currently visible task (if any). 93 Task currentTask = null; 94 95 // The previous task we're going back to. Can be the same as currentTask, if there are 96 // multiple Activities in the Stack. 97 Task prevTask = null; 98 99 // The previous activity we're going back to. This can be either a child of currentTask 100 // if there are more than one Activity in currentTask, or a child of prevTask, if 101 // currentActivity is the last child of currentTask. 102 ActivityRecord prevActivity; 103 WindowContainer<?> removedWindowContainer = null; 104 SurfaceControl animationLeashParent = null; 105 HardwareBuffer screenshotBuffer = null; 106 RemoteAnimationTarget topAppTarget = null; 107 WindowState window; 108 109 int prevTaskId; 110 int prevUserId; 111 boolean prepareAnimation; 112 113 BackNavigationInfo.Builder infoBuilder = new BackNavigationInfo.Builder(); 114 synchronized (wmService.mGlobalLock) { 115 WindowConfiguration taskWindowConfiguration; 116 WindowManagerInternal windowManagerInternal = 117 LocalServices.getService(WindowManagerInternal.class); 118 IBinder focusedWindowToken = windowManagerInternal.getFocusedWindowToken(); 119 120 window = wmService.getFocusedWindowLocked(); 121 122 if (window == null) { 123 EmbeddedWindowController.EmbeddedWindow embeddedWindow = 124 wmService.mEmbeddedWindowController.getByFocusToken(focusedWindowToken); 125 if (embeddedWindow != null) { 126 ProtoLog.d(WM_DEBUG_BACK_PREVIEW, 127 "Current focused window is embeddedWindow. Dispatch KEYCODE_BACK."); 128 return null; 129 } 130 } 131 132 // Lets first gather the states of things 133 // - What is our current window ? 134 // - Does it has an Activity and a Task ? 135 // TODO Temp workaround for Sysui until b/221071505 is fixed 136 if (window != null) { 137 ProtoLog.d(WM_DEBUG_BACK_PREVIEW, 138 "Focused window found using getFocusedWindowToken"); 139 } 140 141 if (window != null) { 142 // This is needed to bridge the old and new back behavior with recents. While in 143 // Overview with live tile enabled, the previous app is technically focused but we 144 // add an input consumer to capture all input that would otherwise go to the apps 145 // being controlled by the animation. This means that the window resolved is not 146 // the right window to consume back while in overview, so we need to route it to 147 // launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing 148 // compat callback in VRI only works when the window is focused. 149 // This symptom also happen while shell transition enabled, we can check that by 150 // isTransientLaunch to know whether the focus window is point to live tile. 151 final RecentsAnimationController recentsAnimationController = 152 wmService.getRecentsAnimationController(); 153 final ActivityRecord ar = window.mActivityRecord; 154 if ((ar != null && ar.isActivityTypeHomeOrRecents() 155 && ar.mTransitionController.isTransientLaunch(ar)) 156 || (recentsAnimationController != null 157 && recentsAnimationController.shouldApplyInputConsumer(ar))) { 158 ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by " 159 + "recents. Overriding back callback to recents controller callback."); 160 return null; 161 } 162 } 163 164 if (window == null) { 165 // We don't have any focused window, fallback ont the top currentTask of the focused 166 // display. 167 ProtoLog.w(WM_DEBUG_BACK_PREVIEW, 168 "No focused window, defaulting to top current task's window"); 169 currentTask = wmService.mAtmService.getTopDisplayFocusedRootTask(); 170 window = currentTask.getWindow(WindowState::isFocused); 171 } 172 173 // Now let's find if this window has a callback from the client side. 174 OnBackInvokedCallbackInfo callbackInfo = null; 175 if (window != null) { 176 currentActivity = window.mActivityRecord; 177 currentTask = window.getTask(); 178 callbackInfo = window.getOnBackInvokedCallbackInfo(); 179 if (callbackInfo == null) { 180 Slog.e(TAG, "No callback registered, returning null."); 181 return null; 182 } 183 if (!callbackInfo.isSystemCallback()) { 184 backType = BackNavigationInfo.TYPE_CALLBACK; 185 } 186 infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback()); 187 if (mFocusObserver != null) { 188 window.registerFocusObserver(mFocusObserver); 189 } 190 } 191 192 ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, " 193 + "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s", 194 currentTask, currentActivity, callbackInfo, window); 195 196 if (window == null) { 197 Slog.e(TAG, "Window is null, returning null."); 198 return null; 199 } 200 201 // If we don't need to set up the animation, we return early. This is the case when 202 // - We have an application callback. 203 // - We don't have any ActivityRecord or Task to animate. 204 // - The IME is opened, and we just need to close it. 205 // - The home activity is the focused activity. 206 if (backType == BackNavigationInfo.TYPE_CALLBACK 207 || currentActivity == null 208 || currentTask == null 209 || currentActivity.isActivityTypeHome()) { 210 infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK); 211 final WindowState finalFocusedWindow = window; 212 infoBuilder.setOnBackNavigationDone(new RemoteCallback(result -> 213 onBackNavigationDone(result, finalFocusedWindow, finalFocusedWindow, 214 BackNavigationInfo.TYPE_CALLBACK, null, null, false))); 215 216 return infoBuilder.setType(backType).build(); 217 } 218 219 // We don't have an application callback, let's find the destination of the back gesture 220 Task finalTask = currentTask; 221 prevActivity = currentTask.getActivity( 222 (r) -> !r.finishing && r.getTask() == finalTask && !r.isTopRunningActivity()); 223 // TODO Dialog window does not need to attach on activity, check 224 // window.mAttrs.type != TYPE_BASE_APPLICATION 225 if ((window.getParent().getChildCount() > 1 226 && window.getParent().getChildAt(0) != window)) { 227 // Are we the top window of our parent? If not, we are a window on top of the 228 // activity, we won't close the activity. 229 backType = BackNavigationInfo.TYPE_DIALOG_CLOSE; 230 removedWindowContainer = window; 231 } else if (prevActivity != null) { 232 // We have another Activity in the same currentTask to go to 233 backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY; 234 removedWindowContainer = currentActivity; 235 } else if (currentTask.returnsToHomeRootTask()) { 236 // Our Task should bring back to home 237 removedWindowContainer = currentTask; 238 backType = BackNavigationInfo.TYPE_RETURN_TO_HOME; 239 } else if (currentActivity.isRootOfTask()) { 240 // TODO(208789724): Create single source of truth for this, maybe in 241 // RootWindowContainer 242 // TODO: Also check Task.shouldUpRecreateTaskLocked() for prevActivity logic 243 prevTask = currentTask.mRootWindowContainer.getTaskBelow(currentTask); 244 removedWindowContainer = currentTask; 245 prevActivity = prevTask.getTopNonFinishingActivity(); 246 if (prevTask.isActivityTypeHome()) { 247 backType = BackNavigationInfo.TYPE_RETURN_TO_HOME; 248 } else { 249 backType = BackNavigationInfo.TYPE_CROSS_TASK; 250 } 251 } 252 infoBuilder.setType(backType); 253 254 prevTaskId = prevTask != null ? prevTask.mTaskId : 0; 255 prevUserId = prevTask != null ? prevTask.mUserId : 0; 256 257 ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Destination is Activity:%s Task:%s " 258 + "removedContainer:%s, backType=%s", 259 prevActivity != null ? prevActivity.mActivityComponent : null, 260 prevTask != null ? prevTask.getName() : null, 261 removedWindowContainer, 262 BackNavigationInfo.typeToString(backType)); 263 264 // For now, we only animate when going home. 265 prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME 266 && requestAnimation 267 // Only create a new leash if no leash has been created. 268 // Otherwise return null for animation target to avoid conflict. 269 // TODO isAnimating, recents can cancel app transition animation, can't back 270 // cancel like recents? 271 && !removedWindowContainer.hasCommittedReparentToAnimationLeash(); 272 273 if (prepareAnimation) { 274 taskWindowConfiguration = 275 currentTask.getTaskInfo().configuration.windowConfiguration; 276 277 infoBuilder.setTaskWindowConfiguration(taskWindowConfiguration); 278 if (!USE_TRANSITION) { 279 // Prepare a leash to animate the current top window 280 // TODO(b/220934562): Use surface animator to better manage animation conflicts. 281 SurfaceControl animLeash = removedWindowContainer.makeAnimationLeash() 282 .setName("BackPreview Leash for " + removedWindowContainer) 283 .setHidden(false) 284 .setBLASTLayer() 285 .build(); 286 removedWindowContainer.reparentSurfaceControl(tx, animLeash); 287 animationLeashParent = removedWindowContainer.getAnimationLeashParent(); 288 topAppTarget = createRemoteAnimationTargetLocked(removedWindowContainer, 289 currentActivity, 290 currentTask, animLeash); 291 infoBuilder.setDepartingAnimationTarget(topAppTarget); 292 } 293 } 294 295 //TODO(207481538) Remove once the infrastructure to support per-activity screenshot is 296 // implemented. For now we simply have the mBackScreenshots hash map that dumbly 297 // saves the screenshots. 298 if (needsScreenshot(backType) && prevActivity != null 299 && prevActivity.mActivityComponent != null) { 300 screenshotBuffer = 301 getActivitySnapshot(currentTask, prevActivity.mActivityComponent); 302 } 303 304 // Special handling for back to home animation 305 if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation 306 && prevTask != null) { 307 if (USE_TRANSITION && mBackNaviAnimationController == null) { 308 if (backAnimationAdaptor != null 309 && backAnimationAdaptor.getSupportType() == backType) { 310 mBackNaviAnimationController = new BackNaviAnimationController( 311 backAnimationAdaptor.getRunner(), this, 312 currentActivity.getDisplayId()); 313 prepareBackToHomeTransition(currentActivity, prevTask); 314 infoBuilder.setPrepareAnimation(true); 315 } 316 } else { 317 currentTask.mBackGestureStarted = true; 318 // Make launcher show from behind by marking its top activity as visible and 319 // launch-behind to bump its visibility for the duration of the back gesture. 320 prevActivity = prevTask.getTopNonFinishingActivity(); 321 if (prevActivity != null) { 322 if (!prevActivity.isVisibleRequested()) { 323 prevActivity.setVisibility(true); 324 } 325 prevActivity.mLaunchTaskBehind = true; 326 ProtoLog.d(WM_DEBUG_BACK_PREVIEW, 327 "Setting Activity.mLauncherTaskBehind to true. Activity=%s", 328 prevActivity); 329 prevActivity.mRootWindowContainer.ensureActivitiesVisible( 330 null /* starting */, 0 /* configChanges */, 331 false /* preserveWindows */); 332 } 333 } 334 } 335 } // Release wm Lock 336 337 // Find a screenshot of the previous activity if we actually have an animation 338 if (topAppTarget != null && needsScreenshot(backType) && prevTask != null 339 && screenshotBuffer == null) { 340 SurfaceControl.Builder builder = new SurfaceControl.Builder() 341 .setName("BackPreview Screenshot for " + prevActivity) 342 .setParent(animationLeashParent) 343 .setHidden(false) 344 .setBLASTLayer(); 345 infoBuilder.setScreenshotSurface(builder.build()); 346 screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId); 347 infoBuilder.setScreenshotBuffer(screenshotBuffer); 348 349 350 // The Animation leash needs to be above the screenshot surface, but the animation leash 351 // needs to be added before to be in the synchronized block. 352 tx.setLayer(topAppTarget.leash, 1); 353 } 354 355 WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer; 356 if (finalRemovedWindowContainer != null) { 357 try { 358 currentActivity.token.linkToDeath( 359 () -> resetSurfaces(finalRemovedWindowContainer), 0); 360 } catch (RemoteException e) { 361 Slog.e(TAG, "Failed to link to death", e); 362 resetSurfaces(removedWindowContainer); 363 return null; 364 } 365 366 int finalBackType = backType; 367 final ActivityRecord finalprevActivity = prevActivity; 368 final Task finalTask = currentTask; 369 final WindowState finalFocusedWindow = window; 370 RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone( 371 result, finalFocusedWindow, finalRemovedWindowContainer, finalBackType, 372 finalTask, finalprevActivity, prepareAnimation)); 373 infoBuilder.setOnBackNavigationDone(onBackNavigationDone); 374 } 375 376 tx.apply(); 377 return infoBuilder.build(); 378 } 379 380 @NonNull createRemoteAnimationTargetLocked( WindowContainer<?> removedWindowContainer, ActivityRecord activityRecord, Task task, SurfaceControl animLeash)381 private static RemoteAnimationTarget createRemoteAnimationTargetLocked( 382 WindowContainer<?> removedWindowContainer, 383 ActivityRecord activityRecord, Task task, SurfaceControl animLeash) { 384 return new RemoteAnimationTarget( 385 task.mTaskId, 386 RemoteAnimationTarget.MODE_CLOSING, 387 animLeash, 388 false /* isTransluscent */, 389 new Rect() /* clipRect */, 390 new Rect() /* contentInsets */, 391 activityRecord.getPrefixOrderIndex(), 392 new Point(0, 0) /* position */, 393 new Rect() /* localBounds */, 394 new Rect() /* screenSpaceBounds */, 395 removedWindowContainer.getWindowConfiguration(), 396 true /* isNotInRecent */, 397 null, 398 null, 399 task.getTaskInfo(), 400 false, 401 activityRecord.windowType); 402 } 403 onBackNavigationDone( Bundle result, WindowState focusedWindow, WindowContainer<?> windowContainer, int backType, @Nullable Task task, @Nullable ActivityRecord prevActivity, boolean prepareAnimation)404 private void onBackNavigationDone( 405 Bundle result, WindowState focusedWindow, WindowContainer<?> windowContainer, 406 int backType, @Nullable Task task, @Nullable ActivityRecord prevActivity, 407 boolean prepareAnimation) { 408 SurfaceControl surfaceControl = windowContainer.getSurfaceControl(); 409 boolean triggerBack = result != null && result.getBoolean( 410 BackNavigationInfo.KEY_TRIGGER_BACK); 411 ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, " 412 + "task=%s, prevActivity=%s", backType, task, prevActivity); 413 if (!USE_TRANSITION) { 414 if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation) { 415 if (triggerBack) { 416 if (surfaceControl != null && surfaceControl.isValid()) { 417 // When going back to home, hide the task surface before it is re-parented 418 // to avoid flicker. 419 SurfaceControl.Transaction t = windowContainer.getSyncTransaction(); 420 t.hide(surfaceControl); 421 t.apply(); 422 } 423 } 424 if (prevActivity != null && !triggerBack) { 425 // Restore the launch-behind state. 426 task.mTaskSupervisor.scheduleLaunchTaskBehindComplete(prevActivity.token); 427 prevActivity.mLaunchTaskBehind = false; 428 ProtoLog.d(WM_DEBUG_BACK_PREVIEW, 429 "Setting Activity.mLauncherTaskBehind to false. Activity=%s", 430 prevActivity); 431 } 432 } else { 433 task.mBackGestureStarted = false; 434 } 435 resetSurfaces(windowContainer); 436 } 437 438 if (mFocusObserver != null) { 439 focusedWindow.unregisterFocusObserver(mFocusObserver); 440 mFocusObserver = null; 441 } 442 } 443 getActivitySnapshot(@onNull Task task, ComponentName activityComponent)444 private HardwareBuffer getActivitySnapshot(@NonNull Task task, 445 ComponentName activityComponent) { 446 // Check if we have a screenshot of the previous activity, indexed by its 447 // component name. 448 SurfaceControl.ScreenshotHardwareBuffer backBuffer = task.mBackScreenshots 449 .get(activityComponent.flattenToString()); 450 return backBuffer != null ? backBuffer.getHardwareBuffer() : null; 451 452 } 453 getTaskSnapshot(int taskId, int userId)454 private HardwareBuffer getTaskSnapshot(int taskId, int userId) { 455 if (mWindowManagerService.mTaskSnapshotController == null) { 456 return null; 457 } 458 TaskSnapshot snapshot = mWindowManagerService.mTaskSnapshotController.getSnapshot(taskId, 459 userId, true /* restoreFromDisk */, false /* isLowResolution */); 460 return snapshot != null ? snapshot.getHardwareBuffer() : null; 461 } 462 needsScreenshot(int backType)463 private boolean needsScreenshot(int backType) { 464 if (!isScreenshotEnabled()) { 465 return false; 466 } 467 switch (backType) { 468 case BackNavigationInfo.TYPE_RETURN_TO_HOME: 469 case BackNavigationInfo.TYPE_DIALOG_CLOSE: 470 return false; 471 } 472 return true; 473 } 474 resetSurfaces(@onNull WindowContainer<?> windowContainer)475 private void resetSurfaces(@NonNull WindowContainer<?> windowContainer) { 476 synchronized (windowContainer.mWmService.mGlobalLock) { 477 ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Back: Reset surfaces"); 478 SurfaceControl.Transaction tx = windowContainer.getSyncTransaction(); 479 SurfaceControl surfaceControl = windowContainer.getSurfaceControl(); 480 if (surfaceControl != null) { 481 tx.reparent(surfaceControl, 482 windowContainer.getParent().getSurfaceControl()); 483 tx.apply(); 484 } 485 } 486 } 487 setWindowManager(WindowManagerService wm)488 void setWindowManager(WindowManagerService wm) { 489 mWindowManagerService = wm; 490 } 491 prepareBackToHomeTransition(ActivityRecord currentActivity, Task homeTask)492 private void prepareBackToHomeTransition(ActivityRecord currentActivity, Task homeTask) { 493 final DisplayContent dc = currentActivity.getDisplayContent(); 494 final ActivityRecord homeActivity = homeTask.getTopNonFinishingActivity(); 495 if (!homeActivity.isVisibleRequested()) { 496 homeActivity.setVisibility(true); 497 } 498 homeActivity.mLaunchTaskBehind = true; 499 dc.ensureActivitiesVisible( 500 null /* starting */, 0 /* configChanges */, 501 false /* preserveWindows */, true); 502 mBackNaviAnimationController.initialize(homeActivity, currentActivity); 503 } 504 finishAnimation()505 void finishAnimation() { 506 mBackNaviAnimationController = null; 507 } 508 } 509