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 FsFile packageFile = appManifest.getApkFile(); 151 parsedPackage = ShadowPackageParser.callParsePackage(packageFile); 152 } 153 154 ApplicationInfo applicationInfo = parsedPackage.applicationInfo; 155 156 // unclear why, but prior to P the processName wasn't set 157 if (sdkConfig.getApiLevel() < P && applicationInfo.processName == null) { 158 applicationInfo.processName = parsedPackage.packageName; 159 } 160 161 setUpPackageStorage(applicationInfo, parsedPackage); 162 163 // Bit of a hack... Context.createPackageContext() is called before the application is created. 164 // It calls through 165 // to ActivityThread for the package which in turn calls the PackageManagerService directly. 166 // This works for now 167 // but it might be nicer to have ShadowPackageManager implementation move into the service as 168 // there is also lots of 169 // code in there that can be reusable, e.g: the XxxxIntentResolver code. 170 ShadowActivityThread.setApplicationInfo(applicationInfo); 171 172 Class<?> contextImplClass = 173 ReflectionHelpers.loadClass( 174 getClass().getClassLoader(), ShadowContextImpl.CLASS_NAME); 175 176 ReflectionHelpers.setField(activityThread, "mCompatConfiguration", configuration); 177 ReflectionHelpers 178 .setStaticField(ActivityThread.class, "sMainThreadHandler", new Handler(Looper.myLooper())); 179 180 Bootstrap.setUpDisplay(configuration, displayMetrics); 181 activityThread.applyConfigurationToResources(configuration); 182 183 Resources systemResources = Resources.getSystem(); 184 systemResources.updateConfiguration(configuration, displayMetrics); 185 186 Context systemContextImpl = ReflectionHelpers.callStaticMethod(contextImplClass, 187 "createSystemContext", from(ActivityThread.class, activityThread)); 188 RuntimeEnvironment.systemContext = systemContextImpl; 189 190 Application application = createApplication(appManifest, config); 191 RuntimeEnvironment.application = application; 192 193 Instrumentation instrumentation = 194 createInstrumentation(activityThread, applicationInfo, application); 195 196 if (application != null) { 197 final Class<?> appBindDataClass; 198 try { 199 appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData"); 200 } catch (ClassNotFoundException e) { 201 throw new RuntimeException(e); 202 } 203 Object data = ReflectionHelpers.newInstance(appBindDataClass); 204 ReflectionHelpers.setField(data, "processName", "org.robolectric"); 205 ReflectionHelpers.setField(data, "appInfo", applicationInfo); 206 ReflectionHelpers.setField(activityThread, "mBoundApplication", data); 207 208 LoadedApk loadedApk = activityThread 209 .getPackageInfo(applicationInfo, null, Context.CONTEXT_INCLUDE_CODE); 210 211 try { 212 Context contextImpl = systemContextImpl 213 .createPackageContext(applicationInfo.packageName, Context.CONTEXT_INCLUDE_CODE); 214 215 ShadowPackageManager shadowPackageManager = Shadow.extract(contextImpl.getPackageManager()); 216 shadowPackageManager.addPackageInternal(parsedPackage); 217 ReflectionHelpers 218 .setField(ActivityThread.class, activityThread, "mInitialApplication", application); 219 ShadowApplication shadowApplication = Shadow.extract(application); 220 shadowApplication.callAttach(contextImpl); 221 ReflectionHelpers.callInstanceMethod( 222 contextImpl, 223 "setOuterContext", 224 ReflectionHelpers.ClassParameter.from(Context.class, application)); 225 } catch (PackageManager.NameNotFoundException e) { 226 throw new RuntimeException(e); 227 } 228 229 Secure.setLocationProviderEnabled(application.getContentResolver(), GPS_PROVIDER, true); 230 231 Resources appResources = application.getResources(); 232 ReflectionHelpers.setField(loadedApk, "mResources", appResources); 233 ReflectionHelpers.setField(loadedApk, "mApplication", application); 234 235 registerBroadcastReceivers(application, appManifest); 236 237 appResources.updateConfiguration(configuration, displayMetrics); 238 239 if (ShadowAssetManager.useLegacy()) { 240 populateAssetPaths(appResources.getAssets(), appManifest); 241 } 242 243 instrumentation.onCreate(new Bundle()); 244 245 PerfStatsCollector.getInstance() 246 .measure("application onCreate()", () -> application.onCreate()); 247 } 248 } 249 injectResourceStuffForLegacy(ApkLoader apkLoader, AndroidManifest appManifest, SdkEnvironment sdkEnvironment)250 private void injectResourceStuffForLegacy(ApkLoader apkLoader, AndroidManifest appManifest, 251 SdkEnvironment sdkEnvironment) { 252 PackageResourceTable systemResourceTable = apkLoader.getSystemResourceTable(sdkEnvironment); 253 PackageResourceTable appResourceTable = apkLoader.getAppResourceTable(appManifest); 254 RoutingResourceTable combinedAppResourceTable = new RoutingResourceTable(appResourceTable, 255 systemResourceTable); 256 257 PackageResourceTable compileTimeSdkResourceTable = apkLoader.getCompileTimeSdkResourceTable(); 258 ResourceTable combinedCompileTimeResourceTable = 259 new RoutingResourceTable(appResourceTable, compileTimeSdkResourceTable); 260 261 RuntimeEnvironment.setCompileTimeResourceTable(combinedCompileTimeResourceTable); 262 RuntimeEnvironment.setAppResourceTable(combinedAppResourceTable); 263 RuntimeEnvironment.setSystemResourceTable(new RoutingResourceTable(systemResourceTable)); 264 265 try { 266 appManifest.initMetaData(combinedAppResourceTable); 267 } catch (RoboNotFoundException e1) { 268 throw new Resources.NotFoundException(e1.getMessage()); 269 } 270 } 271 populateAssetPaths(AssetManager assetManager, AndroidManifest appManifest)272 private void populateAssetPaths(AssetManager assetManager, AndroidManifest appManifest) { 273 for (AndroidManifest manifest : appManifest.getAllManifests()) { 274 if (manifest.getAssetsDirectory() != null) { 275 assetManager.addAssetPath(manifest.getAssetsDirectory().getPath()); 276 } 277 } 278 } 279 280 @VisibleForTesting createApplication(AndroidManifest appManifest, Config config)281 static Application createApplication(AndroidManifest appManifest, Config config) { 282 Application application = null; 283 if (config != null && !Config.Builder.isDefaultApplication(config.application())) { 284 if (config.application().getCanonicalName() != null) { 285 Class<? extends Application> applicationClass; 286 try { 287 applicationClass = ClassNameResolver.resolve(null, config.application().getName()); 288 } catch (ClassNotFoundException e) { 289 throw new RuntimeException(e); 290 } 291 application = ReflectionHelpers.callConstructor(applicationClass); 292 } 293 } else if (appManifest != null && appManifest.getApplicationName() != null) { 294 Class<? extends Application> applicationClass = null; 295 try { 296 applicationClass = ClassNameResolver.resolve(appManifest.getPackageName(), 297 getTestApplicationName(appManifest.getApplicationName())); 298 } catch (ClassNotFoundException e) { 299 // no problem 300 } 301 302 if (applicationClass == null) { 303 try { 304 applicationClass = ClassNameResolver.resolve(appManifest.getPackageName(), 305 appManifest.getApplicationName()); 306 } catch (ClassNotFoundException e) { 307 throw new RuntimeException(e); 308 } 309 } 310 311 application = ReflectionHelpers.callConstructor(applicationClass); 312 } else { 313 application = new Application(); 314 } 315 316 return application; 317 } 318 319 @VisibleForTesting getTestApplicationName(String applicationName)320 static String getTestApplicationName(String applicationName) { 321 int lastDot = applicationName.lastIndexOf('.'); 322 if (lastDot > -1) { 323 return applicationName.substring(0, lastDot) + ".Test" + applicationName.substring(lastDot + 1); 324 } else { 325 return "Test" + applicationName; 326 } 327 } 328 createInstrumentation( ActivityThread activityThread, ApplicationInfo applicationInfo, Application application)329 private static Instrumentation createInstrumentation( 330 ActivityThread activityThread, 331 ApplicationInfo applicationInfo, Application application) { 332 Instrumentation androidInstrumentation = new RoboMonitoringInstrumentation(); 333 ReflectionHelpers.setField(activityThread, "mInstrumentation", androidInstrumentation); 334 335 final ComponentName component = 336 new ComponentName( 337 applicationInfo.packageName, androidInstrumentation.getClass().getSimpleName()); 338 if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.JELLY_BEAN_MR1) { 339 ReflectionHelpers.callInstanceMethod(androidInstrumentation, "init", 340 from(ActivityThread.class, activityThread), 341 from(Context.class, application), 342 from(Context.class, application), 343 from(ComponentName.class, component), 344 from(IInstrumentationWatcher.class, null)); 345 } else { 346 ReflectionHelpers.callInstanceMethod(androidInstrumentation, 347 "init", 348 from(ActivityThread.class, activityThread), 349 from(Context.class, application), 350 from(Context.class, application), 351 from(ComponentName.class, component), 352 from(IInstrumentationWatcher.class, null), 353 from(IUiAutomationConnection.class, null)); 354 } 355 356 return androidInstrumentation; 357 } 358 359 /** 360 * Create a file system safe directory path name for the current test. 361 */ createTestDataDirRootPath(Method method)362 private String createTestDataDirRootPath(Method method) { 363 return method.getClass().getSimpleName() + "_" + method.getName().replaceAll("[^a-zA-Z0-9.-]", "_"); 364 } 365 366 @Override getMainThread()367 public Thread getMainThread() { 368 return RuntimeEnvironment.getMainThread(); 369 } 370 371 @Override setMainThread(Thread newMainThread)372 public void setMainThread(Thread newMainThread) { 373 RuntimeEnvironment.setMainThread(newMainThread); 374 } 375 376 @Override tearDownApplication()377 public void tearDownApplication() { 378 if (RuntimeEnvironment.application != null) { 379 RuntimeEnvironment.application.onTerminate(); 380 } 381 } 382 383 @Override getCurrentApplication()384 public Object getCurrentApplication() { 385 return RuntimeEnvironment.application; 386 } 387 388 // TODO(christianw): reconcile with ShadowPackageManager.setUpPackageStorage setUpPackageStorage(ApplicationInfo applicationInfo, PackageParser.Package parsedPackage)389 private void setUpPackageStorage(ApplicationInfo applicationInfo, 390 PackageParser.Package parsedPackage) { 391 // TempDirectory tempDirectory = RuntimeEnvironment.getTempDirectory(); 392 // packageInfo.setVolumeUuid(tempDirectory.createIfNotExists(packageInfo.packageName + 393 // "-dataDir").toAbsolutePath().toString()); 394 395 if (RuntimeEnvironment.useLegacyResources()) { 396 applicationInfo.sourceDir = 397 createTempDir(applicationInfo.packageName + "-sourceDir"); 398 applicationInfo.publicSourceDir = 399 createTempDir(applicationInfo.packageName + "-publicSourceDir"); 400 } else { 401 if (sdkConfig.getApiLevel() <= VERSION_CODES.KITKAT) { 402 String sourcePath = ReflectionHelpers.getField(parsedPackage, "mPath"); 403 if (sourcePath == null) { 404 sourcePath = createTempDir("sourceDir"); 405 } 406 applicationInfo.publicSourceDir = sourcePath; 407 applicationInfo.sourceDir = sourcePath; 408 } else { 409 applicationInfo.publicSourceDir = parsedPackage.codePath; 410 applicationInfo.sourceDir = parsedPackage.codePath; 411 } 412 } 413 414 applicationInfo.dataDir = createTempDir(applicationInfo.packageName + "-dataDir"); 415 416 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N) { 417 applicationInfo.credentialProtectedDataDir = createTempDir("userDataDir"); 418 applicationInfo.deviceProtectedDataDir = createTempDir("deviceDataDir"); 419 } 420 } 421 createTempDir(String name)422 private String createTempDir(String name) { 423 return RuntimeEnvironment.getTempDirectory() 424 .createIfNotExists(name) 425 .toAbsolutePath() 426 .toString(); 427 } 428 429 // TODO move/replace this with packageManager 430 @VisibleForTesting registerBroadcastReceivers( Application application, AndroidManifest androidManifest)431 static void registerBroadcastReceivers( 432 Application application, AndroidManifest androidManifest) { 433 for (BroadcastReceiverData receiver : androidManifest.getBroadcastReceivers()) { 434 IntentFilter filter = new IntentFilter(); 435 for (String action : receiver.getActions()) { 436 filter.addAction(action); 437 } 438 String receiverClassName = replaceLastDotWith$IfInnerStaticClass(receiver.getName()); 439 application.registerReceiver((BroadcastReceiver) newInstanceOf(receiverClassName), filter); 440 } 441 } 442 replaceLastDotWith$IfInnerStaticClass(String receiverClassName)443 private static String replaceLastDotWith$IfInnerStaticClass(String receiverClassName) { 444 String[] splits = receiverClassName.split("\\.", 0); 445 String staticInnerClassRegex = "[A-Z][a-zA-Z]*"; 446 if (splits.length > 1 447 && splits[splits.length - 1].matches(staticInnerClassRegex) 448 && splits[splits.length - 2].matches(staticInnerClassRegex)) { 449 int lastDotIndex = receiverClassName.lastIndexOf("."); 450 StringBuilder buffer = new StringBuilder(receiverClassName); 451 buffer.setCharAt(lastDotIndex, '$'); 452 return buffer.toString(); 453 } 454 return receiverClassName; 455 } 456 } 457