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