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