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