1 package org.robolectric.shadows; 2 3 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; 4 import static android.os.Build.VERSION_CODES.M; 5 import static android.os.Build.VERSION_CODES.O; 6 import static android.os.Build.VERSION_CODES.P; 7 import static android.os.Build.VERSION_CODES.R; 8 import static java.util.stream.Collectors.toCollection; 9 import static org.robolectric.util.reflector.Reflector.reflector; 10 11 import android.annotation.RequiresApi; 12 import android.annotation.RequiresPermission; 13 import android.app.ActivityManager; 14 import android.app.ApplicationExitInfo; 15 import android.app.IActivityManager; 16 import android.content.Context; 17 import android.content.pm.ConfigurationInfo; 18 import android.content.pm.IPackageDataObserver; 19 import android.content.pm.PackageManager; 20 import android.content.pm.PackageManager.NameNotFoundException; 21 import android.os.Build.VERSION_CODES; 22 import android.os.Handler; 23 import android.os.LocaleList; 24 import android.os.Process; 25 import android.os.UserHandle; 26 import android.util.ArrayMap; 27 import android.util.SparseIntArray; 28 import com.google.common.base.Preconditions; 29 import com.google.errorprone.annotations.CanIgnoreReturnValue; 30 import java.io.InputStream; 31 import java.util.ArrayDeque; 32 import java.util.ArrayList; 33 import java.util.Deque; 34 import java.util.List; 35 import java.util.Locale; 36 import java.util.concurrent.CopyOnWriteArrayList; 37 import org.robolectric.RuntimeEnvironment; 38 import org.robolectric.annotation.ClassName; 39 import org.robolectric.annotation.HiddenApi; 40 import org.robolectric.annotation.Implementation; 41 import org.robolectric.annotation.Implements; 42 import org.robolectric.annotation.RealObject; 43 import org.robolectric.annotation.Resetter; 44 import org.robolectric.shadow.api.Shadow; 45 import org.robolectric.util.ReflectionHelpers; 46 import org.robolectric.util.ReflectionHelpers.ClassParameter; 47 import org.robolectric.util.reflector.Direct; 48 import org.robolectric.util.reflector.ForType; 49 50 /** Shadow for {@link android.app.ActivityManager} */ 51 @Implements(value = ActivityManager.class) 52 public class ShadowActivityManager { 53 private int memoryClass = 16; 54 private static String backgroundPackage; 55 private static ActivityManager.MemoryInfo memoryInfo; 56 private static final List<ActivityManager.AppTask> appTasks = new CopyOnWriteArrayList<>(); 57 private static final List<ActivityManager.RecentTaskInfo> recentTasks = 58 new CopyOnWriteArrayList<>(); 59 private static final List<ActivityManager.RunningTaskInfo> tasks = new CopyOnWriteArrayList<>(); 60 private static final List<ActivityManager.RunningServiceInfo> services = 61 new CopyOnWriteArrayList<>(); 62 private static final List<ActivityManager.RunningAppProcessInfo> processes = 63 new CopyOnWriteArrayList<>(); 64 private static final List<ImportanceListener> importanceListeners = new CopyOnWriteArrayList<>(); 65 private static final SparseIntArray uidImportances = new SparseIntArray(); 66 @RealObject private ActivityManager realObject; 67 private static Boolean isLowRamDeviceOverride = null; 68 private int lockTaskModeState = ActivityManager.LOCK_TASK_MODE_NONE; 69 private boolean isBackgroundRestricted; 70 private static final Deque<Object> appExitInfoList = new ArrayDeque<>(); 71 private ConfigurationInfo configurationInfo; 72 private Context context; 73 private static final ArrayList<Locale> supportedLocales = new ArrayList<>(); 74 private LocaleList deviceLocales; 75 76 @Implementation __constructor__(Context context, Handler handler)77 protected void __constructor__(Context context, Handler handler) { 78 Shadow.invokeConstructor( 79 ActivityManager.class, 80 realObject, 81 ClassParameter.from(Context.class, context), 82 ClassParameter.from(Handler.class, handler)); 83 this.context = context; 84 ActivityManager.RunningAppProcessInfo processInfo = new ActivityManager.RunningAppProcessInfo(); 85 fillInProcessInfo(processInfo); 86 processInfo.processName = context.getPackageName(); 87 processInfo.pkgList = new String[] {context.getPackageName()}; 88 processes.add(processInfo); 89 } 90 91 @Implementation getMemoryClass()92 protected int getMemoryClass() { 93 return memoryClass; 94 } 95 96 @Implementation isUserAMonkey()97 protected static boolean isUserAMonkey() { 98 return false; 99 } 100 101 @Implementation 102 @HiddenApi 103 @RequiresPermission( 104 anyOf = { 105 "android.permission.INTERACT_ACROSS_USERS", 106 "android.permission.INTERACT_ACROSS_USERS_FULL" 107 }) getCurrentUser()108 protected static int getCurrentUser() { 109 return UserHandle.myUserId(); 110 } 111 112 @Implementation getRunningTasks(int maxNum)113 protected List<ActivityManager.RunningTaskInfo> getRunningTasks(int maxNum) { 114 return tasks; 115 } 116 117 /** 118 * For tests, returns the list of {@link android.app.ActivityManager.AppTask} set using {@link 119 * #setAppTasks(List)}. Returns empty list if nothing is set. 120 * 121 * @see #setAppTasks(List) 122 * @return List of current AppTask. 123 */ 124 @Implementation getAppTasks()125 protected List<ActivityManager.AppTask> getAppTasks() { 126 return appTasks; 127 } 128 129 /** 130 * For tests, returns the list of {@link android.app.ActivityManager.RecentTaskInfo} set using 131 * {@link #setAppTasks(List)} with at most {@code maxNum} tasks. Returns empty list if nothing is 132 * set {@code flags} is ignored. 133 * 134 * @see #setAppTasks(List) 135 * @return List of current AppTask. 136 */ 137 @Implementation getRecentTasks(int maxNum, int flags)138 protected List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags) { 139 return recentTasks.size() > maxNum ? recentTasks.subList(0, maxNum) : recentTasks; 140 } 141 142 /** 143 * Sets the current locales of the device. If the input is {@code null}, sets the {@link 144 * Locale#ENGLISH} as default locale. 145 */ 146 @Implementation(minSdk = VERSION_CODES.Q) setDeviceLocales(LocaleList locales)147 protected void setDeviceLocales(LocaleList locales) { 148 deviceLocales = 149 locales == null ? LocaleList.forLanguageTags(Locale.ENGLISH.toLanguageTag()) : locales; 150 } 151 152 /** 153 * Gets the values set by {@link #setDeviceLocales(LocaleList)}. 154 * 155 * @return an {@link LocaleList} object contains the current locales. 156 */ getDeviceLocales()157 public LocaleList getDeviceLocales() { 158 return deviceLocales; 159 } 160 161 @Implementation getRunningServices(int maxNum)162 protected List<ActivityManager.RunningServiceInfo> getRunningServices(int maxNum) { 163 return services; 164 } 165 166 @Implementation getRunningAppProcesses()167 protected List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() { 168 // This method is explicitly documented not to return an empty list 169 if (processes.isEmpty()) { 170 return null; 171 } 172 return processes; 173 } 174 175 /** Returns information seeded by {@link #setProcesses}. */ 176 @Implementation getMyMemoryState(ActivityManager.RunningAppProcessInfo inState)177 protected static void getMyMemoryState(ActivityManager.RunningAppProcessInfo inState) { 178 fillInProcessInfo(inState); 179 for (ActivityManager.RunningAppProcessInfo info : processes) { 180 if (info.pid == Process.myPid()) { 181 inState.importance = info.importance; 182 inState.lru = info.lru; 183 inState.importanceReasonCode = info.importanceReasonCode; 184 inState.importanceReasonPid = info.importanceReasonPid; 185 inState.lastTrimLevel = info.lastTrimLevel; 186 inState.pkgList = info.pkgList; 187 inState.processName = info.processName; 188 } 189 } 190 } 191 fillInProcessInfo(ActivityManager.RunningAppProcessInfo processInfo)192 private static void fillInProcessInfo(ActivityManager.RunningAppProcessInfo processInfo) { 193 processInfo.pid = Process.myPid(); 194 processInfo.uid = Process.myUid(); 195 } 196 197 @HiddenApi 198 @Implementation switchUser(int userid)199 protected boolean switchUser(int userid) { 200 ShadowUserManager shadowUserManager = 201 Shadow.extract(context.getSystemService(Context.USER_SERVICE)); 202 shadowUserManager.switchUser(userid); 203 return true; 204 } 205 206 @Implementation(minSdk = VERSION_CODES.Q) switchUser(UserHandle userHandle)207 protected boolean switchUser(UserHandle userHandle) { 208 return switchUser(userHandle.getIdentifier()); 209 } 210 211 @Implementation killBackgroundProcesses(String packageName)212 protected void killBackgroundProcesses(String packageName) { 213 backgroundPackage = packageName; 214 } 215 216 @Implementation getMemoryInfo(ActivityManager.MemoryInfo outInfo)217 protected void getMemoryInfo(ActivityManager.MemoryInfo outInfo) { 218 if (memoryInfo != null) { 219 outInfo.availMem = memoryInfo.availMem; 220 outInfo.lowMemory = memoryInfo.lowMemory; 221 outInfo.threshold = memoryInfo.threshold; 222 outInfo.totalMem = memoryInfo.totalMem; 223 } 224 } 225 226 @Implementation getDeviceConfigurationInfo()227 protected android.content.pm.ConfigurationInfo getDeviceConfigurationInfo() { 228 return configurationInfo == null ? new ConfigurationInfo() : configurationInfo; 229 } 230 231 /** 232 * Sets the {@link android.content.pm.ConfigurationInfo} returned by {@link 233 * ActivityManager#getDeviceConfigurationInfo()}, but has no effect otherwise. 234 */ setDeviceConfigurationInfo(ConfigurationInfo configurationInfo)235 public void setDeviceConfigurationInfo(ConfigurationInfo configurationInfo) { 236 this.configurationInfo = configurationInfo; 237 } 238 239 /** 240 * @param tasks List of running tasks. 241 */ setTasks(List<ActivityManager.RunningTaskInfo> tasks)242 public void setTasks(List<ActivityManager.RunningTaskInfo> tasks) { 243 this.tasks.clear(); 244 this.tasks.addAll(tasks); 245 } 246 247 /** 248 * Sets the values to be returned by {@link #getAppTasks()}. 249 * 250 * @see #getAppTasks() 251 * @param appTasks List of app tasks. 252 */ setAppTasks(List<ActivityManager.AppTask> appTasks)253 public void setAppTasks(List<ActivityManager.AppTask> appTasks) { 254 this.appTasks.clear(); 255 this.appTasks.addAll(appTasks); 256 } 257 258 /** 259 * Sets the values to be returned by {@link #getRecentTasks()}. 260 * 261 * @see #getRecentTasks() 262 * @param recentTasks List of recent tasks. 263 */ setRecentTasks(List<ActivityManager.RecentTaskInfo> recentTasks)264 public void setRecentTasks(List<ActivityManager.RecentTaskInfo> recentTasks) { 265 this.recentTasks.clear(); 266 this.recentTasks.addAll(recentTasks); 267 } 268 269 /** 270 * @param services List of running services. 271 */ setServices(List<ActivityManager.RunningServiceInfo> services)272 public void setServices(List<ActivityManager.RunningServiceInfo> services) { 273 this.services.clear(); 274 this.services.addAll(services); 275 } 276 277 /** 278 * @param processes List of running processes. 279 */ setProcesses(List<ActivityManager.RunningAppProcessInfo> processes)280 public void setProcesses(List<ActivityManager.RunningAppProcessInfo> processes) { 281 ShadowActivityManager.processes.clear(); 282 ShadowActivityManager.processes.addAll(processes); 283 } 284 285 /** 286 * @return Get the package name of the last background processes killed. 287 */ getBackgroundPackage()288 public String getBackgroundPackage() { 289 return backgroundPackage; 290 } 291 292 /** 293 * @param memoryClass Set the application's memory class. 294 */ setMemoryClass(int memoryClass)295 public void setMemoryClass(int memoryClass) { 296 this.memoryClass = memoryClass; 297 } 298 299 /** 300 * @param memoryInfo Set the application's memory info. 301 */ setMemoryInfo(ActivityManager.MemoryInfo memoryInfo)302 public void setMemoryInfo(ActivityManager.MemoryInfo memoryInfo) { 303 this.memoryInfo = memoryInfo; 304 } 305 306 @Implementation(minSdk = O) getService()307 protected static IActivityManager getService() { 308 return ReflectionHelpers.createNullProxy(IActivityManager.class); 309 } 310 311 @Implementation isLowRamDevice()312 protected boolean isLowRamDevice() { 313 if (isLowRamDeviceOverride != null) { 314 return isLowRamDeviceOverride; 315 } 316 return reflector(ActivityManagerReflector.class, realObject).isLowRamDevice(); 317 } 318 319 /** Override the return value of isLowRamDevice(). */ setIsLowRamDevice(boolean isLowRamDevice)320 public void setIsLowRamDevice(boolean isLowRamDevice) { 321 isLowRamDeviceOverride = isLowRamDevice; 322 } 323 324 @Implementation(minSdk = O) addOnUidImportanceListener( @lassName"android.app.ActivityManager$OnUidImportanceListener") Object listener, int importanceCutpoint)325 protected void addOnUidImportanceListener( 326 @ClassName("android.app.ActivityManager$OnUidImportanceListener") Object listener, 327 int importanceCutpoint) { 328 importanceListeners.add(new ImportanceListener(listener, (Integer) importanceCutpoint)); 329 } 330 331 @Implementation(minSdk = O) removeOnUidImportanceListener( @lassName"android.app.ActivityManager$OnUidImportanceListener") Object listener)332 protected void removeOnUidImportanceListener( 333 @ClassName("android.app.ActivityManager$OnUidImportanceListener") Object listener) { 334 importanceListeners.remove(new ImportanceListener(listener)); 335 } 336 337 @Implementation(minSdk = M) getPackageImportance(String packageName)338 protected int getPackageImportance(String packageName) { 339 try { 340 return uidImportances.get( 341 context.getPackageManager().getPackageUid(packageName, 0), IMPORTANCE_GONE); 342 } catch (NameNotFoundException e) { 343 return IMPORTANCE_GONE; 344 } 345 } 346 347 @Implementation(minSdk = O) getUidImportance(int uid)348 protected int getUidImportance(int uid) { 349 return uidImportances.get(uid, IMPORTANCE_GONE); 350 } 351 setUidImportance(int uid, int importance)352 public void setUidImportance(int uid, int importance) { 353 uidImportances.put(uid, importance); 354 for (ImportanceListener listener : importanceListeners) { 355 listener.onUidImportanceChanged(uid, importance); 356 } 357 } 358 359 @Implementation(minSdk = VERSION_CODES.M) getLockTaskModeState()360 protected int getLockTaskModeState() { 361 return lockTaskModeState; 362 } 363 364 @Implementation isInLockTaskMode()365 protected boolean isInLockTaskMode() { 366 return getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE; 367 } 368 369 /** 370 * Sets lock task mode state to be reported by {@link ActivityManager#getLockTaskModeState}, but 371 * has no effect otherwise. 372 */ setLockTaskModeState(int lockTaskModeState)373 public void setLockTaskModeState(int lockTaskModeState) { 374 this.lockTaskModeState = lockTaskModeState; 375 } 376 377 @Resetter reset()378 public static void reset() { 379 backgroundPackage = null; 380 memoryInfo = null; 381 appTasks.clear(); 382 recentTasks.clear(); 383 tasks.clear(); 384 services.clear(); 385 processes.clear(); 386 importanceListeners.clear(); 387 uidImportances.clear(); 388 appExitInfoList.clear(); 389 isLowRamDeviceOverride = null; 390 supportedLocales.clear(); 391 } 392 393 /** Returns the background restriction state set by {@link #setBackgroundRestricted}. */ 394 @Implementation(minSdk = P) isBackgroundRestricted()395 protected boolean isBackgroundRestricted() { 396 return isBackgroundRestricted; 397 } 398 399 /** 400 * Sets the background restriction state reported by {@link 401 * ActivityManager#isBackgroundRestricted}, but has no effect otherwise. 402 */ setBackgroundRestricted(boolean isBackgroundRestricted)403 public void setBackgroundRestricted(boolean isBackgroundRestricted) { 404 this.isBackgroundRestricted = isBackgroundRestricted; 405 } 406 407 /** 408 * Returns the matched {@link ApplicationExitInfo} added by {@link #addApplicationExitInfo}. 409 * {@code packageName} is ignored. 410 */ 411 @Implementation(minSdk = R) getHistoricalProcessExitReasons( String packageName, int pid, int maxNum)412 protected List</*android.app.ApplicationExitInfo*/ ?> getHistoricalProcessExitReasons( 413 String packageName, int pid, int maxNum) { 414 return appExitInfoList.stream() 415 .filter( 416 appExitInfo -> 417 (int) pid == 0 || ((ApplicationExitInfo) appExitInfo).getPid() == (int) pid) 418 .limit((int) maxNum == 0 ? appExitInfoList.size() : (int) maxNum) 419 .collect(toCollection(ArrayList::new)); 420 } 421 422 /** 423 * Adds an {@link ApplicationExitInfo} with the given information 424 * 425 * @deprecated Prefer using overload with {@link ApplicationExitInfoBuilder} 426 */ 427 @Deprecated 428 @RequiresApi(api = R) addApplicationExitInfo(String processName, int pid, int reason, int status)429 public void addApplicationExitInfo(String processName, int pid, int reason, int status) { 430 ApplicationExitInfo info = 431 ApplicationExitInfoBuilder.newBuilder() 432 .setProcessName(processName) 433 .setPid(pid) 434 .setReason(reason) 435 .setStatus(status) 436 .build(); 437 addApplicationExitInfo(info); 438 } 439 440 /** Adds given {@link ApplicationExitInfo}, see {@link ApplicationExitInfoBuilder} */ 441 @RequiresApi(api = R) addApplicationExitInfo(Object info)442 public void addApplicationExitInfo(Object info) { 443 Preconditions.checkArgument(info instanceof ApplicationExitInfo); 444 appExitInfoList.addFirst(info); 445 } 446 447 @Implementation clearApplicationUserData(String packageName, IPackageDataObserver observer)448 protected boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) { 449 // The real ActivityManager calls clearApplicationUserData on the ActivityManagerService that 450 // calls PackageManager#clearApplicationUserData. 451 context.getPackageManager().clearApplicationUserData(packageName, observer); 452 return true; 453 } 454 455 /** 456 * Returns true after clearing application user data was requested by calling {@link 457 * ActivityManager#clearApplicationUserData()}. 458 */ isApplicationUserDataCleared()459 public boolean isApplicationUserDataCleared() { 460 PackageManager packageManager = RuntimeEnvironment.getApplication().getPackageManager(); 461 return Shadow.<ShadowApplicationPackageManager>extract(packageManager) 462 .getClearedApplicationUserDataPackages() 463 .contains(RuntimeEnvironment.getApplication().getPackageName()); 464 } 465 466 /** Builder class for {@link ApplicationExitInfo} */ 467 @RequiresApi(api = R) 468 public static class ApplicationExitInfoBuilder { 469 470 private final ApplicationExitInfo instance; 471 private final ShadowApplicationExitInfo shadow; 472 newBuilder()473 public static ApplicationExitInfoBuilder newBuilder() { 474 return new ApplicationExitInfoBuilder(); 475 } 476 477 @CanIgnoreReturnValue setDefiningUid(int uid)478 public ApplicationExitInfoBuilder setDefiningUid(int uid) { 479 instance.setDefiningUid(uid); 480 return this; 481 } 482 483 @CanIgnoreReturnValue setDescription(String description)484 public ApplicationExitInfoBuilder setDescription(String description) { 485 instance.setDescription(description); 486 return this; 487 } 488 489 @CanIgnoreReturnValue setImportance(int importance)490 public ApplicationExitInfoBuilder setImportance(int importance) { 491 instance.setImportance(importance); 492 return this; 493 } 494 495 @CanIgnoreReturnValue setPackageUid(int packageUid)496 public ApplicationExitInfoBuilder setPackageUid(int packageUid) { 497 instance.setPackageUid(packageUid); 498 return this; 499 } 500 501 @CanIgnoreReturnValue setPid(int pid)502 public ApplicationExitInfoBuilder setPid(int pid) { 503 instance.setPid(pid); 504 return this; 505 } 506 507 @CanIgnoreReturnValue setProcessName(String processName)508 public ApplicationExitInfoBuilder setProcessName(String processName) { 509 instance.setProcessName(processName); 510 return this; 511 } 512 513 @CanIgnoreReturnValue setProcessStateSummary(byte[] processStateSummary)514 public ApplicationExitInfoBuilder setProcessStateSummary(byte[] processStateSummary) { 515 instance.setProcessStateSummary(processStateSummary); 516 return this; 517 } 518 519 @CanIgnoreReturnValue setPss(long pss)520 public ApplicationExitInfoBuilder setPss(long pss) { 521 instance.setPss(pss); 522 return this; 523 } 524 525 @CanIgnoreReturnValue setRealUid(int realUid)526 public ApplicationExitInfoBuilder setRealUid(int realUid) { 527 instance.setRealUid(realUid); 528 return this; 529 } 530 531 @CanIgnoreReturnValue setReason(int reason)532 public ApplicationExitInfoBuilder setReason(int reason) { 533 instance.setReason(reason); 534 return this; 535 } 536 537 @CanIgnoreReturnValue setRss(long rss)538 public ApplicationExitInfoBuilder setRss(long rss) { 539 instance.setRss(rss); 540 return this; 541 } 542 543 @CanIgnoreReturnValue setStatus(int status)544 public ApplicationExitInfoBuilder setStatus(int status) { 545 instance.setStatus(status); 546 return this; 547 } 548 549 @CanIgnoreReturnValue setTimestamp(long timestamp)550 public ApplicationExitInfoBuilder setTimestamp(long timestamp) { 551 instance.setTimestamp(timestamp); 552 return this; 553 } 554 555 @CanIgnoreReturnValue setTraceInputStream(InputStream in)556 public ApplicationExitInfoBuilder setTraceInputStream(InputStream in) { 557 shadow.setTraceInputStream(in); 558 return this; 559 } 560 build()561 public ApplicationExitInfo build() { 562 return instance; 563 } 564 ApplicationExitInfoBuilder()565 private ApplicationExitInfoBuilder() { 566 this.instance = new ApplicationExitInfo(); 567 this.shadow = Shadow.extract(instance); 568 } 569 } 570 571 @ForType(ActivityManager.class) 572 interface ActivityManagerReflector { 573 574 @Direct isLowRamDevice()575 boolean isLowRamDevice(); 576 } 577 578 /** 579 * Helper class mimicking the package-private UidObserver class inside {@link ActivityManager}. 580 * 581 * <p>This class is responsible for maintaining the cutpoint of the corresponding {@link 582 * ActivityManager.OnUidImportanceListener} and invoking the listener only when the importance of 583 * a given UID crosses the cutpoint. 584 */ 585 private static class ImportanceListener { 586 587 private final ActivityManager.OnUidImportanceListener listener; 588 private final int importanceCutpoint; 589 590 private final ArrayMap<Integer, Boolean> lastAboveCuts = new ArrayMap<>(); 591 ImportanceListener(Object listener)592 ImportanceListener(Object listener) { 593 this(listener, 0); 594 } 595 ImportanceListener(Object listener, int importanceCutpoint)596 ImportanceListener(Object listener, int importanceCutpoint) { 597 this.listener = (ActivityManager.OnUidImportanceListener) listener; 598 this.importanceCutpoint = importanceCutpoint; 599 } 600 onUidImportanceChanged(int uid, int importance)601 void onUidImportanceChanged(int uid, int importance) { 602 Boolean isAboveCut = importance > importanceCutpoint; 603 if (!isAboveCut.equals(lastAboveCuts.get(uid))) { 604 lastAboveCuts.put(uid, isAboveCut); 605 listener.onUidImportance(uid, importance); 606 } 607 } 608 609 @Override equals(Object o)610 public boolean equals(Object o) { 611 if (this == o) { 612 return true; 613 } 614 if (!(o instanceof ImportanceListener)) { 615 return false; 616 } 617 618 ImportanceListener that = (ImportanceListener) o; 619 return listener.equals(that.listener); 620 } 621 622 @Override hashCode()623 public int hashCode() { 624 return listener.hashCode(); 625 } 626 } 627 } 628