• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.TIRAMISU;
4 import static com.google.common.base.Preconditions.checkArgument;
5 
6 import android.annotation.NonNull;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.Map;
10 import java.util.Set;
11 import java.util.concurrent.ThreadLocalRandom;
12 import javax.annotation.concurrent.GuardedBy;
13 import org.robolectric.annotation.Implementation;
14 import org.robolectric.annotation.Implements;
15 import org.robolectric.annotation.Resetter;
16 
17 @Implements(android.os.Process.class)
18 public class ShadowProcess {
19   private static int pid;
20   private static final int UID = getRandomApplicationUid();
21   private static Integer uidOverride;
22   private static int tid = getRandomApplicationUid();
23   private static final Object threadPrioritiesLock = new Object();
24   private static final Object killedProcessesLock = new Object();
25   // The range of thread priority values is specified by
26   // android.os.Process#setThreadPriority(int, int), which is [-20,19].
27   private static final int THREAD_PRIORITY_HIGHEST = -20;
28   private static final int THREAD_PRIORITY_LOWEST = 19;
29 
30   @GuardedBy("threadPrioritiesLock")
31   private static final Map<Integer, Integer> threadPriorities = new HashMap<Integer, Integer>();
32 
33   @GuardedBy("killedProcessesLock")
34   private static final Set<Integer> killedProcesses = new HashSet<>();
35 
36   /**
37    * Stores requests for killing processes. Processe that were requested to be killed can be
38    * retrieved by calling {@link #wasKilled(int)}. Use {@link #clearKilledProcesses()} to clear the
39    * list.
40    */
41   @Implementation
killProcess(int pid)42   protected static void killProcess(int pid) {
43     synchronized (killedProcessesLock) {
44       killedProcesses.add(pid);
45     }
46   }
47 
48   @Implementation
myPid()49   protected static int myPid() {
50     return pid;
51   }
52 
53   /**
54    * Returns the identifier of this process's uid. Unlike Android UIDs are randomly initialized to
55    * prevent tests from depending on any given value. Tests should access the current process UID
56    * via {@link android.os.Process#myUid()}. You can override this value by calling {@link
57    * #setUid(int)}.
58    */
59   @Implementation
myUid()60   protected static int myUid() {
61     if (uidOverride != null) {
62       return uidOverride;
63     }
64     return UID;
65   }
66 
67   /**
68    * Returns the identifier ({@link java.lang.Thread#getId()}) of the current thread ({@link
69    * java.lang.Thread#currentThread()}).
70    */
71   @Implementation
myTid()72   protected static int myTid() {
73     return (int) Thread.currentThread().getId();
74   }
75 
76   /**
77    * Stores priority for the current thread, but doesn't actually change it to not mess up with test
78    * runner. Unlike real implementation does not throw any exceptions.
79    */
80   @Implementation
setThreadPriority(int priority)81   protected static void setThreadPriority(int priority) {
82     synchronized (threadPrioritiesLock) {
83       threadPriorities.put(ShadowProcess.myTid(), priority);
84     }
85   }
86 
87   /**
88    * Stores priority for the given thread, but doesn't actually change it to not mess up with test
89    * runner. Unlike real implementation does not throw any exceptions.
90    *
91    * @param tid The identifier of the thread. If equals zero, the identifier of the calling thread
92    *     will be used.
93    * @param priority The priority to be set for the thread. The range of values accepted is
94    *     specified by {@link android.os.Process#setThreadPriority(int, int)}, which is [-20,19].
95    */
96   @Implementation
setThreadPriority(int tid, int priority)97   protected static void setThreadPriority(int tid, int priority) {
98     checkArgument(
99         priority >= THREAD_PRIORITY_HIGHEST && priority <= THREAD_PRIORITY_LOWEST,
100         "priority %s out of range [%s, %s]. It is recommended to use a Process.THREAD_PRIORITY_*"
101             + " constant.",
102         priority,
103         Integer.toString(THREAD_PRIORITY_HIGHEST),
104         Integer.toString(THREAD_PRIORITY_LOWEST));
105 
106     if (tid == 0) {
107       tid = ShadowProcess.myTid();
108     }
109     synchronized (threadPrioritiesLock) {
110       threadPriorities.put(tid, priority);
111     }
112   }
113 
114   /**
115    * Returns priority stored for the given thread.
116    *
117    * @param tid The identifier of the thread. If equals zero, the identifier of the calling thread
118    *     will be used.
119    */
120   @Implementation
getThreadPriority(int tid)121   protected static int getThreadPriority(int tid) {
122     if (tid == 0) {
123       tid = ShadowProcess.myTid();
124     }
125     synchronized (threadPrioritiesLock) {
126       return threadPriorities.getOrDefault(tid, 0);
127     }
128   }
129 
clearKilledProcesses()130   public static void clearKilledProcesses() {
131     synchronized (killedProcessesLock) {
132       killedProcesses.clear();
133     }
134   }
135 
136   /**
137    * Sets the identifier of this process.
138    */
setUid(int uid)139   public static void setUid(int uid) {
140     ShadowProcess.uidOverride = uid;
141   }
142 
143   /**
144    * Sets the identifier of this process.
145    */
setPid(int pid)146   public static void setPid(int pid) {
147     ShadowProcess.pid = pid;
148   }
149 
150   @Resetter
reset()151   public static void reset() {
152     ShadowProcess.pid = 0;
153     ShadowProcess.clearKilledProcesses();
154     synchronized (threadPrioritiesLock) {
155       threadPriorities.clear();
156     }
157     // We cannot re-randomize uid, because it would break code that statically depends on
158     // android.os.Process.myUid(), which persists between tests.
159     ShadowProcess.uidOverride = null;
160     ShadowProcess.processName = "";
161   }
162 
getRandomApplicationUid()163   static int getRandomApplicationUid() {
164     // UIDs are randomly initialized to prevent tests from depending on any given value. Tests
165     // should access the current process UID via android.os.Process::myUid().
166     return ThreadLocalRandom.current()
167         .nextInt(
168             android.os.Process.FIRST_APPLICATION_UID, android.os.Process.LAST_APPLICATION_UID + 1);
169   }
170 
171   /**
172    * Gets an indication of whether or not a process was killed (using {@link #killProcess(int)}).
173    */
wasKilled(int pid)174   public static boolean wasKilled(int pid) {
175     synchronized (killedProcessesLock) {
176       return killedProcesses.contains(pid);
177     }
178   }
179 
180   private static String processName = "";
181 
182   /**
183    * Returns the name of the process. You can override this value by calling {@link
184    * #setProcessName(String)}.
185    *
186    * @return process name.
187    */
188   @Implementation(minSdk = TIRAMISU)
myProcessName()189   protected static String myProcessName() {
190     return processName;
191   }
192 
193   /**
194    * Sets the process name returned by {@link #myProcessName()}.
195    *
196    * @param processName New process name to set. Cannot be null.
197    */
setProcessName(@onNull String processName)198   public static void setProcessName(@NonNull String processName) {
199     ShadowProcess.processName = processName;
200   }
201 }
202