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