• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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 android.app.ActivityManager
19 import android.app.ActivityManager.RunningTaskInfo
20 import android.app.ActivityOptions
21 import android.app.PendingIntent
22 import android.content.ComponentName
23 import android.content.Context
24 import android.content.Intent
25 import android.content.pm.ShortcutInfo
26 import android.graphics.Point
27 import android.graphics.Rect
28 import android.os.Bundle
29 import android.os.Handler
30 import android.os.IBinder
31 import android.os.Message
32 import android.os.RemoteException
33 import android.os.UserHandle
34 import android.util.Log
35 import android.view.IRemoteAnimationRunner
36 import android.view.KeyEvent
37 import android.view.MotionEvent
38 import android.view.RemoteAnimationTarget
39 import android.view.SurfaceControl
40 import android.view.SurfaceControl.Transaction
41 import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS
42 import android.window.IOnBackInvokedCallback
43 import android.window.RemoteTransition
44 import android.window.TaskSnapshot
45 import android.window.TransitionFilter
46 import android.window.TransitionInfo
47 import androidx.annotation.MainThread
48 import androidx.annotation.VisibleForTesting
49 import androidx.annotation.WorkerThread
50 import com.android.internal.logging.InstanceId
51 import com.android.internal.util.ScreenshotRequest
52 import com.android.internal.view.AppearanceRegion
53 import com.android.launcher3.Flags
54 import com.android.launcher3.dagger.ApplicationContext
55 import com.android.launcher3.dagger.LauncherAppComponent
56 import com.android.launcher3.dagger.LauncherAppSingleton
57 import com.android.launcher3.util.DaggerSingletonObject
58 import com.android.launcher3.util.Executors
59 import com.android.launcher3.util.Preconditions
60 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
61 import com.android.quickstep.util.ActiveGestureProtoLogProxy
62 import com.android.quickstep.util.ContextualSearchInvoker
63 import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider
64 import com.android.systemui.contextualeducation.GestureType
65 import com.android.systemui.shared.recents.ISystemUiProxy
66 import com.android.systemui.shared.recents.model.ThumbnailData.Companion.wrap
67 import com.android.systemui.shared.system.QuickStepContract
68 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
69 import com.android.systemui.shared.system.RecentsAnimationControllerCompat
70 import com.android.systemui.shared.system.RecentsAnimationListener
71 import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
72 import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
73 import com.android.systemui.shared.system.smartspace.SmartspaceState
74 import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
75 import com.android.systemui.unfold.progress.IUnfoldAnimation
76 import com.android.systemui.unfold.progress.IUnfoldTransitionListener
77 import com.android.wm.shell.back.IBackAnimation
78 import com.android.wm.shell.bubbles.IBubbles
79 import com.android.wm.shell.bubbles.IBubblesListener
80 import com.android.wm.shell.common.pip.IPip
81 import com.android.wm.shell.common.pip.IPipAnimationListener
82 import com.android.wm.shell.desktopmode.IDesktopMode
83 import com.android.wm.shell.desktopmode.IDesktopTaskListener
84 import com.android.wm.shell.desktopmode.IMoveToDesktopCallback
85 import com.android.wm.shell.draganddrop.IDragAndDrop
86 import com.android.wm.shell.onehanded.IOneHanded
87 import com.android.wm.shell.recents.IRecentTasks
88 import com.android.wm.shell.recents.IRecentTasksListener
89 import com.android.wm.shell.recents.IRecentsAnimationController
90 import com.android.wm.shell.recents.IRecentsAnimationRunner
91 import com.android.wm.shell.shared.GroupedTaskInfo
92 import com.android.wm.shell.shared.IShellTransitions
93 import com.android.wm.shell.shared.bubbles.BubbleBarLocation
94 import com.android.wm.shell.shared.bubbles.BubbleBarLocation.UpdateSource
95 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
96 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
97 import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason
98 import com.android.wm.shell.shared.split.SplitBounds
99 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition
100 import com.android.wm.shell.splitscreen.ISplitScreen
101 import com.android.wm.shell.splitscreen.ISplitScreenListener
102 import com.android.wm.shell.splitscreen.ISplitSelectListener
103 import com.android.wm.shell.startingsurface.IStartingWindow
104 import com.android.wm.shell.startingsurface.IStartingWindowListener
105 import java.io.PrintWriter
106 import javax.inject.Inject
107 
108 /** Holds the reference to SystemUI. */
109 @LauncherAppSingleton
110 class SystemUiProxy @Inject constructor(@ApplicationContext private val context: Context) :
111     NavHandle {
112 
113     private var systemUiProxy: ISystemUiProxy? = null
114     private var pip: IPip? = null
115     private var bubbles: IBubbles? = null
116     private var sysuiUnlockAnimationController: ISysuiUnlockAnimationController? = null
117     private var splitScreen: ISplitScreen? = null
118     private var oneHanded: IOneHanded? = null
119     private var shellTransitions: IShellTransitions? = null
120     private var startingWindow: IStartingWindow? = null
121     private var recentTasks: IRecentTasks? = null
122     private var backAnimation: IBackAnimation? = null
123     private var desktopMode: IDesktopMode? = null
124     private var unfoldAnimation: IUnfoldAnimation? = null
125 
126     private val systemUiProxyDeathRecipient =
127         IBinder.DeathRecipient { Executors.MAIN_EXECUTOR.execute { clearProxy() } }
128 
129     // Save the listeners passed into the proxy since LauncherProxyService may not have been bound
130     // yet, and we'll need to set/register these listeners with SysUI when they do.  Note that it is
131     // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
132     // in case SysUI needs to rebind.
133     private var pipAnimationListener: IPipAnimationListener? = null
134     private var bubblesListener: IBubblesListener? = null
135     private var splitScreenListener: ISplitScreenListener? = null
136     private var splitSelectListener: ISplitSelectListener? = null
137     private var startingWindowListener: IStartingWindowListener? = null
138     private var launcherUnlockAnimationController: ILauncherUnlockAnimationController? = null
139     private var launcherActivityClass: String? = null
140     private var recentTasksListener: IRecentTasksListener? = null
141     private var unfoldAnimationListener: IUnfoldTransitionListener? = null
142     private var desktopTaskListener: IDesktopTaskListener? = null
143     private val remoteTransitions = LinkedHashMap<RemoteTransition, TransitionFilter>()
144 
145     private val stateChangeCallbacks: MutableList<Runnable> = ArrayList()
146 
147     private var originalTransactionToken: IBinder? = null
148     private var backToLauncherCallback: IOnBackInvokedCallback? = null
149     private var backToLauncherRunner: IRemoteAnimationRunner? = null
150     private var dragAndDrop: IDragAndDrop? = null
151     val homeVisibilityState = HomeVisibilityState()
152     val focusState = FocusState()
153 
154     // Used to dedupe calls to SystemUI
155     private var lastShelfHeight = 0
156     private var lastShelfVisible = false
157 
158     // Used to dedupe calls to SystemUI
159     private var lastLauncherKeepClearAreaHeight = 0
160     private var lastLauncherKeepClearAreaHeightVisible = false
161 
162     private val asyncHandler =
163         Handler(Executors.UI_HELPER_EXECUTOR.looper) { handleMessageAsync(it) }
164 
165     // TODO(141886704): Find a way to remove this
166     @SystemUiStateFlags var lastSystemUiStateFlags: Long = 0
167 
168     /**
169      * This is a singleton pending intent that is used to start recents via Shell (which is a
170      * different process). It is bare-bones, so it's expected that the component and options will be
171      * provided via fill-in intent.
172      */
173     private val recentsPendingIntent by lazy {
174         PendingIntent.getActivity(
175             context,
176             0,
177             Intent().setPackage(context.packageName),
178             PendingIntent.FLAG_MUTABLE or
179                 PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or
180                 Intent.FILL_IN_COMPONENT,
181             ActivityOptions.makeBasic()
182                 .setPendingIntentCreatorBackgroundActivityStartMode(
183                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
184                 )
185                 .toBundle(),
186         )
187     }
188 
189     val unfoldTransitionProvider: ProxyUnfoldTransitionProvider? =
190         if ((Flags.enableUnfoldStateAnimation() && ResourceUnfoldTransitionConfig().isEnabled))
191             ProxyUnfoldTransitionProvider()
192         else null
193 
194     private inline fun executeWithErrorLog(
195         errorMsg: () -> String,
196         tag: String = TAG,
197         callback: () -> Any?,
198     ) {
199         try {
200             callback.invoke()
201         } catch (e: RemoteException) {
202             Log.w(tag, errorMsg.invoke(), e)
203         }
204     }
205 
206     fun onBackEvent(backEvent: KeyEvent?) =
207         executeWithErrorLog({ "Failed call onBackPressed" }) {
208             systemUiProxy?.onBackEvent(backEvent)
209         }
210 
211     fun onImeSwitcherPressed() =
212         executeWithErrorLog({ "Failed call onImeSwitcherPressed" }) {
213             systemUiProxy?.onImeSwitcherPressed()
214         }
215 
216     fun onImeSwitcherLongPress() =
217         executeWithErrorLog({ "Failed call onImeSwitcherLongPress" }) {
218             systemUiProxy?.onImeSwitcherLongPress()
219         }
220 
221     fun updateContextualEduStats(isTrackpadGesture: Boolean, gestureType: GestureType) =
222         executeWithErrorLog({ "Failed call updateContextualEduStats" }) {
223             systemUiProxy?.updateContextualEduStats(isTrackpadGesture, gestureType.name)
224         }
225 
226     fun setHomeRotationEnabled(enabled: Boolean) =
227         executeWithErrorLog({ "Failed call setHomeRotationEnabled" }) {
228             systemUiProxy?.setHomeRotationEnabled(enabled)
229         }
230 
231     /**
232      * Sets proxy state, including death linkage, various listeners, and other configuration objects
233      */
234     @MainThread
235     fun setProxy(
236         proxy: ISystemUiProxy?,
237         pip: IPip?,
238         bubbles: IBubbles?,
239         splitScreen: ISplitScreen?,
240         oneHanded: IOneHanded?,
241         shellTransitions: IShellTransitions?,
242         startingWindow: IStartingWindow?,
243         recentTasks: IRecentTasks?,
244         sysuiUnlockAnimationController: ISysuiUnlockAnimationController?,
245         backAnimation: IBackAnimation?,
246         desktopMode: IDesktopMode?,
247         unfoldAnimation: IUnfoldAnimation?,
248         dragAndDrop: IDragAndDrop?,
249     ) {
250         Preconditions.assertUIThread()
251         unlinkToDeath()
252         systemUiProxy = proxy
253         this.pip = pip
254         this.bubbles = bubbles
255         this.splitScreen = splitScreen
256         this.oneHanded = oneHanded
257         this.shellTransitions = shellTransitions
258         this.startingWindow = startingWindow
259         this.sysuiUnlockAnimationController = sysuiUnlockAnimationController
260         this.recentTasks = recentTasks
261         this.backAnimation = backAnimation
262         this.desktopMode = desktopMode
263         this.unfoldAnimation = if (Flags.enableUnfoldStateAnimation()) null else unfoldAnimation
264         this.dragAndDrop = dragAndDrop
265         linkToDeath()
266         // re-attach the listeners once missing due to setProxy has not been initialized yet.
267         setPipAnimationListener(pipAnimationListener)
268         setBubblesListener(bubblesListener)
269         registerSplitScreenListener(splitScreenListener)
270         registerSplitSelectListener(splitSelectListener)
271         homeVisibilityState.init(this.shellTransitions)
272         focusState.init(this.shellTransitions)
273         setStartingWindowListener(startingWindowListener)
274         setLauncherUnlockAnimationController(
275             launcherActivityClass,
276             launcherUnlockAnimationController,
277         )
278         LinkedHashMap(remoteTransitions).forEach { (remoteTransition, filter) ->
279             registerRemoteTransition(remoteTransition, filter)
280         }
281         setupTransactionQueue()
282         registerRecentTasksListener(recentTasksListener)
283         setBackToLauncherCallback(backToLauncherCallback, backToLauncherRunner)
284         setUnfoldAnimationListener(unfoldAnimationListener)
285         setDesktopTaskListener(desktopTaskListener)
286         setAssistantOverridesRequested(
287             ContextualSearchInvoker(context).getSysUiAssistOverrideInvocationTypes()
288         )
289         stateChangeCallbacks.forEach { it.run() }
290 
291         if (unfoldTransitionProvider != null) {
292             if (unfoldAnimation != null) {
293                 try {
294                     unfoldAnimation.setListener(unfoldTransitionProvider)
295                     unfoldTransitionProvider.isActive = true
296                 } catch (e: RemoteException) {
297                     // Ignore
298                 }
299             } else {
300                 unfoldTransitionProvider.isActive = false
301             }
302         }
303     }
304 
305     /**
306      * Clear the proxy to release held resources and turn the majority of its operations into no-ops
307      */
308     @MainThread
309     fun clearProxy() =
310         setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null)
311 
312     /** Adds a callback to be notified whenever the active state changes */
313     fun addOnStateChangeListener(callback: Runnable) = stateChangeCallbacks.add(callback)
314 
315     /** Removes a previously added state change callback */
316     fun removeOnStateChangeListener(callback: Runnable) = stateChangeCallbacks.remove(callback)
317 
318     fun isActive() = systemUiProxy != null
319 
320     private fun linkToDeath() =
321         executeWithErrorLog({ "Failed to link sysui proxy death recipient" }) {
322             systemUiProxy?.asBinder()?.linkToDeath(systemUiProxyDeathRecipient, 0 /* flags */)
323         }
324 
325     private fun unlinkToDeath() =
326         systemUiProxy?.asBinder()?.unlinkToDeath(systemUiProxyDeathRecipient, 0 /* flags */)
327 
328     fun startScreenPinning(taskId: Int) =
329         executeWithErrorLog({ "Failed call startScreenPinning" }) {
330             systemUiProxy?.startScreenPinning(taskId)
331         }
332 
333     fun onOverviewShown(fromHome: Boolean, tag: String = TAG) =
334         executeWithErrorLog(
335             { "Failed call onOverviewShown from: ${(if (fromHome) "home" else "app")}" },
336             tag = tag,
337         ) {
338             systemUiProxy?.onOverviewShown(fromHome)
339         }
340 
341     @MainThread
342     fun onStatusBarTouchEvent(event: MotionEvent) {
343         Preconditions.assertUIThread()
344         executeWithErrorLog({ "Failed call onStatusBarTouchEvent with arg: $event" }) {
345             systemUiProxy?.onStatusBarTouchEvent(event)
346         }
347     }
348 
349     fun onStatusBarTrackpadEvent(event: MotionEvent) =
350         executeWithErrorLog({ "Failed call onStatusBarTrackpadEvent with arg: $event" }) {
351             systemUiProxy?.onStatusBarTrackpadEvent(event)
352         }
353 
354     fun onAssistantProgress(progress: Float) =
355         executeWithErrorLog({ "Failed call onAssistantProgress with progress: $progress" }) {
356             systemUiProxy?.onAssistantProgress(progress)
357         }
358 
359     fun onAssistantGestureCompletion(velocity: Float) =
360         executeWithErrorLog({ "Failed call onAssistantGestureCompletion" }) {
361             systemUiProxy?.onAssistantGestureCompletion(velocity)
362         }
363 
364     fun startAssistant(args: Bundle) =
365         executeWithErrorLog({ "Failed call startAssistant" }) {
366             systemUiProxy?.startAssistant(args)
367         }
368 
369     fun setAssistantOverridesRequested(invocationTypes: IntArray) =
370         executeWithErrorLog({ "Failed call setAssistantOverridesRequested" }) {
371             systemUiProxy?.setAssistantOverridesRequested(invocationTypes)
372         }
373 
374     override fun animateNavBarLongPress(isTouchDown: Boolean, shrink: Boolean, durationMs: Long) =
375         executeWithErrorLog({ "Failed call animateNavBarLongPress" }) {
376             systemUiProxy?.animateNavBarLongPress(isTouchDown, shrink, durationMs)
377         }
378 
379     fun setOverrideHomeButtonLongPress(duration: Long, slopMultiplier: Float, haptic: Boolean) =
380         executeWithErrorLog({ "Failed call setOverrideHomeButtonLongPress" }) {
381             systemUiProxy?.setOverrideHomeButtonLongPress(duration, slopMultiplier, haptic)
382         }
383 
384     fun notifyAccessibilityButtonClicked(displayId: Int) =
385         executeWithErrorLog({ "Failed call notifyAccessibilityButtonClicked" }) {
386             systemUiProxy?.notifyAccessibilityButtonClicked(displayId)
387         }
388 
389     fun notifyAccessibilityButtonLongClicked() =
390         executeWithErrorLog({ "Failed call notifyAccessibilityButtonLongClicked" }) {
391             systemUiProxy?.notifyAccessibilityButtonLongClicked()
392         }
393 
394     fun stopScreenPinning() =
395         executeWithErrorLog({ "Failed call stopScreenPinning" }) {
396             systemUiProxy?.stopScreenPinning()
397         }
398 
399     fun notifyPrioritizedRotation(rotation: Int) =
400         executeWithErrorLog({ "Failed call notifyPrioritizedRotation with arg: $rotation" }) {
401             systemUiProxy?.notifyPrioritizedRotation(rotation)
402         }
403 
404     fun notifyTaskbarStatus(visible: Boolean, stashed: Boolean) =
405         executeWithErrorLog({ "Failed call notifyTaskbarStatus with arg: $visible, $stashed" }) {
406             systemUiProxy?.notifyTaskbarStatus(visible, stashed)
407         }
408 
409     /**
410      * NOTE: If called to suspend, caller MUST call this method to also un-suspend. [suspend] should
411      * be `true` to stop auto-hide, `false` to resume normal behavior
412      */
413     fun notifyTaskbarAutohideSuspend(suspend: Boolean) =
414         executeWithErrorLog({ "Failed call notifyTaskbarAutohideSuspend with arg: $suspend" }) {
415             systemUiProxy?.notifyTaskbarAutohideSuspend(suspend)
416         }
417 
418     fun takeScreenshot(request: ScreenshotRequest) =
419         executeWithErrorLog({ "Failed call takeScreenshot" }) {
420             systemUiProxy?.takeScreenshot(request)
421         }
422 
423     fun expandNotificationPanel() =
424         executeWithErrorLog({ "Failed call expandNotificationPanel" }) {
425             systemUiProxy?.expandNotificationPanel()
426         }
427 
428     fun toggleNotificationPanel() =
429         executeWithErrorLog({ "Failed call toggleNotificationPanel" }) {
430             systemUiProxy?.toggleNotificationPanel()
431         }
432 
433     fun toggleQuickSettingsPanel() =
434         executeWithErrorLog({ "Failed call toggleQuickSettingsPanel" }) {
435             systemUiProxy?.toggleQuickSettingsPanel()
436         }
437 
438     //
439     // Pip
440     //
441     /** Sets the shelf height. */
442     fun setShelfHeight(visible: Boolean, shelfHeight: Int) =
443         Message.obtain(asyncHandler, MSG_SET_SHELF_HEIGHT, if (visible) 1 else 0, shelfHeight)
444             .sendToTarget()
445 
446     @WorkerThread
447     private fun setShelfHeightAsync(visibleInt: Int, shelfHeight: Int) {
448         val visible = visibleInt != 0
449         val changed = visible != lastShelfVisible || shelfHeight != lastShelfHeight
450         val pip = pip
451         if (pip != null && changed) {
452             lastShelfVisible = visible
453             lastShelfHeight = shelfHeight
454             executeWithErrorLog({
455                 "Failed call setShelfHeight visible: $visible height: $shelfHeight"
456             }) {
457                 pip.setShelfHeight(visible, shelfHeight)
458             }
459         }
460     }
461 
462     /**
463      * Sets the height of the keep clear area that is going to be reported by the Launcher for the
464      * Hotseat.
465      */
466     fun setLauncherKeepClearAreaHeight(visible: Boolean, height: Int) =
467         Message.obtain(
468                 asyncHandler,
469                 MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT,
470                 if (visible) 1 else 0,
471                 height,
472             )
473             .sendToTarget()
474 
475     @WorkerThread
476     private fun setLauncherKeepClearAreaHeight(visibleInt: Int, height: Int) {
477         val visible = visibleInt != 0
478         val changed =
479             visible != lastLauncherKeepClearAreaHeightVisible ||
480                 height != lastLauncherKeepClearAreaHeight
481         val pip = pip
482         if (pip != null && changed) {
483             lastLauncherKeepClearAreaHeightVisible = visible
484             lastLauncherKeepClearAreaHeight = height
485             executeWithErrorLog({
486                 "Failed call setLauncherKeepClearAreaHeight visible: $visible height: $height"
487             }) {
488                 pip.setLauncherKeepClearAreaHeight(visible, height)
489             }
490         }
491     }
492 
493     /** Sets listener to get pip animation callbacks. */
494     fun setPipAnimationListener(listener: IPipAnimationListener?) {
495         executeWithErrorLog({ "Failed call setPinnedStackAnimationListener" }) {
496             pip?.setPipAnimationListener(listener)
497         }
498         pipAnimationListener = listener
499     }
500 
501     /** @return Destination bounds of auto-pip animation, `null` if the animation is not ready. */
502     fun startSwipePipToHome(
503         taskInfo: RunningTaskInfo,
504         launcherRotation: Int,
505         hotseatKeepClearArea: Rect?,
506     ): Rect? {
507         executeWithErrorLog({ "Failed call startSwipePipToHome" }) {
508             return pip?.startSwipePipToHome(taskInfo, launcherRotation, hotseatKeepClearArea)
509         }
510         return null
511     }
512 
513     /**
514      * Notifies WM Shell that launcher has finished the preparation of the animation for swipe to
515      * home. WM Shell can choose to fade out the overlay when entering PIP is finished, and WM Shell
516      * should be responsible for cleaning up the overlay.
517      */
518     fun stopSwipePipToHome(
519         taskId: Int,
520         componentName: ComponentName?,
521         destinationBounds: Rect?,
522         overlay: SurfaceControl?,
523         appBounds: Rect?,
524         sourceRectHint: Rect?,
525     ) =
526         executeWithErrorLog({ "Failed call stopSwipePipToHome" }) {
527             pip?.stopSwipePipToHome(
528                 taskId,
529                 componentName,
530                 destinationBounds,
531                 overlay,
532                 appBounds,
533                 sourceRectHint,
534             )
535         }
536 
537     /**
538      * Notifies WM Shell that launcher has aborted all the animation for swipe to home. WM Shell can
539      * use this callback to clean up its internal states.
540      */
541     fun abortSwipePipToHome(taskId: Int, componentName: ComponentName?) =
542         executeWithErrorLog({ "Failed call abortSwipePipToHome" }) {
543             pip?.abortSwipePipToHome(taskId, componentName)
544         }
545 
546     /** Sets the next pip animation type to be the alpha animation. */
547     fun setPipAnimationTypeToAlpha() =
548         executeWithErrorLog({ "Failed call setPipAnimationTypeToAlpha" }) {
549             pip?.setPipAnimationTypeToAlpha()
550         }
551 
552     /** Sets the app icon size in pixel used by Launcher all apps. */
553     fun setLauncherAppIconSize(iconSizePx: Int) =
554         executeWithErrorLog({ "Failed call setLauncherAppIconSize" }) {
555             pip?.setLauncherAppIconSize(iconSizePx)
556         }
557 
558     //
559     // Bubbles
560     //
561     /** Sets the listener to be notified of bubble state changes. */
562     fun setBubblesListener(listener: IBubblesListener?) {
563         executeWithErrorLog({ "Failed call registerBubblesListener" }) {
564             bubbles?.apply {
565                 bubblesListener?.let { unregisterBubbleListener(it) }
566                 listener?.let { registerBubbleListener(it) }
567             }
568         }
569         bubblesListener = listener
570     }
571 
572     /**
573      * Tells SysUI to show the bubble with the provided key.
574      *
575      * @param key the key of the bubble to show.
576      * @param top top coordinate of bubble bar on screen
577      */
578     fun showBubble(key: String?, top: Int) =
579         executeWithErrorLog({ "Failed call showBubble" }) { bubbles?.showBubble(key, top) }
580 
581     /** Tells SysUI to remove all bubbles. */
582     fun removeAllBubbles() =
583         executeWithErrorLog({ "Failed call removeAllBubbles" }) { bubbles?.removeAllBubbles() }
584 
585     /** Tells SysUI to collapse the bubbles. */
586     fun collapseBubbles() =
587         executeWithErrorLog({ "Failed call collapseBubbles" }) { bubbles?.collapseBubbles() }
588 
589     /**
590      * Tells SysUI when the bubble is being dragged. Should be called only when the bubble bar is
591      * expanded.
592      *
593      * @param bubbleKey key of the bubble being dragged
594      */
595     fun startBubbleDrag(bubbleKey: String?) =
596         executeWithErrorLog({ "Failed call startBubbleDrag" }) {
597             bubbles?.startBubbleDrag(bubbleKey)
598         }
599 
600     /**
601      * Tells SysUI when the bubble stops being dragged. Should be called only when the bubble bar is
602      * expanded.
603      *
604      * @param location location of the bubble bar
605      * @param top new top coordinate for bubble bar on screen
606      */
607     fun stopBubbleDrag(location: BubbleBarLocation?, top: Int) =
608         executeWithErrorLog({ "Failed call stopBubbleDrag" }) {
609             bubbles?.stopBubbleDrag(location, top)
610         }
611 
612     /**
613      * Tells SysUI to dismiss the bubble with the provided key.
614      *
615      * @param key the key of the bubble to dismiss.
616      * @param timestamp the timestamp when the removal happened.
617      */
618     fun dragBubbleToDismiss(key: String?, timestamp: Long) =
619         executeWithErrorLog({ "Failed call dragBubbleToDismiss" }) {
620             bubbles?.dragBubbleToDismiss(key, timestamp)
621         }
622 
623     /**
624      * Tells SysUI to show user education relative to the reference point provided.
625      *
626      * @param position the bubble bar top center position in Screen coordinates.
627      */
628     fun showUserEducation(position: Point) =
629         executeWithErrorLog({ "Failed call showUserEducation" }) {
630             bubbles?.showUserEducation(position.x, position.y)
631         }
632 
633     /**
634      * Tells SysUI to update the bubble bar location to the new location.
635      *
636      * @param location new location for the bubble bar
637      * @param source what triggered the location update
638      */
639     fun setBubbleBarLocation(location: BubbleBarLocation?, @UpdateSource source: Int) =
640         executeWithErrorLog({ "Failed call setBubbleBarLocation" }) {
641             bubbles?.setBubbleBarLocation(location, source)
642         }
643 
644     /**
645      * Tells SysUI the top coordinate of bubble bar on screen
646      *
647      * @param topOnScreen top coordinate for bubble bar on screen
648      */
649     fun updateBubbleBarTopOnScreen(topOnScreen: Int) =
650         executeWithErrorLog({ "Failed call updateBubbleBarTopOnScreen" }) {
651             bubbles?.updateBubbleBarTopOnScreen(topOnScreen)
652         }
653 
654     /**
655      * Tells SysUI to show a shortcut bubble.
656      *
657      * @param info the shortcut info used to create or identify the bubble.
658      * @param bubbleBarLocation the optional location of the bubble bar.
659      */
660     @JvmOverloads
661     fun showShortcutBubble(info: ShortcutInfo?, bubbleBarLocation: BubbleBarLocation? = null) =
662         executeWithErrorLog({ "Failed call showShortcutBubble" }) {
663             bubbles?.showShortcutBubble(info, bubbleBarLocation)
664         }
665 
666     /**
667      * Tells SysUI to show a bubble of an app.
668      *
669      * @param intent the intent used to create the bubble.
670      * @param bubbleBarLocation the optional location of the bubble bar.
671      */
672     @JvmOverloads
673     fun showAppBubble(
674         intent: Intent?,
675         user: UserHandle,
676         bubbleBarLocation: BubbleBarLocation? = null,
677     ) =
678         executeWithErrorLog({ "Failed call showAppBubble" }) {
679             bubbles?.showAppBubble(intent, user, bubbleBarLocation)
680         }
681 
682     /** Tells SysUI to show the expanded view. */
683     fun showExpandedView() =
684         executeWithErrorLog({ "Failed call showExpandedView" }) { bubbles?.showExpandedView() }
685 
686     /** Tells SysUI to show the bubble drop target. */
687     @JvmOverloads
688     fun showBubbleDropTarget(show: Boolean, bubbleBarLocation: BubbleBarLocation? = null) =
689         executeWithErrorLog({ "Failed call showDropTarget" }) {
690             bubbles?.showDropTarget(show, bubbleBarLocation)
691         }
692 
693     /** Tells SysUI to move the dragged bubble to full screen. */
694     fun moveDraggedBubbleToFullscreen(key: String, dropLocation: Point) {
695         executeWithErrorLog({ "Failed to call moveDraggedBubbleToFullscreen"}) {
696             bubbles?.moveDraggedBubbleToFullscreen(key, dropLocation)
697         }
698     }
699 
700     //
701     // Splitscreen
702     //
703     fun registerSplitScreenListener(listener: ISplitScreenListener?) {
704         executeWithErrorLog({ "Failed call registerSplitScreenListener" }) {
705             splitScreen?.registerSplitScreenListener(listener)
706         }
707         splitScreenListener = listener
708     }
709 
710     fun unregisterSplitScreenListener(listener: ISplitScreenListener?) {
711         executeWithErrorLog({ "Failed call unregisterSplitScreenListener" }) {
712             splitScreen?.unregisterSplitScreenListener(listener)
713         }
714         splitScreenListener = null
715     }
716 
717     fun registerSplitSelectListener(listener: ISplitSelectListener?) {
718         executeWithErrorLog({ "Failed call registerSplitSelectListener" }) {
719             splitScreen?.registerSplitSelectListener(listener)
720         }
721         splitSelectListener = listener
722     }
723 
724     fun unregisterSplitSelectListener(listener: ISplitSelectListener?) {
725         executeWithErrorLog({ "Failed call unregisterSplitSelectListener" }) {
726             splitScreen?.unregisterSplitSelectListener(listener)
727         }
728         splitSelectListener = null
729     }
730 
731     /** Start multiple tasks in split-screen simultaneously. */
732     fun startTasks(
733         taskId1: Int,
734         options1: Bundle?,
735         taskId2: Int,
736         options2: Bundle?,
737         @StagePosition splitPosition: Int,
738         @PersistentSnapPosition snapPosition: Int,
739         remoteTransition: RemoteTransition?,
740         instanceId: InstanceId?,
741     ) =
742         executeWithErrorLog({ "Failed call startTasks" }) {
743             splitScreen?.startTasks(
744                 taskId1,
745                 options1,
746                 taskId2,
747                 options2,
748                 splitPosition,
749                 snapPosition,
750                 remoteTransition,
751                 instanceId,
752             )
753         }
754 
755     fun startIntentAndTask(
756         pendingIntent: PendingIntent?,
757         userId1: Int,
758         options1: Bundle?,
759         taskId: Int,
760         options2: Bundle?,
761         @StagePosition splitPosition: Int,
762         @PersistentSnapPosition snapPosition: Int,
763         remoteTransition: RemoteTransition?,
764         instanceId: InstanceId?,
765     ) =
766         executeWithErrorLog({ "Failed call startIntentAndTask" }) {
767             splitScreen?.startIntentAndTask(
768                 pendingIntent,
769                 userId1,
770                 options1,
771                 taskId,
772                 options2,
773                 splitPosition,
774                 snapPosition,
775                 remoteTransition,
776                 instanceId,
777             )
778         }
779 
780     fun startIntents(
781         pendingIntent1: PendingIntent?,
782         userId1: Int,
783         shortcutInfo1: ShortcutInfo?,
784         options1: Bundle?,
785         pendingIntent2: PendingIntent?,
786         userId2: Int,
787         shortcutInfo2: ShortcutInfo?,
788         options2: Bundle?,
789         @StagePosition splitPosition: Int,
790         @PersistentSnapPosition snapPosition: Int,
791         remoteTransition: RemoteTransition?,
792         instanceId: InstanceId?,
793     ) =
794         executeWithErrorLog({ "Failed call startIntents" }) {
795             splitScreen?.startIntents(
796                 pendingIntent1,
797                 userId1,
798                 shortcutInfo1,
799                 options1,
800                 pendingIntent2,
801                 userId2,
802                 shortcutInfo2,
803                 options2,
804                 splitPosition,
805                 snapPosition,
806                 remoteTransition,
807                 instanceId,
808             )
809         }
810 
811     fun startShortcutAndTask(
812         shortcutInfo: ShortcutInfo?,
813         options1: Bundle?,
814         taskId: Int,
815         options2: Bundle?,
816         @StagePosition splitPosition: Int,
817         @PersistentSnapPosition snapPosition: Int,
818         remoteTransition: RemoteTransition?,
819         instanceId: InstanceId?,
820     ) =
821         executeWithErrorLog({ "Failed call startShortcutAndTask" }) {
822             splitScreen?.startShortcutAndTask(
823                 shortcutInfo,
824                 options1,
825                 taskId,
826                 options2,
827                 splitPosition,
828                 snapPosition,
829                 remoteTransition,
830                 instanceId,
831             )
832         }
833 
834     fun startShortcut(
835         packageName: String?,
836         shortcutId: String?,
837         position: Int,
838         options: Bundle?,
839         user: UserHandle?,
840         instanceId: InstanceId?,
841     ) =
842         executeWithErrorLog({ "Failed call startShortcut" }) {
843             splitScreen?.startShortcut(packageName, shortcutId, position, options, user, instanceId)
844         }
845 
846     fun startIntent(
847         intent: PendingIntent?,
848         userId: Int,
849         fillInIntent: Intent?,
850         position: Int,
851         options: Bundle?,
852         instanceId: InstanceId?,
853     ) =
854         executeWithErrorLog({ "Failed call startIntent" }) {
855             splitScreen?.startIntent(intent, userId, fillInIntent, position, options, instanceId)
856         }
857 
858     /**
859      * Call the desktop mode interface to start a TRANSIT_OPEN transition when launching an intent
860      * from the taskbar so that it can be handled in desktop mode.
861      */
862     fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) =
863         executeWithErrorLog({ "Failed call startLaunchIntentTransition" }) {
864             desktopMode?.startLaunchIntentTransition(intent, options, displayId)
865         }
866 
867     //
868     // One handed
869     //
870     fun startOneHandedMode() =
871         executeWithErrorLog({ "Failed call startOneHandedMode" }) { oneHanded?.startOneHanded() }
872 
873     fun stopOneHandedMode() =
874         executeWithErrorLog({ "Failed call stopOneHandedMode" }) { oneHanded?.stopOneHanded() }
875 
876     //
877     // Remote transitions
878     //
879     fun registerRemoteTransition(remoteTransition: RemoteTransition?, filter: TransitionFilter) {
880         remoteTransition ?: return
881         executeWithErrorLog({ "Failed call registerRemoteTransition" }) {
882             shellTransitions?.registerRemote(filter, remoteTransition)
883         }
884         remoteTransitions.putIfAbsent(remoteTransition, filter)
885     }
886 
887     fun unregisterRemoteTransition(remoteTransition: RemoteTransition?) {
888         executeWithErrorLog({ "Failed call unregisterRemoteTransition" }) {
889             shellTransitions?.unregisterRemote(remoteTransition)
890         }
891         remoteTransitions.remove(remoteTransition)
892     }
893 
894     /**
895      * Returns a surface which can be used to attach overlays to home task or null if the task
896      * doesn't exist or sysui is not connected
897      */
898     fun getHomeTaskOverlayContainer(): SurfaceControl? {
899         executeWithErrorLog({ "Failed call getHomeTaskOverlayContainer" }) {
900             return shellTransitions?.homeTaskOverlayContainer
901         }
902         return null
903     }
904 
905     /**
906      * Use SystemUI's transaction-queue instead of Launcher's independent one. This is necessary if
907      * Launcher and SystemUI need to coordinate transactions (eg. for shell transitions).
908      */
909     fun shareTransactionQueue() {
910         if (originalTransactionToken == null) {
911             originalTransactionToken = Transaction.getDefaultApplyToken()
912         }
913         setupTransactionQueue()
914     }
915 
916     /** Switch back to using Launcher's independent transaction queue. */
917     fun unshareTransactionQueue() {
918         if (originalTransactionToken == null) {
919             return
920         }
921         Transaction.setDefaultApplyToken(originalTransactionToken)
922         originalTransactionToken = null
923     }
924 
925     private fun setupTransactionQueue() =
926         executeWithErrorLog({ "Error getting Shell's apply token" }) {
927             val token: IBinder =
928                 shellTransitions?.shellApplyToken ?: originalTransactionToken ?: return
929             Transaction.setDefaultApplyToken(token)
930         }
931 
932     //
933     // Starting window
934     //
935     /** Sets listener to get callbacks when launching a task. */
936     fun setStartingWindowListener(listener: IStartingWindowListener?) {
937         executeWithErrorLog({ "Failed call setStartingWindowListener" }) {
938             startingWindow?.setStartingWindowListener(listener)
939         }
940         startingWindowListener = listener
941     }
942 
943     //
944     // SmartSpace transitions
945     //
946     /**
947      * Sets the instance of [ILauncherUnlockAnimationController] that System UI should use to
948      * control the launcher side of the unlock animation. This will also cause us to dispatch the
949      * current state of the smartspace to System UI (this will subsequently happen if the state
950      * changes).
951      */
952     fun setLauncherUnlockAnimationController(
953         activityClass: String?,
954         controller: ILauncherUnlockAnimationController?,
955     ) {
956         executeWithErrorLog({ "Failed call setLauncherUnlockAnimationController" }) {
957             sysuiUnlockAnimationController?.apply {
958                 setLauncherUnlockController(activityClass, controller)
959                 controller?.dispatchSmartspaceStateToSysui()
960             }
961         }
962         launcherActivityClass = activityClass
963         launcherUnlockAnimationController = controller
964     }
965 
966     /**
967      * Tells System UI that the Launcher's smartspace state has been updated, so that it can prepare
968      * the unlock animation accordingly.
969      */
970     fun notifySysuiSmartspaceStateUpdated(state: SmartspaceState?) =
971         executeWithErrorLog({ "Failed call notifySysuiSmartspaceStateUpdated" }) {
972             sysuiUnlockAnimationController?.onLauncherSmartspaceStateUpdated(state)
973         }
974 
975     //
976     // Recents
977     //
978     fun registerRecentTasksListener(listener: IRecentTasksListener?) {
979         executeWithErrorLog({ "Failed call registerRecentTasksListener" }) {
980             recentTasks?.registerRecentTasksListener(listener)
981         }
982         recentTasksListener = listener
983     }
984 
985     fun unregisterRecentTasksListener(listener: IRecentTasksListener?) {
986         executeWithErrorLog({ "Failed call unregisterRecentTasksListener" }) {
987             recentTasks?.unregisterRecentTasksListener(listener)
988         }
989         recentTasksListener = null
990     }
991 
992     //
993     // Back navigation transitions
994     //
995     /** Sets the launcher [android.window.IOnBackInvokedCallback] to shell */
996     fun setBackToLauncherCallback(
997         callback: IOnBackInvokedCallback?,
998         runner: IRemoteAnimationRunner?,
999     ) {
1000         backToLauncherCallback = callback
1001         backToLauncherRunner = runner
1002         if (callback == null) return
1003         try {
1004             backAnimation?.setBackToLauncherCallback(callback, runner)
1005         } catch (e: RemoteException) {
1006             Log.e(TAG, "Failed call setBackToLauncherCallback", e)
1007         } catch (e: SecurityException) {
1008             Log.e(TAG, "Failed call setBackToLauncherCallback", e)
1009         }
1010     }
1011 
1012     /**
1013      * Clears the previously registered [IOnBackInvokedCallback].
1014      *
1015      * @param callback The previously registered callback instance.
1016      */
1017     fun clearBackToLauncherCallback(callback: IOnBackInvokedCallback) {
1018         if (backToLauncherCallback !== callback) {
1019             return
1020         }
1021         backToLauncherCallback = null
1022         backToLauncherRunner = null
1023         executeWithErrorLog({ "Failed call clearBackToLauncherCallback" }) {
1024             backAnimation?.clearBackToLauncherCallback()
1025         }
1026     }
1027 
1028     /** Called when the status bar color needs to be customized when back navigation. */
1029     fun customizeStatusBarAppearance(appearance: AppearanceRegion?) =
1030         executeWithErrorLog({ "Failed call customizeStatusBarAppearance" }) {
1031             backAnimation?.customizeStatusBarAppearance(appearance)
1032         }
1033 
1034     class GetRecentTasksException : Exception {
1035         constructor(message: String?) : super(message)
1036 
1037         constructor(message: String?, cause: Throwable?) : super(message, cause)
1038     }
1039 
1040     /**
1041      * Retrieves a list of Recent tasks from ActivityManager.
1042      *
1043      * @throws GetRecentTasksException if IRecentTasks is not initialized, or when we get
1044      *   RemoteException from server side
1045      */
1046     @Throws(GetRecentTasksException::class)
1047     fun getRecentTasks(numTasks: Int, userId: Int): ArrayList<GroupedTaskInfo> {
1048         if (recentTasks == null) {
1049             Log.e(TAG, "getRecentTasks() failed due to null mRecentTasks")
1050             throw GetRecentTasksException("null mRecentTasks")
1051         }
1052         try {
1053             val rawTasks =
1054                 recentTasks?.getRecentTasks(
1055                     numTasks,
1056                     ActivityManager.RECENT_IGNORE_UNAVAILABLE,
1057                     userId,
1058                 ) ?: return ArrayList()
1059             return ArrayList(rawTasks.asList())
1060         } catch (e: RemoteException) {
1061             Log.e(TAG, "Failed call getRecentTasks", e)
1062             throw GetRecentTasksException("Failed call getRecentTasks", e)
1063         }
1064     }
1065 
1066     /** Gets the set of running tasks. */
1067     fun getRunningTasks(numTasks: Int): List<RunningTaskInfo> {
1068         if (!shouldEnableRunningTasksForDesktopMode()) return emptyList()
1069         executeWithErrorLog({ "Failed call getRunningTasks" }) {
1070             return recentTasks?.getRunningTasks(numTasks)?.asList() ?: emptyList()
1071         }
1072         return emptyList()
1073     }
1074 
1075     private fun shouldEnableRunningTasksForDesktopMode(): Boolean =
1076         DesktopModeStatus.canEnterDesktopMode(context) &&
1077             ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS.isTrue
1078 
1079     private fun handleMessageAsync(msg: Message): Boolean {
1080         return when (msg.what) {
1081             MSG_SET_SHELF_HEIGHT -> {
1082                 setShelfHeightAsync(msg.arg1, msg.arg2)
1083                 true
1084             }
1085 
1086             MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT -> {
1087                 setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2)
1088                 true
1089             }
1090 
1091             else -> false
1092         }
1093     }
1094 
1095     //
1096     // Desktop Mode
1097     //
1098     /** Calls shell to create a new desk (if possible) on the display whose ID is `displayId`. */
1099     fun createDesk(displayId: Int) =
1100         executeWithErrorLog({ "Failed call createDesk" }) { desktopMode?.createDesk(displayId) }
1101 
1102     /**
1103      * Calls shell to activate the desk whose ID is `deskId` on whatever display it exists on. This
1104      * will bring all tasks on this desk to the front.
1105      */
1106     fun activateDesk(deskId: Int, transition: RemoteTransition?) =
1107         executeWithErrorLog({ "Failed call activateDesk" }) {
1108             desktopMode?.activateDesk(deskId, transition)
1109         }
1110 
1111     /** Calls shell to remove the desk whose ID is `deskId`. */
1112     fun removeDesk(deskId: Int) =
1113         executeWithErrorLog({ "Failed call removeDesk" }) { desktopMode?.removeDesk(deskId) }
1114 
1115     /** Calls shell to remove all the available desks on all displays. */
1116     fun removeAllDesks() =
1117         executeWithErrorLog({ "Failed call removeAllDesks" }) { desktopMode?.removeAllDesks() }
1118 
1119     /** Call shell to show all apps active on the desktop */
1120     fun showDesktopApps(displayId: Int, transition: RemoteTransition?) =
1121         executeWithErrorLog({ "Failed call showDesktopApps" }) {
1122             desktopMode?.showDesktopApps(displayId, transition)
1123         }
1124 
1125     /** If task with the given id is on the desktop, bring it to front */
1126     fun showDesktopApp(
1127         taskId: Int,
1128         transition: RemoteTransition?,
1129         toFrontReason: DesktopTaskToFrontReason,
1130     ) =
1131         executeWithErrorLog({ "Failed call showDesktopApp" }) {
1132             desktopMode?.showDesktopApp(taskId, transition, toFrontReason)
1133         }
1134 
1135     /** Set a listener on shell to get updates about desktop task state */
1136     fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
1137         desktopTaskListener = listener
1138         executeWithErrorLog({ "Failed call setDesktopTaskListener" }) {
1139             desktopMode?.setTaskListener(listener)
1140         }
1141     }
1142 
1143     /** Perform cleanup transactions after animation to split select is complete */
1144     fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo?) =
1145         executeWithErrorLog({ "Failed call onDesktopSplitSelectAnimComplete" }) {
1146             desktopMode?.onDesktopSplitSelectAnimComplete(taskInfo)
1147         }
1148 
1149     /** Call shell to move a task with given `taskId` to desktop */
1150     fun moveToDesktop(
1151         taskId: Int,
1152         transitionSource: DesktopModeTransitionSource?,
1153         transition: RemoteTransition?,
1154         successCallback: Runnable,
1155     ) =
1156         executeWithErrorLog({ "Failed call moveToDesktop" }) {
1157             desktopMode?.moveToDesktop(
1158                 taskId,
1159                 transitionSource,
1160                 transition,
1161                 object : IMoveToDesktopCallback.Stub() {
1162                     override fun onTaskMovedToDesktop() {
1163                         successCallback.run()
1164                     }
1165                 },
1166             )
1167         }
1168 
1169     /** Call shell to remove the desktop that is on given `displayId` */
1170     fun removeDefaultDeskInDisplay(displayId: Int) =
1171         executeWithErrorLog({ "Failed call removeDefaultDeskInDisplay" }) {
1172             desktopMode?.removeDefaultDeskInDisplay(displayId)
1173         }
1174 
1175     /** Call shell to move a task with given `taskId` to external display. */
1176     fun moveToExternalDisplay(taskId: Int) =
1177         executeWithErrorLog({ "Failed call moveToExternalDisplay" }) {
1178             desktopMode?.moveToExternalDisplay(taskId)
1179         }
1180 
1181     //
1182     // Unfold transition
1183     //
1184     /** Sets the unfold animation lister to sysui. */
1185     fun setUnfoldAnimationListener(callback: IUnfoldTransitionListener?) {
1186         unfoldAnimationListener = callback
1187         executeWithErrorLog({ "Failed call setUnfoldAnimationListener" }) {
1188             unfoldAnimation?.setListener(callback)
1189         }
1190     }
1191 
1192     //
1193     // Recents
1194     //
1195     /** Starts the recents activity. The caller should manage the thread on which this is called. */
1196     fun startRecentsActivity(
1197         intent: Intent?,
1198         options: ActivityOptions,
1199         listener: RecentsAnimationListener,
1200         useSyntheticRecentsTransition: Boolean,
1201     ): Boolean {
1202         executeWithErrorLog({ "Error starting recents via shell" }) {
1203             recentTasks?.startRecentsTransition(
1204                 recentsPendingIntent,
1205                 intent,
1206                 options.toBundle().apply {
1207                     if (useSyntheticRecentsTransition) {
1208                         putBoolean("is_synthetic_recents_transition", true)
1209                     }
1210                 },
1211                 context.iApplicationThread,
1212                 RecentsAnimationListenerStub(listener),
1213             )
1214                 ?: run {
1215                     ActiveGestureProtoLogProxy.logRecentTasksMissing()
1216                     return false
1217                 }
1218             return true
1219         }
1220         return false
1221     }
1222 
1223     private class RecentsAnimationListenerStub(val listener: RecentsAnimationListener) :
1224         IRecentsAnimationRunner.Stub() {
1225         override fun onAnimationStart(
1226             controller: IRecentsAnimationController,
1227             apps: Array<RemoteAnimationTarget>?,
1228             wallpapers: Array<RemoteAnimationTarget>?,
1229             homeContentInsets: Rect?,
1230             minimizedHomeBounds: Rect?,
1231             extras: Bundle?,
1232             transitionInfo: TransitionInfo?,
1233         ) =
1234             listener.onAnimationStart(
1235                 RecentsAnimationControllerCompat(controller),
1236                 apps,
1237                 wallpapers,
1238                 homeContentInsets,
1239                 minimizedHomeBounds,
1240                 extras?.apply {
1241                     // Aidl bundles need to explicitly set class loader
1242                     // https://developer.android.com/guide/components/aidl#Bundles
1243                     classLoader = SplitBounds::class.java.classLoader
1244                 },
1245                 transitionInfo,
1246             )
1247 
1248         override fun onAnimationCanceled(taskIds: IntArray?, taskSnapshots: Array<TaskSnapshot>?) =
1249             listener.onAnimationCanceled(wrap(taskIds, taskSnapshots))
1250 
1251         override fun onTasksAppeared(
1252             apps: Array<RemoteAnimationTarget>?,
1253             transitionInfo: TransitionInfo?,
1254         ) {
1255             listener.onTasksAppeared(apps, transitionInfo)
1256         }
1257     }
1258 
1259     //
1260     // Drag and drop
1261     //
1262     /**
1263      * For testing purposes. Returns `true` only if the shell drop target has shown and drawn and is
1264      * ready to handle drag events and the subsequent drop.
1265      */
1266     fun isDragAndDropReady(): Boolean {
1267         executeWithErrorLog({ "Error querying drag state" }) {
1268             return dragAndDrop?.isReadyToHandleDrag ?: false
1269         }
1270         return false
1271     }
1272 
1273     fun dump(pw: PrintWriter) {
1274         pw.println("$TAG:")
1275 
1276         pw.println("\tmSystemUiProxy=$systemUiProxy")
1277         pw.println("\tmPip=$pip")
1278         pw.println("\tmPipAnimationListener=$pipAnimationListener")
1279         pw.println("\tmBubbles=$bubbles")
1280         pw.println("\tmBubblesListener=$bubblesListener")
1281         pw.println("\tmSplitScreen=$splitScreen")
1282         pw.println("\tmSplitScreenListener=$splitScreenListener")
1283         pw.println("\tmSplitSelectListener=$splitSelectListener")
1284         pw.println("\tmOneHanded=$oneHanded")
1285         pw.println("\tmShellTransitions=$shellTransitions")
1286         pw.println("\tmHomeVisibilityState=" + homeVisibilityState)
1287         pw.println("\tmFocusState=" + focusState)
1288         pw.println("\tmStartingWindow=$startingWindow")
1289         pw.println("\tmStartingWindowListener=$startingWindowListener")
1290         pw.println("\tmSysuiUnlockAnimationController=$sysuiUnlockAnimationController")
1291         pw.println("\tmLauncherActivityClass=$launcherActivityClass")
1292         pw.println("\tmLauncherUnlockAnimationController=$launcherUnlockAnimationController")
1293         pw.println("\tmRecentTasks=$recentTasks")
1294         pw.println("\tmRecentTasksListener=$recentTasksListener")
1295         pw.println("\tmBackAnimation=$backAnimation")
1296         pw.println("\tmBackToLauncherCallback=$backToLauncherCallback")
1297         pw.println("\tmBackToLauncherRunner=$backToLauncherRunner")
1298         pw.println("\tmDesktopMode=$desktopMode")
1299         pw.println("\tmDesktopTaskListener=$desktopTaskListener")
1300         pw.println("\tmUnfoldAnimation=$unfoldAnimation")
1301         pw.println("\tmUnfoldAnimationListener=$unfoldAnimationListener")
1302         pw.println("\tmDragAndDrop=$dragAndDrop")
1303     }
1304 
1305     /** Adds all interfaces held by this proxy to the bundle */
1306     @VisibleForTesting
1307     fun addAllInterfaces(out: Bundle) {
1308         QuickStepContract.addInterface(systemUiProxy, out)
1309         QuickStepContract.addInterface(pip, out)
1310         QuickStepContract.addInterface(bubbles, out)
1311         QuickStepContract.addInterface(sysuiUnlockAnimationController, out)
1312         QuickStepContract.addInterface(splitScreen, out)
1313         QuickStepContract.addInterface(oneHanded, out)
1314         QuickStepContract.addInterface(shellTransitions, out)
1315         QuickStepContract.addInterface(startingWindow, out)
1316         QuickStepContract.addInterface(recentTasks, out)
1317         QuickStepContract.addInterface(backAnimation, out)
1318         QuickStepContract.addInterface(desktopMode, out)
1319         QuickStepContract.addInterface(unfoldAnimation, out)
1320         QuickStepContract.addInterface(dragAndDrop, out)
1321     }
1322 
1323     companion object {
1324         private const val TAG = "SystemUiProxy"
1325 
1326         @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getSystemUiProxy)
1327 
1328         private const val MSG_SET_SHELF_HEIGHT = 1
1329         private const val MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2
1330     }
1331 }
1332