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