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