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