• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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