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