1 /*
2  * Copyright (C) 2022 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.desktopmode;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
24 import static android.view.WindowManager.TRANSIT_CHANGE;
25 import static android.view.WindowManager.TRANSIT_NONE;
26 import static android.view.WindowManager.TRANSIT_OPEN;
27 import static android.view.WindowManager.TRANSIT_TO_FRONT;
28 
29 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
30 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
31 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
32 
33 import android.app.ActivityManager.RunningTaskInfo;
34 import android.app.WindowConfiguration;
35 import android.content.Context;
36 import android.content.res.TypedArray;
37 import android.database.ContentObserver;
38 import android.graphics.Region;
39 import android.net.Uri;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.RemoteException;
43 import android.os.UserHandle;
44 import android.provider.Settings;
45 import android.util.ArraySet;
46 import android.view.SurfaceControl;
47 import android.view.WindowManager;
48 import android.window.DisplayAreaInfo;
49 import android.window.TransitionInfo;
50 import android.window.TransitionRequestInfo;
51 import android.window.WindowContainerTransaction;
52 
53 import androidx.annotation.BinderThread;
54 import androidx.annotation.NonNull;
55 import androidx.annotation.Nullable;
56 
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.internal.protolog.common.ProtoLog;
59 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
60 import com.android.wm.shell.ShellTaskOrganizer;
61 import com.android.wm.shell.common.ExternalInterfaceBinder;
62 import com.android.wm.shell.common.RemoteCallable;
63 import com.android.wm.shell.common.ShellExecutor;
64 import com.android.wm.shell.common.annotations.ExternalThread;
65 import com.android.wm.shell.common.annotations.ShellMainThread;
66 import com.android.wm.shell.sysui.ShellController;
67 import com.android.wm.shell.sysui.ShellInit;
68 import com.android.wm.shell.transition.Transitions;
69 
70 import java.util.ArrayList;
71 import java.util.Comparator;
72 import java.util.List;
73 import java.util.concurrent.Executor;
74 import java.util.function.Consumer;
75 
76 /**
77  * Handles windowing changes when desktop mode system setting changes
78  */
79 public class DesktopModeController implements RemoteCallable<DesktopModeController>,
80         Transitions.TransitionHandler {
81 
82     private final Context mContext;
83     private final ShellController mShellController;
84     private final ShellTaskOrganizer mShellTaskOrganizer;
85     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
86     private final Transitions mTransitions;
87     private final DesktopModeTaskRepository mDesktopModeTaskRepository;
88     private final ShellExecutor mMainExecutor;
89     private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
90     private final SettingsObserver mSettingsObserver;
91 
DesktopModeController(Context context, ShellInit shellInit, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, Transitions transitions, DesktopModeTaskRepository desktopModeTaskRepository, @ShellMainThread Handler mainHandler, @ShellMainThread ShellExecutor mainExecutor)92     public DesktopModeController(Context context,
93             ShellInit shellInit,
94             ShellController shellController,
95             ShellTaskOrganizer shellTaskOrganizer,
96             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
97             Transitions transitions,
98             DesktopModeTaskRepository desktopModeTaskRepository,
99             @ShellMainThread Handler mainHandler,
100             @ShellMainThread ShellExecutor mainExecutor) {
101         mContext = context;
102         mShellController = shellController;
103         mShellTaskOrganizer = shellTaskOrganizer;
104         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
105         mTransitions = transitions;
106         mDesktopModeTaskRepository = desktopModeTaskRepository;
107         mMainExecutor = mainExecutor;
108         mSettingsObserver = new SettingsObserver(mContext, mainHandler);
109         if (DesktopModeStatus.isProto1Enabled()) {
110             shellInit.addInitCallback(this::onInit, this);
111         }
112     }
113 
onInit()114     private void onInit() {
115         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
116         mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
117                 this::createExternalInterface, this);
118         mSettingsObserver.observe();
119         if (DesktopModeStatus.isActive(mContext)) {
120             updateDesktopModeActive(true);
121         }
122         mTransitions.addHandler(this);
123     }
124 
125     @Override
getContext()126     public Context getContext() {
127         return mContext;
128     }
129 
130     @Override
getRemoteCallExecutor()131     public ShellExecutor getRemoteCallExecutor() {
132         return mMainExecutor;
133     }
134 
135     /**
136      * Get connection interface between sysui and shell
137      */
asDesktopMode()138     public DesktopMode asDesktopMode() {
139         return mDesktopModeImpl;
140     }
141 
142     /**
143      * Creates a new instance of the external interface to pass to another process.
144      */
createExternalInterface()145     private ExternalInterfaceBinder createExternalInterface() {
146         return new IDesktopModeImpl(this);
147     }
148 
149     /**
150      * Adds a listener to find out about changes in the visibility of freeform tasks.
151      *
152      * @param listener the listener to add.
153      * @param callbackExecutor the executor to call the listener on.
154      */
addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener, Executor callbackExecutor)155     public void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
156             Executor callbackExecutor) {
157         mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor);
158     }
159 
160     /**
161      * Adds a listener to track changes to corners of desktop mode tasks.
162      * @param listener the listener to add.
163      * @param callbackExecutor the executor to call the listener on.
164      */
addTaskCornerListener(Consumer<Region> listener, Executor callbackExecutor)165     public void addTaskCornerListener(Consumer<Region> listener,
166             Executor callbackExecutor) {
167         mDesktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor);
168     }
169 
170     @VisibleForTesting
updateDesktopModeActive(boolean active)171     void updateDesktopModeActive(boolean active) {
172         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
173 
174         int displayId = mContext.getDisplayId();
175 
176         ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
177 
178         WindowContainerTransaction wct = new WindowContainerTransaction();
179         // Reset freeform windowing mode that is set per task level so tasks inherit it
180         clearFreeformForStandardTasks(runningTasks, wct);
181         if (active) {
182             moveHomeBehindVisibleTasks(runningTasks, wct);
183             setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
184         } else {
185             clearBoundsForStandardTasks(runningTasks, wct);
186             setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
187         }
188         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
189             mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
190         } else {
191             mRootTaskDisplayAreaOrganizer.applyTransaction(wct);
192         }
193     }
194 
clearBoundsForStandardTasks( ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct)195     private WindowContainerTransaction clearBoundsForStandardTasks(
196             ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
197         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
198         for (RunningTaskInfo taskInfo : runningTasks) {
199             if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
200                 ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
201                         taskInfo.token, taskInfo);
202                 wct.setBounds(taskInfo.token, null);
203             }
204         }
205         return wct;
206     }
207 
clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct)208     private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
209             WindowContainerTransaction wct) {
210         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
211         for (RunningTaskInfo taskInfo : runningTasks) {
212             if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
213                     && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
214                 ProtoLog.v(WM_SHELL_DESKTOP_MODE,
215                         "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
216                         taskInfo);
217                 wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
218             }
219         }
220     }
221 
moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct)222     private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
223             WindowContainerTransaction wct) {
224         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
225         RunningTaskInfo homeTask = null;
226         ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
227         for (RunningTaskInfo taskInfo : runningTasks) {
228             if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
229                 homeTask = taskInfo;
230             } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
231                     && taskInfo.isVisible()) {
232                 visibleTasks.add(taskInfo);
233             }
234         }
235         if (homeTask == null) {
236             ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
237         } else {
238             ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
239                     visibleTasks.size());
240             wct.reorder(homeTask.getToken(), true /* onTop */);
241             for (RunningTaskInfo task : visibleTasks) {
242                 wct.reorder(task.getToken(), true /* onTop */);
243             }
244         }
245     }
246 
setDisplayAreaWindowingMode(int displayId, @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct)247     private void setDisplayAreaWindowingMode(int displayId,
248             @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
249         DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
250                 displayId);
251         if (displayAreaInfo == null) {
252             ProtoLog.e(WM_SHELL_DESKTOP_MODE,
253                     "unable to update windowing mode for display %d display not found", displayId);
254             return;
255         }
256 
257         ProtoLog.v(WM_SHELL_DESKTOP_MODE,
258                 "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
259                 displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
260                 windowingMode);
261 
262         wct.setWindowingMode(displayAreaInfo.token, windowingMode);
263     }
264 
265     /**
266      * Show apps on desktop
267      */
showDesktopApps(int displayId)268     void showDesktopApps(int displayId) {
269         // Bring apps to front, ignoring their visibility status to always ensure they are on top.
270         WindowContainerTransaction wct = new WindowContainerTransaction();
271         bringDesktopAppsToFront(displayId, wct);
272 
273         if (!wct.isEmpty()) {
274             if (Transitions.ENABLE_SHELL_TRANSITIONS) {
275                 // TODO(b/268662477): add animation for the transition
276                 mTransitions.startTransition(TRANSIT_NONE, wct, null /* handler */);
277             } else {
278                 mShellTaskOrganizer.applyTransaction(wct);
279             }
280         }
281     }
282 
283     /** Get number of tasks that are marked as visible */
getVisibleTaskCount(int displayId)284     int getVisibleTaskCount(int displayId) {
285         return mDesktopModeTaskRepository.getVisibleTaskCount(displayId);
286     }
287 
bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct)288     private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) {
289         final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId);
290         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
291 
292         final List<RunningTaskInfo> taskInfos = new ArrayList<>();
293         for (Integer taskId : activeTasks) {
294             RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
295             if (taskInfo != null) {
296                 taskInfos.add(taskInfo);
297             }
298         }
299 
300         if (taskInfos.isEmpty()) {
301             return;
302         }
303 
304         moveHomeTaskToFront(wct);
305 
306         ProtoLog.d(WM_SHELL_DESKTOP_MODE,
307                 "bringDesktopAppsToFront: reordering all active tasks to the front");
308         final List<Integer> allTasksInZOrder =
309                 mDesktopModeTaskRepository.getFreeformTasksInZOrder();
310         // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
311         // in the WCT.
312         taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
313         for (RunningTaskInfo task : taskInfos) {
314             wct.reorder(task.token, true);
315         }
316     }
317 
moveHomeTaskToFront(WindowContainerTransaction wct)318     private void moveHomeTaskToFront(WindowContainerTransaction wct) {
319         for (RunningTaskInfo task : mShellTaskOrganizer.getRunningTasks(mContext.getDisplayId())) {
320             if (task.getActivityType() == ACTIVITY_TYPE_HOME) {
321                 wct.reorder(task.token, true /* onTop */);
322                 return;
323             }
324         }
325     }
326 
327     /**
328      * Update corner rects stored for a specific task
329      * @param taskId task to update
330      * @param taskCorners task's new corner handles
331      */
onTaskCornersChanged(int taskId, Region taskCorners)332     public void onTaskCornersChanged(int taskId, Region taskCorners) {
333         mDesktopModeTaskRepository.updateTaskCorners(taskId, taskCorners);
334     }
335 
336     /**
337      * Remove corners saved for a task. Likely used due to task closure.
338      * @param taskId task to remove
339      */
removeCornersForTask(int taskId)340     public void removeCornersForTask(int taskId) {
341         mDesktopModeTaskRepository.removeTaskCorners(taskId);
342     }
343 
344     /**
345      * Moves a specifc task to the front.
346      * @param taskInfo the task to show in front.
347      */
moveTaskToFront(RunningTaskInfo taskInfo)348     public void moveTaskToFront(RunningTaskInfo taskInfo) {
349         WindowContainerTransaction wct = new WindowContainerTransaction();
350         wct.reorder(taskInfo.token, true /* onTop */);
351         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
352             mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null);
353         } else {
354             mShellTaskOrganizer.applyTransaction(wct);
355         }
356     }
357 
358     /**
359      * Turn desktop mode on or off
360      * @param active the desired state for desktop mode setting
361      */
setDesktopModeActive(boolean active)362     public void setDesktopModeActive(boolean active) {
363         int value = active ? 1 : 0;
364         Settings.System.putInt(mContext.getContentResolver(), Settings.System.DESKTOP_MODE, value);
365     }
366 
367     /**
368      * Returns the windowing mode of the display area with the specified displayId.
369      * @param displayId
370      * @return
371      */
getDisplayAreaWindowingMode(int displayId)372     public int getDisplayAreaWindowingMode(int displayId) {
373         return mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
374                 .configuration.windowConfiguration.getWindowingMode();
375     }
376 
377     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)378     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
379             @NonNull SurfaceControl.Transaction startTransaction,
380             @NonNull SurfaceControl.Transaction finishTransaction,
381             @NonNull Transitions.TransitionFinishCallback finishCallback) {
382         // This handler should never be the sole handler, so should not animate anything.
383         return false;
384     }
385 
386     @Nullable
387     @Override
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)388     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
389             @NonNull TransitionRequestInfo request) {
390         RunningTaskInfo triggerTask = request.getTriggerTask();
391         // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in
392         // freeform
393         if (!DesktopModeStatus.isActive(mContext)) {
394             ProtoLog.d(WM_SHELL_DESKTOP_MODE,
395                     "skip shell transition request: desktop mode not active");
396             return null;
397         }
398         if (request.getType() != TRANSIT_OPEN && request.getType() != TRANSIT_TO_FRONT) {
399             ProtoLog.d(WM_SHELL_DESKTOP_MODE,
400                     "skip shell transition request: unsupported type %s",
401                     WindowManager.transitTypeToString(request.getType()));
402             return null;
403         }
404         if (triggerTask == null || triggerTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
405             ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
406             return null;
407         }
408         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
409 
410         WindowContainerTransaction wct = new WindowContainerTransaction();
411         bringDesktopAppsToFront(triggerTask.displayId, wct);
412         wct.reorder(triggerTask.token, true /* onTop */);
413 
414         return wct;
415     }
416 
417     /**
418      * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
419      * This is intended to be used when desktop mode is part of another animation but isn't, itself,
420      * animating.
421      */
syncSurfaceState(@onNull TransitionInfo info, SurfaceControl.Transaction finishTransaction)422     public void syncSurfaceState(@NonNull TransitionInfo info,
423             SurfaceControl.Transaction finishTransaction) {
424         // Add rounded corners to freeform windows
425         final TypedArray ta = mContext.obtainStyledAttributes(
426                 new int[]{android.R.attr.dialogCornerRadius});
427         final int cornerRadius = ta.getDimensionPixelSize(0, 0);
428         ta.recycle();
429         for (TransitionInfo.Change change: info.getChanges()) {
430             if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
431                 finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
432             }
433         }
434     }
435 
436     /**
437      * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
438      */
439     private final class SettingsObserver extends ContentObserver {
440 
441         private final Uri mDesktopModeSetting = Settings.System.getUriFor(
442                 Settings.System.DESKTOP_MODE);
443 
444         private final Context mContext;
445 
SettingsObserver(Context context, Handler handler)446         SettingsObserver(Context context, Handler handler) {
447             super(handler);
448             mContext = context;
449         }
450 
observe()451         public void observe() {
452             // TODO(b/242867463): listen for setting change for all users
453             mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
454                     false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
455         }
456 
457         @Override
onChange(boolean selfChange, @Nullable Uri uri)458         public void onChange(boolean selfChange, @Nullable Uri uri) {
459             if (mDesktopModeSetting.equals(uri)) {
460                 ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
461                 desktopModeSettingChanged();
462             }
463         }
464 
desktopModeSettingChanged()465         private void desktopModeSettingChanged() {
466             boolean enabled = DesktopModeStatus.isActive(mContext);
467             updateDesktopModeActive(enabled);
468         }
469     }
470 
471     /**
472      * The interface for calls from outside the shell, within the host process.
473      */
474     @ExternalThread
475     private final class DesktopModeImpl implements DesktopMode {
476 
477         @Override
addVisibleTasksListener( DesktopModeTaskRepository.VisibleTasksListener listener, Executor callbackExecutor)478         public void addVisibleTasksListener(
479                 DesktopModeTaskRepository.VisibleTasksListener listener,
480                 Executor callbackExecutor) {
481             mMainExecutor.execute(() -> {
482                 DesktopModeController.this.addVisibleTasksListener(listener, callbackExecutor);
483             });
484         }
485 
486         @Override
addDesktopGestureExclusionRegionListener(Consumer<Region> listener, Executor callbackExecutor)487         public void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
488                 Executor callbackExecutor) {
489             mMainExecutor.execute(() -> {
490                 DesktopModeController.this.addTaskCornerListener(listener, callbackExecutor);
491             });
492         }
493     }
494 
495     /**
496      * The interface for calls from outside the host process.
497      */
498     @BinderThread
499     private static class IDesktopModeImpl extends IDesktopMode.Stub
500             implements ExternalInterfaceBinder {
501 
502         private DesktopModeController mController;
503 
IDesktopModeImpl(DesktopModeController controller)504         IDesktopModeImpl(DesktopModeController controller) {
505             mController = controller;
506         }
507 
508         /**
509          * Invalidates this instance, preventing future calls from updating the controller.
510          */
511         @Override
invalidate()512         public void invalidate() {
513             mController = null;
514         }
515 
516         @Override
showDesktopApps(int displayId)517         public void showDesktopApps(int displayId) {
518             executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
519                     controller -> controller.showDesktopApps(displayId));
520         }
521 
522         @Override
showDesktopApp(int taskId)523         public void showDesktopApp(int taskId) throws RemoteException {
524             // TODO
525         }
526 
527         @Override
getVisibleTaskCount(int displayId)528         public int getVisibleTaskCount(int displayId) throws RemoteException {
529             int[] result = new int[1];
530             executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
531                     controller -> result[0] = controller.getVisibleTaskCount(displayId),
532                     true /* blocking */
533             );
534             return result[0];
535         }
536 
537         @Override
onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo)538         public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
539 
540         }
541 
542         @Override
stashDesktopApps(int displayId)543         public void stashDesktopApps(int displayId) throws RemoteException {
544             // Stashing of desktop apps not needed. Apps always launch on desktop
545         }
546 
547         @Override
hideStashedDesktopApps(int displayId)548         public void hideStashedDesktopApps(int displayId) throws RemoteException {
549             // Stashing of desktop apps not needed. Apps always launch on desktop
550         }
551 
552         @Override
setTaskListener(IDesktopTaskListener listener)553         public void setTaskListener(IDesktopTaskListener listener) throws RemoteException {
554             // TODO(b/261234402): move visibility from sysui state to listener
555         }
556     }
557 }
558