• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.car.builtin.app.ActivityManagerHelper.INVALID_TASK_ID;
19 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
20 import static android.os.Process.INVALID_UID;
21 
22 import static com.android.car.CarLog.TAG_AM;
23 import static com.android.car.CarServiceUtils.getHandlerThread;
24 import static com.android.car.CarServiceUtils.isEventOfType;
25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
26 
27 import android.annotation.NonNull;
28 import android.annotation.UserIdInt;
29 import android.app.ActivityManager;
30 import android.app.ActivityOptions;
31 import android.app.TaskInfo;
32 import android.car.builtin.app.ActivityManagerHelper;
33 import android.car.builtin.app.ActivityManagerHelper.ProcessObserverCallback;
34 import android.car.builtin.app.TaskInfoHelper;
35 import android.car.builtin.content.ContextHelper;
36 import android.car.builtin.content.pm.PackageManagerHelper;
37 import android.car.builtin.util.Slogf;
38 import android.car.hardware.power.CarPowerManager;
39 import android.car.user.CarUserManager.UserLifecycleListener;
40 import android.car.user.UserLifecycleEventFilter;
41 import android.content.BroadcastReceiver;
42 import android.content.ComponentName;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.IntentFilter;
46 import android.content.pm.ActivityInfo;
47 import android.content.pm.PackageInfo;
48 import android.content.pm.PackageManager;
49 import android.hardware.display.DisplayManager;
50 import android.hardware.display.DisplayManager.DisplayListener;
51 import android.net.Uri;
52 import android.os.BaseBundle;
53 import android.os.Bundle;
54 import android.os.Handler;
55 import android.os.SystemClock;
56 import android.os.UserHandle;
57 import android.os.UserManager;
58 import android.util.Log;
59 import android.util.SparseArray;
60 import android.util.proto.ProtoOutputStream;
61 import android.view.Display;
62 
63 import com.android.car.CarLocalServices;
64 import com.android.car.CarServiceBase;
65 import com.android.car.R;
66 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
67 import com.android.car.internal.util.IndentingPrintWriter;
68 import com.android.car.user.CarUserService;
69 import com.android.car.user.UserHandleHelper;
70 import com.android.internal.annotations.GuardedBy;
71 import com.android.internal.annotations.VisibleForTesting;
72 
73 import java.lang.reflect.Array;
74 import java.util.List;
75 import java.util.Objects;
76 import java.util.Set;
77 
78 /**
79  * Monitors top activity for a display and guarantee activity in fixed mode is re-launched if it has
80  * crashed or gone to background for whatever reason.
81  *
82  * <p>This component also monitors the update of the target package and re-launch it once
83  * update is complete.</p>
84  */
85 public final class FixedActivityService implements CarServiceBase {
86 
87     private static final boolean DBG = Slogf.isLoggable(TAG_AM, Log.DEBUG);
88 
89     private static final long RECHECK_INTERVAL_MS = 500;
90     private static final int MAX_NUMBER_OF_CONSECUTIVE_CRASH_RETRY = 5;
91     // If process keep running without crashing, will reset consecutive crash counts.
92     private static final long CRASH_FORGET_INTERVAL_MS = 2 * 60 * 1000; // 2 mins
93 
94     private static class RunningActivityInfo {
95         @NonNull
96         public final Intent intent;
97 
98         @NonNull
99         public final Bundle activityOptions;
100 
101         @UserIdInt
102         public final int userId;
103 
104         public boolean isVisible;
105         // Whether startActivity was called for this Activity. If the flag is false,
106         // FixedActivityService will call startActivity() even if the Activity is currently visible.
107         public boolean isStarted;
108 
109         public long lastLaunchTimeMs;
110 
111         public int consecutiveRetries;
112 
113         public int taskId = INVALID_TASK_ID;
114 
115         public int previousTaskId = INVALID_TASK_ID;
116 
117         public boolean inBackground;
118 
119         public boolean failureLogged;
120 
RunningActivityInfo(@onNull Intent intent, @NonNull Bundle activityOptions, @UserIdInt int userId)121         RunningActivityInfo(@NonNull Intent intent, @NonNull Bundle activityOptions,
122                 @UserIdInt int userId) {
123             this.intent = intent;
124             this.activityOptions = activityOptions;
125             this.userId = userId;
126         }
127 
resetCrashCounterLocked()128         private void resetCrashCounterLocked() {
129             consecutiveRetries = 0;
130             failureLogged = false;
131         }
132 
133         @Override
toString()134         public String toString() {
135             return "RunningActivityInfo{intent:" + intent + ",activityOptions:" + activityOptions
136                     + ",userId:" + userId + ",isVisible:" + isVisible + ",isStarted:" + isStarted
137                     + ",lastLaunchTimeMs:" + lastLaunchTimeMs
138                     + ",consecutiveRetries:" + consecutiveRetries + ",taskId:" + taskId
139                     + ",previousTaskId:" + previousTaskId + ",inBackground:" + inBackground + "}";
140         }
141     }
142 
143     private final Context mContext;
144 
145     private final CarActivityService mActivityService;
146 
147     private final DisplayManager mDm;
148 
149     private final UserLifecycleListener mUserLifecycleListener = event -> {
150         if (!isEventOfType(TAG_AM, event, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) {
151             return;
152         }
153         if (DBG) {
154             Slogf.d(TAG_AM, "onEvent(" + event + ")");
155         }
156 
157         synchronized (FixedActivityService.this.mLock) {
158             clearRunningActivitiesLocked();
159         }
160     };
161 
162     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
163         @Override
164         public void onReceive(Context context, Intent intent) {
165             String action = intent.getAction();
166             if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
167                     || Intent.ACTION_PACKAGE_REPLACED.equals(action)
168                     || Intent.ACTION_PACKAGE_ADDED.equals(action)
169                     || Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
170                 Uri packageData = intent.getData();
171                 if (packageData == null) {
172                     Slogf.w(TAG_AM, "null packageData");
173                     return;
174                 }
175                 String packageName = packageData.getSchemeSpecificPart();
176                 if (packageName == null) {
177                     Slogf.w(TAG_AM, "null packageName");
178                     return;
179                 }
180                 int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
181                 int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
182                 boolean tryLaunch = false;
183                 synchronized (mLock) {
184                     for (int i = 0; i < mRunningActivities.size(); i++) {
185                         RunningActivityInfo info = mRunningActivities.valueAt(i);
186                         // Should do this for all activities as it can happen for multiple
187                         // displays. Package name is ignored as one package can affect
188                         // others.
189                         if (info.userId == userId) {
190                             Slogf.i(TAG_AM, "Package changed:" + packageName
191                                     + ",user:" + userId + ",action:" + action);
192                             info.resetCrashCounterLocked();
193                             tryLaunch = true;
194                             break;
195                         }
196                     }
197                 }
198                 if (tryLaunch) {
199                     launchIfNecessary();
200                 }
201             }
202         }
203     };
204 
205     private final ProcessObserverCallback mProcessObserver = new ProcessObserverCallback() {
206         @Override
207         public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
208             launchIfNecessary();
209         }
210         @Override
211         public void onProcessDied(int pid, int uid) {
212             launchIfNecessary();
213         }
214     };
215 
216     private final DisplayListener mDisplayListener = new DisplayListener() {
217 
218         @Override
219         public void onDisplayChanged(int displayId) {
220             if (DBG) {
221                 Slogf.d(TAG_AM, "onDisplayChanged(%d)", displayId);
222             }
223             launchForDisplay(displayId);
224         }
225 
226         @Override
227         public void onDisplayAdded(int displayId) {
228             // do nothing.
229         }
230 
231         @Override
232         public void onDisplayRemoved(int displayId) {
233             // do nothing.
234         }
235     };
236 
237     private final Handler mHandler;
238 
239     private final Runnable mActivityCheckRunnable = () -> {
240         launchIfNecessary();
241     };
242 
243     private final Object mLock = new Object();
244 
245     // key: displayId
246     @GuardedBy("mLock")
247     private final SparseArray<RunningActivityInfo> mRunningActivities =
248             new SparseArray<>(/* capacity= */ 1); // default to one cluster only case
249 
250     @GuardedBy("mLock")
251     private boolean mEventMonitoringActive;
252 
253     @GuardedBy("mLock")
254     private CarPowerManager mCarPowerManager;
255 
256     private final CarPowerManager.CarPowerStateListener mCarPowerStateListener = (state) -> {
257         if (state != CarPowerManager.STATE_ON) {
258             return;
259         }
260         synchronized (mLock) {
261             for (int i = 0; i < mRunningActivities.size(); i++) {
262                 RunningActivityInfo info = mRunningActivities.valueAt(i);
263                 info.resetCrashCounterLocked();
264             }
265         }
266         launchIfNecessary();
267     };
268 
269     private final UserHandleHelper mUserHandleHelper;
270 
FixedActivityService(Context context, CarActivityService activityService)271     public FixedActivityService(Context context, CarActivityService activityService) {
272         this(context, activityService,
273                 context.getSystemService(DisplayManager.class),
274                 new UserHandleHelper(context, context.getSystemService(UserManager.class)));
275     }
276 
277     @VisibleForTesting
FixedActivityService(Context context, CarActivityService activityService, DisplayManager displayManager, UserHandleHelper userHandleHelper)278     FixedActivityService(Context context, CarActivityService activityService,
279             DisplayManager displayManager, UserHandleHelper userHandleHelper) {
280         mContext = context;
281         mActivityService = activityService;
282         mDm = displayManager;
283         mHandler = new Handler(getHandlerThread(
284                 FixedActivityService.class.getSimpleName()).getLooper());
285         mUserHandleHelper = userHandleHelper;
286     }
287 
288     @Override
init()289     public void init() {
290         // Register display listener here, not in startMonitoringEvents(), because we need
291         // display listener even when no activity is launched.
292         mDm.registerDisplayListener(mDisplayListener, mHandler);
293     }
294 
295     @Override
release()296     public void release() {
297         stopMonitoringEvents();
298         mDm.unregisterDisplayListener(mDisplayListener);
299     }
300 
301     @Override
302     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)303     public void dump(IndentingPrintWriter writer) {
304         writer.println("*FixedActivityService*");
305         synchronized (mLock) {
306             writer.println("mRunningActivities:" + mRunningActivities
307                     + " ,mEventMonitoringActive:" + mEventMonitoringActive);
308         }
309     }
310 
311     @Override
312     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)313     public void dumpProto(ProtoOutputStream proto) {}
314 
315     @GuardedBy("mLock")
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         mHandler.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         UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder()
341                 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build();
342         userService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener);
343         IntentFilter filter = new IntentFilter();
344         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
345         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
346         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
347         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
348         filter.addDataScheme("package");
349         mContext.registerReceiverForAllUsers(mBroadcastReceiver, filter,
350                 /* broadcastPermission= */ null, /* scheduler= */ null,
351                 Context.RECEIVER_NOT_EXPORTED);
352         ActivityManagerHelper.registerProcessObserverCallback(mProcessObserver);
353         try {
354             carPowerManager.setListener(mContext.getMainExecutor(), mCarPowerStateListener);
355         } catch (Exception e) {
356             // should not happen
357             Slogf.e(TAG_AM, "Got exception from CarPowerManager", e);
358         }
359     }
360 
stopMonitoringEvents()361     private void stopMonitoringEvents() {
362         CarPowerManager carPowerManager;
363         synchronized (mLock) {
364             if (!mEventMonitoringActive) {
365                 return;
366             }
367             mEventMonitoringActive = false;
368             carPowerManager = mCarPowerManager;
369             mCarPowerManager = null;
370         }
371         if (carPowerManager != null) {
372             carPowerManager.clearListener();
373         }
374         mHandler.removeCallbacks(mActivityCheckRunnable);
375         CarUserService userService = CarLocalServices.getService(CarUserService.class);
376         userService.removeUserLifecycleListener(mUserLifecycleListener);
377         ActivityManagerHelper.unregisterProcessObserverCallback(mProcessObserver);
378         mContext.unregisterReceiver(mBroadcastReceiver);
379     }
380 
381     /**
382      * Launches all stored fixed mode activities if necessary.
383      *
384      * @param displayId Display id to check if it is visible. If check is not necessary, should pass
385      *         {@link Display#INVALID_DISPLAY}.
386      * @return true if fixed Activity for given {@code displayId} is visible / successfully
387      *         launched. It will return false for {@link Display#INVALID_DISPLAY} {@code displayId}.
388      */
launchIfNecessary(int displayId)389     private boolean launchIfNecessary(int displayId) {
390         if (DBG) {
391             Slogf.d(TAG_AM, "launchIfNecessary(%d)", displayId);
392         }
393         // Get the visible tasks on the specified display. INVALID_DISPLAY means all displays.
394         List<? extends TaskInfo> infos = mActivityService.getVisibleTasksInternal(displayId);
395         if (infos == null) {
396             Slogf.e(TAG_AM, "cannot get RootTaskInfo from AM");
397             return false;
398         }
399         long now = SystemClock.elapsedRealtime();
400         synchronized (mLock) {
401             for (int i = mRunningActivities.size() - 1; i >= 0; i--) {
402                 int displayIdForActivity = mRunningActivities.keyAt(i);
403                 Display display = mDm.getDisplay(displayIdForActivity);
404                 if (display == null) {
405                     Slogf.e(TAG_AM, "Stop fixed activity for unavailable display%d",
406                             displayIdForActivity);
407                     mRunningActivities.removeAt(i);
408                     continue;
409                 }
410 
411                 RunningActivityInfo activityInfo = mRunningActivities.valueAt(i);
412                 // Do not reset isVisible flag when the recheck interval has not passed yet,
413                 // since the last launch attempt.
414                 if (!activityInfo.isStarted
415                         || (now - activityInfo.lastLaunchTimeMs) > RECHECK_INTERVAL_MS) {
416                     activityInfo.isVisible = false;
417                 }
418                 if (isUserAllowedToLaunchActivity(activityInfo.userId)) {
419                     continue;
420                 }
421                 if (activityInfo.taskId != INVALID_TASK_ID) {
422                     Slogf.i(TAG_AM, "Finishing fixed activity on user switching:"
423                             + activityInfo);
424                     ActivityManagerHelper.removeTask(activityInfo.taskId);
425                     Intent intent = new Intent()
426                             .setComponent(
427                                     ComponentName.unflattenFromString(
428                                             mContext.getString(R.string.continuousBlankActivity)
429                                     ))
430                             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
431                             .addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
432                     ActivityOptions activityOptions = ActivityOptions.makeBasic()
433                             .setLaunchDisplayId(displayIdForActivity);
434                     ContextHelper.startActivityAsUser(mContext, intent, activityOptions.toBundle(),
435                             UserHandle.of(ActivityManager.getCurrentUser()));
436                 }
437                 mRunningActivities.removeAt(i);
438             }
439             if (mRunningActivities.size() == 0) {
440                 // it must have been stopped.
441                 Slogf.i(TAG_AM, "empty activity list");
442                 stopMonitoringEvents();
443                 return false;
444             }
445             if (DBG) {
446                 Slogf.d(TAG_AM, "Visible Tasks: %d", infos.size());
447             }
448             for (int i = 0, size = infos.size(); i < size; ++i) {
449                 TaskInfo taskInfo = infos.get(i);
450                 int taskDisplayId = TaskInfoHelper.getDisplayId(taskInfo);
451                 RunningActivityInfo activityInfo = mRunningActivities.get(taskDisplayId);
452                 if (activityInfo == null) {
453                     continue;
454                 }
455                 if (DBG) {
456                     Slogf.i(TAG_AM, "Task#%d: U%d top=%s orig=%s", i, activityInfo.userId,
457                             taskInfo.topActivity, taskInfo.origActivity);
458                 }
459                 int taskUserId = TaskInfoHelper.getUserId(taskInfo);
460                 ComponentName expectedTopActivity = activityInfo.intent.getComponent();
461                 if ((expectedTopActivity.equals(taskInfo.topActivity)
462                         || expectedTopActivity.equals(taskInfo.origActivity))  // for Activity-alias
463                         && activityInfo.userId == taskUserId) {
464                     // top one is matching.
465                     activityInfo.isVisible = true;
466                     activityInfo.taskId = taskInfo.taskId;
467                     continue;
468                 }
469                 activityInfo.previousTaskId = taskInfo.taskId;
470                 Slogf.i(TAG_AM, "Unmatched top activity will be removed:"
471                         + taskInfo.topActivity + " top task id:" + activityInfo.previousTaskId
472                         + " user:" + taskUserId + " display:" + taskDisplayId);
473                 activityInfo.inBackground = expectedTopActivity.equals(taskInfo.baseActivity);
474                 if (!activityInfo.inBackground) {
475                     activityInfo.taskId = INVALID_TASK_ID;
476                 }
477             }
478 
479             for (int i = 0; i < mRunningActivities.size(); i++) {
480                 RunningActivityInfo activityInfo = mRunningActivities.valueAt(i);
481                 long timeSinceLastLaunchMs = now - activityInfo.lastLaunchTimeMs;
482                 if (activityInfo.isVisible && activityInfo.isStarted) {
483                     if (timeSinceLastLaunchMs >= CRASH_FORGET_INTERVAL_MS) {
484                         activityInfo.consecutiveRetries = 0;
485                     }
486                     continue;
487                 }
488                 if (!isComponentAvailable(activityInfo.intent.getComponent(),
489                         activityInfo.userId)) {
490                     continue;
491                 }
492                 // For 1st call (consecutiveRetries == 0), do not wait as there can be no posting
493                 // for recheck.
494                 if (activityInfo.consecutiveRetries > 0 && (timeSinceLastLaunchMs
495                         < RECHECK_INTERVAL_MS)) {
496                     // wait until next check interval comes.
497                     continue;
498                 }
499                 if (activityInfo.consecutiveRetries >= MAX_NUMBER_OF_CONSECUTIVE_CRASH_RETRY) {
500                     // re-tried too many times, give up for now.
501                     if (!activityInfo.failureLogged) {
502                         activityInfo.failureLogged = true;
503                         Slogf.w(TAG_AM, "Too many relaunch failure of fixed activity:"
504                                 + activityInfo);
505                     }
506                     continue;
507                 }
508 
509                 Slogf.i(TAG_AM, "Launching Activity for fixed mode. Intent:" + activityInfo.intent
510                         + ",userId:" + UserHandle.of(activityInfo.userId) + ",displayId:"
511                         + mRunningActivities.keyAt(i));
512                 // Increase retry count if task is not in background. In case like other app is
513                 // launched and the target activity is still in background, do not consider it
514                 // as retry.
515                 if (!activityInfo.inBackground) {
516                     activityInfo.consecutiveRetries++;
517                 }
518                 try {
519                     postRecheck(RECHECK_INTERVAL_MS);
520                     postRecheck(CRASH_FORGET_INTERVAL_MS);
521                     ContextHelper.startActivityAsUser(mContext, activityInfo.intent,
522                             activityInfo.activityOptions, UserHandle.of(activityInfo.userId));
523                     activityInfo.isVisible = true;
524                     activityInfo.isStarted = true;
525                     activityInfo.lastLaunchTimeMs = SystemClock.elapsedRealtime();
526                 } catch (Exception e) { // Catch all for any app related issues.
527                     Slogf.w(TAG_AM, "Cannot start activity:" + activityInfo.intent, e);
528                 }
529             }
530             RunningActivityInfo activityInfo = mRunningActivities.get(displayId);
531             if (DBG) {
532                 Slogf.d(TAG_AM, "ActivityInfo for display %d: %s", displayId, activityInfo);
533             }
534             if (activityInfo == null) {
535                 return false;
536             }
537             return activityInfo.isVisible;
538         }
539     }
540 
541     @VisibleForTesting
launchIfNecessary()542     void launchIfNecessary() {
543         launchIfNecessary(Display.INVALID_DISPLAY);
544     }
545 
logComponentNotFound(ComponentName component, @UserIdInt int userId, Exception e)546     private void logComponentNotFound(ComponentName component, @UserIdInt int userId,
547             Exception e) {
548         Slogf.e(TAG_AM, "Specified Component not found:" + component
549                 + " for userid:" + userId, e);
550     }
551 
isComponentAvailable(ComponentName component, @UserIdInt int userId)552     private boolean isComponentAvailable(ComponentName component, @UserIdInt int userId) {
553         PackageInfo packageInfo;
554         try {
555             packageInfo = PackageManagerHelper.getPackageInfoAsUser(mContext.getPackageManager(),
556                     component.getPackageName(), PackageManager.GET_ACTIVITIES, userId);
557         } catch (PackageManager.NameNotFoundException e) {
558             logComponentNotFound(component, userId, e);
559             return false;
560         }
561         if (packageInfo == null || packageInfo.activities == null) {
562             // may not be necessary but additional safety check
563             logComponentNotFound(component, userId, new RuntimeException());
564             return false;
565         }
566         String fullName = component.getClassName();
567         String shortName = component.getShortClassName();
568         for (ActivityInfo info : packageInfo.activities) {
569             if (info.name.equals(fullName) || info.name.equals(shortName)) {
570                 return true;
571             }
572         }
573         logComponentNotFound(component, userId, new RuntimeException());
574         return false;
575     }
576 
isUserAllowedToLaunchActivity(@serIdInt int userId)577     private boolean isUserAllowedToLaunchActivity(@UserIdInt int userId) {
578         int currentUser = ActivityManager.getCurrentUser();
579         if (userId == currentUser || userId == UserHandle.SYSTEM.getIdentifier()) {
580             return true;
581         }
582         List<UserHandle> profiles = mUserHandleHelper.getEnabledProfiles(currentUser);
583         // null can happen in test env when UserManager is mocked. So this check is not necessary
584         // in real env but add it to make test impl easier.
585         if (profiles == null) {
586             return false;
587         }
588         for (UserHandle profile : profiles) {
589             if (profile.getIdentifier() == userId) {
590                 return true;
591             }
592         }
593         return false;
594     }
595 
isDisplayAllowedForFixedMode(int displayId)596     private boolean isDisplayAllowedForFixedMode(int displayId) {
597         if (displayId == Display.DEFAULT_DISPLAY || displayId == Display.INVALID_DISPLAY) {
598             Slogf.w(TAG_AM, "Target display cannot be used for fixed mode, displayId:" + displayId,
599                     new RuntimeException());
600             return false;
601         }
602         return true;
603     }
604 
605     @VisibleForTesting
hasRunningFixedActivity(int displayId)606     boolean hasRunningFixedActivity(int displayId) {
607         synchronized (mLock) {
608             return mRunningActivities.get(displayId) != null;
609         }
610     }
611 
launchForDisplay(int displayId)612     private boolean launchForDisplay(int displayId) {
613         Display display = mDm.getDisplay(displayId);
614         // Skip launching the activity if the display is not ON. It can be launched later when
615         // the display turns on, by the display listener.
616         if (display != null && display.getState() != Display.STATE_ON) {
617             if (DBG) {
618                 Slogf.d(TAG_AM, "Display %d is not on. The activity is not launched this time.",
619                         displayId);
620             }
621             return false;
622         }
623         synchronized (mLock) {
624             // Do nothing if there is no activity for the given display.
625             if (!mRunningActivities.contains(displayId)) {
626                 return false;
627             }
628         }
629 
630         boolean launched = launchIfNecessary(displayId);
631         if (launched) {
632             startMonitoringEvents();
633         } else {
634             synchronized (mLock) {
635                 Slogf.w(TAG_AM, "Activity was not launched on display %d, and removed "
636                         + " from the running activity list.", displayId);
637                 mRunningActivities.remove(displayId);
638             }
639         }
640         return launched;
641     }
642 
643     /**
644      * Checks {@link InstrumentClusterRenderingService#startFixedActivityModeForDisplayAndUser(
645      * Intent, ActivityOptions, int)}
646      */
startFixedActivityModeForDisplayAndUser(@onNull Intent intent, @NonNull ActivityOptions options, int displayId, @UserIdInt int userId)647     public boolean startFixedActivityModeForDisplayAndUser(@NonNull Intent intent,
648             @NonNull ActivityOptions options, int displayId, @UserIdInt int userId) {
649         if (!isDisplayAllowedForFixedMode(displayId)) {
650             return false;
651         }
652         if (options == null) {
653             Slogf.e(TAG_AM, "startFixedActivityModeForDisplayAndUser, null options");
654             return false;
655         }
656         if (!isUserAllowedToLaunchActivity(userId)) {
657             Slogf.e(TAG_AM, "startFixedActivityModeForDisplayAndUser, requested user:" + userId
658                     + " cannot launch activity, Intent:" + intent);
659             return false;
660         }
661         ComponentName component = intent.getComponent();
662         if (component == null) {
663             Slogf.e(TAG_AM, "startFixedActivityModeForDisplayAndUser: No component specified for "
664                     + "requested Intent" + intent);
665             return false;
666         }
667         if (!isComponentAvailable(component, userId)) {
668             return false;
669         }
670         Bundle optionsBundle = options.toBundle();
671         synchronized (mLock) {
672             RunningActivityInfo activityInfo = mRunningActivities.get(displayId);
673             boolean replaceEntry = true;
674             if (activityInfo != null && intentEquals(activityInfo.intent, intent)
675                     && bundleEquals(optionsBundle, activityInfo.activityOptions)
676                     && userId == activityInfo.userId) {
677                 replaceEntry = false;
678                 if (activityInfo.isVisible) { // already shown.
679                     return true;
680                 }
681             }
682             if (replaceEntry) {
683                 activityInfo = new RunningActivityInfo(intent, optionsBundle, userId);
684                 mRunningActivities.put(displayId, activityInfo);
685             }
686         }
687 
688         return launchForDisplay(displayId);
689     }
690 
691     /** Check {@link InstrumentClusterRenderingService#stopFixedActivityMode(int)} */
stopFixedActivityMode(int displayId)692     public void stopFixedActivityMode(int displayId) {
693         if (!isDisplayAllowedForFixedMode(displayId)) {
694             return;
695         }
696         boolean stopMonitoringEvents = false;
697         synchronized (mLock) {
698             mRunningActivities.remove(displayId);
699             if (mRunningActivities.size() == 0) {
700                 stopMonitoringEvents = true;
701             }
702         }
703         if (stopMonitoringEvents) {
704             stopMonitoringEvents();
705         }
706     }
707 
708     // Intent doesn't have the deep equals method.
intentEquals(Intent intent1, Intent intent2)709     private static boolean intentEquals(Intent intent1, Intent intent2) {
710         // both are null? return true
711         if (intent1 == null && intent2 == null) {
712             return true;
713         }
714         // Only one is null? return false
715         if (intent1 == null || intent2 == null) {
716             return false;
717         }
718         return intent1.getComponent().equals(intent2.getComponent())
719                 && bundleEquals(intent1.getExtras(), intent2.getExtras());
720     }
721 
bundleEquals(BaseBundle bundle1, BaseBundle bundle2)722     private static boolean bundleEquals(BaseBundle bundle1, BaseBundle bundle2) {
723         // both are null? return true
724         if (bundle1 == null && bundle2 == null) {
725             return true;
726         }
727         // Only one is null? return false
728         if (bundle1 == null || bundle2 == null) {
729             return false;
730         }
731         if (bundle1.size() != bundle2.size()) {
732             return false;
733         }
734         Set<String> keys = bundle1.keySet();
735         for (String key : keys) {
736             Object value1 = bundle1.get(key);
737             Object value2 = bundle2.get(key);
738             if (value1 != null && value1.getClass().isArray()
739                     && value2 != null && value2.getClass().isArray()) {
740                 if (!arrayEquals(value1, value2)) {
741                     return false;
742                 }
743             } else if (value1 instanceof BaseBundle && value2 instanceof BaseBundle) {
744                 if (!bundleEquals((BaseBundle) value1, (BaseBundle) value2)) {
745                     return false;
746                 }
747             } else if (!Objects.equals(value1, value2)) {
748                 return false;
749             }
750         }
751         return true;
752     }
753 
arrayEquals(Object value1, Object value2)754     private static boolean arrayEquals(Object value1, Object value2) {
755         final int length = Array.getLength(value1);
756         if (length != Array.getLength(value2)) {
757             return false;
758         }
759         for (int i = 0; i < length; i++) {
760             if (!Objects.equals(Array.get(value1, i), Array.get(value2, i))) {
761                 return false;
762             }
763         }
764         return true;
765     }
766 }
767