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