1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.setupwizardlib.robolectric;
18 
19 import static org.robolectric.RuntimeEnvironment.application;
20 import static org.robolectric.Shadows.shadowOf;
21 
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager.NameNotFoundException;
24 import android.content.res.AssetManager;
25 import android.content.res.Configuration;
26 import androidx.annotation.AnyRes;
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import android.util.DisplayMetrics;
30 import java.util.HashMap;
31 import java.util.Map;
32 import org.robolectric.res.ResName;
33 import org.robolectric.res.ResType;
34 import org.robolectric.res.TypedResource;
35 import org.robolectric.shadows.ShadowPackageManager;
36 
37 /**
38  * Utility class to inject resources for an "external" application in Robolectric tests. This can be
39  * used with {@link org.robolectric.shadows.ShadowPackageManager#resources} to simulate loading
40  * resources from another package.
41  */
42 public final class ExternalResources {
43 
injectExternalResources(String packageName)44   public static Resources injectExternalResources(String packageName) {
45     return injectExternalResources(createPackageInfo(packageName));
46   }
47 
injectExternalResources(PackageInfo packageInfo)48   public static Resources injectExternalResources(PackageInfo packageInfo) {
49     try {
50       application.getPackageManager().getPackageInfo(packageInfo.packageName, 0);
51     } catch (NameNotFoundException e) {
52       // Add the package if it does not exist
53       shadowOf(application.getPackageManager()).addPackage(packageInfo);
54     }
55     Resources resources = Resources.forPackageName(packageInfo.packageName);
56     ShadowPackageManager.resources.put(packageInfo.packageName, resources);
57     return resources;
58   }
59 
60   /**
61    * Constructed resources for testing, representing resources external to the current package under
62    * test.
63    */
64   public static class Resources extends android.content.res.Resources {
65 
66     private final String packageName;
67 
forPackageName(String packageName)68     public static Resources forPackageName(String packageName) {
69       android.content.res.Resources res = application.getResources();
70       return new Resources(
71           packageName, res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
72     }
73 
Resources( String packageName, AssetManager assets, DisplayMetrics metrics, Configuration config)74     private Resources(
75         String packageName, AssetManager assets, DisplayMetrics metrics, Configuration config) {
76       super(assets, metrics, config);
77       this.packageName = packageName;
78     }
79 
80     @Override
getIdentifier(String name, String defType, String defPackage)81     public int getIdentifier(String name, String defType, String defPackage) {
82       Integer resourceId = resourceIds.get(ResName.qualifyResName(name, defPackage, defType));
83       if (resourceId == null) {
84         return 0;
85       }
86       return resourceId;
87     }
88 
89     @Override
getInteger(int id)90     public int getInteger(int id) {
91       return (int) get(id, ResType.INTEGER);
92     }
93 
putInteger(String name, int value)94     public void putInteger(String name, int value) {
95       put(
96           ResName.qualifyResName(name, packageName, "integer"),
97           new TypedResource<>(value, ResType.INTEGER, null));
98     }
99 
100     @Override
getColor(int id)101     public int getColor(int id) {
102       return (int) get(id, ResType.COLOR);
103     }
104 
105     @Override
getColor(int id, @Nullable Theme theme)106     public int getColor(int id, @Nullable Theme theme) {
107       return (int) get(id, ResType.COLOR);
108     }
109 
putColor(String name, int value)110     public void putColor(String name, int value) {
111       put(
112           ResName.qualifyResName(name, packageName, "color"),
113           new TypedResource<>(value, ResType.COLOR, null));
114     }
115 
116     @NonNull
117     @Override
getText(int id)118     public CharSequence getText(int id) {
119       return (CharSequence) get(id, ResType.CHAR_SEQUENCE);
120     }
121 
122     @NonNull
123     @Override
getString(int id)124     public String getString(int id) {
125       return get(id, ResType.CHAR_SEQUENCE).toString();
126     }
127 
putText(String name, CharSequence value)128     public void putText(String name, CharSequence value) {
129       put(
130           ResName.qualifyResName(name, packageName, "string"),
131           new TypedResource<>(value, ResType.CHAR_SEQUENCE, null));
132     }
133 
134     private final Map<Integer, TypedResource<?>> overrideResources = new HashMap<>();
135     private final Map<ResName, Integer> resourceIds = new HashMap<>();
136     private int nextId = 1;
137 
put(ResName resName, TypedResource<T> value)138     private <T> void put(ResName resName, TypedResource<T> value) {
139       int id = nextId++;
140       overrideResources.put(id, value);
141       resourceIds.put(resName, id);
142     }
143 
get(@nyRes int id, ResType type)144     private Object get(@AnyRes int id, ResType type) {
145       TypedResource<?> override = overrideResources.get(id);
146       if (override != null && override.getResType() == type) {
147         return override.getData();
148       }
149       throw new NotFoundException();
150     }
151   }
152 
createPackageInfo(String packageName)153   private static PackageInfo createPackageInfo(String packageName) {
154     PackageInfo packageInfo = new PackageInfo();
155     packageInfo.packageName = packageName;
156     return packageInfo;
157   }
158 
ExternalResources()159   private ExternalResources() {}
160 }
161