Skip to content

Commit

Permalink
Merge pull request #2330 from jongerrish/resource_loader_cleanup
Browse files Browse the repository at this point in the history
Fix for NPE when calling Context.createPackageContext()
  • Loading branch information
jongerrish committed Mar 8, 2016
2 parents a599678 + c84251a commit eb4f129
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public String getShadowContextImplClassName() {

@Override
public void setSystemResources(ResourceLoader systemResourceLoader) {
ShadowResources.setSystemResources(systemResourceLoader);
ShadowAssetManager.setSystemResources(systemResourceLoader);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public Context getApplicationContext() {
@Implementation
public AssetManager getAssets() {
if (assetManager == null) {
assetManager = ShadowAssetManager.bind(newInstanceOf(AssetManager.class), appManifest, resourceLoader);
assetManager = new AssetManager();
}
return assetManager;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import android.view.Display;

import org.jetbrains.annotations.NotNull;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
Expand All @@ -29,7 +28,6 @@
import org.robolectric.res.ResType;
import org.robolectric.res.ResourceIndex;
import org.robolectric.res.ResourceLoader;
import org.robolectric.res.StringResources;
import org.robolectric.res.Style;
import org.robolectric.res.TypedResource;
import org.robolectric.res.builder.ResourceParser;
Expand Down Expand Up @@ -60,7 +58,6 @@ public class ShadowResources {
private DisplayMetrics displayMetrics;
private Display display;
@RealObject Resources realResources;
private ResourceLoader resourceLoader;

@Resetter
public static void reset() {
Expand Down Expand Up @@ -91,31 +88,17 @@ private static List<LongSparseArray<?>> obtainResettableArrays() {
return resettableArrays;
}

public static void setSystemResources(ResourceLoader systemResourceLoader) {
AssetManager assetManager = new AssetManager();
ShadowAssetManager.bind(assetManager, null, systemResourceLoader);
DisplayMetrics metrics = new DisplayMetrics();
Configuration config = new Configuration();
system = ShadowResources.bind(new Resources(assetManager, metrics, config), systemResourceLoader);
}

static Resources bind(Resources resources, ResourceLoader resourceLoader) {
ShadowResources shadowResources = shadowOf(resources);
if (shadowResources.resourceLoader != null) throw new RuntimeException("ResourceLoader already set!");
shadowResources.resourceLoader = resourceLoader;
return resources;
}

@Implementation
public static Resources getSystem() {
if (system == null) {
AssetManager assetManager = AssetManager.getSystem();
DisplayMetrics metrics = new DisplayMetrics();
Configuration config = new Configuration();
system = new Resources(assetManager, metrics, config);
}
return system;
}

public static Resources createFor(ResourceLoader resourceLoader) {
AssetManager assetManager = ShadowAssetManager.bind(ReflectionHelpers.callConstructor(AssetManager.class), null, resourceLoader);
return bind(new Resources(assetManager, new DisplayMetrics(), new Configuration()), resourceLoader);
}

private TypedArray attrsToTypedArray(AttributeSet set, int[] attrs, int defStyleAttr, int themeResourceId, int defStyleRes) {
/*
* When determining the final value of a particular attribute, there are four inputs that come into play:
Expand Down Expand Up @@ -453,10 +436,7 @@ public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCoo
}

public ResourceLoader getResourceLoader() {
if (resourceLoader == null) {
resourceLoader = shadowOf(RuntimeEnvironment.application).getResourceLoader();
}
return resourceLoader;
return shadowOf(realResources.getAssets()).getResourceLoader();
}

@Implements(Resources.Theme.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import java.util.Map;
import java.util.ArrayList;
import org.jetbrains.annotations.NotNull;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.RealObject;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
Expand Down Expand Up @@ -52,19 +53,18 @@ public final class ShadowAssetManager {
public static final int STYLE_CHANGING_CONFIGURATIONS = 4;
public static final int STYLE_DENSITY = 5;

private String qualifiers = "";
private static ResourceLoader systemResources;

private Map<$ptrClassBoxed, Resources.Theme> themesById = new LinkedHashMap<>();
private Map<$ptrClassBoxed, List<OverlayedStyle>> appliedStyles = new HashMap<>();
private int nextInternalThemeId = 1000;
private AndroidManifest appManifest;
private ResourceLoader resourceLoader;

static AssetManager bind(AssetManager assetManager, AndroidManifest androidManifest, ResourceLoader resourceLoader) {
ShadowAssetManager shadowAssetManager = shadowOf(assetManager);
if (shadowAssetManager.appManifest != null) throw new RuntimeException("ResourceLoader already set!");
shadowAssetManager.appManifest = androidManifest;
shadowAssetManager.resourceLoader = resourceLoader;
return assetManager;
@RealObject
AssetManager realObject;

public static void setSystemResources(ResourceLoader systemResources) {
ShadowAssetManager.systemResources = systemResources;
}

@HiddenApi @Implementation
Expand Down Expand Up @@ -92,7 +92,7 @@ public final class ShadowAssetManager {

@HiddenApi @Implementation
public int getResourceIdentifier(String name, String defType, String defPackage) {
ResourceIndex resourceIndex = resourceLoader.getResourceIndex();
ResourceIndex resourceIndex = getResourceLoader().getResourceIndex();
ResName resName = ResName.qualifyResName(name, defPackage, defType);
Integer resourceId = resourceIndex.getResourceId(resName);
if (resourceId == null) return 0;
Expand All @@ -114,7 +114,7 @@ public final class ShadowAssetManager {

@HiddenApi @Implementation
public CharSequence[] getResourceTextArray(final int id) {
ResName resName = resourceLoader.getResourceIndex().getResName(id);
ResName resName = getResourceLoader().getResourceIndex().getResName(id);
if (resName == null) throw new Resources.NotFoundException("unknown resource " + id);
TypedResource value = getAndResolve(resName, getQualifiers(), true);
if (value == null) return null;
Expand All @@ -129,7 +129,7 @@ public final class ShadowAssetManager {

@HiddenApi @Implementation
public boolean getThemeValue($ptrClass theme, int ident, TypedValue outValue, boolean resolveRefs) {
ResourceIndex resourceIndex = resourceLoader.getResourceIndex();
ResourceIndex resourceIndex = getResourceLoader().getResourceIndex();
ResName resName = resourceIndex.getResName(ident);
Resources.Theme theTheme = getThemeByInternalId(theme);
// Load the style for the theme we represent. E.g. "@style/Theme.Robolectric"
Expand All @@ -140,7 +140,7 @@ public final class ShadowAssetManager {
Style themeStyle = resolveStyle(resourceLoader, null, themeStyleName, getQualifiers());

//// Load the theme attribute for the default style attributes. E.g., attr/buttonStyle
//ResName defStyleName = resourceLoader.getResourceIndex().getResName(ident);
//ResName defStyleName = getResourceLoader().getResourceIndex().getResName(ident);
//
//// Load the style for the default style attribute. E.g. "@style/Widget.Robolectric.Button";
//String defStyleNameValue = themeStyle.getAttrValue(defStyleName);
Expand All @@ -167,24 +167,24 @@ public final class ShadowAssetManager {

@Implementation
public final InputStream open(String fileName) throws IOException {
return appManifest.getAssetsDirectory().join(fileName).getInputStream();
return ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(fileName).getInputStream();
}

@Implementation
public final InputStream open(String fileName, int accessMode) throws IOException {
return appManifest.getAssetsDirectory().join(fileName).getInputStream();
return ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(fileName).getInputStream();
}

@Implementation
public final AssetFileDescriptor openFd(String fileName) throws IOException {
File file = new File(appManifest.getAssetsDirectory().join(fileName).getPath());
File file = new File(ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(fileName).getPath());
ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return new AssetFileDescriptor(parcelFileDescriptor, 0, file.length());
}

@Implementation
public final String[] list(String path) throws IOException {
FsFile file = appManifest.getAssetsDirectory().join(path);
FsFile file = ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(path);
if (file.isDirectory()) {
return file.listFileNames();
}
Expand All @@ -194,7 +194,7 @@ public final class ShadowAssetManager {
@HiddenApi @Implementation
public final InputStream openNonAsset(int cookie, String fileName, int accessMode) throws IOException {
final ResName resName = qualifyFromNonAssetFileName(fileName);
final DrawableNode drawableNode = resourceLoader.getDrawableNode(resName, getQualifiers());
final DrawableNode drawableNode = getResourceLoader().getDrawableNode(resName, getQualifiers());

if (drawableNode == null) {
throw new IOException("Unable to find resource for " + fileName);
Expand All @@ -212,7 +212,7 @@ public final class ShadowAssetManager {
// Must remove "jar:" prefix, or else qualifyFromFilePath fails on Windows
return ResName.qualifyFromFilePath("android", fileName.replaceFirst("jar:", ""));
} else {
return ResName.qualifyFromFilePath(appManifest.getPackageName(), fileName);
return ResName.qualifyFromFilePath(ShadowApplication.getInstance().getAppManifest().getPackageName(), fileName);
}
}

Expand Down Expand Up @@ -255,7 +255,7 @@ public final class ShadowAssetManager {

@HiddenApi @Implementation
public int[] getArrayIntResource(int arrayRes) {
ResName resName = resourceLoader.getResourceIndex().getResName(arrayRes);
ResName resName = getResourceLoader().getResourceIndex().getResName(arrayRes);
if (resName == null) throw new Resources.NotFoundException("unknown resource " + arrayRes);
TypedResource value = getAndResolve(resName, getQualifiers(), true);
if (value == null) return null;
Expand Down Expand Up @@ -356,20 +356,20 @@ public final class ShadowAssetManager {
}

TypedResource getAndResolve(int resId, String qualifiers, boolean resolveRefs) {
ResName resName = resourceLoader.getResourceIndex().getResName(resId);
ResName resName = getResourceLoader().getResourceIndex().getResName(resId);
if (resName == null) throw new Resources.NotFoundException("unknown resource " + resId);
return getAndResolve(resName, qualifiers, resolveRefs);
}

TypedResource getAndResolve(@NotNull ResName resName, String qualifiers, boolean resolveRefs) {
TypedResource value = resourceLoader.getValue(resName, qualifiers);
TypedResource value = getResourceLoader().getValue(resName, qualifiers);
if (resolveRefs) {
value = resolve(value, qualifiers, resName);
}

// todo: make the drawable loader put stuff into the normal spot...
if (value == null && DrawableResourceLoader.isStillHandledHere(resName)) {
DrawableNode drawableNode = resourceLoader.getDrawableNode(resName, qualifiers);
DrawableNode drawableNode = getResourceLoader().getDrawableNode(resName, qualifiers);
return new TypedResource<FsFile>(drawableNode.getFsFile(), ResType.FILE);
}

Expand All @@ -386,9 +386,9 @@ public final class ShadowAssetManager {
}

ResName resolveResName(int resId, String qualifiers) {
ResName resName = resourceLoader.getResourceIndex().getResName(resId);
ResName resName = getResourceLoader().getResourceIndex().getResName(resId);
if (resName == null) return null;
TypedResource value = resourceLoader.getValue(resName, qualifiers);
TypedResource value = getResourceLoader().getValue(resName, qualifiers);
return resolveResource(value, qualifiers, resName).resName;
}

Expand All @@ -400,13 +400,20 @@ public final class ShadowAssetManager {
} else {
String refStr = s.substring(1).replace("+", "");
resName = ResName.qualifyResName(refStr, resName);
value = resourceLoader.getValue(resName, qualifiers);
value = getResourceLoader().getValue(resName, qualifiers);
}
}

return new Resource(value, resName);
}

ResourceLoader getResourceLoader() {
if (resourceLoader == null) {
resourceLoader = ShadowApplication.getInstance().getResourceLoader();
}
return resourceLoader;
}

private boolean isReference(TypedResource value) {
if (value != null) {
Object data = value.getData();
Expand All @@ -419,7 +426,7 @@ public final class ShadowAssetManager {
}

public FsFile getAssetsDirectory() {
return appManifest.getAssetsDirectory();
return ShadowApplication.getInstance().getAppManifest().getAssetsDirectory();
}

public String getQualifiers() {
Expand Down Expand Up @@ -607,4 +614,11 @@ public final class ShadowAssetManager {

}

@Implementation
public static AssetManager getSystem() {
AssetManager assetManager = new AssetManager();
ShadowAssetManager shadowAssetManager = shadowOf(assetManager);
shadowAssetManager.resourceLoader = systemResources;
return assetManager;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,9 @@ public class ShadowContextImpl extends ShadowContext {
public void sendBroadcast(Intent intent) {
realObject.getApplicationContext().sendBroadcast(intent);
}

@Implementation
public ClassLoader getClassLoader() {
return this.getClass().getClassLoader();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,36 +75,6 @@ public void shouldBeAContext() throws Exception {
assertThat(new Activity().getApplication().getApplicationContext()).isSameAs(RuntimeEnvironment.application);
}

@Test
public void shouldBeBindableToAResourceLoader() throws Exception {
ResourceLoader resourceLoader1 = new EmptyResourceLoader() {
@Override public TypedResource getValue(ResName resName, String qualifiers) {
return new TypedResource("title from resourceLoader1", ResType.CHAR_SEQUENCE);
}

@Override public ResourceIndex getResourceIndex() {
return new ImperviousResourceExtractor();
}
};
ResourceLoader resourceLoader2 = new EmptyResourceLoader() {
@Override public TypedResource getValue(ResName resName, String qualifiers) {
return new TypedResource("title from resourceLoader2", ResType.CHAR_SEQUENCE);
}

@Override public ResourceIndex getResourceIndex() {
return new ImperviousResourceExtractor();
}
};

final Application app1 = new Application();
final Application app2 = new Application();
shadowOf(app1).bind(null, resourceLoader1);
shadowOf(app2).bind(null, resourceLoader2);

assertEquals("title from resourceLoader1", new ContextWrapper(app1).getResources().getString(R.string.howdy));
assertEquals("title from resourceLoader2", new ContextWrapper(app2).getResources().getString(R.string.howdy));
}

@Test
public void shouldProvideServices() throws Exception {
checkSystemService(Context.ACTIVITY_SERVICE, android.app.ActivityManager.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
import android.content.Intent;
import android.content.IntentSender;
import android.os.Build;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.RemoteViews;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.R;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.TestApplication;
import org.robolectric.TestRunners;
Expand Down Expand Up @@ -71,5 +75,20 @@ public void startIntentSender_serviceIntent() throws IntentSender.SendIntentExce
assertThat(ShadowApplication.getInstance().getNextStartedService().getComponent().getClassName()).isEqualTo("ServiceIntent");
}

@Test
public void createPackageContext() throws Exception {
Context packageContext = context.createPackageContext(RuntimeEnvironment.application.getPackageName(), 0);

LayoutInflater inflater = (LayoutInflater) RuntimeEnvironment.application.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.cloneInContext(packageContext);

inflater.inflate(R.layout.remote_views, new FrameLayout(RuntimeEnvironment.application), false);
}

@Test
public void createPackageContextRemoteViews() throws Exception {
RemoteViews remoteViews = new RemoteViews(RuntimeEnvironment.application.getPackageName(), R.layout.remote_views);
remoteViews.apply(RuntimeEnvironment.application, new FrameLayout(RuntimeEnvironment.application));
}
}

0 comments on commit eb4f129

Please sign in to comment.