1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.M;
4 import static android.os.Build.VERSION_CODES.N;
5 import static android.os.Build.VERSION_CODES.O;
6 import static android.os.Build.VERSION_CODES.Q;
7 import static android.os.Build.VERSION_CODES.TIRAMISU;
8 import static org.robolectric.shadow.api.Shadow.directlyOn;
9 import static org.robolectric.util.reflector.Reflector.reflector;
10 
11 import android.annotation.Nullable;
12 import android.annotation.RequiresPermission;
13 import android.app.ActivityThread;
14 import android.app.LoadedApk;
15 import android.content.BroadcastReceiver;
16 import android.content.ComponentName;
17 import android.content.ContentResolver;
18 import android.content.Context;
19 import android.content.IContentProvider;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.content.IntentSender;
23 import android.content.ServiceConnection;
24 import android.content.SharedPreferences;
25 import android.content.pm.ActivityInfo;
26 import android.content.res.Configuration;
27 import android.os.Build.VERSION_CODES;
28 import android.os.Bundle;
29 import android.os.Environment;
30 import android.os.FileUtils;
31 import android.os.Handler;
32 import android.os.IBinder;
33 import android.os.UserHandle;
34 import com.google.common.base.Strings;
35 import com.google.errorprone.annotations.concurrent.GuardedBy;
36 import java.io.File;
37 import java.nio.file.Paths;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.concurrent.Executor;
43 import org.robolectric.RuntimeEnvironment;
44 import org.robolectric.annotation.Implementation;
45 import org.robolectric.annotation.Implements;
46 import org.robolectric.annotation.RealObject;
47 import org.robolectric.annotation.Resetter;
48 import org.robolectric.shadow.api.Shadow;
49 import org.robolectric.util.ReflectionHelpers;
50 import org.robolectric.util.reflector.Accessor;
51 import org.robolectric.util.reflector.Direct;
52 import org.robolectric.util.reflector.ForType;
53 import org.robolectric.util.reflector.Static;
54 
55 @Implements(className = ShadowContextImpl.CLASS_NAME)
56 @SuppressWarnings("NewApi")
57 public class ShadowContextImpl {
58 
59   public static final String CLASS_NAME = "android.app.ContextImpl";
60 
61   @RealObject private Context realContextImpl;
62 
63   private final Map<String, Object> systemServices = new HashMap<>();
64   private final Set<String> removedSystemServices = new HashSet<>();
65   private final Object contentResolverLock = new Object();
66 
67   @GuardedBy("contentResolverLock")
68   private ContentResolver contentResolver;
69 
70   private Integer userId;
71 
72   /**
73    * Returns the handle to a system-level service by name. If the service is not available in
74    * Roboletric, or it is set to unavailable in {@link ShadowServiceManager#setServiceAvailability},
75    * {@code null} will be returned.
76    */
77   @Implementation
78   @Nullable
getSystemService(String name)79   protected Object getSystemService(String name) {
80     if (removedSystemServices.contains(name)) {
81       return null;
82     }
83     if (!systemServices.containsKey(name)) {
84       return reflector(_ContextImpl_.class, realContextImpl).getSystemService(name);
85     }
86     return systemServices.get(name);
87   }
88 
setSystemService(String key, Object service)89   public void setSystemService(String key, Object service) {
90     systemServices.put(key, service);
91   }
92 
93   /**
94    * Makes {@link #getSystemService(String)} return {@code null} for the given system service name,
95    * mimicking a device that doesn't have that system service.
96    */
removeSystemService(String name)97   public void removeSystemService(String name) {
98     removedSystemServices.add(name);
99   }
100 
101   @Implementation
startIntentSender( IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options)102   protected void startIntentSender(
103       IntentSender intent,
104       Intent fillInIntent,
105       int flagsMask,
106       int flagsValues,
107       int extraFlags,
108       Bundle options)
109       throws IntentSender.SendIntentException {
110     intent.sendIntent(realContextImpl, 0, fillInIntent, null, null, null);
111   }
112 
113   @Implementation
getClassLoader()114   protected ClassLoader getClassLoader() {
115     return this.getClass().getClassLoader();
116   }
117 
118   @Implementation
checkCallingPermission(String permission)119   protected int checkCallingPermission(String permission) {
120     return checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid());
121   }
122 
123   @Implementation
checkCallingOrSelfPermission(String permission)124   protected int checkCallingOrSelfPermission(String permission) {
125     return checkCallingPermission(permission);
126   }
127 
128   @Implementation
getContentResolver()129   protected ContentResolver getContentResolver() {
130     synchronized (contentResolverLock) {
131       if (contentResolver == null) {
132         contentResolver =
133             new ContentResolver(realContextImpl) {
134               @Override
135               protected IContentProvider acquireProvider(Context c, String name) {
136                 return null;
137               }
138 
139               @Override
140               public boolean releaseProvider(IContentProvider icp) {
141                 return false;
142               }
143 
144               @Override
145               protected IContentProvider acquireUnstableProvider(Context c, String name) {
146                 return null;
147               }
148 
149               @Override
150               public boolean releaseUnstableProvider(IContentProvider icp) {
151                 return false;
152               }
153 
154               @Override
155               public void unstableProviderDied(IContentProvider icp) {}
156             };
157       }
158       return contentResolver;
159     }
160   }
161 
162   @Implementation
sendBroadcast(Intent intent)163   protected void sendBroadcast(Intent intent) {
164     getShadowInstrumentation()
165         .sendBroadcastWithPermission(
166             intent, /* userHandle= */ null, /* receiverPermission= */ null, realContextImpl);
167   }
168 
169   @Implementation
sendBroadcast(Intent intent, String receiverPermission)170   protected void sendBroadcast(Intent intent, String receiverPermission) {
171     getShadowInstrumentation()
172         .sendBroadcastWithPermission(
173             intent, /* userHandle= */ null, receiverPermission, realContextImpl);
174   }
175 
176   @Implementation(minSdk = TIRAMISU)
sendBroadcast(Intent intent, String receiverPermission, Bundle options)177   protected void sendBroadcast(Intent intent, String receiverPermission, Bundle options) {
178     getShadowInstrumentation()
179         .sendBroadcastWithPermission(
180             intent, receiverPermission, realContextImpl, options, /* resultCode= */ 0);
181   }
182 
183   @Implementation
184   @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
sendBroadcastAsUser(@equiresPermission Intent intent, UserHandle user)185   protected void sendBroadcastAsUser(@RequiresPermission Intent intent, UserHandle user) {
186     getShadowInstrumentation()
187         .sendBroadcastWithPermission(intent, user, /* receiverPermission= */ null, realContextImpl);
188   }
189 
190   @Implementation
191   @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
sendBroadcastAsUser( @equiresPermission Intent intent, UserHandle user, @Nullable String receiverPermission)192   protected void sendBroadcastAsUser(
193       @RequiresPermission Intent intent, UserHandle user, @Nullable String receiverPermission) {
194     getShadowInstrumentation()
195         .sendBroadcastWithPermission(intent, user, receiverPermission, realContextImpl);
196   }
197 
198   @Implementation
sendOrderedBroadcast(Intent intent, String receiverPermission)199   protected void sendOrderedBroadcast(Intent intent, String receiverPermission) {
200     getShadowInstrumentation()
201         .sendOrderedBroadcastWithPermission(intent, receiverPermission, realContextImpl);
202   }
203 
204   @Implementation
sendOrderedBroadcast( Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)205   protected void sendOrderedBroadcast(
206       Intent intent,
207       String receiverPermission,
208       BroadcastReceiver resultReceiver,
209       Handler scheduler,
210       int initialCode,
211       String initialData,
212       Bundle initialExtras) {
213     getShadowInstrumentation()
214         .sendOrderedBroadcastAsUser(
215             intent,
216             /* userHandle= */ null,
217             receiverPermission,
218             resultReceiver,
219             scheduler,
220             initialCode,
221             initialData,
222             initialExtras,
223             realContextImpl);
224   }
225 
226   /**
227    * Allows the test to query for the broadcasts for specific users, for everything else behaves as
228    * {@link #sendOrderedBroadcastAsUser}.
229    */
230   @Implementation
sendOrderedBroadcastAsUser( Intent intent, UserHandle userHandle, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)231   protected void sendOrderedBroadcastAsUser(
232       Intent intent,
233       UserHandle userHandle,
234       String receiverPermission,
235       BroadcastReceiver resultReceiver,
236       Handler scheduler,
237       int initialCode,
238       String initialData,
239       Bundle initialExtras) {
240     getShadowInstrumentation()
241         .sendOrderedBroadcastAsUser(
242             intent,
243             userHandle,
244             receiverPermission,
245             resultReceiver,
246             scheduler,
247             initialCode,
248             initialData,
249             initialExtras,
250             realContextImpl);
251   }
252 
253   /** Behaves as {@link #sendOrderedBroadcastAsUser}. Currently ignores appOp and options. */
254   @Implementation(minSdk = M)
sendOrderedBroadcastAsUser( Intent intent, UserHandle userHandle, String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)255   protected void sendOrderedBroadcastAsUser(
256       Intent intent,
257       UserHandle userHandle,
258       String receiverPermission,
259       int appOp,
260       Bundle options,
261       BroadcastReceiver resultReceiver,
262       Handler scheduler,
263       int initialCode,
264       String initialData,
265       Bundle initialExtras) {
266     sendOrderedBroadcastAsUser(
267         intent,
268         userHandle,
269         receiverPermission,
270         resultReceiver,
271         scheduler,
272         initialCode,
273         initialData,
274         initialExtras);
275   }
276 
277   @Implementation
sendStickyBroadcast(Intent intent)278   protected void sendStickyBroadcast(Intent intent) {
279     getShadowInstrumentation().sendStickyBroadcast(intent, realContextImpl);
280   }
281 
282   @Implementation
checkPermission(String permission, int pid, int uid)283   protected int checkPermission(String permission, int pid, int uid) {
284     return getShadowInstrumentation().checkPermission(permission, pid, uid);
285   }
286 
287   @Implementation
registerReceiver(BroadcastReceiver receiver, IntentFilter filter)288   protected Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
289     return getShadowInstrumentation().registerReceiver(receiver, filter, 0, realContextImpl);
290   }
291 
292   @Implementation(minSdk = O)
registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags)293   protected Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
294     return getShadowInstrumentation().registerReceiver(receiver, filter, flags, realContextImpl);
295   }
296 
297   @Implementation
registerReceiver( BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)298   protected Intent registerReceiver(
299       BroadcastReceiver receiver,
300       IntentFilter filter,
301       String broadcastPermission,
302       Handler scheduler) {
303     return getShadowInstrumentation()
304         .registerReceiver(receiver, filter, broadcastPermission, scheduler, 0, realContextImpl);
305   }
306 
307   @Implementation(minSdk = O)
registerReceiver( BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler, int flags)308   protected Intent registerReceiver(
309       BroadcastReceiver receiver,
310       IntentFilter filter,
311       String broadcastPermission,
312       Handler scheduler,
313       int flags) {
314     return getShadowInstrumentation()
315         .registerReceiver(receiver, filter, broadcastPermission, scheduler, flags, realContextImpl);
316   }
317 
318   @Implementation
registerReceiverAsUser( BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler)319   protected Intent registerReceiverAsUser(
320       BroadcastReceiver receiver,
321       UserHandle user,
322       IntentFilter filter,
323       String broadcastPermission,
324       Handler scheduler) {
325     return getShadowInstrumentation()
326         .registerReceiverWithContext(
327             receiver, filter, broadcastPermission, scheduler, 0, realContextImpl);
328   }
329 
330   @Implementation
unregisterReceiver(BroadcastReceiver broadcastReceiver)331   protected void unregisterReceiver(BroadcastReceiver broadcastReceiver) {
332     getShadowInstrumentation().unregisterReceiver(broadcastReceiver);
333   }
334 
335   @Implementation
startService(Intent service)336   protected ComponentName startService(Intent service) {
337     validateServiceIntent(service);
338     return getShadowInstrumentation().startService(service);
339   }
340 
341   @Implementation(minSdk = O)
startForegroundService(Intent service)342   protected ComponentName startForegroundService(Intent service) {
343     return startService(service);
344   }
345 
346   @Implementation
stopService(Intent name)347   protected boolean stopService(Intent name) {
348     validateServiceIntent(name);
349     return getShadowInstrumentation().stopService(name);
350   }
351 
352   @Implementation(minSdk = Q)
bindService( Intent service, int flags, Executor executor, ServiceConnection conn)353   protected boolean bindService(
354       Intent service, int flags, Executor executor, ServiceConnection conn) {
355     return getShadowInstrumentation().bindService(service, flags, executor, conn);
356   }
357 
358   @Implementation
bindService(Intent intent, final ServiceConnection serviceConnection, int i)359   protected boolean bindService(Intent intent, final ServiceConnection serviceConnection, int i) {
360     validateServiceIntent(intent);
361     return getShadowInstrumentation().bindService(intent, serviceConnection, i);
362   }
363 
364   /** Binds to a service but ignores the given UserHandle. */
365   @Implementation
bindServiceAsUser( Intent intent, final ServiceConnection serviceConnection, int i, UserHandle userHandle)366   protected boolean bindServiceAsUser(
367       Intent intent, final ServiceConnection serviceConnection, int i, UserHandle userHandle) {
368     return bindService(intent, serviceConnection, i);
369   }
370 
371   @Implementation
unbindService(final ServiceConnection serviceConnection)372   protected void unbindService(final ServiceConnection serviceConnection) {
373     getShadowInstrumentation().unbindService(serviceConnection);
374   }
375 
376   // This is a private method in ContextImpl so we copy the relevant portions of it here.
377   @Implementation
validateServiceIntent(Intent service)378   protected void validateServiceIntent(Intent service) {
379     if (service.getComponent() == null && service.getPackage() == null) {
380       throw new IllegalArgumentException("Service Intent must be explicit: " + service);
381     }
382   }
383 
384   /**
385    * Behaves as {@link android.app.ContextImpl#startActivity(Intent, Bundle)}. The user parameter is
386    * ignored.
387    */
388   @Implementation
startActivityAsUser(Intent intent, Bundle options, UserHandle user)389   protected void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
390     // TODO: Remove this once {@link com.android.server.wmActivityTaskManagerService} is
391     // properly shadowed.
392     reflector(_ContextImpl_.class, realContextImpl).startActivity(intent, options);
393   }
394 
395   /** Set the user id returned by {@link #getUserId()}. */
setUserId(int userId)396   public void setUserId(int userId) {
397     this.userId = userId;
398   }
399 
400   @Implementation
getUserId()401   protected int getUserId() {
402     if (userId != null) {
403       return userId;
404     } else {
405       return directlyOn(realContextImpl, ShadowContextImpl.CLASS_NAME, "getUserId");
406     }
407   }
408 
409   @Implementation
getExternalFilesDir(String type)410   protected File getExternalFilesDir(String type) {
411     File externalDir = Environment.getExternalStoragePublicDirectory(/* type= */ null);
412     if (externalDir == null) {
413       return null;
414     }
415 
416     File externalFilesDir =
417         new File(externalDir, "Android/data/" + realContextImpl.getPackageName());
418     if (type != null) {
419       externalFilesDir = new File(externalFilesDir, type);
420     }
421     externalFilesDir.mkdirs();
422     return externalFilesDir;
423   }
424 
425   @Implementation
getExternalFilesDirs(String type)426   protected File[] getExternalFilesDirs(String type) {
427     return new File[] {getExternalFilesDir(type)};
428   }
429 
430   @Resetter
reset()431   public static void reset() {
432     String prefsCacheFieldName =
433         RuntimeEnvironment.getApiLevel() >= N ? "sSharedPrefsCache" : "sSharedPrefs";
434     Class<?> contextImplClass =
435         ReflectionHelpers.loadClass(
436             ShadowContextImpl.class.getClassLoader(), "android.app.ContextImpl");
437     ReflectionHelpers.setStaticField(contextImplClass, prefsCacheFieldName, null);
438 
439     if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.LOLLIPOP_MR1) {
440       HashMap<String, Object> fetchers =
441           ReflectionHelpers.getStaticField(contextImplClass, "SYSTEM_SERVICE_MAP");
442       Class staticServiceFetcherClass =
443           ReflectionHelpers.loadClass(
444               ShadowContextImpl.class.getClassLoader(),
445               "android.app.ContextImpl$StaticServiceFetcher");
446 
447       for (Object o : fetchers.values()) {
448         if (staticServiceFetcherClass.isInstance(o)) {
449           ReflectionHelpers.setField(staticServiceFetcherClass, o, "mCachedInstance", null);
450         }
451       }
452 
453       Object windowServiceFetcher = fetchers.get(Context.WINDOW_SERVICE);
454       ReflectionHelpers.setField(
455           windowServiceFetcher.getClass(), windowServiceFetcher, "mDefaultDisplay", null);
456     }
457   }
458 
getShadowInstrumentation()459   private ShadowInstrumentation getShadowInstrumentation() {
460     ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
461     return Shadow.extract(activityThread.getInstrumentation());
462   }
463 
464   @Implementation
getDatabasePath(String name)465   public File getDatabasePath(String name) {
466     // Windows is an abomination.
467     if (File.separatorChar == '\\' && Paths.get(name).isAbsolute()) {
468       String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
469       File dir = new File(dirPath);
470       name = name.substring(name.lastIndexOf(File.separatorChar));
471       File f = new File(dir, name);
472       if (!dir.isDirectory() && dir.mkdir()) {
473         FileUtils.setPermissions(dir.getPath(), 505, -1, -1);
474       }
475       return f;
476     } else {
477       return reflector(_ContextImpl_.class, realContextImpl).getDatabasePath(name);
478     }
479   }
480 
481   @Implementation
getSharedPreferences(String name, int mode)482   protected SharedPreferences getSharedPreferences(String name, int mode) {
483     // Windows does not allow colons in file names, which may be used in shared preference
484     // names. URL-encode any colons in Windows.
485     if (!Strings.isNullOrEmpty(name) && File.separatorChar == '\\') {
486       name = name.replace(":", "%3A");
487     }
488     return reflector(_ContextImpl_.class, realContextImpl).getSharedPreferences(name, mode);
489   }
490 
491   /** Reflector interface for {@link android.app.ContextImpl}'s internals. */
492   @ForType(className = CLASS_NAME)
493   public interface _ContextImpl_ {
494     @Static
createSystemContext(ActivityThread activityThread)495     Context createSystemContext(ActivityThread activityThread);
496 
497     @Static
createAppContext(ActivityThread activityThread, LoadedApk loadedApk)498     Context createAppContext(ActivityThread activityThread, LoadedApk loadedApk);
499 
500     @Static
createActivityContext( ActivityThread mainThread, LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration)501     Context createActivityContext(
502         ActivityThread mainThread,
503         LoadedApk packageInfo,
504         ActivityInfo activityInfo,
505         IBinder activityToken,
506         int displayId,
507         Configuration overrideConfiguration);
508 
setOuterContext(Context context)509     void setOuterContext(Context context);
510 
511     @Direct
getSystemService(String name)512     Object getSystemService(String name);
513 
startActivity(Intent intent, Bundle options)514     void startActivity(Intent intent, Bundle options);
515 
516     @Direct
getDatabasePath(String name)517     File getDatabasePath(String name);
518 
519     @Direct
getSharedPreferences(String name, int mode)520     SharedPreferences getSharedPreferences(String name, int mode);
521 
522     @Accessor("mClassLoader")
setClassLoader(ClassLoader classLoader)523     void setClassLoader(ClassLoader classLoader);
524   }
525 }
526