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