• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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