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