1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.am;
17 
18 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
19 import static android.os.Process.INVALID_UID;
20 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
21 
22 import static com.android.car.CarLog.TAG_AM;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.UserIdInt;
27 import android.app.ActivityManager;
28 import android.app.ActivityOptions;
29 import android.app.ActivityTaskManager.RootTaskInfo;
30 import android.app.IActivityManager;
31 import android.app.IProcessObserver;
32 import android.app.Presentation;
33 import android.app.TaskStackListener;
34 import android.car.hardware.power.CarPowerManager;
35 import android.car.user.CarUserManager;
36 import android.car.user.CarUserManager.UserLifecycleListener;
37 import android.content.BroadcastReceiver;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.pm.ActivityInfo;
43 import android.content.pm.PackageInfo;
44 import android.content.pm.PackageManager;
45 import android.hardware.display.DisplayManager;
46 import android.net.Uri;
47 import android.os.HandlerThread;
48 import android.os.RemoteException;
49 import android.os.SystemClock;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.util.IndentingPrintWriter;
53 import android.util.Log;
54 import android.util.Slog;
55 import android.util.SparseArray;
56 import android.view.Display;
57 
58 import com.android.car.CarLocalServices;
59 import com.android.car.CarServiceBase;
60 import com.android.car.CarServiceUtils;
61 import com.android.car.R;
62 import com.android.car.user.CarUserService;
63 import com.android.internal.annotations.GuardedBy;
64 import com.android.internal.annotations.VisibleForTesting;
65 
66 import java.util.List;
67 
68 /**
69  * Monitors top activity for a display and guarantee activity in fixed mode is re-launched if it has
70  * crashed or gone to background for whatever reason.
71  *
72  * <p>This component also monitors the upddate of the target package and re-launch it once
73  * update is complete.</p>
74  */
75 public final class FixedActivityService implements CarServiceBase {
76 
77     private static final boolean DBG = false;
78 
79     private static final long RECHECK_INTERVAL_MS = 500;
80     private static final int MAX_NUMBER_OF_CONSECUTIVE_CRASH_RETRY = 5;
81     // If process keep running without crashing, will reset consecutive crash counts.
82     private static final long CRASH_FORGET_INTERVAL_MS = 2 * 60 * 1000; // 2 mins
83 
84     private static class RunningActivityInfo {
85         @NonNull
86         public final Intent intent;
87 
88         @NonNull
89         public final ActivityOptions activityOptions;
90 
91         @UserIdInt
92         public final int userId;
93 
94         @GuardedBy("mLock")
95         public boolean isVisible;
96         @GuardedBy("mLock")
97         public long lastLaunchTimeMs = 0;
98         @GuardedBy("mLock")
99         public int consecutiveRetries = 0;
100         @GuardedBy("mLock")
101         public int taskId = INVALID_TASK_ID;
102         @GuardedBy("mLock")
103         public int previousTaskId = INVALID_TASK_ID;
104         @GuardedBy("mLock")
105         public boolean inBackground;
106         @GuardedBy("mLock")
107         public boolean failureLogged;
108 
RunningActivityInfo(@onNull Intent intent, @NonNull ActivityOptions activityOptions, @UserIdInt int userId)109         RunningActivityInfo(@NonNull Intent intent, @NonNull ActivityOptions activityOptions,
110                 @UserIdInt int userId) {
111             this.intent = intent;
112             this.activityOptions = activityOptions;
113             this.userId = userId;
114         }
115 
resetCrashCounterLocked()116         private void resetCrashCounterLocked() {
117             consecutiveRetries = 0;
118             failureLogged = false;
119         }
120 
121         @Override
toString()122         public String toString() {
123             return "RunningActivityInfo{intent:" + intent + ",activityOptions:" + activityOptions
124                     + ",userId:" + userId + ",isVisible:" + isVisible
125                     + ",lastLaunchTimeMs:" + lastLaunchTimeMs
126                     + ",consecutiveRetries:" + consecutiveRetries + ",taskId:" + taskId + "}";
127         }
128     }
129 
130     private final Context mContext;
131 
132     private final IActivityManager mAm;
133 
134     private final DisplayManager mDm;
135 
136     private final UserManager mUm;
137 
138     private final UserLifecycleListener mUserLifecycleListener = event -> {
139         if (Log.isLoggable(TAG_AM, Log.DEBUG)) {
140             Slog.d(TAG_AM, "onEvent(" + event + ")");
141         }
142         if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
143             synchronized (FixedActivityService.this.mLock) {
144                 clearRunningActivitiesLocked();
145             }
146         }
147     };
148 
149     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
150         @Override
151         public void onReceive(Context context, Intent intent) {
152             String action = intent.getAction();
153             if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
154                     || Intent.ACTION_PACKAGE_REPLACED.equals(action)
155                     || Intent.ACTION_PACKAGE_ADDED.equals(action)
156                     || Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
157                 Uri packageData = intent.getData();
158                 if (packageData == null) {
159                     Slog.w(TAG_AM, "null packageData");
160                     return;
161                 }
162                 String packageName = packageData.getSchemeSpecificPart();
163                 if (packageName == null) {
164                     Slog.w(TAG_AM, "null packageName");
165                     return;
166                 }
167                 int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
168                 int userId = UserHandle.getUserId(uid);
169                 boolean tryLaunch = false;
170                 synchronized (mLock) {
171                     for (int i = 0; i < mRunningActivities.size(); i++) {
172                         RunningActivityInfo info = mRunningActivities.valueAt(i);
173                         // Should do this for all activities as it can happen for multiple
174                         // displays. Package name is ignored as one package can affect
175                         // others.
176                         if (info.userId == userId) {
177                             Slog.i(TAG_AM, "Package changed:" + packageName
178                                     + ",user:" + userId + ",action:" + action);
179                             info.resetCrashCounterLocked();
180                             tryLaunch = true;
181                             break;
182                         }
183                     }
184                 }
185                 if (tryLaunch) {
186                     launchIfNecessary();
187                 }
188             }
189         }
190     };
191 
192     // It says listener but is actually callback.
193     private final TaskStackListener mTaskStackListener = new TaskStackListener() {
194         @Override
195         public void onTaskStackChanged() {
196             launchIfNecessary();
197         }
198 
199         @Override
200         public void onTaskCreated(int taskId, ComponentName componentName) {
201             launchIfNecessary();
202         }
203 
204         @Override
205         public void onTaskRemoved(int taskId) {
206             launchIfNecessary();
207         }
208 
209         @Override
210         public void onTaskMovedToFront(int taskId) {
211             launchIfNecessary();
212         }
213 
214         @Override
215         public void onTaskRemovalStarted(int taskId) {
216             launchIfNecessary();
217         }
218     };
219 
220 
221     private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
222         @Override
223         public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
224             launchIfNecessary();
225         }
226 
227         @Override
228         public void onForegroundServicesChanged(int pid, int uid, int fgServiceTypes) {
229           // ignore
230         }
231 
232         @Override
233         public void onProcessDied(int pid, int uid) {
234             launchIfNecessary();
235         }
236     };
237 
238     private final HandlerThread mHandlerThread;
239 
240     private final Runnable mActivityCheckRunnable = () -> {
241         launchIfNecessary();
242     };
243 
244     private final Object mLock = new Object();
245 
246     // key: displayId
247     @GuardedBy("mLock")
248     private final SparseArray<RunningActivityInfo> mRunningActivities =
249             new SparseArray<>(/* capacity= */ 1); // default to one cluster only case
250 
251     @GuardedBy("mLock")
252     private final SparseArray<Presentation> mBlockingPresentations = new SparseArray<>(1);
253 
254     @GuardedBy("mLock")
255     private boolean mEventMonitoringActive;
256 
257     @GuardedBy("mLock")
258     private CarPowerManager mCarPowerManager;
259 
260     private final CarPowerManager.CarPowerStateListener mCarPowerStateListener = (state) -> {
261         if (state != CarPowerManager.CarPowerStateListener.ON) {
262             return;
263         }
264         synchronized (mLock) {
265             for (int i = 0; i < mRunningActivities.size(); i++) {
266                 RunningActivityInfo info = mRunningActivities.valueAt(i);
267                 info.resetCrashCounterLocked();
268             }
269         }
270         launchIfNecessary();
271     };
272 
FixedActivityService(Context context)273     public FixedActivityService(Context context) {
274         this(context, ActivityManager.getService(), context.getSystemService(UserManager.class),
275                 context.getSystemService(DisplayManager.class));
276     }
277 
FixedActivityService(Context context, IActivityManager activityManager, UserManager userManager, DisplayManager displayManager)278     FixedActivityService(Context context, IActivityManager activityManager,
279             UserManager userManager, DisplayManager displayManager) {
280         mContext = context;
281         mAm = activityManager;
282         mUm = userManager;
283         mDm = displayManager;
284         mHandlerThread = CarServiceUtils.getHandlerThread(
285                 FixedActivityService.class.getSimpleName());
286     }
287 
288     @Override
init()289     public void init() {
290         // nothing to do
291     }
292 
293     @Override
release()294     public void release() {
295         stopMonitoringEvents();
296     }
297 
298     @Override
dump(IndentingPrintWriter writer)299     public void dump(IndentingPrintWriter writer) {
300         writer.println("*FixedActivityService*");
301         synchronized (mLock) {
302             writer.println("mRunningActivities:" + mRunningActivities
303                     + " ,mEventMonitoringActive:" + mEventMonitoringActive);
304             writer.println("mBlockingPresentations:");
305             for (int i = 0; i < mBlockingPresentations.size(); i++) {
306                 Presentation p = mBlockingPresentations.valueAt(i);
307                 if (p == null) {
308                     continue;
309                 }
310                 writer.println("display:" + mBlockingPresentations.keyAt(i)
311                         + " showing:" + p.isShowing());
312             }
313         }
314     }
315 
clearRunningActivitiesLocked()316     private void clearRunningActivitiesLocked() {
317         for (int i = mRunningActivities.size() - 1; i >= 0; i--) {
318             RunningActivityInfo info = mRunningActivities.valueAt(i);
319             if (info == null || !isUserAllowedToLaunchActivity(info.userId)) {
320                 mRunningActivities.removeAt(i);
321             }
322         }
323     }
324 
postRecheck(long delayMs)325     private void postRecheck(long delayMs) {
326         mHandlerThread.getThreadHandler().postDelayed(mActivityCheckRunnable, delayMs);
327     }
328 
startMonitoringEvents()329     private void startMonitoringEvents() {
330         CarPowerManager carPowerManager;
331         synchronized (mLock) {
332             if (mEventMonitoringActive) {
333                 return;
334             }
335             mEventMonitoringActive = true;
336             carPowerManager = CarLocalServices.createCarPowerManager(mContext);
337             mCarPowerManager = carPowerManager;
338         }
339         CarUserService userService = CarLocalServices.getService(CarUserService.class);
340         userService.addUserLifecycleListener(mUserLifecycleListener);
341         IntentFilter filter = new IntentFilter();
342         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
343         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
344         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
345         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
346         filter.addDataScheme("package");
347         mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
348                 /* broadcastPermission= */ null, /* scheduler= */ null);
349         try {
350             mAm.registerTaskStackListener(mTaskStackListener);
351             mAm.registerProcessObserver(mProcessObserver);
352         } catch (RemoteException e) {
353             Slog.e(TAG_AM, "remote exception from AM", e);
354         }
355         try {
356             carPowerManager.setListener(mCarPowerStateListener);
357         } catch (Exception e) {
358             // should not happen
359             Slog.e(TAG_AM, "Got exception from CarPowerManager", e);
360         }
361     }
362 
stopMonitoringEvents()363     private void stopMonitoringEvents() {
364         CarPowerManager carPowerManager;
365         synchronized (mLock) {
366             if (!mEventMonitoringActive) {
367                 return;
368             }
369             mEventMonitoringActive = false;
370             carPowerManager = mCarPowerManager;
371             mCarPowerManager = null;
372         }
373         if (carPowerManager != null) {
374             carPowerManager.clearListener();
375         }
376         mHandlerThread.getThreadHandler().removeCallbacks(mActivityCheckRunnable);
377         CarUserService userService = CarLocalServices.getService(CarUserService.class);
378         userService.removeUserLifecycleListener(mUserLifecycleListener);
379         try {
380             mAm.unregisterTaskStackListener(mTaskStackListener);
381             mAm.unregisterProcessObserver(mProcessObserver);
382         } catch (RemoteException e) {
383             Slog.e(TAG_AM, "remote exception from AM", e);
384         }
385         mContext.unregisterReceiver(mBroadcastReceiver);
386     }
387 
388     @Nullable
getRootTaskInfos()389     private List<RootTaskInfo> getRootTaskInfos() {
390         try {
391             return mAm.getAllRootTaskInfos();
392         } catch (RemoteException e) {
393             Slog.e(TAG_AM, "remote exception from AM", e);
394         }
395         return null;
396     }
397 
398     /**
399      * Launches all stored fixed mode activities if necessary.
400      * @param displayId Display id to check if it is visible. If check is not necessary, should pass
401      *        {@link Display#INVALID_DISPLAY}.
402      * @return true if fixed Activity for given {@code displayId} is visible / successfully
403      *         launched. It will return false for {@link Display#INVALID_DISPLAY} {@code displayId}.
404      */
launchIfNecessary(int displayId)405     private boolean launchIfNecessary(int displayId) {
406         List<RootTaskInfo> infos = getRootTaskInfos();
407         if (infos == null) {
408             Slog.e(TAG_AM, "cannot get RootTaskInfo from AM");
409             return false;
410         }
411         long now = SystemClock.elapsedRealtime();
412         synchronized (mLock) {
413             if (mRunningActivities.size() == 0) {
414                 // it must have been stopped.
415                 if (DBG) {
416                     Slog.i(TAG_AM, "empty activity list", new RuntimeException());
417                 }
418                 return false;
419             }
420             for (int i = mRunningActivities.size() - 1; i >= 0; i--) {
421                 int displayIdForActivity = mRunningActivities.keyAt(i);
422                 Display display = mDm.getDisplay(displayIdForActivity);
423                 if (display == null) {
424                     Slog.e(TAG_AM, "Stop fixed activity for non-available display"
425                             + displayIdForActivity);
426                     mRunningActivities.removeAt(i);
427                     continue;
428                 }
429 
430                 RunningActivityInfo activityInfo = mRunningActivities.valueAt(i);
431                 activityInfo.isVisible = false;
432                 if (isUserAllowedToLaunchActivity(activityInfo.userId)) {
433                     continue;
434                 }
435                 if (activityInfo.taskId != INVALID_TASK_ID) {
436                     Slog.i(TAG_AM, "Finishing fixed activity on user switching:"
437                             + activityInfo);
438                     try {
439                         mAm.removeTask(activityInfo.taskId);
440                     } catch (RemoteException e) {
441                         Slog.e(TAG_AM, "remote exception from AM", e);
442                     }
443                     CarServiceUtils.runOnMain(() -> {
444                         Presentation p = new Presentation(mContext, display,
445                                 android.R.style.Theme_Black_NoTitleBar_Fullscreen,
446                                 // TYPE_PRESENTATION can't be used in the internal display.
447                                 // Select TYPE_KEYGUARD_DIALOG, since it's used in
448                                 // {@Code KeyguardDisplayManager.KeyguardPresentation}.
449                                 TYPE_KEYGUARD_DIALOG);
450                         p.setContentView(R.layout.activity_continuous_blank);
451                         synchronized (mLock) {
452                             RunningActivityInfo info = mRunningActivities.get(displayIdForActivity);
453                             if (info != null && info.userId == ActivityManager.getCurrentUser()) {
454                                 Slog.i(TAG_AM, "Do not show Presentation, new req already made");
455                                 return;
456                             }
457                             mBlockingPresentations.append(displayIdForActivity, p);
458                         }
459                         p.show();
460                     });
461                 }
462                 mRunningActivities.removeAt(i);
463             }
464             for (RootTaskInfo taskInfo : infos) {
465                 RunningActivityInfo activityInfo = mRunningActivities.get(taskInfo.displayId);
466                 if (activityInfo == null) {
467                     continue;
468                 }
469                 int topUserId = taskInfo.childTaskUserIds[taskInfo.childTaskUserIds.length - 1];
470                 if (activityInfo.intent.getComponent().equals(taskInfo.topActivity)
471                         && activityInfo.userId == topUserId && taskInfo.visible) {
472                     // top one is matching.
473                     activityInfo.isVisible = true;
474                     activityInfo.taskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1];
475                     continue;
476                 }
477                 activityInfo.previousTaskId =
478                         taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1];
479                 Slog.i(TAG_AM, "Unmatched top activity will be removed:"
480                         + taskInfo.topActivity + " top task id:" + activityInfo.previousTaskId
481                         + " user:" + topUserId + " display:" + taskInfo.displayId);
482                 activityInfo.inBackground = false;
483                 for (int i = 0; i < taskInfo.childTaskIds.length - 1; i++) {
484                     if (activityInfo.taskId == taskInfo.childTaskIds[i]) {
485                         activityInfo.inBackground = true;
486                     }
487                 }
488                 if (!activityInfo.inBackground) {
489                     activityInfo.taskId = INVALID_TASK_ID;
490                 }
491             }
492 
493             for (int i = 0; i < mRunningActivities.size(); i++) {
494                 RunningActivityInfo activityInfo = mRunningActivities.valueAt(i);
495                 long timeSinceLastLaunchMs = now - activityInfo.lastLaunchTimeMs;
496                 if (activityInfo.isVisible) {
497                     if (timeSinceLastLaunchMs >= CRASH_FORGET_INTERVAL_MS) {
498                         activityInfo.consecutiveRetries = 0;
499                     }
500                     continue;
501                 }
502                 if (!isComponentAvailable(activityInfo.intent.getComponent(),
503                         activityInfo.userId)) {
504                     continue;
505                 }
506                 // For 1st call (consecutiveRetries == 0), do not wait as there can be no posting
507                 // for recheck.
508                 if (activityInfo.consecutiveRetries > 0 && (timeSinceLastLaunchMs
509                         < RECHECK_INTERVAL_MS)) {
510                     // wait until next check interval comes.
511                     continue;
512                 }
513                 if (activityInfo.consecutiveRetries >= MAX_NUMBER_OF_CONSECUTIVE_CRASH_RETRY) {
514                     // re-tried too many times, give up for now.
515                     if (!activityInfo.failureLogged) {
516                         activityInfo.failureLogged = true;
517                         Slog.w(TAG_AM, "Too many relaunch failure of fixed activity:"
518                                 + activityInfo);
519                     }
520                     continue;
521                 }
522 
523                 Slog.i(TAG_AM, "Launching Activity for fixed mode. Intent:" + activityInfo.intent
524                         + ",userId:" + UserHandle.of(activityInfo.userId) + ",displayId:"
525                         + mRunningActivities.keyAt(i));
526                 // Increase retry count if task is not in background. In case like other app is
527                 // launched and the target activity is still in background, do not consider it
528                 // as retry.
529                 if (!activityInfo.inBackground) {
530                     activityInfo.consecutiveRetries++;
531                 }
532                 try {
533                     postRecheck(RECHECK_INTERVAL_MS);
534                     postRecheck(CRASH_FORGET_INTERVAL_MS);
535                     mContext.startActivityAsUser(activityInfo.intent,
536                             activityInfo.activityOptions.toBundle(),
537                             UserHandle.of(activityInfo.userId));
538                     activityInfo.isVisible = true;
539                     activityInfo.lastLaunchTimeMs = SystemClock.elapsedRealtime();
540                 } catch (Exception e) { // Catch all for any app related issues.
541                     Slog.w(TAG_AM, "Cannot start activity:" + activityInfo.intent, e);
542                 }
543             }
544             RunningActivityInfo activityInfo = mRunningActivities.get(displayId);
545             if (activityInfo == null) {
546                 return false;
547             }
548             return activityInfo.isVisible;
549         }
550     }
551 
552     @VisibleForTesting
launchIfNecessary()553     void launchIfNecessary() {
554         launchIfNecessary(Display.INVALID_DISPLAY);
555     }
556 
logComponentNotFound(ComponentName component, @UserIdInt int userId, Exception e)557     private void logComponentNotFound(ComponentName component, @UserIdInt  int userId,
558             Exception e) {
559         Slog.e(TAG_AM, "Specified Component not found:" + component
560                 + " for userid:" + userId, e);
561     }
562 
isComponentAvailable(ComponentName component, @UserIdInt int userId)563     private boolean isComponentAvailable(ComponentName component, @UserIdInt int userId) {
564         PackageInfo packageInfo;
565         try {
566             packageInfo = mContext.getPackageManager().getPackageInfoAsUser(
567                     component.getPackageName(), PackageManager.GET_ACTIVITIES, userId);
568         } catch (PackageManager.NameNotFoundException e) {
569             logComponentNotFound(component, userId, e);
570             return false;
571         }
572         if (packageInfo == null || packageInfo.activities == null) {
573             // may not be necessary but additional safety check
574             logComponentNotFound(component, userId, new RuntimeException());
575             return false;
576         }
577         String fullName = component.getClassName();
578         String shortName = component.getShortClassName();
579         for (ActivityInfo info : packageInfo.activities) {
580             if (info.name.equals(fullName) || info.name.equals(shortName)) {
581                 return true;
582             }
583         }
584         logComponentNotFound(component, userId, new RuntimeException());
585         return false;
586     }
587 
isUserAllowedToLaunchActivity(@serIdInt int userId)588     private boolean isUserAllowedToLaunchActivity(@UserIdInt int userId) {
589         int currentUser = ActivityManager.getCurrentUser();
590         if (userId == currentUser || userId == UserHandle.USER_SYSTEM) {
591             return true;
592         }
593         int[] profileIds = mUm.getEnabledProfileIds(currentUser);
594         // null can happen in test env when UserManager is mocked. So this check is not necessary
595         // in real env but add it to make test impl easier.
596         if (profileIds == null) {
597             return false;
598         }
599         for (int id : profileIds) {
600             if (id == userId) {
601                 return true;
602             }
603         }
604         return false;
605     }
606 
isDisplayAllowedForFixedMode(int displayId)607     private boolean isDisplayAllowedForFixedMode(int displayId) {
608         if (displayId == Display.DEFAULT_DISPLAY || displayId == Display.INVALID_DISPLAY) {
609             Slog.w(TAG_AM, "Target display cannot be used for fixed mode, displayId:" + displayId,
610                     new RuntimeException());
611             return false;
612         }
613         return true;
614     }
615 
616     @VisibleForTesting
getRunningFixedActivity(int displayId)617     RunningActivityInfo getRunningFixedActivity(int displayId) {
618         synchronized (mLock) {
619             return mRunningActivities.get(displayId);
620         }
621     }
622 
623     /**
624      * Checks {@link InstrumentClusterRenderingService#startFixedActivityModeForDisplayAndUser(
625      * Intent, ActivityOptions, int)}
626      */
startFixedActivityModeForDisplayAndUser(@onNull Intent intent, @NonNull ActivityOptions options, int displayId, @UserIdInt int userId)627     public boolean startFixedActivityModeForDisplayAndUser(@NonNull Intent intent,
628             @NonNull ActivityOptions options, int displayId, @UserIdInt int userId) {
629         if (!isDisplayAllowedForFixedMode(displayId)) {
630             return false;
631         }
632         if (options == null) {
633             Slog.e(TAG_AM, "startFixedActivityModeForDisplayAndUser, null options");
634             return false;
635         }
636         if (!isUserAllowedToLaunchActivity(userId)) {
637             Slog.e(TAG_AM, "startFixedActivityModeForDisplayAndUser, requested user:" + userId
638                     + " cannot launch activity, Intent:" + intent);
639             return false;
640         }
641         ComponentName component = intent.getComponent();
642         if (component == null) {
643             Slog.e(TAG_AM,
644                     "startFixedActivityModeForDisplayAndUser: No component specified for "
645                             + "requested Intent"
646                             + intent);
647             return false;
648         }
649         if (!isComponentAvailable(component, userId)) {
650             return false;
651         }
652         boolean startMonitoringEvents = false;
653         synchronized (mLock) {
654             Presentation p = mBlockingPresentations.removeReturnOld(displayId);
655             if (p != null) {
656                 p.dismiss();
657             }
658             if (mRunningActivities.size() == 0) {
659                 startMonitoringEvents = true;
660             }
661             RunningActivityInfo activityInfo = mRunningActivities.get(displayId);
662             boolean replaceEntry = true;
663             if (activityInfo != null && activityInfo.intent.equals(intent)
664                     && options.equals(activityInfo.activityOptions)
665                     && userId == activityInfo.userId) {
666                 replaceEntry = false;
667                 if (activityInfo.isVisible) { // already shown.
668                     return true;
669                 }
670             }
671             if (replaceEntry) {
672                 activityInfo = new RunningActivityInfo(intent, options, userId);
673                 mRunningActivities.put(displayId, activityInfo);
674             }
675         }
676         boolean launched = launchIfNecessary(displayId);
677         if (!launched) {
678             synchronized (mLock) {
679                 mRunningActivities.remove(displayId);
680             }
681         }
682         // If first trial fails, let client know and do not retry as it can be wrong setting.
683         if (startMonitoringEvents && launched) {
684             startMonitoringEvents();
685         }
686         return launched;
687     }
688 
689     /** Check {@link InstrumentClusterRenderingService#stopFixedActivityMode(int)} */
stopFixedActivityMode(int displayId)690     public void stopFixedActivityMode(int displayId) {
691         if (!isDisplayAllowedForFixedMode(displayId)) {
692             return;
693         }
694         boolean stopMonitoringEvents = false;
695         synchronized (mLock) {
696             mRunningActivities.remove(displayId);
697             if (mRunningActivities.size() == 0) {
698                 stopMonitoringEvents = true;
699             }
700         }
701         if (stopMonitoringEvents) {
702             stopMonitoringEvents();
703         }
704     }
705 }
706