1 /* 2 * Copyright (C) 2019 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 package com.android.quickstep; 17 18 import static android.view.RemoteAnimationTarget.MODE_CLOSING; 19 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; 20 21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 22 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION; 23 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION; 24 25 import android.graphics.Rect; 26 import android.util.ArraySet; 27 import android.view.RemoteAnimationTarget; 28 29 import androidx.annotation.BinderThread; 30 import androidx.annotation.NonNull; 31 import androidx.annotation.UiThread; 32 33 import com.android.launcher3.Utilities; 34 import com.android.launcher3.util.Preconditions; 35 import com.android.quickstep.util.ActiveGestureErrorDetector; 36 import com.android.quickstep.util.ActiveGestureLog; 37 import com.android.systemui.shared.recents.model.ThumbnailData; 38 import com.android.systemui.shared.system.RecentsAnimationControllerCompat; 39 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.HashMap; 43 import java.util.Set; 44 45 /** 46 * Wrapper around {@link com.android.systemui.shared.system.RecentsAnimationListener} which 47 * delegates callbacks to multiple listeners on the main thread 48 */ 49 public class RecentsAnimationCallbacks implements 50 com.android.systemui.shared.system.RecentsAnimationListener { 51 52 private final Set<RecentsAnimationListener> mListeners = new ArraySet<>(); 53 private final SystemUiProxy mSystemUiProxy; 54 private final boolean mAllowMinimizeSplitScreen; 55 56 // TODO(141886704): Remove these references when they are no longer needed 57 private RecentsAnimationController mController; 58 59 private boolean mCancelled; 60 RecentsAnimationCallbacks(SystemUiProxy systemUiProxy, boolean allowMinimizeSplitScreen)61 public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy, 62 boolean allowMinimizeSplitScreen) { 63 mSystemUiProxy = systemUiProxy; 64 mAllowMinimizeSplitScreen = allowMinimizeSplitScreen; 65 } 66 67 @UiThread addListener(RecentsAnimationListener listener)68 public void addListener(RecentsAnimationListener listener) { 69 Preconditions.assertUIThread(); 70 mListeners.add(listener); 71 } 72 73 @UiThread removeListener(RecentsAnimationListener listener)74 public void removeListener(RecentsAnimationListener listener) { 75 Preconditions.assertUIThread(); 76 mListeners.remove(listener); 77 } 78 79 @UiThread removeAllListeners()80 public void removeAllListeners() { 81 Preconditions.assertUIThread(); 82 mListeners.clear(); 83 } 84 notifyAnimationCanceled()85 public void notifyAnimationCanceled() { 86 mCancelled = true; 87 onAnimationCanceled(new HashMap<>()); 88 } 89 90 // Called only in Q platform 91 @BinderThread 92 @Deprecated onAnimationStart(RecentsAnimationControllerCompat controller, RemoteAnimationTarget[] appTargets, Rect homeContentInsets, Rect minimizedHomeBounds)93 public final void onAnimationStart(RecentsAnimationControllerCompat controller, 94 RemoteAnimationTarget[] appTargets, Rect homeContentInsets, 95 Rect minimizedHomeBounds) { 96 onAnimationStart(controller, appTargets, new RemoteAnimationTarget[0], 97 homeContentInsets, minimizedHomeBounds); 98 } 99 100 // Called only in R+ platform 101 @BinderThread onAnimationStart(RecentsAnimationControllerCompat animationController, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, Rect homeContentInsets, Rect minimizedHomeBounds)102 public final void onAnimationStart(RecentsAnimationControllerCompat animationController, 103 RemoteAnimationTarget[] appTargets, 104 RemoteAnimationTarget[] wallpaperTargets, 105 Rect homeContentInsets, Rect minimizedHomeBounds) { 106 long appCount = Arrays.stream(appTargets) 107 .filter(app -> app.mode == MODE_CLOSING) 108 .count(); 109 if (appCount == 0) { 110 // Edge case, if there are no closing app targets, then Launcher has nothing to handle 111 ActiveGestureLog.INSTANCE.addLog( 112 /* event= */ "RecentsAnimationCallbacks.onAnimationStart (canceled)", 113 /* extras= */ 0, 114 /* gestureEvent= */ START_RECENTS_ANIMATION); 115 notifyAnimationCanceled(); 116 animationController.finish(false /* toHome */, false /* sendUserLeaveHint */); 117 return; 118 } 119 120 mController = new RecentsAnimationController(animationController, 121 mAllowMinimizeSplitScreen, this::onAnimationFinished); 122 if (mCancelled) { 123 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), 124 mController::finishAnimationToApp); 125 } else { 126 RemoteAnimationTarget[] nonAppTargets; 127 if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 128 nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(appTargets); 129 } else { 130 final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>(); 131 final ArrayList<RemoteAnimationTarget> nonApps = new ArrayList<>(); 132 classifyTargets(appTargets, apps, nonApps); 133 appTargets = apps.toArray(new RemoteAnimationTarget[apps.size()]); 134 nonAppTargets = nonApps.toArray(new RemoteAnimationTarget[nonApps.size()]); 135 } 136 if (nonAppTargets == null) { 137 nonAppTargets = new RemoteAnimationTarget[0]; 138 } 139 final RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets, 140 wallpaperTargets, nonAppTargets, homeContentInsets, minimizedHomeBounds); 141 142 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { 143 ActiveGestureLog.INSTANCE.addLog( 144 /* event= */ "RecentsAnimationCallbacks.onAnimationStart", 145 /* extras= */ targets.apps.length, 146 /* gestureEvent= */ START_RECENTS_ANIMATION); 147 for (RecentsAnimationListener listener : getListeners()) { 148 listener.onRecentsAnimationStart(mController, targets); 149 } 150 }); 151 } 152 } 153 154 @BinderThread 155 @Override onAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)156 public final void onAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 157 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { 158 ActiveGestureLog.INSTANCE.addLog( 159 /* event= */ "RecentsAnimationCallbacks.onAnimationCanceled", 160 /* gestureEvent= */ CANCEL_RECENTS_ANIMATION); 161 for (RecentsAnimationListener listener : getListeners()) { 162 listener.onRecentsAnimationCanceled(thumbnailDatas); 163 } 164 }); 165 } 166 167 @BinderThread 168 @Override onTasksAppeared(RemoteAnimationTarget[] apps)169 public void onTasksAppeared(RemoteAnimationTarget[] apps) { 170 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { 171 ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onTasksAppeared", 172 ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED); 173 for (RecentsAnimationListener listener : getListeners()) { 174 listener.onTasksAppeared(apps); 175 } 176 }); 177 } 178 179 @BinderThread 180 @Override onSwitchToScreenshot(Runnable onFinished)181 public boolean onSwitchToScreenshot(Runnable onFinished) { 182 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { 183 for (RecentsAnimationListener listener : getListeners()) { 184 if (listener.onSwitchToScreenshot(onFinished)) return; 185 } 186 onFinished.run(); 187 }); 188 return true; 189 } 190 onAnimationFinished(RecentsAnimationController controller)191 private final void onAnimationFinished(RecentsAnimationController controller) { 192 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { 193 ActiveGestureLog.INSTANCE.addLog( 194 /* event= */ "RecentsAnimationCallbacks.onAnimationFinished"); 195 for (RecentsAnimationListener listener : getListeners()) { 196 listener.onRecentsAnimationFinished(controller); 197 } 198 }); 199 } 200 getListeners()201 private RecentsAnimationListener[] getListeners() { 202 return mListeners.toArray(new RecentsAnimationListener[mListeners.size()]); 203 } 204 classifyTargets(RemoteAnimationTarget[] appTargets, ArrayList<RemoteAnimationTarget> apps, ArrayList<RemoteAnimationTarget> nonApps)205 private void classifyTargets(RemoteAnimationTarget[] appTargets, 206 ArrayList<RemoteAnimationTarget> apps, ArrayList<RemoteAnimationTarget> nonApps) { 207 for (int i = 0; i < appTargets.length; i++) { 208 RemoteAnimationTarget target = appTargets[i]; 209 if (target.windowType == TYPE_DOCK_DIVIDER) { 210 nonApps.add(target); 211 } else { 212 apps.add(target); 213 } 214 } 215 } 216 217 /** 218 * Listener for the recents animation callbacks. 219 */ 220 public interface RecentsAnimationListener { onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)221 default void onRecentsAnimationStart(RecentsAnimationController controller, 222 RecentsAnimationTargets targets) {} 223 224 /** 225 * Callback from the system when the recents animation is canceled. {@param thumbnailData} 226 * is passed back for rendering screenshot to replace live tile. 227 */ onRecentsAnimationCanceled( @onNull HashMap<Integer, ThumbnailData> thumbnailDatas)228 default void onRecentsAnimationCanceled( 229 @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {} 230 231 /** 232 * Callback made whenever the recents animation is finished. 233 */ onRecentsAnimationFinished(@onNull RecentsAnimationController controller)234 default void onRecentsAnimationFinished(@NonNull RecentsAnimationController controller) {} 235 236 /** 237 * Callback made when a task started from the recents is ready for an app transition. 238 */ onTasksAppeared(@onNull RemoteAnimationTarget[] appearedTaskTarget)239 default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget) {} 240 241 /** 242 * @return whether this will call onFinished or not (onFinished should only be called once). 243 */ onSwitchToScreenshot(Runnable onFinished)244 default boolean onSwitchToScreenshot(Runnable onFinished) { 245 return false; 246 } 247 } 248 } 249