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