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