• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.wm.shell.transition;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.window.DesktopModeFlags.ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX;
22 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
23 
24 import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP;
25 import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE;
26 import static com.android.wm.shell.transition.Transitions.TransitionObserver;
27 
28 import android.annotation.NonNull;
29 import android.app.ActivityManager;
30 import android.content.Context;
31 import android.os.IBinder;
32 import android.view.InsetsState;
33 import android.view.SurfaceControl;
34 import android.window.TransitionInfo;
35 
36 import com.android.wm.shell.common.DisplayInsetsController;
37 import com.android.wm.shell.common.RemoteCallable;
38 import com.android.wm.shell.common.ShellExecutor;
39 import com.android.wm.shell.common.SingleInstanceRemoteListener;
40 import com.android.wm.shell.shared.IHomeTransitionListener;
41 import com.android.wm.shell.shared.TransitionUtil;
42 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
43 import com.android.wm.shell.sysui.ShellInit;
44 
45 /**
46  * The {@link TransitionObserver} that observes for transitions involving the home
47  * activity on the {@link android.view.Display#DEFAULT_DISPLAY} only.
48  * It reports transitions to the caller via {@link IHomeTransitionListener}.
49  */
50 public class HomeTransitionObserver implements TransitionObserver,
51         RemoteCallable<HomeTransitionObserver> {
52     private SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
53             mListener;
54 
55     private @NonNull final Context mContext;
56     private @NonNull final ShellExecutor mMainExecutor;
57     private @NonNull final DisplayInsetsController mDisplayInsetsController;
58     private IBinder mPendingStartDragTransition;
59     private Boolean mPendingHomeVisibilityUpdate;
60 
HomeTransitionObserver(@onNull Context context, @NonNull ShellExecutor mainExecutor, @NonNull DisplayInsetsController displayInsetsController, @NonNull ShellInit shellInit)61     public HomeTransitionObserver(@NonNull Context context,
62             @NonNull ShellExecutor mainExecutor,
63             @NonNull DisplayInsetsController displayInsetsController,
64             @NonNull ShellInit shellInit) {
65         mContext = context;
66         mMainExecutor = mainExecutor;
67         mDisplayInsetsController = displayInsetsController;
68 
69         shellInit.addInitCallback(this::onInit, this);
70     }
71 
onInit()72     private void onInit() {
73         mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY,
74                 new DisplayInsetsController.OnInsetsChangedListener() {
75                     @Override
76                     public void insetsChanged(InsetsState insetsState) {
77                         if (mListener == null) return;
78                         mListener.call(l -> l.onDisplayInsetsChanged(insetsState));
79                     }
80                 });
81     }
82 
83     @Override
onTransitionReady(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)84     public void onTransitionReady(@NonNull IBinder transition,
85             @NonNull TransitionInfo info,
86             @NonNull SurfaceControl.Transaction startTransaction,
87             @NonNull SurfaceControl.Transaction finishTransaction) {
88         Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info);
89 
90         if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) {
91             // Do not apply at the start of desktop drag as that updates launcher UI visibility.
92             // Store the value and apply with a next transition or when cancelling the
93             // desktop-drag transition.
94             storePendingHomeVisibilityUpdate(transition, homeVisibilityUpdate);
95             return;
96         }
97 
98         if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()
99                 && info.getType() == TRANSIT_CONVERT_TO_BUBBLE
100                 && homeVisibilityUpdate == null) {
101             // We are converting to bubble and we did not get a change to home visibility in this
102             // transition. Apply the value from start of drag.
103             homeVisibilityUpdate = mPendingHomeVisibilityUpdate;
104         }
105 
106         if (homeVisibilityUpdate != null) {
107             mPendingHomeVisibilityUpdate = null;
108             mPendingStartDragTransition = null;
109             notifyHomeVisibilityChanged(homeVisibilityUpdate);
110         }
111     }
112 
storePendingHomeVisibilityUpdate( IBinder transition, Boolean homeVisibilityUpdate)113     private void storePendingHomeVisibilityUpdate(
114             IBinder transition, Boolean homeVisibilityUpdate) {
115         if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()
116                 && !ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) {
117             return;
118         }
119         mPendingHomeVisibilityUpdate = homeVisibilityUpdate;
120         mPendingStartDragTransition = transition;
121     }
122 
getHomeVisibilityUpdate(TransitionInfo info)123     private Boolean getHomeVisibilityUpdate(TransitionInfo info) {
124         Boolean homeVisibilityUpdate = null;
125         for (TransitionInfo.Change change : info.getChanges()) {
126             final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
127             if (taskInfo == null
128                     || taskInfo.displayId != DEFAULT_DISPLAY
129                     || taskInfo.taskId == -1
130                     || !taskInfo.isRunning) {
131                 continue;
132             }
133             Boolean update = getHomeVisibilityUpdate(info, change, taskInfo);
134             if (update != null) {
135                 homeVisibilityUpdate = update;
136             }
137         }
138         return homeVisibilityUpdate;
139     }
140 
getHomeVisibilityUpdate(TransitionInfo info, TransitionInfo.Change change, ActivityManager.RunningTaskInfo taskInfo)141     private Boolean getHomeVisibilityUpdate(TransitionInfo info,
142             TransitionInfo.Change change, ActivityManager.RunningTaskInfo taskInfo) {
143         final int mode = change.getMode();
144         final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
145         if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
146             final boolean gestureToHomeTransition = isBackGesture
147                     && TransitionUtil.isClosingType(info.getType());
148             if (gestureToHomeTransition || TransitionUtil.isClosingMode(mode)
149                     || (!isBackGesture && TransitionUtil.isOpeningMode(mode))) {
150                 return gestureToHomeTransition || TransitionUtil.isOpeningType(mode);
151             }
152         }
153         return null;
154     }
155 
156     @Override
onTransitionStarting(@onNull IBinder transition)157     public void onTransitionStarting(@NonNull IBinder transition) {}
158 
159     @Override
onTransitionMerged(@onNull IBinder merged, @NonNull IBinder playing)160     public void onTransitionMerged(@NonNull IBinder merged,
161             @NonNull IBinder playing) {}
162 
163     @Override
onTransitionFinished(@onNull IBinder transition, boolean aborted)164     public void onTransitionFinished(@NonNull IBinder transition,
165             boolean aborted) {
166         if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) {
167             return;
168         }
169         // Handle the case where the DragToDesktop START transition is interrupted and we never
170         // receive a CANCEL/END transition.
171         if (mPendingStartDragTransition == null
172                 || mPendingStartDragTransition != transition) {
173             return;
174         }
175         mPendingStartDragTransition = null;
176 
177         if (mPendingHomeVisibilityUpdate != null) {
178             notifyHomeVisibilityChanged(mPendingHomeVisibilityUpdate);
179             mPendingHomeVisibilityUpdate = null;
180         }
181     }
182 
183     /**
184      * Sets the home transition listener that receives any transitions resulting in a change of
185      *
186      */
setHomeTransitionListener(Transitions transitions, IHomeTransitionListener listener)187     public void setHomeTransitionListener(Transitions transitions,
188             IHomeTransitionListener listener) {
189         if (mListener == null) {
190             mListener = new SingleInstanceRemoteListener<>(this,
191                     c -> transitions.registerObserver(this),
192                     c -> transitions.unregisterObserver(this));
193         }
194 
195         if (listener != null) {
196             mListener.register(listener);
197         } else {
198             mListener.unregister();
199         }
200     }
201 
202     /**
203      * Notifies the listener that the home visibility has changed.
204      * @param isVisible true when home activity is visible, false otherwise.
205      */
notifyHomeVisibilityChanged(boolean isVisible)206     public void notifyHomeVisibilityChanged(boolean isVisible) {
207         if (mListener != null) {
208             mListener.call(l -> l.onHomeVisibilityChanged(isVisible));
209         }
210     }
211 
212     @Override
getContext()213     public Context getContext() {
214         return mContext;
215     }
216 
217     @Override
getRemoteCallExecutor()218     public ShellExecutor getRemoteCallExecutor() {
219         return mMainExecutor;
220     }
221 
222     /**
223      * Invalidates this controller, preventing future calls to send updates.
224      */
invalidate(Transitions transitions)225     public void invalidate(Transitions transitions) {
226         transitions.unregisterObserver(this);
227         if (mListener != null) {
228             // Unregister the listener to ensure any registered binder death recipients are unlinked
229             mListener.unregister();
230         }
231     }
232 }
233