| package org.robolectric.android.internal; |
| |
| import static org.robolectric.Shadows.shadowOf; |
| import static org.robolectric.util.ReflectionHelpers.ClassParameter; |
| |
| import android.app.ActivityThread; |
| import android.app.Application; |
| import android.app.LoadedApk; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageParser; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.os.Build; |
| import android.os.Build.VERSION_CODES; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.util.DisplayMetrics; |
| import com.google.common.annotations.VisibleForTesting; |
| import java.lang.reflect.Method; |
| import java.security.Security; |
| import java.util.Locale; |
| import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| import org.robolectric.Robolectric; |
| import org.robolectric.RuntimeEnvironment; |
| import org.robolectric.TestLifecycle; |
| import org.robolectric.android.Bootstrap; |
| import org.robolectric.android.fakes.RoboInstrumentation; |
| import org.robolectric.annotation.Config; |
| import org.robolectric.internal.ParallelUniverseInterface; |
| import org.robolectric.internal.SdkConfig; |
| import org.robolectric.manifest.AndroidManifest; |
| import org.robolectric.manifest.RoboNotFoundException; |
| import org.robolectric.res.ResourceTable; |
| import org.robolectric.shadows.ClassNameResolver; |
| import org.robolectric.shadows.LegacyManifestParser; |
| import org.robolectric.shadows.ShadowActivityThread; |
| import org.robolectric.shadows.ShadowContextImpl; |
| import org.robolectric.shadows.ShadowLog; |
| import org.robolectric.shadows.ShadowLooper; |
| import org.robolectric.shadows.ShadowPackageParser; |
| import org.robolectric.util.ReflectionHelpers; |
| import org.robolectric.util.Scheduler; |
| import org.robolectric.util.TempDirectory; |
| |
| public class ParallelUniverse implements ParallelUniverseInterface { |
| |
| private boolean loggingInitialized = false; |
| private SdkConfig sdkConfig; |
| |
| @Override |
| public void resetStaticState(Config config) { |
| RuntimeEnvironment.setMainThread(Thread.currentThread()); |
| Robolectric.reset(); |
| |
| if (!loggingInitialized) { |
| ShadowLog.setupLogging(); |
| loggingInitialized = true; |
| } |
| } |
| |
| @Override |
| public void setUpApplicationState(Method method, TestLifecycle testLifecycle, AndroidManifest appManifest, |
| Config config, ResourceTable compileTimeResourceTable, |
| ResourceTable appResourceTable, |
| ResourceTable systemResourceTable) { |
| ReflectionHelpers.setStaticField(RuntimeEnvironment.class, "apiLevel", sdkConfig.getApiLevel()); |
| |
| RuntimeEnvironment.application = null; |
| RuntimeEnvironment.setTempDirectory(new TempDirectory(createTestDataDirRootPath(method))); |
| RuntimeEnvironment.setMasterScheduler(new Scheduler()); |
| RuntimeEnvironment.setMainThread(Thread.currentThread()); |
| |
| RuntimeEnvironment.setCompileTimeResourceTable(compileTimeResourceTable); |
| RuntimeEnvironment.setAppResourceTable(appResourceTable); |
| RuntimeEnvironment.setSystemResourceTable(systemResourceTable); |
| |
| try { |
| appManifest.initMetaData(appResourceTable); |
| } catch (RoboNotFoundException e1) { |
| throw new Resources.NotFoundException(e1.getMessage()); |
| } |
| RuntimeEnvironment.setApplicationManifest(appManifest); |
| |
| if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { |
| Security.insertProviderAt(new BouncyCastleProvider(), 1); |
| } |
| |
| Configuration configuration = new Configuration(); |
| DisplayMetrics displayMetrics = new DisplayMetrics(); |
| |
| Bootstrap.applyQualifiers(config.qualifiers(), sdkConfig.getApiLevel(), configuration, |
| displayMetrics); |
| |
| Locale locale = sdkConfig.getApiLevel() >= VERSION_CODES.N |
| ? configuration.getLocales().get(0) |
| : configuration.locale; |
| Locale.setDefault(locale); |
| |
| // Looper needs to be prepared before the activity thread is created |
| if (Looper.myLooper() == null) { |
| Looper.prepareMainLooper(); |
| } |
| ShadowLooper.getShadowMainLooper().resetScheduler(); |
| ActivityThread activityThread = ReflectionHelpers.newInstance(ActivityThread.class); |
| RuntimeEnvironment.setActivityThread(activityThread); |
| |
| RoboInstrumentation androidInstrumentation = new RoboInstrumentation(); |
| ReflectionHelpers.setField(activityThread, "mInstrumentation", androidInstrumentation); |
| PackageParser.Package parsedPackage = null; |
| |
| ApplicationInfo applicationInfo = null; |
| if (appManifest.getAndroidManifestFile() != null |
| && appManifest.getAndroidManifestFile().exists()) { |
| if (Boolean.parseBoolean(System.getProperty("use_framework_manifest_parser", "false"))) { |
| parsedPackage = ShadowPackageParser.callParsePackage(appManifest.getAndroidManifestFile()); |
| } else { |
| parsedPackage = LegacyManifestParser.createPackage(appManifest); |
| } |
| } else { |
| parsedPackage = new PackageParser.Package("org.robolectric.default"); |
| parsedPackage.applicationInfo.targetSdkVersion = appManifest.getTargetSdkVersion(); |
| } |
| applicationInfo = parsedPackage.applicationInfo; |
| |
| // Support overriding the package name specified in the Manifest. |
| if (!Config.DEFAULT_PACKAGE_NAME.equals(config.packageName())) { |
| parsedPackage.packageName = config.packageName(); |
| parsedPackage.applicationInfo.packageName = config.packageName(); |
| } |
| // TempDirectory tempDirectory = RuntimeEnvironment.getTempDirectory(); |
| // packageInfo.setVolumeUuid(tempDirectory.createIfNotExists(packageInfo.packageName + |
| // "-dataDir").toAbsolutePath().toString()); |
| setUpPackageStorage(applicationInfo); |
| |
| // Bit of a hack... Context.createPackageContext() is called before the application is created. |
| // It calls through |
| // to ActivityThread for the package which in turn calls the PackageManagerService directly. |
| // This works for now |
| // but it might be nicer to have ShadowPackageManager implementation move into the service as |
| // there is also lots of |
| // code in there that can be reusable, e.g: the XxxxIntentResolver code. |
| ShadowActivityThread.setApplicationInfo(applicationInfo); |
| |
| Class<?> contextImplClass = |
| ReflectionHelpers.loadClass( |
| getClass().getClassLoader(), ShadowContextImpl.CLASS_NAME); |
| |
| ReflectionHelpers.setField(activityThread, "mCompatConfiguration", configuration); |
| ReflectionHelpers.setStaticField(ActivityThread.class, "sMainThreadHandler", new Handler(Looper.myLooper())); |
| |
| Bootstrap.setUpDisplay(configuration, displayMetrics); |
| |
| Resources systemResources = Resources.getSystem(); |
| systemResources.updateConfiguration(configuration, displayMetrics); |
| |
| Context systemContextImpl = ReflectionHelpers.callStaticMethod(contextImplClass, "createSystemContext", ClassParameter.from(ActivityThread.class, activityThread)); |
| RuntimeEnvironment.systemContext = systemContextImpl; |
| |
| Application app; |
| try { |
| app = (Application) testLifecycle.createApplication(method, appManifest, config); |
| System.out.println("*** TestLifecycle.createApplication() is a deprecated interface and will be removed in Robolectric 3.7;" + |
| "*** please refactor your tests and remove " + testLifecycle.getClass().getName() + "."); |
| } catch (Exception e) { |
| app = createApplication(appManifest, config); |
| } |
| Application application = app; |
| RuntimeEnvironment.application = application; |
| |
| if (application != null) { |
| shadowOf(application).bind(appManifest); |
| |
| final Class<?> appBindDataClass; |
| try { |
| appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData"); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException(e); |
| } |
| Object data = ReflectionHelpers.newInstance(appBindDataClass); |
| ReflectionHelpers.setField(data, "processName", "org.robolectric"); |
| ReflectionHelpers.setField(data, "appInfo", applicationInfo); |
| ReflectionHelpers.setField(activityThread, "mBoundApplication", data); |
| |
| LoadedApk loadedApk = activityThread.getPackageInfo(applicationInfo, null, Context.CONTEXT_INCLUDE_CODE); |
| |
| try { |
| Context contextImpl = systemContextImpl.createPackageContext(applicationInfo.packageName, Context.CONTEXT_INCLUDE_CODE); |
| shadowOf(contextImpl.getPackageManager()).addPackage(parsedPackage); |
| ReflectionHelpers.setField(ActivityThread.class, activityThread, "mInitialApplication", application); |
| shadowOf(application).callAttach(contextImpl); |
| } catch (PackageManager.NameNotFoundException e) { |
| throw new RuntimeException(e); |
| } |
| |
| Resources appResources = application.getResources(); |
| ReflectionHelpers.setField(loadedApk, "mResources", appResources); |
| ReflectionHelpers.setField(loadedApk, "mApplication", application); |
| |
| appResources.updateConfiguration(configuration, displayMetrics); |
| |
| initInstrumentation(activityThread, androidInstrumentation, applicationInfo); |
| |
| application.onCreate(); |
| } |
| } |
| |
| @VisibleForTesting |
| static Application createApplication(AndroidManifest appManifest, Config config) { |
| Application application = null; |
| if (config != null && !Config.Builder.isDefaultApplication(config.application())) { |
| if (config.application().getCanonicalName() != null) { |
| Class<? extends Application> applicationClass; |
| try { |
| applicationClass = ClassNameResolver.resolve(null, config.application().getName()); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException(e); |
| } |
| application = ReflectionHelpers.callConstructor(applicationClass); |
| } |
| } else if (appManifest != null && appManifest.getApplicationName() != null) { |
| Class<? extends Application> applicationClass = null; |
| try { |
| applicationClass = ClassNameResolver.resolve(appManifest.getPackageName(), |
| getTestApplicationName(appManifest.getApplicationName())); |
| } catch (ClassNotFoundException e) { |
| // no problem |
| } |
| |
| if (applicationClass == null) { |
| try { |
| applicationClass = ClassNameResolver.resolve(appManifest.getPackageName(), |
| appManifest.getApplicationName()); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| application = ReflectionHelpers.callConstructor(applicationClass); |
| } else { |
| application = new Application(); |
| } |
| |
| return application; |
| } |
| |
| @VisibleForTesting |
| static String getTestApplicationName(String applicationName) { |
| int lastDot = applicationName.lastIndexOf('.'); |
| if (lastDot > -1) { |
| return applicationName.substring(0, lastDot) + ".Test" + applicationName.substring(lastDot + 1); |
| } else { |
| return "Test" + applicationName; |
| } |
| } |
| |
| private void initInstrumentation( |
| ActivityThread activityThread, |
| RoboInstrumentation androidInstrumentation, |
| ApplicationInfo applicationInfo) { |
| final ComponentName component = |
| new ComponentName( |
| applicationInfo.packageName, androidInstrumentation.getClass().getSimpleName()); |
| androidInstrumentation.init( |
| ActivityThread.class, activityThread, RuntimeEnvironment.application, component); |
| androidInstrumentation.onCreate(new Bundle()); |
| } |
| |
| /** |
| * Create a file system safe directory path name for the current test. |
| */ |
| private String createTestDataDirRootPath(Method method) { |
| return method.getClass().getSimpleName() + "_" + method.getName().replaceAll("[^a-zA-Z0-9.-]", "_"); |
| } |
| |
| @Override |
| public Thread getMainThread() { |
| return RuntimeEnvironment.getMainThread(); |
| } |
| |
| @Override |
| public void setMainThread(Thread newMainThread) { |
| RuntimeEnvironment.setMainThread(newMainThread); |
| } |
| |
| @Override |
| public void tearDownApplication() { |
| if (RuntimeEnvironment.application != null) { |
| RuntimeEnvironment.application.onTerminate(); |
| } |
| } |
| |
| @Override |
| public Object getCurrentApplication() { |
| return RuntimeEnvironment.application; |
| } |
| |
| @Override |
| public void setSdkConfig(SdkConfig sdkConfig) { |
| this.sdkConfig = sdkConfig; |
| ReflectionHelpers.setStaticField(RuntimeEnvironment.class, "apiLevel", sdkConfig.getApiLevel()); |
| } |
| |
| private static void setUpPackageStorage(ApplicationInfo applicationInfo) { |
| TempDirectory tempDirectory = RuntimeEnvironment.getTempDirectory(); |
| applicationInfo.sourceDir = |
| tempDirectory |
| .createIfNotExists(applicationInfo.packageName + "-sourceDir") |
| .toAbsolutePath() |
| .toString(); |
| applicationInfo.publicSourceDir = |
| tempDirectory |
| .createIfNotExists(applicationInfo.packageName + "-publicSourceDir") |
| .toAbsolutePath() |
| .toString(); |
| applicationInfo.dataDir = |
| tempDirectory |
| .createIfNotExists(applicationInfo.packageName + "-dataDir") |
| .toAbsolutePath() |
| .toString(); |
| |
| if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N) { |
| applicationInfo.credentialProtectedDataDir = |
| tempDirectory.createIfNotExists("userDataDir").toAbsolutePath().toString(); |
| applicationInfo.deviceProtectedDataDir = |
| tempDirectory.createIfNotExists("deviceDataDir").toAbsolutePath().toString(); |
| } |
| } |
| } |