Home | History | Annotate | Download | only in internal
      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