aboutsummaryrefslogtreecommitdiffstats
path: root/sdk/src/java/org/cyanogenmod
diff options
context:
space:
mode:
Diffstat (limited to 'sdk/src/java/org/cyanogenmod')
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java70
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/statusbar/ExternalQuickSettingsRecord.java53
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/statusbar/IStatusBarCustomTileHolder.aidl25
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl24
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/CmLockPatternUtils.java106
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/ImageUtils.java332
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/QSConstants.java111
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/QSUtils.java312
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/ScreenType.java66
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java687
10 files changed, 1786 insertions, 0 deletions
diff --git a/sdk/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java b/sdk/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java
new file mode 100644
index 0000000..e3303d5
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java
@@ -0,0 +1,70 @@
+/**
+ * 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.logging;
+
+import com.android.internal.logging.MetricsLogger;
+
+/**
+ * Serves as a central location for logging constants that is android release agnostic.
+ */
+public class CMMetricsLogger extends MetricsLogger {
+ private static final int BASE = -Integer.MAX_VALUE;
+ //Since we never want to collide, lets start at the back and move inward
+ public static final int DONT_LOG = BASE + 1;
+
+ public static final int ANONYMOUS_STATS = BASE + 2;
+ public static final int APP_GROUP_CONFIG = BASE + 3;
+ public static final int APP_GROUP_LIST = BASE + 4;
+ public static final int BATTERY_LIGHT_SETTINGS = BASE + 5;
+ public static final int BUTTON_SETTINGS = BASE + 6;
+ public static final int CHOOSE_LOCK_PATTERN_SIZE = BASE + 7;
+ public static final int DISPLAY_ROTATION = BASE + 8;
+ public static final int LIVE_DISPLAY = BASE + 9;
+ public static final int NOTIFICATION_LIGHT_SETTINGS = BASE + 10;
+ public static final int NOTIFICATION_MANAGER_SETTINGS = BASE + 11;
+ public static final int POWER_MENU_ACTIONS = BASE + 12;
+ public static final int PREVIEW_DATA = BASE + 13;
+ public static final int PRIVACY_GUARD_PREFS = BASE + 14;
+ public static final int PRIVACY_SETTINGS = BASE + 15;
+ public static final int PROFILE_GROUP_CONFIG = BASE + 16;
+ public static final int PROFILES_SETTINGS = BASE + 17;
+ public static final int SETUP_ACTIONS_FRAGMENT = BASE + 18;
+ public static final int SETUP_TRIGGERS_FRAGMENT = BASE + 19;
+ public static final int STYLUS_GESTURES = BASE + 20;
+ public static final int TILE_ADB_OVER_NETWORK = BASE + 21;
+ public static final int TILE_AMBIENT_DISPLAY = BASE + 22;
+ public static final int TILE_COMPASS = BASE + 23;
+ public static final int TILE_CUSTOM_QS = BASE + 24;
+ public static final int TILE_CUSTOM_QS_DETAIL = BASE + 25;
+ public static final int TILE_EDIT = BASE + 26;
+ public static final int TILE_LIVE_DISPLAY = BASE + 27;
+ public static final int TILE_LOCKSCREEN_TOGGLE = BASE + 28;
+ public static final int TILE_NFC = BASE + 29;
+ public static final int TILE_PERF_PROFILE = BASE + 30;
+ public static final int TILE_PERF_PROFILE_DETAIL = BASE + 31;
+ public static final int TILE_PROFILES = BASE + 32;
+ public static final int TILE_PROFILES_DETAIL = BASE + 33;
+ public static final int TILE_SCREEN_TIME_OUT = BASE + 34;
+ public static final int TILE_SCREEN_TIME_OUT_DETAIL = BASE + 35;
+ public static final int TILE_SYNC = BASE + 36;
+ public static final int TILE_USB_TETHER = BASE + 37;
+ public static final int TILE_VOLUME = BASE + 38;
+ public static final int TILE_HEADS_UP = BASE + 39;
+ public static final int TILE_BATTERY_SAVER = BASE + 40;
+ public static final int TILE_CAFFEINE = BASE + 41;
+ public static final int WEATHER_SETTINGS = BASE + 42;
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/statusbar/ExternalQuickSettingsRecord.java b/sdk/src/java/org/cyanogenmod/internal/statusbar/ExternalQuickSettingsRecord.java
new file mode 100644
index 0000000..05f8edf
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/statusbar/ExternalQuickSettingsRecord.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2015, 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.statusbar;
+
+import android.os.UserHandle;
+import com.android.internal.annotations.VisibleForTesting;
+
+import cyanogenmod.app.CustomTile;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
+/**
+ * @hide
+ */
+public class ExternalQuickSettingsRecord {
+ public final StatusBarPanelCustomTile sbTile;
+ public boolean isUpdate;
+ public boolean isCanceled;
+
+ @VisibleForTesting
+ public ExternalQuickSettingsRecord(StatusBarPanelCustomTile tile) {
+ sbTile = tile;
+ }
+
+ public CustomTile getCustomTile() {
+ return sbTile.getCustomTile();
+ }
+
+ public UserHandle getUser() {
+ return sbTile.getUser();
+ }
+
+ public int getUserId() {
+ return sbTile.getUserId();
+ }
+
+ public String getKey() {
+ return sbTile.getKey();
+ }
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/statusbar/IStatusBarCustomTileHolder.aidl b/sdk/src/java/org/cyanogenmod/internal/statusbar/IStatusBarCustomTileHolder.aidl
new file mode 100644
index 0000000..90e04de
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/statusbar/IStatusBarCustomTileHolder.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2015, 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.statusbar;
+
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
+/** @hide */
+interface IStatusBarCustomTileHolder {
+ /** Fetch the held StatusBarPanelCustomTile. This method should only be called once per Holder */
+ StatusBarPanelCustomTile get();
+} \ No newline at end of file
diff --git a/sdk/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl b/sdk/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl
new file mode 100644
index 0000000..c69e082
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.themes;
+
+import android.graphics.Bitmap;
+
+/** @hide */
+interface IIconCacheManager {
+ boolean cacheComposedIcon(in Bitmap icon, String path);
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/CmLockPatternUtils.java b/sdk/src/java/org/cyanogenmod/internal/util/CmLockPatternUtils.java
new file mode 100644
index 0000000..75ab0b3
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/CmLockPatternUtils.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import cyanogenmod.platform.Manifest;
+import cyanogenmod.providers.CMSettings;
+
+public class CmLockPatternUtils extends LockPatternUtils {
+
+ /**
+ * Third party keyguard component to be displayed within the keyguard
+ */
+ public static final String THIRD_PARTY_KEYGUARD_COMPONENT = "lockscreen.third_party";
+
+ /**
+ * Action to be broadcasted when the third party keyguard component has been changed
+ */
+ public static final String ACTION_THIRD_PARTY_KEYGUARD_COMPONENT_CHANGED =
+ "org.cyanogenmod.internal.action.THIRD_PARTY_KEYGUARD_COMPONENT_CHANGED";
+
+ private Context mContext;
+
+ public CmLockPatternUtils(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ /**
+ * Sets a third party lock screen.
+ * @param component
+ */
+ public void setThirdPartyKeyguard(ComponentName component)
+ throws PackageManager.NameNotFoundException {
+ if (component != null) {
+ // Check that the package this component belongs to has the third party keyguard perm
+ final PackageManager pm = mContext.getPackageManager();
+ final boolean hasThirdPartyKeyguardPermission = pm.checkPermission(
+ Manifest.permission.THIRD_PARTY_KEYGUARD, component.getPackageName()) ==
+ PackageManager.PERMISSION_GRANTED;
+ if (!hasThirdPartyKeyguardPermission) {
+ throw new SecurityException("Package " + component.getPackageName() + " does not" +
+ "have " + Manifest.permission.THIRD_PARTY_KEYGUARD);
+ }
+ }
+
+ setString(THIRD_PARTY_KEYGUARD_COMPONENT,
+ component != null ? component.flattenToString() : "", getCurrentUser());
+
+ // notify systemui, or whatever other process needs to know, that the third party keyguard
+ // component has changed. What it changed to is up to the receiver to figure out using
+ // the methods provided in this class.
+ mContext.sendOrderedBroadcast(new Intent(ACTION_THIRD_PARTY_KEYGUARD_COMPONENT_CHANGED),
+ null);
+ }
+
+ /**
+ * Get the currently applied 3rd party keyguard component
+ * @return
+ */
+ public ComponentName getThirdPartyKeyguardComponent() {
+ String component = getString(THIRD_PARTY_KEYGUARD_COMPONENT, getCurrentUser());
+ return component != null ? ComponentName.unflattenFromString(component) : null;
+ }
+
+ /**
+ * @return Whether a third party keyguard is set
+ */
+ public boolean isThirdPartyKeyguardEnabled() {
+ String component = getString(THIRD_PARTY_KEYGUARD_COMPONENT, getCurrentUser());
+ return !TextUtils.isEmpty(component);
+ }
+
+ private int getCurrentUser() {
+ return UserHandle.USER_CURRENT;
+ }
+
+ public boolean shouldPassToSecurityView(int userId) {
+ return getBoolean(CMSettings.Secure.LOCK_PASS_TO_SECURITY_VIEW, false, userId);
+ }
+
+ public void setPassToSecurityView(boolean enabled, int userId) {
+ setBoolean(CMSettings.Secure.LOCK_PASS_TO_SECURITY_VIEW, enabled, userId);
+ }
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/ImageUtils.java b/sdk/src/java/org/cyanogenmod/internal/util/ImageUtils.java
new file mode 100644
index 0000000..c67c23c
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/ImageUtils.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2013-2014 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.app.WallpaperManager;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.URLUtil;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import cyanogenmod.providers.ThemesContract.PreviewColumns;
+import cyanogenmod.providers.ThemesContract.ThemesColumns;
+
+import libcore.io.IoUtils;
+
+public class ImageUtils {
+ private static final String TAG = ImageUtils.class.getSimpleName();
+
+ private static final String ASSET_URI_PREFIX = "file:///android_asset/";
+ private static final int DEFAULT_IMG_QUALITY = 100;
+
+ /**
+ * Gets the Width and Height of the image
+ *
+ * @param inputStream The input stream of the image
+ *
+ * @return A point structure that holds the Width and Height (x and y)/*"
+ */
+ public static Point getImageDimension(InputStream inputStream) {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("'inputStream' cannot be null!");
+ }
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(inputStream, null, options);
+ Point point = new Point(options.outWidth,options.outHeight);
+ return point;
+ }
+
+ /**
+ * Crops the input image and returns a new InputStream of the cropped area
+ *
+ * @param inputStream The input stream of the image
+ * @param imageWidth Width of the input image
+ * @param imageHeight Height of the input image
+ * @param inputStream Desired Width
+ * @param inputStream Desired Width
+ *
+ * @return a new InputStream of the cropped area/*"
+ */
+ public static InputStream cropImage(InputStream inputStream, int imageWidth, int imageHeight,
+ int outWidth, int outHeight) throws IllegalArgumentException {
+ if (inputStream == null){
+ throw new IllegalArgumentException("inputStream cannot be null");
+ }
+
+ if (imageWidth <= 0 || imageHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format("imageWidth and imageHeight must be > 0: imageWidth=%d" +
+ " imageHeight=%d", imageWidth, imageHeight));
+ }
+
+ if (outWidth <= 0 || outHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format("outWidth and outHeight must be > 0: outWidth=%d" +
+ " outHeight=%d", imageWidth, outHeight));
+ }
+
+ int scaleDownSampleSize = Math.min(imageWidth / outWidth, imageHeight / outHeight);
+ if (scaleDownSampleSize > 0) {
+ imageWidth /= scaleDownSampleSize;
+ imageHeight /= scaleDownSampleSize;
+ } else {
+ float ratio = (float) outWidth / outHeight;
+ if (imageWidth < imageHeight * ratio) {
+ outWidth = imageWidth;
+ outHeight = (int) (outWidth / ratio);
+ } else {
+ outHeight = imageHeight;
+ outWidth = (int) (outHeight * ratio);
+ }
+ }
+ int left = (imageWidth - outWidth) / 2;
+ int top = (imageHeight - outHeight) / 2;
+ InputStream compressed = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
+ if (bitmap == null) {
+ return null;
+ }
+ Bitmap cropped = Bitmap.createBitmap(bitmap, left, top, outWidth, outHeight);
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+ if (cropped.compress(Bitmap.CompressFormat.PNG, DEFAULT_IMG_QUALITY, tmpOut)) {
+ byte[] outByteArray = tmpOut.toByteArray();
+ compressed = new ByteArrayInputStream(outByteArray);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ }
+ return compressed;
+ }
+
+ /**
+ * Crops the lock screen image and returns a new InputStream of the cropped area
+ *
+ * @param pkgName Name of the theme package
+ * @param context The context
+ *
+ * @return a new InputStream of the cropped image/*"
+ */
+ public static InputStream getCroppedKeyguardStream(String pkgName, Context context)
+ throws IllegalArgumentException {
+ if (TextUtils.isEmpty(pkgName)) {
+ throw new IllegalArgumentException("'pkgName' cannot be null or empty!");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("'context' cannot be null!");
+ }
+
+ InputStream cropped = null;
+ InputStream stream = null;
+ try {
+ stream = getOriginalKeyguardStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ Point point = getImageDimension(stream);
+ IoUtils.closeQuietly(stream);
+ if (point == null || point.x == 0 || point.y == 0) {
+ return null;
+ }
+ WallpaperManager wm = WallpaperManager.getInstance(context);
+ int outWidth = wm.getDesiredMinimumWidth();
+ int outHeight = wm.getDesiredMinimumHeight();
+ stream = getOriginalKeyguardStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ cropped = cropImage(stream, point.x, point.y, outWidth, outHeight);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ return cropped;
+ }
+
+ /**
+ * Crops the wallpaper image and returns a new InputStream of the cropped area
+ *
+ * @param pkgName Name of the theme package
+ * @param context The context
+ *
+ * @return a new InputStream of the cropped image/*"
+ */
+ public static InputStream getCroppedWallpaperStream(String pkgName, long wallpaperId,
+ Context context) {
+ if (TextUtils.isEmpty(pkgName)) {
+ throw new IllegalArgumentException("'pkgName' cannot be null or empty!");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("'context' cannot be null!");
+ }
+
+ InputStream cropped = null;
+ InputStream stream = null;
+ try {
+ stream = getOriginalWallpaperStream(pkgName, wallpaperId, context);
+ if (stream == null) {
+ return null;
+ }
+ Point point = getImageDimension(stream);
+ IoUtils.closeQuietly(stream);
+ if (point == null || point.x == 0 || point.y == 0) {
+ return null;
+ }
+ WallpaperManager wm = WallpaperManager.getInstance(context);
+ int outWidth = wm.getDesiredMinimumWidth();
+ int outHeight = wm.getDesiredMinimumHeight();
+ stream = getOriginalWallpaperStream(pkgName, wallpaperId, context);
+ if (stream == null) {
+ return null;
+ }
+ cropped = cropImage(stream, point.x, point.y, outWidth, outHeight);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ return cropped;
+ }
+
+ private static InputStream getOriginalKeyguardStream(String pkgName, Context context) {
+ if (TextUtils.isEmpty(pkgName) || context == null) {
+ return null;
+ }
+
+ InputStream inputStream = null;
+ try {
+ //Get input WP stream from the theme
+ Context themeCtx = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ String wpPath = ThemeUtils.getLockscreenWallpaperPath(assetManager);
+ if (wpPath == null) {
+ Log.w(TAG, "Not setting lockscreen wp because wallpaper file was not found.");
+ } else {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeCtx,
+ ASSET_URI_PREFIX + wpPath);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "There was an error setting lockscreen wp for pkg " + pkgName, e);
+ }
+ return inputStream;
+ }
+
+ private static InputStream getOriginalWallpaperStream(String pkgName, long componentId,
+ Context context) {
+ String wpPath;
+ if (TextUtils.isEmpty(pkgName) || context == null) {
+ return null;
+ }
+
+ InputStream inputStream = null;
+ String selection = ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = {pkgName};
+ Cursor c = context.getContentResolver().query(ThemesColumns.CONTENT_URI,
+ null, selection,
+ selectionArgs, null);
+ if (c == null || c.getCount() < 1) {
+ if (c != null) c.close();
+ return null;
+ } else {
+ c.moveToFirst();
+ }
+
+ try {
+ Context themeContext = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ boolean isLegacyTheme = c.getInt(
+ c.getColumnIndex(ThemesColumns.IS_LEGACY_THEME)) == 1;
+ String wallpaper = c.getString(
+ c.getColumnIndex(ThemesColumns.WALLPAPER_URI));
+ if (wallpaper != null) {
+ if (URLUtil.isAssetUrl(wallpaper)) {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeContext, wallpaper);
+ } else {
+ inputStream = context.getContentResolver().openInputStream(
+ Uri.parse(wallpaper));
+ }
+ } else {
+ // try and get the wallpaper directly from the apk if the URI was null
+ Context themeCtx = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ wpPath = queryWpPathFromComponentId(context, pkgName, componentId);
+ if (wpPath == null) wpPath = ThemeUtils.getWallpaperPath(assetManager);
+ if (wpPath == null) {
+ Log.e(TAG, "Not setting wp because wallpaper file was not found.");
+ } else {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeCtx,
+ ASSET_URI_PREFIX + wpPath);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "getWallpaperStream: " + e);
+ } finally {
+ c.close();
+ }
+
+ return inputStream;
+ }
+
+ private static String queryWpPathFromComponentId(Context context, String pkgName,
+ long componentId) {
+ String wpPath = null;
+ String[] projection = new String[] { PreviewColumns.COL_VALUE };
+ String selection = ThemesColumns.PKG_NAME + "=? AND " +
+ PreviewColumns.COMPONENT_ID + "=? AND " +
+ PreviewColumns.COL_KEY + "=?";
+ String[] selectionArgs = new String[] {
+ pkgName,
+ Long.toString(componentId),
+ PreviewColumns.WALLPAPER_FULL
+ };
+
+ Cursor c = context.getContentResolver()
+ .query(PreviewColumns.COMPONENTS_URI,
+ projection, selection, selectionArgs, null);
+ if (c != null) {
+ try {
+ if (c.moveToFirst()) {
+ int valIdx = c.getColumnIndex(PreviewColumns.COL_VALUE);
+ wpPath = c.getString(valIdx);
+ }
+ } catch(Exception e) {
+ Log.e(TAG, "Could not get wallpaper path", e);
+ } finally {
+ c.close();
+ }
+ }
+ return wpPath;
+ }
+}
+
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/QSConstants.java b/sdk/src/java/org/cyanogenmod/internal/util/QSConstants.java
new file mode 100644
index 0000000..d47e683
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/QSConstants.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod Open Source 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 java.util.ArrayList;
+
+public class QSConstants {
+ private QSConstants() {}
+
+ public static final String TILE_WIFI = "wifi";
+ public static final String TILE_BLUETOOTH = "bt";
+ public static final String TILE_INVERSION = "inversion";
+ public static final String TILE_CELLULAR = "cell";
+ public static final String TILE_AIRPLANE = "airplane";
+ public static final String TILE_ROTATION = "rotation";
+ public static final String TILE_FLASHLIGHT = "flashlight";
+ public static final String TILE_LOCATION = "location";
+ public static final String TILE_CAST = "cast";
+ public static final String TILE_HOTSPOT = "hotspot";
+ public static final String TILE_NOTIFICATIONS = "notifications";
+ public static final String TILE_DATA = "data";
+ public static final String TILE_ROAMING = "roaming";
+ public static final String TILE_DDS = "dds";
+ public static final String TILE_APN = "apn";
+ public static final String TILE_PROFILES = "profiles";
+ public static final String TILE_PERFORMANCE = "performance";
+ public static final String TILE_ADB_NETWORK = "adb_network";
+ public static final String TILE_NFC = "nfc";
+ public static final String TILE_COMPASS = "compass";
+ public static final String TILE_LOCKSCREEN = "lockscreen";
+ public static final String TILE_LTE = "lte";
+ public static final String TILE_VISUALIZER = "visualizer";
+ public static final String TILE_VOLUME = "volume_panel";
+ public static final String TILE_SCREEN_TIMEOUT = "screen_timeout";
+ public static final String TILE_LIVE_DISPLAY = "live_display";
+ public static final String TILE_USB_TETHER = "usb_tether";
+ public static final String TILE_HEADS_UP = "heads_up";
+ public static final String TILE_AMBIENT_DISPLAY = "ambient_display";
+ public static final String TILE_SYNC = "sync";
+ public static final String TILE_BATTERY_SAVER = "battery_saver";
+ public static final String TILE_CAFFEINE = "caffeine";
+ public static final String TILE_EDIT = "edit";
+ public static final String TILE_DND = "dnd";
+
+ public static final String DYNAMIC_TILE_NEXT_ALARM = "next_alarm";
+ public static final String DYNAMIC_TILE_IME_SELECTOR = "ime_selector";
+ public static final String DYNAMIC_TILE_SU = "su";
+ public static final String DYNAMIC_TILE_ADB = "adb";
+
+ protected static final ArrayList<String> STATIC_TILES_AVAILABLE = new ArrayList<String>();
+ protected static final ArrayList<String> DYNAMIC_TILES_AVAILBLE = new ArrayList<String>();
+ protected static final ArrayList<String> TILES_AVAILABLE = new ArrayList<String>();
+
+ static {
+ STATIC_TILES_AVAILABLE.add(TILE_WIFI);
+ STATIC_TILES_AVAILABLE.add(TILE_BLUETOOTH);
+ STATIC_TILES_AVAILABLE.add(TILE_CELLULAR);
+ STATIC_TILES_AVAILABLE.add(TILE_AIRPLANE);
+ STATIC_TILES_AVAILABLE.add(TILE_ROTATION);
+ STATIC_TILES_AVAILABLE.add(TILE_FLASHLIGHT);
+ STATIC_TILES_AVAILABLE.add(TILE_LOCATION);
+ STATIC_TILES_AVAILABLE.add(TILE_EDIT);
+ STATIC_TILES_AVAILABLE.add(TILE_CAST);
+ STATIC_TILES_AVAILABLE.add(TILE_HOTSPOT);
+ STATIC_TILES_AVAILABLE.add(TILE_INVERSION);
+ STATIC_TILES_AVAILABLE.add(TILE_DND);
+// STATIC_TILES_AVAILABLE.add(TILE_NOTIFICATIONS);
+// STATIC_TILES_AVAILABLE.add(TILE_DATA);
+// STATIC_TILES_AVAILABLE.add(TILE_ROAMING);
+// STATIC_TILES_AVAILABLE.add(TILE_DDS);
+// STATIC_TILES_AVAILABLE.add(TILE_APN);
+ STATIC_TILES_AVAILABLE.add(TILE_PROFILES);
+ STATIC_TILES_AVAILABLE.add(TILE_PERFORMANCE);
+ STATIC_TILES_AVAILABLE.add(TILE_ADB_NETWORK);
+ STATIC_TILES_AVAILABLE.add(TILE_NFC);
+ STATIC_TILES_AVAILABLE.add(TILE_COMPASS);
+ STATIC_TILES_AVAILABLE.add(TILE_LOCKSCREEN);
+// STATIC_TILES_AVAILABLE.add(TILE_LTE);
+// STATIC_TILES_AVAILABLE.add(TILE_VISUALIZER);
+ STATIC_TILES_AVAILABLE.add(TILE_VOLUME);
+ STATIC_TILES_AVAILABLE.add(TILE_SCREEN_TIMEOUT);
+ STATIC_TILES_AVAILABLE.add(TILE_LIVE_DISPLAY);
+ STATIC_TILES_AVAILABLE.add(TILE_USB_TETHER);
+ STATIC_TILES_AVAILABLE.add(TILE_HEADS_UP);
+ STATIC_TILES_AVAILABLE.add(TILE_AMBIENT_DISPLAY);
+ STATIC_TILES_AVAILABLE.add(TILE_SYNC);
+ STATIC_TILES_AVAILABLE.add(TILE_BATTERY_SAVER);
+ STATIC_TILES_AVAILABLE.add(TILE_CAFFEINE);
+
+ TILES_AVAILABLE.addAll(STATIC_TILES_AVAILABLE);
+
+ DYNAMIC_TILES_AVAILBLE.add(DYNAMIC_TILE_ADB);
+ DYNAMIC_TILES_AVAILBLE.add(DYNAMIC_TILE_IME_SELECTOR);
+ DYNAMIC_TILES_AVAILBLE.add(DYNAMIC_TILE_NEXT_ALARM);
+ DYNAMIC_TILES_AVAILBLE.add(DYNAMIC_TILE_SU);
+ }
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/QSUtils.java b/sdk/src/java/org/cyanogenmod/internal/util/QSUtils.java
new file mode 100644
index 0000000..6aedaf8
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/QSUtils.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod Open Source 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.bluetooth.BluetoothAdapter;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.internal.telephony.PhoneConstants;
+import cyanogenmod.power.PerformanceManager;
+import cyanogenmod.providers.CMSettings;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class QSUtils {
+ private static boolean sAvailableTilesFiltered;
+ private static final SparseArray<Context> sSystemUiContextForUser = new SparseArray<>();
+
+ public interface OnQSChanged {
+ void onQSChanged();
+ }
+
+ private QSUtils() {}
+
+ public static boolean isStaticQsTile(String tileSpec) {
+ return QSConstants.STATIC_TILES_AVAILABLE.contains(tileSpec);
+ }
+
+ public static boolean isDynamicQsTile(String tileSpec) {
+ return QSConstants.DYNAMIC_TILES_AVAILBLE.contains(tileSpec);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static List<String> getAvailableTiles(Context context) {
+ filterTiles(context);
+ return (List<String>) QSConstants.TILES_AVAILABLE.clone();
+ }
+
+ public static List<String> getDefaultTiles(Context context) {
+ final List<String> tiles = new ArrayList<>();
+ final String defaults = context.getString(
+ org.cyanogenmod.platform.internal.R.string.config_defaultQuickSettingsTiles);
+ if (!TextUtils.isEmpty(defaults)) {
+ final String[] array = TextUtils.split(defaults, Pattern.quote(","));
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ continue;
+ }
+ tiles.add(item);
+ }
+ filterTiles(context, tiles);
+ }
+ return tiles;
+ }
+
+ public static String getDefaultTilesAsString(Context context) {
+ List<String> list = getDefaultTiles(context);
+ return TextUtils.join(",", list);
+ }
+
+ private static void filterTiles(Context context, List<String> tiles) {
+ boolean deviceSupportsMobile = deviceSupportsMobileData(context);
+
+ // Tiles that need conditional filtering
+ Iterator<String> iterator = tiles.iterator();
+ while (iterator.hasNext()) {
+ String tileKey = iterator.next();
+ boolean removeTile = false;
+ switch (tileKey) {
+ case QSConstants.TILE_CELLULAR:
+ case QSConstants.TILE_HOTSPOT:
+ case QSConstants.TILE_DATA:
+ case QSConstants.TILE_ROAMING:
+ case QSConstants.TILE_APN:
+ removeTile = !deviceSupportsMobile;
+ break;
+ case QSConstants.TILE_DDS:
+ removeTile = !deviceSupportsDdsSupported(context);
+ break;
+ case QSConstants.TILE_FLASHLIGHT:
+ removeTile = !deviceSupportsFlashLight(context);
+ break;
+ case QSConstants.TILE_BLUETOOTH:
+ removeTile = !deviceSupportsBluetooth();
+ break;
+ case QSConstants.TILE_NFC:
+ removeTile = !deviceSupportsNfc(context);
+ break;
+ case QSConstants.TILE_COMPASS:
+ removeTile = !deviceSupportsCompass(context);
+ break;
+ case QSConstants.TILE_AMBIENT_DISPLAY:
+ removeTile = !deviceSupportsDoze(context);
+ break;
+ case QSConstants.TILE_PERFORMANCE:
+ removeTile = !deviceSupportsPowerProfiles(context);
+ break;
+ case QSConstants.TILE_BATTERY_SAVER:
+ removeTile = deviceSupportsPowerProfiles(context);
+ break;
+ }
+ if (removeTile) {
+ iterator.remove();
+ }
+ }
+ }
+
+ private static void filterTiles(Context context) {
+ if (!sAvailableTilesFiltered) {
+ filterTiles(context, QSConstants.TILES_AVAILABLE);
+ sAvailableTilesFiltered = true;
+ }
+ }
+
+ public static int getDynamicQSTileResIconId(Context context, int userId, String tileSpec) {
+ Context ctx = getQSTileContext(context, userId);
+ int index = translateDynamicQsTileSpecToIndex(ctx, tileSpec);
+ if (index == -1) {
+ return 0;
+ }
+
+ try {
+ String resourceName = ctx.getResources().getStringArray(
+ ctx.getResources().getIdentifier("dynamic_qs_tiles_icons_resources_ids",
+ "array", ctx.getPackageName()))[index];
+ return ctx.getResources().getIdentifier(
+ resourceName, "drawable", ctx.getPackageName());
+ } catch (Exception ex) {
+ // Ignore
+ }
+ return 0;
+ }
+
+ public static String getDynamicQSTileLabel(Context context, int userId, String tileSpec) {
+ Context ctx = getQSTileContext(context, userId);
+ int index = translateDynamicQsTileSpecToIndex(ctx, tileSpec);
+ if (index == -1) {
+ return null;
+ }
+
+ try {
+ return ctx.getResources().getStringArray(
+ ctx.getResources().getIdentifier("dynamic_qs_tiles_labels",
+ "array", ctx.getPackageName()))[index];
+ } catch (Exception ex) {
+ // Ignore
+ }
+ return null;
+ }
+
+ private static int translateDynamicQsTileSpecToIndex(Context context, String tileSpec) {
+ String[] keys = context.getResources().getStringArray(context.getResources().getIdentifier(
+ "dynamic_qs_tiles_values", "array", context.getPackageName()));
+ int count = keys.length;
+ for (int i = 0; i < count; i++) {
+ if (keys[i].equals(tileSpec)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static Context getQSTileContext(Context context, int userId) {
+ Context ctx = sSystemUiContextForUser.get(userId);
+ if (ctx == null) {
+ try {
+ ctx = context.createPackageContextAsUser(
+ "com.android.systemui", 0, new UserHandle(userId));
+ sSystemUiContextForUser.put(userId, ctx);
+ } catch (NameNotFoundException ex) {
+ // We can safely ignore this
+ }
+ }
+ return ctx;
+ }
+
+ public static boolean isQSTileEnabledForUser(
+ Context context, String tileSpec, int userId) {
+ final ContentResolver resolver = context.getContentResolver();
+ String order = CMSettings.Secure.getStringForUser(resolver,
+ CMSettings.Secure.QS_TILES, userId);
+ return !TextUtils.isEmpty(order) && Arrays.asList(order.split(",")).contains(tileSpec);
+ }
+
+ public static ContentObserver registerObserverForQSChanges(Context ctx, final OnQSChanged cb) {
+ ContentObserver observer = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ cb.onQSChanged();
+ }
+ };
+
+ ctx.getContentResolver().registerContentObserver(
+ CMSettings.Secure.getUriFor(CMSettings.Secure.QS_TILES),
+ false, observer, UserHandle.USER_ALL);
+ return observer;
+ }
+
+ public static void unregisterObserverForQSChanges(Context ctx, ContentObserver observer) {
+ ctx.getContentResolver().unregisterContentObserver(observer);
+ }
+
+
+ public static boolean deviceSupportsLte(Context ctx) {
+ final TelephonyManager tm = (TelephonyManager)
+ ctx.getSystemService(Context.TELEPHONY_SERVICE);
+ return (tm.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE)
+ || tm.getLteOnGsmMode() != 0;
+ }
+
+ public static boolean deviceSupportsDdsSupported(Context context) {
+ TelephonyManager tm = (TelephonyManager)
+ context.getSystemService(Context.TELEPHONY_SERVICE);
+ return tm.isMultiSimEnabled()
+ && tm.getMultiSimConfiguration() == TelephonyManager.MultiSimVariants.DSDA;
+ }
+
+ public static boolean deviceSupportsMobileData(Context ctx) {
+ ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ return cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ }
+
+ public static boolean deviceSupportsBluetooth() {
+ return BluetoothAdapter.getDefaultAdapter() != null;
+ }
+
+ public static boolean deviceSupportsNfc(Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_NFC);
+ }
+
+ public static boolean deviceSupportsFlashLight(Context context) {
+ CameraManager cameraManager = (CameraManager) context.getSystemService(
+ Context.CAMERA_SERVICE);
+ try {
+ String[] ids = cameraManager.getCameraIdList();
+ for (String id : ids) {
+ CameraCharacteristics c = cameraManager.getCameraCharacteristics(id);
+ Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
+ if (flashAvailable != null
+ && flashAvailable
+ && lensFacing != null
+ && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+ return true;
+ }
+ }
+ } catch (CameraAccessException | AssertionError e) {
+ // Ignore
+ }
+ return false;
+ }
+
+ public static boolean deviceSupportsCompass(Context context) {
+ SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ return sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null
+ && sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null;
+ }
+
+ public static boolean deviceSupportsDoze(Context context) {
+ String name = context.getResources().getString(
+ com.android.internal.R.string.config_dozeComponent);
+ return !TextUtils.isEmpty(name);
+ }
+
+
+ public static boolean deviceSupportsPowerProfiles(Context context) {
+ PerformanceManager pm = PerformanceManager.getInstance(context);
+ return pm.getNumberOfProfiles() > 0;
+ }
+
+ private static boolean supportsRootAccess() {
+ return Build.IS_DEBUGGABLE || "eng".equals(Build.TYPE);
+ }
+} \ No newline at end of file
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/ScreenType.java b/sdk/src/java/org/cyanogenmod/internal/util/ScreenType.java
new file mode 100644
index 0000000..23bd9ad
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/ScreenType.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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.Context;
+import android.util.DisplayMetrics;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+public class ScreenType {
+ // Device type reference
+ private static int sDeviceType = -1;
+
+ // Device types
+ private static final int DEVICE_PHONE = 0;
+ private static final int DEVICE_HYBRID = 1;
+ private static final int DEVICE_TABLET = 2;
+
+ private static int getScreenType(Context context) {
+ if (sDeviceType == -1) {
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayInfo outDisplayInfo = new DisplayInfo();
+ wm.getDefaultDisplay().getDisplayInfo(outDisplayInfo);
+ int shortSize = Math.min(outDisplayInfo.logicalHeight, outDisplayInfo.logicalWidth);
+ int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT
+ / outDisplayInfo.logicalDensityDpi;
+ if (shortSizeDp < 600) {
+ // 0-599dp: "phone" UI with a separate status & navigation bar
+ sDeviceType = DEVICE_PHONE;
+ } else if (shortSizeDp < 720) {
+ // 600-719dp: "phone" UI with modifications for larger screens
+ sDeviceType = DEVICE_HYBRID;
+ } else {
+ // 720dp: "tablet" UI with a single combined status & navigation bar
+ sDeviceType = DEVICE_TABLET;
+ }
+ }
+ return sDeviceType;
+ }
+
+ public static boolean isPhone(Context context) {
+ return getScreenType(context) == DEVICE_PHONE;
+ }
+
+ public static boolean isHybrid(Context context) {
+ return getScreenType(context) == DEVICE_HYBRID;
+ }
+
+ public static boolean isTablet(Context context) {
+ return getScreenType(context) == DEVICE_TABLET;
+ }
+}
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();
+ }
+ }
+}