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