1 package org.robolectric.android.internal; 2 3 import static org.robolectric.Shadows.shadowOf; 4 import static org.robolectric.shadow.api.Shadow.newInstanceOf; 5 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; 6 7 import android.app.ActivityThread; 8 import android.app.Application; 9 import android.app.IInstrumentationWatcher; 10 import android.app.IUiAutomationConnection; 11 import android.app.Instrumentation; 12 import android.app.LoadedApk; 13 import android.content.BroadcastReceiver; 14 import android.content.ComponentName; 15 import android.content.Context; 16 import android.content.IntentFilter; 17 import android.content.pm.ApplicationInfo; 18 import android.content.pm.PackageManager; 19 import android.content.pm.PackageParser; 20 import android.content.res.AssetManager; 21 import android.content.res.Configuration; 22 import android.content.res.Resources; 23 import android.os.Build; 24 import android.os.Build.VERSION_CODES; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.util.DisplayMetrics; 29 import com.google.common.annotations.VisibleForTesting; 30 import java.lang.reflect.Method; 31 import java.security.Security; 32 import java.util.Locale; 33 import org.bouncycastle.jce.provider.BouncyCastleProvider; 34 import org.robolectric.RuntimeEnvironment; 35 import org.robolectric.android.Bootstrap; 36 import org.robolectric.annotation.Config; 37 import org.robolectric.internal.ParallelUniverseInterface; 38 import org.robolectric.internal.SdkConfig; 39 import org.robolectric.manifest.AndroidManifest; 40 import org.robolectric.manifest.BroadcastReceiverData; 41 import org.robolectric.manifest.RoboNotFoundException; 42 import org.robolectric.res.ResourceTable; 43 import org.robolectric.shadows.ClassNameResolver; 44 import org.robolectric.shadows.LegacyManifestParser; 45 import org.robolectric.shadows.ShadowActivityThread; 46 import org.robolectric.shadows.ShadowContextImpl; 47 import org.robolectric.shadows.ShadowLog; 48 import org.robolectric.shadows.ShadowLooper; 49 import org.robolectric.shadows.ShadowPackageParser; 50 import org.robolectric.util.PerfStatsCollector; 51 import org.robolectric.util.ReflectionHelpers; 52 import org.robolectric.util.Scheduler; 53 import org.robolectric.util.TempDirectory; 54 55 public class ParallelUniverse implements ParallelUniverseInterface { 56 57 private boolean loggingInitialized = false; 58 private SdkConfig sdkConfig; 59 60 @Override 61 public void setUpApplicationState( 62 Method method, 63 AndroidManifest appManifest, 64 Config config, 65 ResourceTable compileTimeResourceTable, 66 ResourceTable appResourceTable, 67 ResourceTable systemResourceTable) { 68 ReflectionHelpers.setStaticField(RuntimeEnvironment.class, "apiLevel", sdkConfig.getApiLevel()); 69 70 RuntimeEnvironment.application = null; 71 RuntimeEnvironment.setActivityThread(null); 72 RuntimeEnvironment.setTempDirectory(new TempDirectory(createTestDataDirRootPath(method))); 73 RuntimeEnvironment.setMasterScheduler(new Scheduler()); 74 RuntimeEnvironment.setMainThread(Thread.currentThread()); 75 76 RuntimeEnvironment.setCompileTimeResourceTable(compileTimeResourceTable); 77 RuntimeEnvironment.setAppResourceTable(appResourceTable); 78 RuntimeEnvironment.setSystemResourceTable(systemResourceTable); 79 80 if (!loggingInitialized) { 81 ShadowLog.setupLogging(); 82 loggingInitialized = true; 83 } 84 85 try { 86 appManifest.initMetaData(appResourceTable); 87 } catch (RoboNotFoundException e1) { 88 throw new Resources.NotFoundException(e1.getMessage()); 89 } 90 91 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { 92 Security.insertProviderAt(new BouncyCastleProvider(), 1); 93 } 94 95 Configuration configuration = new Configuration(); 96 DisplayMetrics displayMetrics = new DisplayMetrics(); 97 98 Bootstrap.applyQualifiers(config.qualifiers(), sdkConfig.getApiLevel(), configuration, 99 displayMetrics); 100 101 Locale locale = sdkConfig.getApiLevel() >= VERSION_CODES.N 102 ? configuration.getLocales().get(0) 103 : configuration.locale; 104 Locale.setDefault(locale); 105 106 // Looper needs to be prepared before the activity thread is created 107 if (Looper.myLooper() == null) { 108 Looper.prepareMainLooper(); 109 } 110 ShadowLooper.getShadowMainLooper().resetScheduler(); 111 ActivityThread activityThread = ReflectionHelpers.newInstance(ActivityThread.class); 112 RuntimeEnvironment.setActivityThread(activityThread); 113 114 PackageParser.Package parsedPackage = null; 115 116 ApplicationInfo applicationInfo = null; 117 if (appManifest.getAndroidManifestFile() != null 118 && appManifest.getAndroidManifestFile().exists()) { 119 if (Boolean.parseBoolean(System.getProperty("use_framework_manifest_parser", "false"))) { 120 parsedPackage = ShadowPackageParser.callParsePackage(appManifest.getAndroidManifestFile()); 121 } else { 122 parsedPackage = LegacyManifestParser.createPackage(appManifest); 123 } 124 } else { 125 parsedPackage = new PackageParser.Package("org.robolectric.default"); 126 parsedPackage.applicationInfo.targetSdkVersion = appManifest.getTargetSdkVersion(); 127 } 128 applicationInfo = parsedPackage.applicationInfo; 129 130 // Support overriding the package name specified in the Manifest. 131 if (!Config.DEFAULT_PACKAGE_NAME.equals(config.packageName())) { 132 parsedPackage.packageName = config.packageName(); 133 parsedPackage.applicationInfo.packageName = config.packageName(); 134 } else { 135 parsedPackage.packageName = appManifest.getPackageName(); 136 parsedPackage.applicationInfo.packageName = appManifest.getPackageName(); 137 } 138 // TempDirectory tempDirectory = RuntimeEnvironment.getTempDirectory(); 139 // packageInfo.setVolumeUuid(tempDirectory.createIfNotExists(packageInfo.packageName + 140 // "-dataDir").toAbsolutePath().toString()); 141 setUpPackageStorage(applicationInfo); 142 143 // Bit of a hack... Context.createPackageContext() is called before the application is created. 144 // It calls through 145 // to ActivityThread for the package which in turn calls the PackageManagerService directly. 146 // This works for now 147 // but it might be nicer to have ShadowPackageManager implementation move into the service as 148 // there is also lots of 149 // code in there that can be reusable, e.g: the XxxxIntentResolver code. 150 ShadowActivityThread.setApplicationInfo(applicationInfo); 151 152 Class<?> contextImplClass = 153 ReflectionHelpers.loadClass( 154 getClass().getClassLoader(), ShadowContextImpl.CLASS_NAME); 155 156 ReflectionHelpers.setField(activityThread, "mCompatConfiguration", configuration); 157 ReflectionHelpers.setStaticField(ActivityThread.class, "sMainThreadHandler", new Handler(Looper.myLooper())); 158 159 Bootstrap.setUpDisplay(configuration, displayMetrics); 160 161 Resources systemResources = Resources.getSystem(); 162 systemResources.updateConfiguration(configuration, displayMetrics); 163 164 Context systemContextImpl = ReflectionHelpers.callStaticMethod(contextImplClass, 165 "createSystemContext", from(ActivityThread.class, activityThread)); 166 RuntimeEnvironment.systemContext = systemContextImpl; 167 168 Application application = createApplication(appManifest, config); 169 RuntimeEnvironment.application = application; 170 171 if (application != null) { 172 final Class<?> appBindDataClass; 173 try { 174 appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData"); 175 } catch (ClassNotFoundException e) { 176 throw new RuntimeException(e); 177 } 178 Object data = ReflectionHelpers.newInstance(appBindDataClass); 179 ReflectionHelpers.setField(data, "processName", "org.robolectric"); 180 ReflectionHelpers.setField(data, "appInfo", applicationInfo); 181 ReflectionHelpers.setField(activityThread, "mBoundApplication", data); 182 183 LoadedApk loadedApk = activityThread.getPackageInfo(applicationInfo, null, Context.CONTEXT_INCLUDE_CODE); 184 185 try { 186 Context contextImpl = systemContextImpl.createPackageContext(applicationInfo.packageName, Context.CONTEXT_INCLUDE_CODE); 187 shadowOf(contextImpl.getPackageManager()).addPackage(parsedPackage); 188 ReflectionHelpers.setField(ActivityThread.class, activityThread, "mInitialApplication", application); 189 shadowOf(application).callAttach(contextImpl); 190 } catch (PackageManager.NameNotFoundException e) { 191 throw new RuntimeException(e); 192 } 193 194 Resources appResources = application.getResources(); 195 ReflectionHelpers.setField(loadedApk, "mResources", appResources); 196 ReflectionHelpers.setField(loadedApk, "mApplication", application); 197 198 appResources.updateConfiguration(configuration, displayMetrics); 199 populateAssetPaths(appResources.getAssets(), appManifest); 200 201 initInstrumentation(activityThread, applicationInfo); 202 203 PerfStatsCollector.getInstance().measure("application onCreate()", () -> { 204 application.onCreate(); 205 }); 206 } 207 } 208 209 private void populateAssetPaths(AssetManager assetManager, AndroidManifest appManifest) { 210 for (AndroidManifest manifest : appManifest.getAllManifests()) { 211 if (manifest.getAssetsDirectory() != null) { 212 assetManager.addAssetPath(manifest.getAssetsDirectory().getPath()); 213 } 214 } 215 } 216 217 @VisibleForTesting 218 static Application createApplication(AndroidManifest appManifest, Config config) { 219 Application application = null; 220 if (config != null && !Config.Builder.isDefaultApplication(config.application())) { 221 if (config.application().getCanonicalName() != null) { 222 Class<? extends Application> applicationClass; 223 try { 224 applicationClass = ClassNameResolver.resolve(null, config.application().getName()); 225 } catch (ClassNotFoundException e) { 226 throw new RuntimeException(e); 227 } 228 application = ReflectionHelpers.callConstructor(applicationClass); 229 } 230 } else if (appManifest != null && appManifest.getApplicationName() != null) { 231 Class<? extends Application> applicationClass = null; 232 try { 233 applicationClass = ClassNameResolver.resolve(appManifest.getPackageName(), 234 getTestApplicationName(appManifest.getApplicationName())); 235 } catch (ClassNotFoundException e) { 236 // no problem 237 } 238 239 if (applicationClass == null) { 240 try { 241 applicationClass = ClassNameResolver.resolve(appManifest.getPackageName(), 242 appManifest.getApplicationName()); 243 } catch (ClassNotFoundException e) { 244 throw new RuntimeException(e); 245 } 246 } 247 248 application = ReflectionHelpers.callConstructor(applicationClass); 249 } else { 250 application = new Application(); 251 } 252 253 if (appManifest != null) { 254 registerBroadcastReceivers(application, appManifest); 255 } 256 257 return application; 258 } 259 260 @VisibleForTesting 261 static String getTestApplicationName(String applicationName) { 262 int lastDot = applicationName.lastIndexOf('.'); 263 if (lastDot > -1) { 264 return applicationName.substring(0, lastDot) + ".Test" + applicationName.substring(lastDot + 1); 265 } else { 266 return "Test" + applicationName; 267 } 268 } 269 270 private void initInstrumentation( 271 ActivityThread activityThread, 272 ApplicationInfo applicationInfo) { 273 Instrumentation androidInstrumentation = createInstrumentation(); 274 ReflectionHelpers.setField(activityThread, "mInstrumentation", androidInstrumentation); 275 276 final ComponentName component = 277 new ComponentName( 278 applicationInfo.packageName, androidInstrumentation.getClass().getSimpleName()); 279 if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.JELLY_BEAN_MR1) { 280 ReflectionHelpers.callInstanceMethod(androidInstrumentation, "init", 281 from(ActivityThread.class, activityThread), 282 from(Context.class, RuntimeEnvironment.application), 283 from(Context.class, RuntimeEnvironment.application), 284 from(ComponentName.class, component), 285 from(IInstrumentationWatcher.class, null)); 286 } else { 287 ReflectionHelpers.callInstanceMethod(androidInstrumentation, "init", 288 from(ActivityThread.class, activityThread), 289 from(Context.class, RuntimeEnvironment.application), 290 from(Context.class, RuntimeEnvironment.application), 291 from(ComponentName.class, component), 292 from(IInstrumentationWatcher.class, null), 293 from(IUiAutomationConnection.class, null)); 294 } 295 296 androidInstrumentation.onCreate(new Bundle()); 297 } 298 299 private Instrumentation createInstrumentation() { 300 // Use RoboInstrumentation if its parent class from optional dependency android.support.test is 301 // available. Otherwise use Instrumentation 302 try { 303 Class<? extends Instrumentation> roboInstrumentationClass = 304 Class.forName("org.robolectric.android.fakes.RoboInstrumentation").asSubclass( 305 Instrumentation.class); 306 return ReflectionHelpers.newInstance(roboInstrumentationClass); 307 } catch (ClassNotFoundException | NoClassDefFoundError e) { 308 // fall through 309 } 310 return new Instrumentation(); 311 } 312 313 /** 314 * Create a file system safe directory path name for the current test. 315 */ 316 private String createTestDataDirRootPath(Method method) { 317 return method.getClass().getSimpleName() + "_" + method.getName().replaceAll("[^a-zA-Z0-9.-]", "_"); 318 } 319 320 @Override 321 public Thread getMainThread() { 322 return RuntimeEnvironment.getMainThread(); 323 } 324 325 @Override 326 public void setMainThread(Thread newMainThread) { 327 RuntimeEnvironment.setMainThread(newMainThread); 328 } 329 330 @Override 331 public void tearDownApplication() { 332 if (RuntimeEnvironment.application != null) { 333 RuntimeEnvironment.application.onTerminate(); 334 } 335 } 336 337 @Override 338 public Object getCurrentApplication() { 339 return RuntimeEnvironment.application; 340 } 341 342 @Override 343 public void setSdkConfig(SdkConfig sdkConfig) { 344 this.sdkConfig = sdkConfig; 345 ReflectionHelpers.setStaticField(RuntimeEnvironment.class, "apiLevel", sdkConfig.getApiLevel()); 346 } 347 348 private static void setUpPackageStorage(ApplicationInfo applicationInfo) { 349 TempDirectory tempDirectory = RuntimeEnvironment.getTempDirectory(); 350 applicationInfo.sourceDir = 351 tempDirectory 352 .createIfNotExists(applicationInfo.packageName + "-sourceDir") 353 .toAbsolutePath() 354 .toString(); 355 applicationInfo.publicSourceDir = 356 tempDirectory 357 .createIfNotExists(applicationInfo.packageName + "-publicSourceDir") 358 .toAbsolutePath() 359 .toString(); 360 applicationInfo.dataDir = 361 tempDirectory 362 .createIfNotExists(applicationInfo.packageName + "-dataDir") 363 .toAbsolutePath() 364 .toString(); 365 366 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N) { 367 applicationInfo.credentialProtectedDataDir = 368 tempDirectory.createIfNotExists("userDataDir").toAbsolutePath().toString(); 369 applicationInfo.deviceProtectedDataDir = 370 tempDirectory.createIfNotExists("deviceDataDir").toAbsolutePath().toString(); 371 } 372 } 373 374 // TODO move/replace this with packageManager 375 private static void registerBroadcastReceivers( 376 Application application, AndroidManifest androidManifest) { 377 for (BroadcastReceiverData receiver : androidManifest.getBroadcastReceivers()) { 378 IntentFilter filter = new IntentFilter(); 379 for (String action : receiver.getActions()) { 380 filter.addAction(action); 381 } 382 String receiverClassName = replaceLastDotWith$IfInnerStaticClass(receiver.getName()); 383 shadowOf(application) 384 .registerReceiver((BroadcastReceiver) newInstanceOf(receiverClassName), filter); 385 } 386 } 387 388 private static String replaceLastDotWith$IfInnerStaticClass(String receiverClassName) { 389 String[] splits = receiverClassName.split("\\."); 390 String staticInnerClassRegex = "[A-Z][a-zA-Z]*"; 391 if (splits.length > 1 392 && splits[splits.length - 1].matches(staticInnerClassRegex) 393 && splits[splits.length - 2].matches(staticInnerClassRegex)) { 394 int lastDotIndex = receiverClassName.lastIndexOf("."); 395 StringBuilder buffer = new StringBuilder(receiverClassName); 396 buffer.setCharAt(lastDotIndex, '$'); 397 return buffer.toString(); 398 } 399 return receiverClassName; 400 } 401 } 402