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