1 package org.robolectric.android.internal; 2 3 import static android.location.LocationManager.GPS_PROVIDER; 4 import static android.os.Build.VERSION_CODES.P; 5 import static org.robolectric.shadow.api.Shadow.newInstanceOf; 6 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; 7 8 import android.annotation.SuppressLint; 9 import android.app.ActivityThread; 10 import android.app.Application; 11 import android.app.IInstrumentationWatcher; 12 import android.app.IUiAutomationConnection; 13 import android.app.Instrumentation; 14 import android.app.LoadedApk; 15 import android.content.BroadcastReceiver; 16 import android.content.ComponentName; 17 import android.content.Context; 18 import android.content.IntentFilter; 19 import android.content.pm.ApplicationInfo; 20 import android.content.pm.PackageManager; 21 import android.content.pm.PackageParser; 22 import android.content.res.AssetManager; 23 import android.content.res.Configuration; 24 import android.content.res.Resources; 25 import android.os.Build; 26 import android.os.Build.VERSION_CODES; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.provider.Settings.Secure; 31 import android.util.DisplayMetrics; 32 import com.google.common.annotations.VisibleForTesting; 33 import java.lang.reflect.Method; 34 import java.security.Security; 35 import java.util.Locale; 36 import org.bouncycastle.jce.provider.BouncyCastleProvider; 37 import org.robolectric.ApkLoader; 38 import org.robolectric.RuntimeEnvironment; 39 import org.robolectric.android.Bootstrap; 40 import org.robolectric.android.fakes.RoboMonitoringInstrumentation; 41 import org.robolectric.annotation.Config; 42 import org.robolectric.internal.ParallelUniverseInterface; 43 import org.robolectric.internal.SdkConfig; 44 import org.robolectric.internal.SdkEnvironment; 45 import org.robolectric.manifest.AndroidManifest; 46 import org.robolectric.manifest.BroadcastReceiverData; 47 import org.robolectric.manifest.RoboNotFoundException; 48 import org.robolectric.res.FsFile; 49 import org.robolectric.res.PackageResourceTable; 50 import org.robolectric.res.ResourceTable; 51 import org.robolectric.res.RoutingResourceTable; 52 import org.robolectric.shadow.api.Shadow; 53 import org.robolectric.shadows.ClassNameResolver; 54 import org.robolectric.shadows.LegacyManifestParser; 55 import org.robolectric.shadows.ShadowActivityThread; 56 import org.robolectric.shadows.ShadowApplication; 57 import org.robolectric.shadows.ShadowAssetManager; 58 import org.robolectric.shadows.ShadowContextImpl; 59 import org.robolectric.shadows.ShadowLog; 60 import org.robolectric.shadows.ShadowLooper; 61 import org.robolectric.shadows.ShadowPackageManager; 62 import org.robolectric.shadows.ShadowPackageParser; 63 import org.robolectric.util.PerfStatsCollector; 64 import org.robolectric.util.ReflectionHelpers; 65 import org.robolectric.util.Scheduler; 66 import org.robolectric.util.TempDirectory; 67 68 @SuppressLint("NewApi") 69 public class ParallelUniverse implements ParallelUniverseInterface { 70 71 private boolean loggingInitialized = false; 72 private SdkConfig sdkConfig; 73 74 @Override setSdkConfig(SdkConfig sdkConfig)75 public void setSdkConfig(SdkConfig sdkConfig) { 76 this.sdkConfig = sdkConfig; 77 ReflectionHelpers.setStaticField(RuntimeEnvironment.class, "apiLevel", sdkConfig.getApiLevel()); 78 } 79 80 @Override setResourcesMode(boolean legacyResources)81 public void setResourcesMode(boolean legacyResources) { 82 RuntimeEnvironment.setUseLegacyResources(legacyResources); 83 } 84 85 @Override setUpApplicationState(ApkLoader apkLoader, Method method, Config config, AndroidManifest appManifest, SdkEnvironment sdkEnvironment)86 public void setUpApplicationState(ApkLoader apkLoader, Method method, Config config, 87 AndroidManifest appManifest, SdkEnvironment sdkEnvironment) { 88 ReflectionHelpers.setStaticField(RuntimeEnvironment.class, "apiLevel", sdkConfig.getApiLevel()); 89 90 RuntimeEnvironment.application = null; 91 RuntimeEnvironment.setActivityThread(null); 92 RuntimeEnvironment.setTempDirectory(new TempDirectory(createTestDataDirRootPath(method))); 93 RuntimeEnvironment.setMasterScheduler(new Scheduler()); 94 RuntimeEnvironment.setMainThread(Thread.currentThread()); 95 96 if (!loggingInitialized) { 97 ShadowLog.setupLogging(); 98 loggingInitialized = true; 99 } 100 101 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { 102 Security.insertProviderAt(new BouncyCastleProvider(), 1); 103 } 104 105 Configuration configuration = new Configuration(); 106 DisplayMetrics displayMetrics = new DisplayMetrics(); 107 108 Bootstrap.applyQualifiers(config.qualifiers(), sdkConfig.getApiLevel(), configuration, 109 displayMetrics); 110 111 Locale locale = sdkConfig.getApiLevel() >= VERSION_CODES.N 112 ? configuration.getLocales().get(0) 113 : configuration.locale; 114 Locale.setDefault(locale); 115 116 // Looper needs to be prepared before the activity thread is created 117 if (Looper.myLooper() == null) { 118 Looper.prepareMainLooper(); 119 } 120 ShadowLooper.getShadowMainLooper().resetScheduler(); 121 ActivityThread activityThread = ReflectionHelpers.newInstance(ActivityThread.class); 122 RuntimeEnvironment.setActivityThread(activityThread); 123 124 PackageParser.Package parsedPackage; 125 if (RuntimeEnvironment.useLegacyResources()) { 126 injectResourceStuffForLegacy(apkLoader, appManifest, sdkEnvironment); 127 128 if (appManifest.getAndroidManifestFile() != null 129 && appManifest.getAndroidManifestFile().exists()) { 130 parsedPackage = LegacyManifestParser.createPackage(appManifest); 131 } else { 132 parsedPackage = new PackageParser.Package("org.robolectric.default"); 133 parsedPackage.applicationInfo.targetSdkVersion = appManifest.getTargetSdkVersion(); 134 } 135 // Support overriding the package name specified in the Manifest. 136 if (!Config.DEFAULT_PACKAGE_NAME.equals(config.packageName())) { 137 parsedPackage.packageName = config.packageName(); 138 parsedPackage.applicationInfo.packageName = config.packageName(); 139 } else { 140 parsedPackage.packageName = appManifest.getPackageName(); 141 parsedPackage.applicationInfo.packageName = appManifest.getPackageName(); 142 } 143 } else { 144 RuntimeEnvironment.compileTimeSystemResourcesFile = 145 apkLoader.getCompileTimeSystemResourcesFile(sdkEnvironment); 146 147 RuntimeEnvironment.setAndroidFrameworkJarPath( 148 apkLoader.getArtifactUrl(sdkConfig.getAndroidSdkDependency()).getFile()); 149 150 // PackageParser.parseBaseApkCommon() relies on an initial Application object in order to 151 // resolve PermissionManager service. 152 createInitialApplication(appManifest, config); 153 154 FsFile packageFile = appManifest.getApkFile(); 155 parsedPackage = ShadowPackageParser.callParsePackage(packageFile); 156 157 // Create a new ActivityThread for RuntimeEnvironment since the original one is bound to a 158 // system context stub in createInitialApplication(), which causes Resources not found. 159 activityThread = ReflectionHelpers.newInstance(ActivityThread.class); 160 RuntimeEnvironment.setActivityThread(activityThread); 161 } 162 163 ApplicationInfo applicationInfo = parsedPackage.applicationInfo; 164 165 // unclear why, but prior to P the processName wasn't set 166 if (sdkConfig.getApiLevel() < P && applicationInfo.processName == null) { 167 applicationInfo.processName = parsedPackage.packageName; 168 } 169 170 setUpPackageStorage(applicationInfo, parsedPackage); 171 172 // Bit of a hack... Context.createPackageContext() is called before the application is created. 173 // It calls through 174 // to ActivityThread for the package which in turn calls the PackageManagerService directly. 175 // This works for now 176 // but it might be nicer to have ShadowPackageManager implementation move into the service as 177 // there is also lots of 178 // code in there that can be reusable, e.g: the XxxxIntentResolver code. 179 ShadowActivityThread.setApplicationInfo(applicationInfo); 180 181 Class<?> contextImplClass = 182 ReflectionHelpers.loadClass( 183 getClass().getClassLoader(), ShadowContextImpl.CLASS_NAME); 184 185 if (sdkConfig.getApiLevel() < Build.VERSION_CODES.S) { 186 ReflectionHelpers.setField(activityThread, "mCompatConfiguration", configuration); 187 } else { 188 Class<?> activityThreadInternalClass = 189 ReflectionHelpers.loadClass( 190 getClass().getClassLoader(), "android.app.ActivityThreadInternal"); 191 Class<?> configurationControllerClass = 192 ReflectionHelpers.loadClass( 193 getClass().getClassLoader(), "android.app.ConfigurationController"); 194 Object configController = ReflectionHelpers.callConstructor(configurationControllerClass, 195 from(activityThreadInternalClass, activityThread)); 196 ReflectionHelpers.callInstanceMethod(configController, "setCompatConfiguration", 197 from(Configuration.class, configuration)); 198 configuration = ReflectionHelpers.callInstanceMethod(configController, 199 "getCompatConfiguration"); 200 ReflectionHelpers.setField(activityThread, "mConfigurationController", configController); 201 } 202 ReflectionHelpers 203 .setStaticField(ActivityThread.class, "sMainThreadHandler", new Handler(Looper.myLooper())); 204 205 Bootstrap.setUpDisplay(configuration, displayMetrics); 206 activityThread.applyConfigurationToResources(configuration); 207 208 Resources systemResources = Resources.getSystem(); 209 systemResources.updateConfiguration(configuration, displayMetrics); 210 211 Context systemContextImpl = ReflectionHelpers.callStaticMethod(contextImplClass, 212 "createSystemContext", from(ActivityThread.class, activityThread)); 213 RuntimeEnvironment.systemContext = systemContextImpl; 214 215 Application application = createApplication(appManifest, config); 216 RuntimeEnvironment.application = application; 217 218 Instrumentation instrumentation = 219 createInstrumentation(activityThread, applicationInfo, application); 220 221 if (application != null) { 222 final Class<?> appBindDataClass; 223 try { 224 appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData"); 225 } catch (ClassNotFoundException e) { 226 throw new RuntimeException(e); 227 } 228 Object data = ReflectionHelpers.newInstance(appBindDataClass); 229 ReflectionHelpers.setField(data, "processName", "org.robolectric"); 230 ReflectionHelpers.setField(data, "appInfo", applicationInfo); 231 ReflectionHelpers.setField(activityThread, "mBoundApplication", data); 232 233 LoadedApk loadedApk = activityThread 234 .getPackageInfo(applicationInfo, null, Context.CONTEXT_INCLUDE_CODE); 235 236 try { 237 Context contextImpl = systemContextImpl 238 .createPackageContext(applicationInfo.packageName, Context.CONTEXT_INCLUDE_CODE); 239 240 ShadowPackageManager shadowPackageManager = Shadow.extract(contextImpl.getPackageManager()); 241 shadowPackageManager.addPackageInternal(parsedPackage); 242 ReflectionHelpers 243 .setField(ActivityThread.class, activityThread, "mInitialApplication", application); 244 ShadowApplication shadowApplication = Shadow.extract(application); 245 shadowApplication.callAttach(contextImpl); 246 ReflectionHelpers.callInstanceMethod( 247 contextImpl, 248 "setOuterContext", 249 from(Context.class, application)); 250 } catch (PackageManager.NameNotFoundException e) { 251 throw new RuntimeException(e); 252 } 253 254 Secure.setLocationProviderEnabled(application.getContentResolver(), GPS_PROVIDER, true); 255 256 Resources appResources = application.getResources(); 257 ReflectionHelpers.setField(loadedApk, "mResources", appResources); 258 ReflectionHelpers.setField(loadedApk, "mApplication", application); 259 260 registerBroadcastReceivers(application, appManifest); 261 262 appResources.updateConfiguration(configuration, displayMetrics); 263 264 if (ShadowAssetManager.useLegacy()) { 265 populateAssetPaths(appResources.getAssets(), appManifest); 266 } 267 268 instrumentation.onCreate(new Bundle()); 269 270 PerfStatsCollector.getInstance() 271 .measure("application onCreate()", () -> application.onCreate()); 272 } 273 } 274 createInitialApplication(AndroidManifest appManifest, Config config)275 private void createInitialApplication(AndroidManifest appManifest, Config config) { 276 ShadowActivityThread.setApplicationInfo(createApplicationInfo(appManifest)); 277 ActivityThread activityThread = ActivityThread.currentActivityThread(); 278 279 Class<?> contextImplClass = 280 ReflectionHelpers.loadClass( 281 getClass().getClassLoader(), ShadowContextImpl.CLASS_NAME); 282 Context systemContextImpl = ReflectionHelpers.callStaticMethod(contextImplClass, 283 "createSystemContext", from(ActivityThread.class, activityThread)); 284 285 Application application = createApplication(appManifest, config); 286 287 if (application != null) { 288 try { 289 Context contextImpl = systemContextImpl 290 .createPackageContext(appManifest.getPackageName(), Context.CONTEXT_INCLUDE_CODE); 291 292 ReflectionHelpers 293 .setField(ActivityThread.class, activityThread, "mInitialApplication", application); 294 ShadowApplication shadowApplication = Shadow.extract(application); 295 shadowApplication.callAttach(contextImpl); 296 } catch (PackageManager.NameNotFoundException e) { 297 throw new RuntimeException(e); 298 } 299 } 300 } 301 createApplicationInfo(AndroidManifest appManifest)302 private ApplicationInfo createApplicationInfo(AndroidManifest appManifest) { 303 final ApplicationInfo info = new ApplicationInfo(); 304 info.targetSdkVersion = appManifest.getTargetSdkVersion(); 305 info.packageName = appManifest.getPackageName(); 306 info.processName = appManifest.getProcessName(); 307 return info; 308 } 309 injectResourceStuffForLegacy(ApkLoader apkLoader, AndroidManifest appManifest, SdkEnvironment sdkEnvironment)310 private void injectResourceStuffForLegacy(ApkLoader apkLoader, AndroidManifest appManifest, 311 SdkEnvironment sdkEnvironment) { 312 PackageResourceTable systemResourceTable = apkLoader.getSystemResourceTable(sdkEnvironment); 313 PackageResourceTable appResourceTable = apkLoader.getAppResourceTable(appManifest); 314 RoutingResourceTable combinedAppResourceTable = new RoutingResourceTable(appResourceTable, 315 systemResourceTable); 316 317 PackageResourceTable compileTimeSdkResourceTable = apkLoader.getCompileTimeSdkResourceTable(); 318 ResourceTable combinedCompileTimeResourceTable = 319 new RoutingResourceTable(appResourceTable, compileTimeSdkResourceTable); 320 321 RuntimeEnvironment.setCompileTimeResourceTable(combinedCompileTimeResourceTable); 322 RuntimeEnvironment.setAppResourceTable(combinedAppResourceTable); 323 RuntimeEnvironment.setSystemResourceTable(new RoutingResourceTable(systemResourceTable)); 324 325 try { 326 appManifest.initMetaData(combinedAppResourceTable); 327 } catch (RoboNotFoundException e1) { 328 throw new Resources.NotFoundException(e1.getMessage()); 329 } 330 } 331 populateAssetPaths(AssetManager assetManager, AndroidManifest appManifest)332 private void populateAssetPaths(AssetManager assetManager, AndroidManifest appManifest) { 333 for (AndroidManifest manifest : appManifest.getAllManifests()) { 334 if (manifest.getAssetsDirectory() != null) { 335 assetManager.addAssetPath(manifest.getAssetsDirectory().getPath()); 336 } 337 } 338 } 339 340 @VisibleForTesting createApplication(AndroidManifest appManifest, Config config)341 static Application createApplication(AndroidManifest appManifest, Config config) { 342 Application application = null; 343 if (config != null && !Config.Builder.isDefaultApplication(config.application())) { 344 if (config.application().getCanonicalName() != null) { 345 Class<? extends Application> applicationClass; 346 try { 347 applicationClass = ClassNameResolver.resolve(null, config.application().getName()); 348 } catch (ClassNotFoundException e) { 349 throw new RuntimeException(e); 350 } 351 application = ReflectionHelpers.callConstructor(applicationClass); 352 } 353 } else if (appManifest != null && appManifest.getApplicationName() != null) { 354 Class<? extends Application> applicationClass = null; 355 try { 356 applicationClass = ClassNameResolver.resolve(appManifest.getPackageName(), 357 getTestApplicationName(appManifest.getApplicationName())); 358 } catch (ClassNotFoundException e) { 359 // no problem 360 } 361 362 if (applicationClass == null) { 363 try { 364 applicationClass = ClassNameResolver.resolve(appManifest.getPackageName(), 365 appManifest.getApplicationName()); 366 } catch (ClassNotFoundException e) { 367 throw new RuntimeException(e); 368 } 369 } 370 371 application = ReflectionHelpers.callConstructor(applicationClass); 372 } else { 373 application = new Application(); 374 } 375 376 return application; 377 } 378 379 @VisibleForTesting getTestApplicationName(String applicationName)380 static String getTestApplicationName(String applicationName) { 381 int lastDot = applicationName.lastIndexOf('.'); 382 if (lastDot > -1) { 383 return applicationName.substring(0, lastDot) + ".Test" + applicationName.substring(lastDot + 1); 384 } else { 385 return "Test" + applicationName; 386 } 387 } 388 createInstrumentation( ActivityThread activityThread, ApplicationInfo applicationInfo, Application application)389 private static Instrumentation createInstrumentation( 390 ActivityThread activityThread, 391 ApplicationInfo applicationInfo, Application application) { 392 Instrumentation androidInstrumentation = new RoboMonitoringInstrumentation(); 393 ReflectionHelpers.setField(activityThread, "mInstrumentation", androidInstrumentation); 394 395 final ComponentName component = 396 new ComponentName( 397 applicationInfo.packageName, androidInstrumentation.getClass().getSimpleName()); 398 if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.JELLY_BEAN_MR1) { 399 ReflectionHelpers.callInstanceMethod(androidInstrumentation, "init", 400 from(ActivityThread.class, activityThread), 401 from(Context.class, application), 402 from(Context.class, application), 403 from(ComponentName.class, component), 404 from(IInstrumentationWatcher.class, null)); 405 } else { 406 ReflectionHelpers.callInstanceMethod(androidInstrumentation, 407 "init", 408 from(ActivityThread.class, activityThread), 409 from(Context.class, application), 410 from(Context.class, application), 411 from(ComponentName.class, component), 412 from(IInstrumentationWatcher.class, null), 413 from(IUiAutomationConnection.class, null)); 414 } 415 416 return androidInstrumentation; 417 } 418 419 /** 420 * Create a file system safe directory path name for the current test. 421 */ createTestDataDirRootPath(Method method)422 private String createTestDataDirRootPath(Method method) { 423 return method.getClass().getSimpleName() + "_" + method.getName().replaceAll("[^a-zA-Z0-9.-]", "_"); 424 } 425 426 @Override getMainThread()427 public Thread getMainThread() { 428 return RuntimeEnvironment.getMainThread(); 429 } 430 431 @Override setMainThread(Thread newMainThread)432 public void setMainThread(Thread newMainThread) { 433 RuntimeEnvironment.setMainThread(newMainThread); 434 } 435 436 @Override tearDownApplication()437 public void tearDownApplication() { 438 if (RuntimeEnvironment.application != null) { 439 RuntimeEnvironment.application.onTerminate(); 440 } 441 } 442 443 @Override getCurrentApplication()444 public Object getCurrentApplication() { 445 return RuntimeEnvironment.application; 446 } 447 448 // TODO(christianw): reconcile with ShadowPackageManager.setUpPackageStorage setUpPackageStorage(ApplicationInfo applicationInfo, PackageParser.Package parsedPackage)449 private void setUpPackageStorage(ApplicationInfo applicationInfo, 450 PackageParser.Package parsedPackage) { 451 // TempDirectory tempDirectory = RuntimeEnvironment.getTempDirectory(); 452 // packageInfo.setVolumeUuid(tempDirectory.createIfNotExists(packageInfo.packageName + 453 // "-dataDir").toAbsolutePath().toString()); 454 455 if (RuntimeEnvironment.useLegacyResources()) { 456 applicationInfo.sourceDir = 457 createTempDir(applicationInfo.packageName + "-sourceDir"); 458 applicationInfo.publicSourceDir = 459 createTempDir(applicationInfo.packageName + "-publicSourceDir"); 460 } else { 461 if (sdkConfig.getApiLevel() <= VERSION_CODES.KITKAT) { 462 String sourcePath = ReflectionHelpers.getField(parsedPackage, "mPath"); 463 if (sourcePath == null) { 464 sourcePath = createTempDir("sourceDir"); 465 } 466 applicationInfo.publicSourceDir = sourcePath; 467 applicationInfo.sourceDir = sourcePath; 468 } else { 469 applicationInfo.publicSourceDir = parsedPackage.codePath; 470 applicationInfo.sourceDir = parsedPackage.codePath; 471 } 472 } 473 474 applicationInfo.dataDir = createTempDir(applicationInfo.packageName + "-dataDir"); 475 476 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N) { 477 applicationInfo.credentialProtectedDataDir = createTempDir("userDataDir"); 478 applicationInfo.deviceProtectedDataDir = createTempDir("deviceDataDir"); 479 } 480 } 481 createTempDir(String name)482 private String createTempDir(String name) { 483 return RuntimeEnvironment.getTempDirectory() 484 .createIfNotExists(name) 485 .toAbsolutePath() 486 .toString(); 487 } 488 489 // TODO move/replace this with packageManager 490 @VisibleForTesting registerBroadcastReceivers( Application application, AndroidManifest androidManifest)491 static void registerBroadcastReceivers( 492 Application application, AndroidManifest androidManifest) { 493 for (BroadcastReceiverData receiver : androidManifest.getBroadcastReceivers()) { 494 IntentFilter filter = new IntentFilter(); 495 for (String action : receiver.getActions()) { 496 filter.addAction(action); 497 } 498 String receiverClassName = replaceLastDotWith$IfInnerStaticClass(receiver.getName()); 499 application.registerReceiver((BroadcastReceiver) newInstanceOf(receiverClassName), filter); 500 } 501 } 502 replaceLastDotWith$IfInnerStaticClass(String receiverClassName)503 private static String replaceLastDotWith$IfInnerStaticClass(String receiverClassName) { 504 String[] splits = receiverClassName.split("\\.", 0); 505 String staticInnerClassRegex = "[A-Z][a-zA-Z]*"; 506 if (splits.length > 1 507 && splits[splits.length - 1].matches(staticInnerClassRegex) 508 && splits[splits.length - 2].matches(staticInnerClassRegex)) { 509 int lastDotIndex = receiverClassName.lastIndexOf("."); 510 StringBuilder buffer = new StringBuilder(receiverClassName); 511 buffer.setCharAt(lastDotIndex, '$'); 512 return buffer.toString(); 513 } 514 return receiverClassName; 515 } 516 } 517