• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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