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