diff options
Diffstat (limited to 'sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java')
-rw-r--r-- | sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java | 687 |
1 files changed, 687 insertions, 0 deletions
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java b/sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java new file mode 100644 index 0000000..ef51ced --- /dev/null +++ b/sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java @@ -0,0 +1,687 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.internal.util; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.IntentFilter; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageParser; +import android.content.res.AssetManager; +import android.content.res.ThemeConfig; +import android.database.Cursor; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.FileUtils; +import android.os.SystemProperties; +import android.provider.MediaStore; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.DisplayMetrics; +import android.util.Log; + +import android.view.WindowManager; +import cyanogenmod.providers.CMSettings; +import cyanogenmod.providers.ThemesContract.ThemesColumns; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import static android.content.res.ThemeConfig.SYSTEM_DEFAULT; + +/** + * @hide + */ +public class ThemeUtils { + private static final String TAG = ThemeUtils.class.getSimpleName(); + + // Package name for any app which does not have a specific theme applied + private static final String DEFAULT_PKG = "default"; + + private static final Set<String> SUPPORTED_THEME_COMPONENTS = new ArraySet<>(); + + static { + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_ALARMS); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_BOOT_ANIM); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_FONTS); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_ICONS); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_LAUNCHER); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_LOCKSCREEN); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_NAVIGATION_BAR); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_NOTIFICATIONS); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_OVERLAYS); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_RINGTONES); + SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_STATUS_BAR); + } + + // Constants for theme change broadcast + public static final String ACTION_THEME_CHANGED = "org.cyanogenmod.intent.action.THEME_CHANGED"; + public static final String EXTRA_COMPONENTS = "components"; + public static final String EXTRA_REQUEST_TYPE = "request_type"; + public static final String EXTRA_UPDATE_TIME = "update_time"; + + // path to asset lockscreen and wallpapers directory + public static final String LOCKSCREEN_WALLPAPER_PATH = "lockscreen"; + public static final String WALLPAPER_PATH = "wallpapers"; + + // path to external theme resources, i.e. bootanimation.zip + public static final String SYSTEM_THEME_PATH = "/data/system/theme"; + public static final String SYSTEM_THEME_FONT_PATH = SYSTEM_THEME_PATH + File.separator + "fonts"; + public static final String SYSTEM_THEME_RINGTONE_PATH = SYSTEM_THEME_PATH + + File.separator + "ringtones"; + public static final String SYSTEM_THEME_NOTIFICATION_PATH = SYSTEM_THEME_PATH + + File.separator + "notifications"; + public static final String SYSTEM_THEME_ALARM_PATH = SYSTEM_THEME_PATH + + File.separator + "alarms"; + public static final String SYSTEM_THEME_ICON_CACHE_DIR = SYSTEM_THEME_PATH + + File.separator + "icons"; + // internal path to bootanimation.zip inside theme apk + public static final String THEME_BOOTANIMATION_PATH = "assets/bootanimation/bootanimation.zip"; + + public static final String SYSTEM_MEDIA_PATH = "/system/media/audio"; + public static final String SYSTEM_ALARMS_PATH = SYSTEM_MEDIA_PATH + File.separator + + "alarms"; + public static final String SYSTEM_RINGTONES_PATH = SYSTEM_MEDIA_PATH + File.separator + + "ringtones"; + public static final String SYSTEM_NOTIFICATIONS_PATH = SYSTEM_MEDIA_PATH + File.separator + + "notifications"; + + private static final String MEDIA_CONTENT_URI = "content://media/internal/audio/media"; + + public static final int SYSTEM_TARGET_API = 0; + + /* Path to cached theme resources */ + public static final String RESOURCE_CACHE_DIR = "/data/resource-cache/"; + + /* Path inside a theme APK to the overlay folder */ + public static final String OVERLAY_PATH = "assets/overlays/"; + public static final String ICONS_PATH = "assets/icons/"; + public static final String COMMON_RES_PATH = "assets/overlays/common/"; + + public static final String IDMAP_SUFFIX = "@idmap"; + public static final String COMMON_RES_TARGET = "common"; + + public static final String ICON_HASH_FILENAME = "hash"; + + public static final String FONT_XML = "fonts.xml"; + + public static String getDefaultThemePackageName(Context context) { + final String defaultThemePkg = CMSettings.Secure.getString(context.getContentResolver(), + CMSettings.Secure.DEFAULT_THEME_PACKAGE); + if (!TextUtils.isEmpty(defaultThemePkg)) { + PackageManager pm = context.getPackageManager(); + try { + if (pm.getPackageInfo(defaultThemePkg, 0) != null) { + return defaultThemePkg; + } + } catch (PackageManager.NameNotFoundException e) { + // doesn't exist so system will be default + Log.w(TAG, "Default theme " + defaultThemePkg + " not found", e); + } + } + + return SYSTEM_DEFAULT; + } + + /** + * Returns a mutable list of all theme components + * @return + */ + public static List<String> getAllComponents() { + List<String> components = new ArrayList<>(SUPPORTED_THEME_COMPONENTS.size()); + components.addAll(SUPPORTED_THEME_COMPONENTS); + return components; + } + + /** + * Returns a mutable list of all the theme components supported by a given package + * NOTE: This queries the themes content provider. If there isn't a provider installed + * or if it is too early in the boot process this method will not work. + */ + public static List<String> getSupportedComponents(Context context, String pkgName) { + List<String> supportedComponents = new ArrayList<>(); + + String selection = ThemesColumns.PKG_NAME + "= ?"; + String[] selectionArgs = new String[]{ pkgName }; + Cursor c = context.getContentResolver().query(ThemesColumns.CONTENT_URI, + null, selection, selectionArgs, null); + + if (c != null) { + if (c.moveToFirst()) { + List<String> allComponents = getAllComponents(); + for (String component : allComponents) { + int index = c.getColumnIndex(component); + if (c.getInt(index) == 1) { + supportedComponents.add(component); + } + } + } + c.close(); + } + return supportedComponents; + } + + /** + * Get the components from the default theme. If the default theme is not SYSTEM then any + * components that are not in the default theme will come from SYSTEM to create a complete + * component map. + * @param context + * @return + */ + public static Map<String, String> getDefaultComponents(Context context) { + String defaultThemePkg = getDefaultThemePackageName(context); + List<String> defaultComponents = null; + List<String> systemComponents = getSupportedComponents(context, SYSTEM_DEFAULT); + if (!DEFAULT_PKG.equals(defaultThemePkg)) { + defaultComponents = getSupportedComponents(context, defaultThemePkg); + } + + Map<String, String> componentMap = new HashMap<>(systemComponents.size()); + if (defaultComponents != null) { + for (String component : defaultComponents) { + componentMap.put(component, defaultThemePkg); + } + } + for (String component : systemComponents) { + if (!componentMap.containsKey(component)) { + componentMap.put(component, SYSTEM_DEFAULT); + } + } + + return componentMap; + } + + /** + * Get the path to the icons for the given theme + * @param pkgName + * @return + */ + public static String getIconPackDir(String pkgName) { + return getOverlayResourceCacheDir(pkgName) + File.separator + "icons"; + } + + public static String getIconHashFile(String pkgName) { + return getIconPackDir(pkgName) + File.separator + ICON_HASH_FILENAME; + } + + public static String getIconPackApkPath(String pkgName) { + return getIconPackDir(pkgName) + "/resources.apk"; + } + + public static String getIconPackResPath(String pkgName) { + return getIconPackDir(pkgName) + "/resources.arsc"; + } + + public static String getIdmapPath(String targetPkgName, String overlayPkgName) { + return getTargetCacheDir(targetPkgName, overlayPkgName) + File.separator + "idmap"; + } + + public static String getOverlayPathToTarget(String targetPkgName) { + StringBuilder sb = new StringBuilder(); + sb.append(OVERLAY_PATH); + sb.append(targetPkgName); + sb.append('/'); + return sb.toString(); + } + + public static String getCommonPackageName(String themePackageName) { + if (TextUtils.isEmpty(themePackageName)) return null; + + return COMMON_RES_TARGET; + } + + /** + * Create SYSTEM_THEME_PATH directory if it does not exist + */ + public static void createThemeDirIfNotExists() { + createDirIfNotExists(SYSTEM_THEME_PATH); + } + + /** + * Create SYSTEM_FONT_PATH directory if it does not exist + */ + public static void createFontDirIfNotExists() { + createDirIfNotExists(SYSTEM_THEME_FONT_PATH); + } + + /** + * Create SYSTEM_THEME_RINGTONE_PATH directory if it does not exist + */ + public static void createRingtoneDirIfNotExists() { + createDirIfNotExists(SYSTEM_THEME_RINGTONE_PATH); + } + + /** + * Create SYSTEM_THEME_NOTIFICATION_PATH directory if it does not exist + */ + public static void createNotificationDirIfNotExists() { + createDirIfNotExists(SYSTEM_THEME_NOTIFICATION_PATH); + } + + /** + * Create SYSTEM_THEME_ALARM_PATH directory if it does not exist + */ + public static void createAlarmDirIfNotExists() { + createDirIfNotExists(SYSTEM_THEME_ALARM_PATH); + } + + /** + * Create SYSTEM_THEME_ICON_CACHE_DIR directory if it does not exist + */ + public static void createIconCacheDirIfNotExists() { + createDirIfNotExists(SYSTEM_THEME_ICON_CACHE_DIR); + } + + public static void createCacheDirIfNotExists() throws IOException { + File file = new File(RESOURCE_CACHE_DIR); + if (!file.exists() && !file.mkdir()) { + throw new IOException("Could not create dir: " + file.toString()); + } + FileUtils.setPermissions(file, FileUtils.S_IRWXU + | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1); + } + + public static void createResourcesDirIfNotExists(String targetPkgName, String overlayPkgName) + throws IOException { + createDirIfNotExists(getOverlayResourceCacheDir(overlayPkgName)); + File file = new File(getTargetCacheDir(targetPkgName, overlayPkgName)); + if (!file.exists() && !file.mkdir()) { + throw new IOException("Could not create dir: " + file.toString()); + } + FileUtils.setPermissions(file, FileUtils.S_IRWXU + | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1); + } + + public static void createIconDirIfNotExists(String pkgName) throws IOException { + createDirIfNotExists(getOverlayResourceCacheDir(pkgName)); + File file = new File(getIconPackDir(pkgName)); + if (!file.exists() && !file.mkdir()) { + throw new IOException("Could not create dir: " + file.toString()); + } + FileUtils.setPermissions(file, FileUtils.S_IRWXU + | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1); + } + + public static void clearIconCache() { + FileUtils.deleteContents(new File(SYSTEM_THEME_ICON_CACHE_DIR)); + } + + public static void registerThemeChangeReceiver(final Context context, + final BroadcastReceiver receiver) { + IntentFilter filter = new IntentFilter(ACTION_THEME_CHANGED); + + context.registerReceiver(receiver, filter); + } + + public static String getLockscreenWallpaperPath(AssetManager assetManager) throws IOException { + String[] assets = assetManager.list(LOCKSCREEN_WALLPAPER_PATH); + String asset = getFirstNonEmptyAsset(assets); + if (asset == null) return null; + return LOCKSCREEN_WALLPAPER_PATH + File.separator + asset; + } + + public static String getWallpaperPath(AssetManager assetManager) throws IOException { + String[] assets = assetManager.list(WALLPAPER_PATH); + String asset = getFirstNonEmptyAsset(assets); + if (asset == null) return null; + return WALLPAPER_PATH + File.separator + asset; + } + + public static List<String> getWallpaperPathList(AssetManager assetManager) + throws IOException { + List<String> wallpaperList = new ArrayList<String>(); + String[] assets = assetManager.list(WALLPAPER_PATH); + for (String asset : assets) { + if (!TextUtils.isEmpty(asset)) { + wallpaperList.add(WALLPAPER_PATH + File.separator + asset); + } + } + return wallpaperList; + } + + /** + * Get the root path of the resource cache for the given theme + * @param themePkgName + * @return Root resource cache path for the given theme + */ + public static String getOverlayResourceCacheDir(String themePkgName) { + return RESOURCE_CACHE_DIR + themePkgName; + } + + /** + * Get the path of the resource cache for the given target and theme + * @param targetPkgName + * @param themePkg + * @return Path to the resource cache for this target and theme + */ + public static String getTargetCacheDir(String targetPkgName, PackageInfo themePkg) { + return getTargetCacheDir(targetPkgName, themePkg.packageName); + } + + public static String getTargetCacheDir(String targetPkgName, PackageParser.Package themePkg) { + return getTargetCacheDir(targetPkgName, themePkg.packageName); + } + + public static String getTargetCacheDir(String targetPkgName, String themePkgName) { + return getOverlayResourceCacheDir(themePkgName) + File.separator + targetPkgName; + } + + /** + * Creates a theme'd context using the overlay applied to SystemUI + * @param context Base context + * @return Themed context + */ + public static Context createUiContext(final Context context) { + try { + Context uiContext = context.createPackageContext("com.android.systemui", + Context.CONTEXT_RESTRICTED); + return new ThemedUiContext(uiContext, context.getApplicationContext()); + } catch (PackageManager.NameNotFoundException e) { + } + + return null; + } + + /** + * Scale the boot animation to better fit the device by editing the desc.txt found + * in the bootanimation.zip + * @param context Context to use for getting an instance of the WindowManager + * @param input InputStream of the original bootanimation.zip + * @param dst Path to store the newly created bootanimation.zip + * @throws IOException + */ + public static void copyAndScaleBootAnimation(Context context, InputStream input, String dst) + throws IOException { + final OutputStream os = new FileOutputStream(dst); + final ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os)); + final ZipInputStream bootAni = new ZipInputStream(new BufferedInputStream(input)); + ZipEntry ze; + + zos.setMethod(ZipOutputStream.STORED); + final byte[] bytes = new byte[4096]; + int len; + while ((ze = bootAni.getNextEntry()) != null) { + ZipEntry entry = new ZipEntry(ze.getName()); + entry.setMethod(ZipEntry.STORED); + entry.setCrc(ze.getCrc()); + entry.setSize(ze.getSize()); + entry.setCompressedSize(ze.getSize()); + if (!ze.getName().equals("desc.txt")) { + // just copy this entry straight over into the output zip + zos.putNextEntry(entry); + while ((len = bootAni.read(bytes)) > 0) { + zos.write(bytes, 0, len); + } + } else { + String line; + BufferedReader reader = new BufferedReader(new InputStreamReader(bootAni)); + final String[] info = reader.readLine().split(" "); + + int scaledWidth; + int scaledHeight; + WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics dm = new DisplayMetrics(); + wm.getDefaultDisplay().getRealMetrics(dm); + // just in case the device is in landscape orientation we will + // swap the values since most (if not all) animations are portrait + if (dm.widthPixels > dm.heightPixels) { + scaledWidth = dm.heightPixels; + scaledHeight = dm.widthPixels; + } else { + scaledWidth = dm.widthPixels; + scaledHeight = dm.heightPixels; + } + + int width = Integer.parseInt(info[0]); + int height = Integer.parseInt(info[1]); + + if (width == height) + scaledHeight = scaledWidth; + else { + // adjust scaledHeight to retain original aspect ratio + float scale = (float)scaledWidth / (float)width; + int newHeight = (int)((float)height * scale); + if (newHeight < scaledHeight) + scaledHeight = newHeight; + } + + CRC32 crc32 = new CRC32(); + int size = 0; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + line = String.format("%d %d %s\n", scaledWidth, scaledHeight, info[2]); + buffer.put(line.getBytes()); + size += line.getBytes().length; + crc32.update(line.getBytes()); + while ((line = reader.readLine()) != null) { + line = String.format("%s\n", line); + buffer.put(line.getBytes()); + size += line.getBytes().length; + crc32.update(line.getBytes()); + } + entry.setCrc(crc32.getValue()); + entry.setSize(size); + entry.setCompressedSize(size); + zos.putNextEntry(entry); + zos.write(buffer.array(), 0, size); + } + zos.closeEntry(); + } + zos.close(); + } + + public static boolean isValidAudible(String fileName) { + return (fileName != null && + (fileName.endsWith(".mp3") || fileName.endsWith(".ogg"))); + } + + public static boolean setAudible(Context context, File ringtone, int type, String name) { + final String path = ringtone.getAbsolutePath(); + final String mimeType = name.endsWith(".ogg") ? "audio/ogg" : "audio/mp3"; + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.DATA, path); + values.put(MediaStore.MediaColumns.TITLE, name); + values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); + values.put(MediaStore.MediaColumns.SIZE, ringtone.length()); + values.put(MediaStore.Audio.Media.IS_RINGTONE, type == RingtoneManager.TYPE_RINGTONE); + values.put(MediaStore.Audio.Media.IS_NOTIFICATION, + type == RingtoneManager.TYPE_NOTIFICATION); + values.put(MediaStore.Audio.Media.IS_ALARM, type == RingtoneManager.TYPE_ALARM); + values.put(MediaStore.Audio.Media.IS_MUSIC, false); + + Uri uri = MediaStore.Audio.Media.getContentUriForPath(path); + Uri newUri = null; + Cursor c = context.getContentResolver().query(uri, + new String[] {MediaStore.MediaColumns._ID}, + MediaStore.MediaColumns.DATA + "='" + path + "'", + null, null); + if (c != null && c.getCount() > 0) { + c.moveToFirst(); + long id = c.getLong(0); + c.close(); + newUri = Uri.withAppendedPath(Uri.parse(MEDIA_CONTENT_URI), "" + id); + context.getContentResolver().update(uri, values, + MediaStore.MediaColumns._ID + "=" + id, null); + } + if (newUri == null) + newUri = context.getContentResolver().insert(uri, values); + try { + RingtoneManager.setActualDefaultRingtoneUri(context, type, newUri); + } catch (Exception e) { + return false; + } + return true; + } + + public static boolean setDefaultAudible(Context context, int type) { + final String audiblePath = getDefaultAudiblePath(type); + if (audiblePath != null) { + Uri uri = MediaStore.Audio.Media.getContentUriForPath(audiblePath); + Cursor c = context.getContentResolver().query(uri, + new String[] {MediaStore.MediaColumns._ID}, + MediaStore.MediaColumns.DATA + "='" + audiblePath + "'", + null, null); + if (c != null && c.getCount() > 0) { + c.moveToFirst(); + long id = c.getLong(0); + c.close(); + uri = Uri.withAppendedPath( + Uri.parse(MEDIA_CONTENT_URI), "" + id); + } + if (uri != null) + RingtoneManager.setActualDefaultRingtoneUri(context, type, uri); + } else { + return false; + } + return true; + } + + public static String getDefaultAudiblePath(int type) { + final String name; + final String path; + switch (type) { + case RingtoneManager.TYPE_ALARM: + name = SystemProperties.get("ro.config.alarm_alert", null); + path = name != null ? SYSTEM_ALARMS_PATH + File.separator + name : null; + break; + case RingtoneManager.TYPE_NOTIFICATION: + name = SystemProperties.get("ro.config.notification_sound", null); + path = name != null ? SYSTEM_NOTIFICATIONS_PATH + File.separator + name : null; + break; + case RingtoneManager.TYPE_RINGTONE: + name = SystemProperties.get("ro.config.ringtone", null); + path = name != null ? SYSTEM_RINGTONES_PATH + File.separator + name : null; + break; + default: + path = null; + break; + } + return path; + } + + public static void clearAudibles(Context context, String audiblePath) { + final File audibleDir = new File(audiblePath); + if (audibleDir.exists()) { + String[] files = audibleDir.list(); + final ContentResolver resolver = context.getContentResolver(); + for (String s : files) { + final String filePath = audiblePath + File.separator + s; + Uri uri = MediaStore.Audio.Media.getContentUriForPath(filePath); + resolver.delete(uri, MediaStore.MediaColumns.DATA + "=\"" + + filePath + "\"", null); + (new File(filePath)).delete(); + } + } + } + + public static InputStream getInputStreamFromAsset(Context ctx, String path) throws IOException { + if (ctx == null || path == null) return null; + + InputStream is; + String ASSET_BASE = "file:///android_asset/"; + path = path.substring(ASSET_BASE.length()); + AssetManager assets = ctx.getAssets(); + is = assets.open(path); + return is; + } + + /** + * Convenience method to determine if a theme component is a per app theme and not a standard + * component. + * @param component + * @return + */ + public static boolean isPerAppThemeComponent(String component) { + return !(DEFAULT_PKG.equals(component) + || ThemeConfig.SYSTEMUI_STATUS_BAR_PKG.equals(component) + || ThemeConfig.SYSTEMUI_NAVBAR_PKG.equals(component)); + } + + /** + * Returns the first non-empty asset name. Empty assets can occur if the APK is built + * with folders included as zip entries in the APK. Searching for files inside "folderName" via + * assetManager.list("folderName") can cause these entries to be included as empty strings. + * @param assets + * @return + */ + private static String getFirstNonEmptyAsset(String[] assets) { + if (assets == null) return null; + String filename = null; + for(String asset : assets) { + if (!TextUtils.isEmpty(asset)) { + filename = asset; + break; + } + } + return filename; + } + + private static boolean dirExists(String dirPath) { + final File dir = new File(dirPath); + return dir.exists() && dir.isDirectory(); + } + + private static void createDirIfNotExists(String dirPath) { + if (!dirExists(dirPath)) { + File dir = new File(dirPath); + if (dir.mkdir()) { + FileUtils.setPermissions(dir, FileUtils.S_IRWXU | + FileUtils.S_IRWXG| FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1); + } + } + } + + private static class ThemedUiContext extends ContextWrapper { + private Context mAppContext; + + public ThemedUiContext(Context context, Context appContext) { + super(context); + mAppContext = appContext; + } + + @Override + public Context getApplicationContext() { + return mAppContext; + } + + @Override + public String getPackageName() { + return mAppContext.getPackageName(); + } + } +} |