1 package com.android.customization.model.theme;
2 
3 import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
4 import static com.android.customization.model.ResourceConstants.ICONS_FOR_PREVIEW;
5 import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
6 import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
7 
8 import android.content.Context;
9 import android.content.om.OverlayInfo;
10 import android.content.om.OverlayManager;
11 import android.content.pm.PackageManager.NameNotFoundException;
12 import android.content.res.Resources;
13 import android.content.res.Resources.NotFoundException;
14 import android.graphics.Typeface;
15 import android.graphics.drawable.Drawable;
16 import android.os.UserHandle;
17 import android.text.TextUtils;
18 import android.util.Log;
19 
20 import androidx.annotation.Dimension;
21 import androidx.annotation.Nullable;
22 
23 import com.android.customization.model.ResourceConstants;
24 import com.android.customization.model.theme.ThemeBundle.Builder;
25 import com.android.wallpaper.R;
26 
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.function.Consumer;
30 
31 class OverlayThemeExtractor {
32 
33     private static final String TAG = "OverlayThemeExtractor";
34 
35     private final Context mContext;
36     private final Map<String, OverlayInfo> mOverlayInfos = new HashMap<>();
37     // List of packages
38     private final String[] mShapePreviewIconPackages;
39 
OverlayThemeExtractor(Context context)40     OverlayThemeExtractor(Context context) {
41         mContext = context;
42         OverlayManager om = context.getSystemService(OverlayManager.class);
43         if (om != null) {
44             Consumer<OverlayInfo> addToMap = overlayInfo -> mOverlayInfos.put(
45                     overlayInfo.getPackageName(), overlayInfo);
46 
47             UserHandle user = UserHandle.of(UserHandle.myUserId());
48             om.getOverlayInfosForTarget(ANDROID_PACKAGE, user).forEach(addToMap);
49             om.getOverlayInfosForTarget(SYSUI_PACKAGE, user).forEach(addToMap);
50             om.getOverlayInfosForTarget(SETTINGS_PACKAGE, user).forEach(addToMap);
51             om.getOverlayInfosForTarget(ResourceConstants.getLauncherPackage(context), user)
52                     .forEach(addToMap);
53             om.getOverlayInfosForTarget(context.getPackageName(), user).forEach(addToMap);
54         }
55         mShapePreviewIconPackages = context.getResources().getStringArray(
56                 R.array.icon_shape_preview_packages);
57     }
58 
isAvailable()59     boolean isAvailable() {
60         return !mOverlayInfos.isEmpty();
61     }
62 
addColorOverlay(Builder builder, String colorOverlayPackage)63     void addColorOverlay(Builder builder, String colorOverlayPackage)
64             throws NameNotFoundException {
65         if (!TextUtils.isEmpty(colorOverlayPackage)) {
66             builder.addOverlayPackage(getOverlayCategory(colorOverlayPackage),
67                     colorOverlayPackage)
68                     .setColorAccentLight(loadColor(ResourceConstants.ACCENT_COLOR_LIGHT_NAME,
69                             colorOverlayPackage))
70                     .setColorAccentDark(loadColor(ResourceConstants.ACCENT_COLOR_DARK_NAME,
71                             colorOverlayPackage));
72         } else {
73             addSystemDefaultColor(builder);
74         }
75     }
76 
addShapeOverlay(Builder builder, String shapeOverlayPackage)77     void addShapeOverlay(Builder builder, String shapeOverlayPackage)
78             throws NameNotFoundException {
79         addShapeOverlay(builder, shapeOverlayPackage, true);
80     }
81 
addShapeOverlay(Builder builder, String shapeOverlayPackage, boolean addPreview)82     void addShapeOverlay(Builder builder, String shapeOverlayPackage, boolean addPreview)
83             throws NameNotFoundException {
84         if (!TextUtils.isEmpty(shapeOverlayPackage)) {
85             builder.addOverlayPackage(getOverlayCategory(shapeOverlayPackage),
86                     shapeOverlayPackage)
87                     .setShapePath(
88                             loadString(ResourceConstants.CONFIG_ICON_MASK, shapeOverlayPackage))
89                     .setBottomSheetCornerRadius(
90                             loadDimen(ResourceConstants.CONFIG_CORNERRADIUS, shapeOverlayPackage));
91         } else {
92             addSystemDefaultShape(builder);
93         }
94         if (addPreview) {
95             addShapePreviewIcons(builder);
96         }
97     }
98 
addShapePreviewIcons(Builder builder)99     private void addShapePreviewIcons(Builder builder) {
100         for (String packageName : mShapePreviewIconPackages) {
101             try {
102                 builder.addShapePreviewIcon(
103                         mContext.getPackageManager().getApplicationIcon(
104                                 packageName));
105             } catch (NameNotFoundException e) {
106                 Log.d(TAG, "Couldn't find app " + packageName
107                         + ", won't use it for icon shape preview");
108             }
109         }
110     }
111 
addNoPreviewIconOverlay(Builder builder, String overlayPackage)112     void addNoPreviewIconOverlay(Builder builder, String overlayPackage) {
113         if (!TextUtils.isEmpty(overlayPackage)) {
114             builder.addOverlayPackage(getOverlayCategory(overlayPackage),
115                     overlayPackage);
116         }
117     }
118 
addSysUiIconOverlay(Builder builder, String iconSysUiOverlayPackage)119     void addSysUiIconOverlay(Builder builder, String iconSysUiOverlayPackage)
120             throws NameNotFoundException {
121         if (!TextUtils.isEmpty(iconSysUiOverlayPackage)) {
122             addIconOverlay(builder, iconSysUiOverlayPackage);
123         }
124     }
125 
addAndroidIconOverlay(Builder builder, String iconAndroidOverlayPackage)126     void addAndroidIconOverlay(Builder builder, String iconAndroidOverlayPackage)
127             throws NameNotFoundException {
128         if (!TextUtils.isEmpty(iconAndroidOverlayPackage)) {
129             addIconOverlay(builder, iconAndroidOverlayPackage, ICONS_FOR_PREVIEW);
130         } else {
131             addSystemDefaultIcons(builder, ANDROID_PACKAGE, ICONS_FOR_PREVIEW);
132         }
133     }
134 
addIconOverlay(Builder builder, String packageName, String... previewIcons)135     void addIconOverlay(Builder builder, String packageName, String... previewIcons)
136             throws NameNotFoundException {
137         builder.addOverlayPackage(getOverlayCategory(packageName), packageName);
138         for (String iconName : previewIcons) {
139             builder.addIcon(loadIconPreviewDrawable(iconName, packageName, false));
140         }
141     }
142 
addFontOverlay(Builder builder, String fontOverlayPackage)143     void addFontOverlay(Builder builder, String fontOverlayPackage)
144             throws NameNotFoundException {
145         if (!TextUtils.isEmpty(fontOverlayPackage)) {
146             builder.addOverlayPackage(getOverlayCategory(fontOverlayPackage),
147                     fontOverlayPackage)
148                     .setBodyFontFamily(loadTypeface(
149                             ResourceConstants.CONFIG_BODY_FONT_FAMILY,
150                             fontOverlayPackage))
151                     .setHeadlineFontFamily(loadTypeface(
152                             ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,
153                             fontOverlayPackage));
154         } else {
155             addSystemDefaultFont(builder);
156         }
157     }
158 
addSystemDefaultIcons(Builder builder, String packageName, String... previewIcons)159     void addSystemDefaultIcons(Builder builder, String packageName,
160             String... previewIcons) {
161         try {
162             for (String iconName : previewIcons) {
163                 builder.addIcon(loadIconPreviewDrawable(iconName, packageName, true));
164             }
165         } catch (NameNotFoundException | NotFoundException e) {
166             Log.w(TAG, "Didn't find android package icons, will skip preview", e);
167         }
168     }
169 
addSystemDefaultShape(Builder builder)170     void addSystemDefaultShape(Builder builder) {
171         Resources system = Resources.getSystem();
172         String iconMaskPath = system.getString(
173                 system.getIdentifier(ResourceConstants.CONFIG_ICON_MASK,
174                         "string", ResourceConstants.ANDROID_PACKAGE));
175         builder.setShapePath(iconMaskPath)
176                 .setBottomSheetCornerRadius(
177                         system.getDimensionPixelOffset(
178                                 system.getIdentifier(ResourceConstants.CONFIG_CORNERRADIUS,
179                                         "dimen", ResourceConstants.ANDROID_PACKAGE)));
180     }
181 
addSystemDefaultColor(Builder builder)182     void addSystemDefaultColor(Builder builder) {
183         Resources system = Resources.getSystem();
184         int colorAccentLight = system.getColor(
185                 system.getIdentifier(ResourceConstants.ACCENT_COLOR_LIGHT_NAME, "color",
186                         ResourceConstants.ANDROID_PACKAGE), null);
187         builder.setColorAccentLight(colorAccentLight);
188 
189         int colorAccentDark = system.getColor(
190                 system.getIdentifier(ResourceConstants.ACCENT_COLOR_DARK_NAME, "color",
191                         ResourceConstants.ANDROID_PACKAGE), null);
192         builder.setColorAccentDark(colorAccentDark);
193     }
194 
addSystemDefaultFont(Builder builder)195     void addSystemDefaultFont(Builder builder) {
196         Resources system = Resources.getSystem();
197         String headlineFontFamily = system.getString(system.getIdentifier(
198                 ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY, "string",
199                 ResourceConstants.ANDROID_PACKAGE));
200         String bodyFontFamily = system.getString(system.getIdentifier(
201                 ResourceConstants.CONFIG_BODY_FONT_FAMILY,
202                 "string", ResourceConstants.ANDROID_PACKAGE));
203         builder.setHeadlineFontFamily(Typeface.create(headlineFontFamily, Typeface.NORMAL))
204                 .setBodyFontFamily(Typeface.create(bodyFontFamily, Typeface.NORMAL));
205     }
206 
loadTypeface(String configName, String fontOverlayPackage)207     Typeface loadTypeface(String configName, String fontOverlayPackage)
208             throws NameNotFoundException, NotFoundException {
209 
210         // TODO(santie): check for font being present in system
211 
212         Resources overlayRes = mContext.getPackageManager()
213                 .getResourcesForApplication(fontOverlayPackage);
214 
215         String fontFamily = overlayRes.getString(overlayRes.getIdentifier(configName,
216                 "string", fontOverlayPackage));
217         return Typeface.create(fontFamily, Typeface.NORMAL);
218     }
219 
loadColor(String colorName, String colorPackage)220     int loadColor(String colorName, String colorPackage)
221             throws NameNotFoundException, NotFoundException {
222 
223         Resources overlayRes = mContext.getPackageManager()
224                 .getResourcesForApplication(colorPackage);
225         return overlayRes.getColor(overlayRes.getIdentifier(colorName, "color", colorPackage),
226                 null);
227     }
228 
loadString(String stringName, String packageName)229     String loadString(String stringName, String packageName)
230             throws NameNotFoundException, NotFoundException {
231 
232         Resources overlayRes =
233                 mContext.getPackageManager().getResourcesForApplication(
234                         packageName);
235         return overlayRes.getString(overlayRes.getIdentifier(stringName, "string", packageName));
236     }
237 
238     @Dimension
loadDimen(String dimenName, String packageName)239     int loadDimen(String dimenName, String packageName)
240             throws NameNotFoundException, NotFoundException {
241 
242         Resources overlayRes =
243                 mContext.getPackageManager().getResourcesForApplication(
244                         packageName);
245         return overlayRes.getDimensionPixelOffset(overlayRes.getIdentifier(
246                 dimenName, "dimen", packageName));
247     }
248 
loadBoolean(String booleanName, String packageName)249     boolean loadBoolean(String booleanName, String packageName)
250             throws NameNotFoundException, NotFoundException {
251 
252         Resources overlayRes =
253                 mContext.getPackageManager().getResourcesForApplication(
254                         packageName);
255         return overlayRes.getBoolean(overlayRes.getIdentifier(
256                 booleanName, "boolean", packageName));
257     }
258 
loadIconPreviewDrawable(String drawableName, String packageName, boolean fromSystem)259     Drawable loadIconPreviewDrawable(String drawableName, String packageName,
260             boolean fromSystem) throws NameNotFoundException, NotFoundException {
261 
262         Resources packageRes =
263                 mContext.getPackageManager().getResourcesForApplication(
264                         packageName);
265         Resources res = fromSystem ? Resources.getSystem() : packageRes;
266         return res.getDrawable(
267                 packageRes.getIdentifier(drawableName, "drawable", packageName), null);
268     }
269 
270     @Nullable
getOverlayCategory(String packageName)271     String getOverlayCategory(String packageName) {
272         OverlayInfo info = mOverlayInfos.get(packageName);
273         return info != null ? info.getCategory() : null;
274     }
275 
getShapePreviewIconPackages()276     String[] getShapePreviewIconPackages() {
277         return mShapePreviewIconPackages;
278     }
279 }