1 /*
2  * Copyright (C) 2014 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.systemui.recents;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
22 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
23 
24 import android.app.ActivityManager;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.ServiceConnection;
29 import android.content.pm.ActivityInfo;
30 import android.content.res.Configuration;
31 import android.content.res.Resources;
32 import android.graphics.Point;
33 import android.graphics.Rect;
34 import android.hardware.display.DisplayManager;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.RemoteException;
38 import android.os.UserHandle;
39 import android.util.EventLog;
40 import android.util.Log;
41 import android.view.Display;
42 import android.widget.Toast;
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
45 import com.android.systemui.EventLogConstants;
46 import com.android.systemui.EventLogTags;
47 import com.android.systemui.R;
48 import com.android.systemui.SysUiServiceProvider;
49 import com.android.systemui.pip.PipUI;
50 import com.android.systemui.recents.events.EventBus;
51 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
52 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
53 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
54 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
55 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
56 import com.android.systemui.recents.events.component.ExpandPipEvent;
57 import com.android.systemui.recents.events.component.HidePipMenuEvent;
58 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
59 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
60 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
61 import com.android.systemui.recents.events.component.ShowUserToastEvent;
62 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
63 import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
64 import com.android.systemui.recents.misc.SystemServicesProxy;
65 import com.android.systemui.recents.model.RecentsTaskLoader;
66 import com.android.systemui.shared.system.ActivityManagerWrapper;
67 import com.android.systemui.stackdivider.Divider;
68 import java.io.PrintWriter;
69 import java.util.ArrayList;
70 import java.util.HashSet;
71 import java.util.Set;
72 
73 /**
74  * An implementation of the SystemUI recents component, which supports both system and secondary
75  * users.
76  */
77 public class LegacyRecentsImpl implements RecentsImplementation {
78 
79     private final static String TAG = "Recents";
80 
81     public final static int EVENT_BUS_PRIORITY = 1;
82     public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
83 
84     public final static Set<String> RECENTS_ACTIVITIES = new HashSet<>();
85     static {
86         RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY);
87     }
88 
89     private static final String COUNTER_WINDOW_SUPPORTED = "window_enter_supported";
90     private static final String COUNTER_WINDOW_UNSUPPORTED = "window_enter_unsupported";
91     private static final String COUNTER_WINDOW_INCOMPATIBLE = "window_enter_incompatible";
92 
93     private static SystemServicesProxy sSystemServicesProxy;
94     private static RecentsDebugFlags sDebugFlags;
95     private static RecentsTaskLoader sTaskLoader;
96     private static RecentsConfiguration sConfiguration;
97 
98     private Context mContext;
99     private SysUiServiceProvider mSysUiServiceProvider;
100     private Handler mHandler;
101     private RecentsImpl mImpl;
102 
103     // Only For system user, this is the callbacks instance we return to each secondary user
104     private RecentsSystemUser mSystemToUserCallbacks;
105 
106     // Only for secondary users, this is the callbacks instance provided by the system user to make
107     // calls back
108     private IRecentsSystemUserCallbacks mUserToSystemCallbacks;
109 
110     // The set of runnables to run after binding to the system user's service.
111     private final ArrayList<Runnable> mOnConnectRunnables = new ArrayList<>();
112 
113     // Only for secondary users, this is the death handler for the binder from the system user
114     private final IBinder.DeathRecipient mUserToSystemCallbacksDeathRcpt = new IBinder.DeathRecipient() {
115         @Override
116         public void binderDied() {
117             mUserToSystemCallbacks = null;
118             EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
119                     EventLogConstants.SYSUI_RECENTS_CONNECTION_USER_SYSTEM_UNBOUND,
120                     sSystemServicesProxy.getProcessUser());
121 
122             // Retry after a fixed duration
123             mHandler.postDelayed(new Runnable() {
124                 @Override
125                 public void run() {
126                     registerWithSystemUser();
127                 }
128             }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
129         }
130     };
131 
132     // Only for secondary users, this is the service connection we use to connect to the system user
133     private final ServiceConnection mUserToSystemServiceConnection = new ServiceConnection() {
134         @Override
135         public void onServiceConnected(ComponentName name, IBinder service) {
136             if (service != null) {
137                 mUserToSystemCallbacks = IRecentsSystemUserCallbacks.Stub.asInterface(
138                         service);
139                 EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
140                         EventLogConstants.SYSUI_RECENTS_CONNECTION_USER_SYSTEM_BOUND,
141                         sSystemServicesProxy.getProcessUser());
142 
143                 // Listen for system user's death, so that we can reconnect later
144                 try {
145                     service.linkToDeath(mUserToSystemCallbacksDeathRcpt, 0);
146                 } catch (RemoteException e) {
147                     Log.e(TAG, "Lost connection to (System) SystemUI", e);
148                 }
149 
150                 // Run each of the queued runnables
151                 runAndFlushOnConnectRunnables();
152             }
153 
154             // Unbind ourselves now that we've registered our callbacks.  The
155             // binder to the system user are still valid at this point.
156             mContext.unbindService(this);
157         }
158 
159         @Override
160         public void onServiceDisconnected(ComponentName name) {
161             // Do nothing
162         }
163     };
164 
165     /**
166      * Returns the callbacks interface that non-system users can call.
167      */
getSystemUserCallbacks()168     public IBinder getSystemUserCallbacks() {
169         return mSystemToUserCallbacks;
170     }
171 
getTaskLoader()172     public static RecentsTaskLoader getTaskLoader() {
173         return sTaskLoader;
174     }
175 
176 
getSystemServices()177     public static SystemServicesProxy getSystemServices() {
178         return sSystemServicesProxy;
179     }
180 
getConfiguration()181     public static RecentsConfiguration getConfiguration() {
182         return sConfiguration;
183     }
184 
getDebugFlags()185     public static RecentsDebugFlags getDebugFlags() {
186         return sDebugFlags;
187     }
188 
189     @Override
onStart(Context context, SysUiServiceProvider sysUiServiceProvider)190     public void onStart(Context context, SysUiServiceProvider sysUiServiceProvider) {
191         mContext = context;
192         mSysUiServiceProvider = sysUiServiceProvider;
193         final Resources res = mContext.getResources();
194         final int defaultTaskBarBackgroundColor =
195                 mContext.getColor(R.color.recents_task_bar_default_background_color);
196         final int defaultTaskViewBackgroundColor =
197                 mContext.getColor(R.color.recents_task_view_default_background_color);
198         getComponent(Recents.class).putComponent(LegacyRecentsImpl.class, this);
199         sDebugFlags = new RecentsDebugFlags();
200         sSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
201         sConfiguration = new RecentsConfiguration(mContext);
202         sTaskLoader = new RecentsTaskLoader(mContext,
203                 // TODO: Once we start building the AAR, move these into the loader
204                 res.getInteger(R.integer.config_recents_max_thumbnail_count),
205                 res.getInteger(R.integer.config_recents_max_icon_count),
206                 res.getInteger(R.integer.recents_svelte_level));
207         sTaskLoader.setDefaultColors(defaultTaskBarBackgroundColor, defaultTaskViewBackgroundColor);
208         mHandler = new Handler();
209         mImpl = new RecentsImpl(mContext);
210 
211         // Register with the event bus
212         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
213         EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY);
214         EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
215 
216         // Due to the fact that RecentsActivity is per-user, we need to establish and interface for
217         // the system user's Recents component to pass events (like show/hide/toggleRecents) to the
218         // secondary user, and vice versa (like visibility change, screen pinning).
219         final int processUser = sSystemServicesProxy.getProcessUser();
220         if (sSystemServicesProxy.isSystemUser(processUser)) {
221             // For the system user, initialize an instance of the interface that we can pass to the
222             // secondary user
223             mSystemToUserCallbacks = new RecentsSystemUser(mContext, mImpl);
224         } else {
225             // For the secondary user, bind to the primary user's service to get a persistent
226             // interface to register its implementation and to later update its state
227             registerWithSystemUser();
228         }
229     }
230 
231     @Override
onBootCompleted()232     public void onBootCompleted() {
233         mImpl.onBootCompleted();
234     }
235 
236 
237     @Override
growRecents()238     public void growRecents() {
239         EventBus.getDefault().send(new RecentsGrowingEvent());
240     }
241 
242     /**
243      * Shows the Recents.
244      */
245     @Override
showRecentApps(boolean triggeredFromAltTab)246     public void showRecentApps(boolean triggeredFromAltTab) {
247         ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
248         int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
249         int currentUser = sSystemServicesProxy.getCurrentUser();
250         if (sSystemServicesProxy.isSystemUser(currentUser)) {
251             mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
252                     true /* animate */, recentsGrowTarget);
253         } else {
254             if (mSystemToUserCallbacks != null) {
255                 IRecentsNonSystemUserCallbacks callbacks =
256                         mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
257                 if (callbacks != null) {
258                     try {
259                         callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
260                                 true /* animate */, recentsGrowTarget);
261                     } catch (RemoteException e) {
262                         Log.e(TAG, "Callback failed", e);
263                     }
264                 } else {
265                     Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
266                 }
267             }
268         }
269     }
270 
271     /**
272      * Hides the Recents.
273      */
274     @Override
hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey)275     public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
276         int currentUser = sSystemServicesProxy.getCurrentUser();
277         if (sSystemServicesProxy.isSystemUser(currentUser)) {
278             mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
279         } else {
280             if (mSystemToUserCallbacks != null) {
281                 IRecentsNonSystemUserCallbacks callbacks =
282                         mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
283                 if (callbacks != null) {
284                     try {
285                         callbacks.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
286                     } catch (RemoteException e) {
287                         Log.e(TAG, "Callback failed", e);
288                     }
289                 } else {
290                     Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
291                 }
292             }
293         }
294     }
295 
296     /**
297      * Toggles the Recents activity.
298      */
299     @Override
toggleRecentApps()300     public void toggleRecentApps() {
301         int growTarget = getComponent(Divider.class).getView().growsRecents();
302         int currentUser = sSystemServicesProxy.getCurrentUser();
303         if (sSystemServicesProxy.isSystemUser(currentUser)) {
304             mImpl.toggleRecents(growTarget);
305         } else {
306             if (mSystemToUserCallbacks != null) {
307                 IRecentsNonSystemUserCallbacks callbacks =
308                         mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
309                 if (callbacks != null) {
310                     try {
311                         callbacks.toggleRecents(growTarget);
312                     } catch (RemoteException e) {
313                         Log.e(TAG, "Callback failed", e);
314                     }
315                 } else {
316                     Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
317                 }
318             }
319         }
320     }
321 
322     /**
323      * Preloads info for the Recents activity.
324      */
325     @Override
preloadRecentApps()326     public void preloadRecentApps() {
327         int currentUser = sSystemServicesProxy.getCurrentUser();
328         if (sSystemServicesProxy.isSystemUser(currentUser)) {
329             mImpl.preloadRecents();
330         } else {
331             if (mSystemToUserCallbacks != null) {
332                 IRecentsNonSystemUserCallbacks callbacks =
333                         mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
334                 if (callbacks != null) {
335                     try {
336                         callbacks.preloadRecents();
337                     } catch (RemoteException e) {
338                         Log.e(TAG, "Callback failed", e);
339                     }
340                 } else {
341                     Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
342                 }
343             }
344         }
345     }
346 
347     @Override
cancelPreloadRecentApps()348     public void cancelPreloadRecentApps() {
349         int currentUser = sSystemServicesProxy.getCurrentUser();
350         if (sSystemServicesProxy.isSystemUser(currentUser)) {
351             mImpl.cancelPreloadingRecents();
352         } else {
353             if (mSystemToUserCallbacks != null) {
354                 IRecentsNonSystemUserCallbacks callbacks =
355                         mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
356                 if (callbacks != null) {
357                     try {
358                         callbacks.cancelPreloadingRecents();
359                     } catch (RemoteException e) {
360                         Log.e(TAG, "Callback failed", e);
361                     }
362                 } else {
363                     Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
364                 }
365             }
366         }
367     }
368 
369     @Override
splitPrimaryTask(int stackCreateMode, Rect initialBounds, int metricsDockAction)370     public boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds, int metricsDockAction) {
371         Point realSize = new Point();
372         if (initialBounds == null) {
373             mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY)
374                     .getRealSize(realSize);
375             initialBounds = new Rect(0, 0, realSize.x, realSize.y);
376         }
377 
378         int currentUser = sSystemServicesProxy.getCurrentUser();
379         ActivityManager.RunningTaskInfo runningTask =
380                 ActivityManagerWrapper.getInstance().getRunningTask();
381         final int activityType = runningTask != null
382                 ? runningTask.configuration.windowConfiguration.getActivityType()
383                 : ACTIVITY_TYPE_UNDEFINED;
384         boolean screenPinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
385         boolean isRunningTaskInHomeOrRecentsStack =
386                 activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
387         if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
388             logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
389             if (runningTask.supportsSplitScreenMultiWindow) {
390                 if (metricsDockAction != -1) {
391                     MetricsLogger.action(mContext, metricsDockAction,
392                             runningTask.topActivity.flattenToShortString());
393                 }
394                 if (sSystemServicesProxy.isSystemUser(currentUser)) {
395                     mImpl.splitPrimaryTask(runningTask.id, stackCreateMode, initialBounds);
396                 } else {
397                     if (mSystemToUserCallbacks != null) {
398                         IRecentsNonSystemUserCallbacks callbacks =
399                                 mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
400                         if (callbacks != null) {
401                             try {
402                                 callbacks.splitPrimaryTask(runningTask.id, stackCreateMode,
403                                         initialBounds);
404                             } catch (RemoteException e) {
405                                 Log.e(TAG, "Callback failed", e);
406                             }
407                         } else {
408                             Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
409                         }
410                     }
411                 }
412 
413                 return true;
414             } else {
415                 EventBus.getDefault().send(new ShowUserToastEvent(
416                         R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT));
417                 return false;
418             }
419         } else {
420             return false;
421         }
422     }
423 
logDockAttempt(Context ctx, ComponentName activity, int resizeMode)424     public static void logDockAttempt(Context ctx, ComponentName activity, int resizeMode) {
425         if (resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE) {
426             MetricsLogger.action(ctx, MetricsEvent.ACTION_WINDOW_DOCK_UNRESIZABLE,
427                     activity.flattenToShortString());
428         }
429         MetricsLogger.count(ctx, getMetricsCounterForResizeMode(resizeMode), 1);
430     }
431 
getMetricsCounterForResizeMode(int resizeMode)432     private static String getMetricsCounterForResizeMode(int resizeMode) {
433         switch (resizeMode) {
434             case ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE:
435                 return COUNTER_WINDOW_UNSUPPORTED;
436             case ActivityInfo.RESIZE_MODE_RESIZEABLE:
437             case ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION:
438                 return COUNTER_WINDOW_SUPPORTED;
439             default:
440                 return COUNTER_WINDOW_INCOMPATIBLE;
441         }
442     }
443 
444     @Override
onAppTransitionFinished()445     public void onAppTransitionFinished() {
446         if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
447             // Fallback, reset the flag once an app transition ends
448             EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(
449                     false /* waitingForTransitionStart */));
450         }
451     }
452 
453     /**
454      * Updates on configuration change.
455      */
onConfigurationChanged(Configuration newConfig)456     public void onConfigurationChanged(Configuration newConfig) {
457         int currentUser = sSystemServicesProxy.getCurrentUser();
458         if (sSystemServicesProxy.isSystemUser(currentUser)) {
459             mImpl.onConfigurationChanged();
460         } else {
461             if (mSystemToUserCallbacks != null) {
462                 IRecentsNonSystemUserCallbacks callbacks =
463                         mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
464                 if (callbacks != null) {
465                     try {
466                         callbacks.onConfigurationChanged();
467                     } catch (RemoteException e) {
468                         Log.e(TAG, "Callback failed", e);
469                     }
470                 } else {
471                     Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
472                 }
473             }
474         }
475     }
476 
477     /**
478      * Handle Recents activity visibility changed.
479      */
onBusEvent(final RecentsVisibilityChangedEvent event)480     public final void onBusEvent(final RecentsVisibilityChangedEvent event) {
481         SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
482         int processUser = ssp.getProcessUser();
483         if (ssp.isSystemUser(processUser)) {
484             mImpl.onVisibilityChanged(event.applicationContext, event.visible);
485         } else {
486             postToSystemUser(new Runnable() {
487                 @Override
488                 public void run() {
489                     try {
490                         mUserToSystemCallbacks.updateRecentsVisibility(event.visible);
491                     } catch (RemoteException e) {
492                         Log.e(TAG, "Callback failed", e);
493                     }
494                 }
495             });
496         }
497 
498         // This will catch the cases when a user launches from recents to another app
499         // (and vice versa) that is not in the recents stack (such as home or bugreport) and it
500         // would not reset the wait for transition flag. This will catch it and make sure that the
501         // flag is reset.
502         if (!event.visible) {
503             mImpl.setWaitingForTransitionStart(false);
504         }
505     }
506 
onBusEvent(DockedFirstAnimationFrameEvent event)507     public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
508         SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
509         int processUser = ssp.getProcessUser();
510         if (ssp.isSystemUser(processUser)) {
511             final Divider divider = getComponent(Divider.class);
512             if (divider != null) {
513                 divider.onDockedFirstAnimationFrame();
514             }
515         } else {
516             postToSystemUser(new Runnable() {
517                 @Override
518                 public void run() {
519                     try {
520                         mUserToSystemCallbacks.sendDockedFirstAnimationFrameEvent();
521                     } catch (RemoteException e) {
522                         Log.e(TAG, "Callback failed", e);
523                     }
524                 }
525             });
526         }
527     }
528 
529     /**
530      * Handle screen pinning request.
531      */
onBusEvent(final ScreenPinningRequestEvent event)532     public final void onBusEvent(final ScreenPinningRequestEvent event) {
533         int processUser = sSystemServicesProxy.getProcessUser();
534         if (sSystemServicesProxy.isSystemUser(processUser)) {
535             mImpl.onStartScreenPinning(event.applicationContext, event.taskId);
536         } else {
537             postToSystemUser(new Runnable() {
538                 @Override
539                 public void run() {
540                     try {
541                         mUserToSystemCallbacks.startScreenPinning(event.taskId);
542                     } catch (RemoteException e) {
543                         Log.e(TAG, "Callback failed", e);
544                     }
545                 }
546             });
547         }
548     }
549 
onBusEvent(final RecentsDrawnEvent event)550     public final void onBusEvent(final RecentsDrawnEvent event) {
551         int processUser = sSystemServicesProxy.getProcessUser();
552         if (sSystemServicesProxy.isSystemUser(processUser)) {
553             final Divider divider = getComponent(Divider.class);
554             if (divider != null) {
555                 divider.onRecentsDrawn();
556             }
557         } else {
558             postToSystemUser(new Runnable() {
559                 @Override
560                 public void run() {
561                     try {
562                         mUserToSystemCallbacks.sendRecentsDrawnEvent();
563                     } catch (RemoteException e) {
564                         Log.e(TAG, "Callback failed", e);
565                     }
566                 }
567             });
568         }
569     }
570 
onBusEvent(final DockedTopTaskEvent event)571     public final void onBusEvent(final DockedTopTaskEvent event) {
572         int processUser = sSystemServicesProxy.getProcessUser();
573         if (sSystemServicesProxy.isSystemUser(processUser)) {
574             final Divider divider = getComponent(Divider.class);
575             if (divider != null) {
576                 divider.onDockedTopTask();
577             }
578         } else {
579             postToSystemUser(new Runnable() {
580                 @Override
581                 public void run() {
582                     try {
583                         mUserToSystemCallbacks.sendDockingTopTaskEvent(event.initialRect);
584                     } catch (RemoteException e) {
585                         Log.e(TAG, "Callback failed", e);
586                     }
587                 }
588             });
589         }
590     }
591 
onBusEvent(final RecentsActivityStartingEvent event)592     public final void onBusEvent(final RecentsActivityStartingEvent event) {
593         int processUser = sSystemServicesProxy.getProcessUser();
594         if (sSystemServicesProxy.isSystemUser(processUser)) {
595             final Divider divider = getComponent(Divider.class);
596             if (divider != null) {
597                 divider.onRecentsActivityStarting();
598             }
599         } else {
600             postToSystemUser(new Runnable() {
601                 @Override
602                 public void run() {
603                     try {
604                         mUserToSystemCallbacks.sendLaunchRecentsEvent();
605                     } catch (RemoteException e) {
606                         Log.e(TAG, "Callback failed", e);
607                     }
608                 }
609             });
610         }
611     }
612 
onBusEvent(LaunchTaskFailedEvent event)613     public final void onBusEvent(LaunchTaskFailedEvent event) {
614         // Reset the transition when tasks fail to launch
615         mImpl.setWaitingForTransitionStart(false);
616     }
617 
onBusEvent(ConfigurationChangedEvent event)618     public final void onBusEvent(ConfigurationChangedEvent event) {
619         // Update the configuration for the Recents component when the activity configuration
620         // changes as well
621         mImpl.onConfigurationChanged();
622     }
623 
onBusEvent(ShowUserToastEvent event)624     public final void onBusEvent(ShowUserToastEvent event) {
625         int currentUser = sSystemServicesProxy.getCurrentUser();
626         if (sSystemServicesProxy.isSystemUser(currentUser)) {
627             mImpl.onShowCurrentUserToast(event.msgResId, event.msgLength);
628         } else {
629             if (mSystemToUserCallbacks != null) {
630                 IRecentsNonSystemUserCallbacks callbacks =
631                         mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
632                 if (callbacks != null) {
633                     try {
634                         callbacks.showCurrentUserToast(event.msgResId, event.msgLength);
635                     } catch (RemoteException e) {
636                         Log.e(TAG, "Callback failed", e);
637                     }
638                 } else {
639                     Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
640                 }
641             }
642         }
643     }
644 
onBusEvent(SetWaitingForTransitionStartEvent event)645     public final void onBusEvent(SetWaitingForTransitionStartEvent event) {
646         int processUser = sSystemServicesProxy.getProcessUser();
647         if (sSystemServicesProxy.isSystemUser(processUser)) {
648             mImpl.setWaitingForTransitionStart(event.waitingForTransitionStart);
649         } else {
650             postToSystemUser(new Runnable() {
651                 @Override
652                 public void run() {
653                     try {
654                         mUserToSystemCallbacks.setWaitingForTransitionStartEvent(
655                                 event.waitingForTransitionStart);
656                     } catch (RemoteException e) {
657                         Log.e(TAG, "Callback failed", e);
658                     }
659                 }
660             });
661         }
662     }
663 
onBusEvent(ExpandPipEvent event)664     public final void onBusEvent(ExpandPipEvent event) {
665         PipUI pipUi = getComponent(PipUI.class);
666         if (pipUi == null) {
667             return;
668         }
669         pipUi.expandPip();
670     }
671 
onBusEvent(HidePipMenuEvent event)672     public final void onBusEvent(HidePipMenuEvent event) {
673         PipUI pipUi = getComponent(PipUI.class);
674         if (pipUi == null) {
675             return;
676         }
677         event.getAnimationTrigger().increment();
678         pipUi.hidePipMenu(() -> {
679                 event.getAnimationTrigger().increment();
680             }, () -> {
681                 event.getAnimationTrigger().decrement();
682             });
683         event.getAnimationTrigger().decrement();
684     }
685 
686     /**
687      * Attempts to register with the system user.
688      */
registerWithSystemUser()689     private void registerWithSystemUser() {
690         final int processUser = sSystemServicesProxy.getProcessUser();
691         postToSystemUser(new Runnable() {
692             @Override
693             public void run() {
694                 try {
695                     mUserToSystemCallbacks.registerNonSystemUserCallbacks(
696                             new RecentsImplProxy(mImpl), processUser);
697                 } catch (RemoteException e) {
698                     Log.e(TAG, "Failed to register", e);
699                 }
700             }
701         });
702     }
703 
704     /**
705      * Runs the runnable in the system user's Recents context, connecting to the service if
706      * necessary.
707      */
postToSystemUser(final Runnable onConnectRunnable)708     private void postToSystemUser(final Runnable onConnectRunnable) {
709         mOnConnectRunnables.add(onConnectRunnable);
710         if (mUserToSystemCallbacks == null) {
711             Intent systemUserServiceIntent = new Intent();
712             systemUserServiceIntent.setClass(mContext, RecentsSystemUserService.class);
713             boolean bound = mContext.bindServiceAsUser(systemUserServiceIntent,
714                     mUserToSystemServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
715             EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
716                     EventLogConstants.SYSUI_RECENTS_CONNECTION_USER_BIND_SERVICE,
717                     sSystemServicesProxy.getProcessUser());
718             if (!bound) {
719                 // Retry after a fixed duration
720                 mHandler.postDelayed(new Runnable() {
721                     @Override
722                     public void run() {
723                         registerWithSystemUser();
724                     }
725                 }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
726             }
727         } else {
728             runAndFlushOnConnectRunnables();
729         }
730     }
731 
732     /**
733      * Runs all the queued runnables after a service connection is made.
734      */
runAndFlushOnConnectRunnables()735     private void runAndFlushOnConnectRunnables() {
736         for (Runnable r : mOnConnectRunnables) {
737             r.run();
738         }
739         mOnConnectRunnables.clear();
740     }
741 
getComponent(Class<T> clazz)742     private <T> T getComponent(Class<T> clazz) {
743         return mSysUiServiceProvider.getComponent(clazz);
744     }
745 
746     @Override
dump(PrintWriter pw)747     public void dump(PrintWriter pw) {
748         pw.println("Recents");
749         pw.println("  currentUserId=" + SystemServicesProxy.getInstance(mContext).getCurrentUser());
750     }
751 }
752