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