diff options
Diffstat (limited to 'sdk/src/java/org/cyanogenmod')
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(); + } + } +} |