aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authord34d <clark@cyngn.com>2016-02-23 09:58:53 -0800
committerd34d <clark@cyngn.com>2016-03-04 13:59:33 -0800
commitb3ea2859fd920ea68afc3ae7010b665e2dd515ea (patch)
tree973bded90ebc12bb1d4a0472c14f9adafdcfca30 /src
parent567b43017af6f51d67ee05397df665bf136cb177 (diff)
downloadvendor_cmsdk-b3ea2859fd920ea68afc3ae7010b665e2dd515ea.zip
vendor_cmsdk-b3ea2859fd920ea68afc3ae7010b665e2dd515ea.tar.gz
vendor_cmsdk-b3ea2859fd920ea68afc3ae7010b665e2dd515ea.tar.bz2
Themes: Refactor themes to CMSDK [2/6]
First attempt at moving as much as possible out of F/B and into cmsdk Change-Id: I9e53d1c32e01e88fc3918663dabe0001df922bc2 TICKET: CYNGNOS-2126
Diffstat (limited to 'src')
-rw-r--r--src/java/cyanogenmod/app/CMContextConstants.java14
-rw-r--r--src/java/cyanogenmod/content/Intent.java46
-rw-r--r--src/java/cyanogenmod/providers/ThemesContract.java717
-rw-r--r--src/java/cyanogenmod/themes/IThemeChangeListener.aidl23
-rw-r--r--src/java/cyanogenmod/themes/IThemeProcessingListener.aidl22
-rw-r--r--src/java/cyanogenmod/themes/IThemeService.aidl44
-rw-r--r--src/java/cyanogenmod/themes/ThemeChangeRequest.aidl19
-rw-r--r--src/java/cyanogenmod/themes/ThemeChangeRequest.java329
-rw-r--r--src/java/cyanogenmod/themes/ThemeManager.java383
-rw-r--r--src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl24
-rw-r--r--src/java/org/cyanogenmod/internal/util/ImageUtils.java332
-rw-r--r--src/java/org/cyanogenmod/internal/util/ThemeUtils.java687
12 files changed, 2640 insertions, 0 deletions
diff --git a/src/java/cyanogenmod/app/CMContextConstants.java b/src/java/cyanogenmod/app/CMContextConstants.java
index 6c5e39b..a1da29c 100644
--- a/src/java/cyanogenmod/app/CMContextConstants.java
+++ b/src/java/cyanogenmod/app/CMContextConstants.java
@@ -97,4 +97,18 @@ public final class CMContextConstants {
* @hide
*/
public static final String CM_PERFORMANCE_SERVICE = "cmperformance";
+
+ /**
+ * Controls changing and applying themes
+ *
+ * @hide
+ */
+ public static final String CM_THEME_SERVICE = "cmthemes";
+
+ /**
+ * Manages composed icons
+ *
+ * @hide
+ */
+ public static final String CM_ICON_CACHE_SERVICE = "cmiconcache";
}
diff --git a/src/java/cyanogenmod/content/Intent.java b/src/java/cyanogenmod/content/Intent.java
index 5a1f612..8b7c106 100644
--- a/src/java/cyanogenmod/content/Intent.java
+++ b/src/java/cyanogenmod/content/Intent.java
@@ -88,4 +88,50 @@ public class Intent {
public static final String ACTION_INITIALIZE_CM_HARDWARE =
"cyanogenmod.intent.action.INITIALIZE_CM_HARDWARE";
+ /**
+ * Broadcast Action: Indicate that an unrecoverable error happened during app launch.
+ * Could indicate that curently applied theme is malicious.
+ * @hide
+ */
+ public static final String ACTION_APP_FAILURE = "cyanogenmod.intent.action.APP_FAILURE";
+
+ /**
+ * Used to indicate that a theme package has been installed or un-installed.
+ */
+ public static final String CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE =
+ "cyanogenmod.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE";
+
+ /**
+ * Action sent from the provider when a theme has been fully installed. Fully installed
+ * means that the apk was installed by PackageManager and the theme resources were
+ * processed and cached by {@link org.cyanogenmod.platform.internal.ThemeManagerService}
+ * Requires the {@link cyanogenmod.platform.Manifest.permission#READ_THEMES} permission to
+ * receive this broadcast.
+ */
+ public static final String ACTION_THEME_INSTALLED =
+ "cyanogenmod.intent.action.THEME_INSTALLED";
+
+ /**
+ * Action sent from the provider when a theme has been updated.
+ * Requires the {@link cyanogenmod.platform.Manifest.permission#READ_THEMES} permission to
+ * receive this broadcast.
+ */
+ public static final String ACTION_THEME_UPDATED =
+ "cyanogenmod.intent.action.THEME_UPDATED";
+
+ /**
+ * Action sent from the provider when a theme has been removed.
+ * Requires the {@link cyanogenmod.platform.Manifest.permission#READ_THEMES} permission to
+ * receive this broadcast.
+ */
+ public static final String ACTION_THEME_REMOVED =
+ "cyanogenmod.intent.action.THEME_REMOVED";
+
+ /**
+ * Uri scheme used to broadcast the theme's package name when broadcasting
+ * {@link Intent#ACTION_THEME_INSTALLED} or
+ * {@link Intent#ACTION_THEME_REMOVED}
+ */
+ public static final String URI_SCHEME_PACKAGE = "package";
+
}
diff --git a/src/java/cyanogenmod/providers/ThemesContract.java b/src/java/cyanogenmod/providers/ThemesContract.java
new file mode 100644
index 0000000..4cdfeb9
--- /dev/null
+++ b/src/java/cyanogenmod/providers/ThemesContract.java
@@ -0,0 +1,717 @@
+/*
+ * 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 cyanogenmod.providers;
+
+import android.net.Uri;
+
+/**
+ * <p>
+ * The contract between the themes provider and applications. Contains
+ * definitions for the supported URIs and columns.
+ * </p>
+ */
+public class ThemesContract {
+ /** The authority for the themes provider */
+ public static final String AUTHORITY = "com.cyanogenmod.themes";
+ /** A content:// style uri to the authority for the themes provider */
+ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ public static class ThemesColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "themes");
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The user visible title.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TITLE = "title";
+
+ /**
+ * Unique text to identify the apk pkg. ie "com.foo.bar"
+ * <P>Type: TEXT</P>
+ */
+ public static final String PKG_NAME = "pkg_name";
+
+ /**
+ * A 32 bit RRGGBB color representative of the themes color scheme
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PRIMARY_COLOR = "primary_color";
+
+ /**
+ * A 2nd 32 bit RRGGBB color representative of the themes color scheme
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SECONDARY_COLOR = "secondary_color";
+
+ /**
+ * Name of the author of the theme
+ * <P>Type: TEXT</P>
+ */
+ public static final String AUTHOR = "author";
+
+ /**
+ * The time that this row was created on its originating client (msecs
+ * since the epoch).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DATE_CREATED = "created";
+
+ /**
+ * URI to an image that shows the homescreen with the theme applied
+ * since the epoch).
+ * <P>Type: TEXT</P>
+ */
+ public static final String HOMESCREEN_URI = "homescreen_uri";
+
+ /**
+ * URI to an image that shows the lockscreen with theme applied
+ * <P>Type: TEXT</P>
+ */
+ public static final String LOCKSCREEN_URI = "lockscreen_uri";
+
+ /**
+ * URI to an image that shows the style (aka skin) with theme applied
+ * <P>Type: TEXT</P>
+ */
+ public static final String STYLE_URI = "style_uri";
+
+ /**
+ * TODO: Figure structure for actual animation instead of static
+ * URI to an image of the boot_anim.
+ * <P>Type: TEXT</P>
+ */
+ public static final String BOOT_ANIM_URI = "bootanim_uri";
+
+ /**
+ * URI to an image of the status bar for this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String STATUSBAR_URI = "status_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String FONT_URI = "font_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ICON_URI = "icon_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String OVERLAYS_URI = "overlays_uri";
+
+ /**
+ * 1 if theme modifies the launcher/homescreen else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LAUNCHER = "mods_homescreen";
+
+ /**
+ * 1 if theme modifies the lockscreen else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LOCKSCREEN = "mods_lockscreen";
+
+ /**
+ * 1 if theme modifies icons else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_ICONS = "mods_icons";
+
+ /**
+ * 1 if theme modifies fonts
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_FONTS = "mods_fonts";
+
+ /**
+ * 1 if theme modifies boot animation
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_BOOT_ANIM = "mods_bootanim";
+
+ /**
+ * 1 if theme modifies notifications
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_NOTIFICATIONS = "mods_notifications";
+
+ /**
+ * 1 if theme modifies alarm sounds
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_ALARMS = "mods_alarms";
+
+ /**
+ * 1 if theme modifies ringtones
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_RINGTONES = "mods_ringtones";
+
+ /**
+ * 1 if theme has overlays
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_OVERLAYS = "mods_overlays";
+
+ /**
+ * 1 if theme has an overlay for SystemUI/StatusBar
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_STATUS_BAR = "mods_status_bar";
+
+ /**
+ * 1 if theme has an overlay for SystemUI/NavBar
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_NAVIGATION_BAR = "mods_navigation_bar";
+
+ /**
+ * 1 if theme has a live lock screen
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LIVE_LOCK_SCREEN = "mods_live_lock_screen";
+
+ /**
+ * URI to the theme's wallpaper. We should support multiple wallpaper
+ * but for now we will just have 1.
+ * <P>Type: TEXT</P>
+ */
+ public static final String WALLPAPER_URI = "wallpaper_uri";
+
+ /**
+ * 1 if this row should actually be presented as a theme to the user.
+ * For example if a "theme" only modifies one component (ex icons) then
+ * we do not present it to the user under the themes table.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String PRESENT_AS_THEME = "present_as_theme";
+
+ /**
+ * 1 if this theme is a legacy theme.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_LEGACY_THEME = "is_legacy_theme";
+
+ /**
+ * 1 if this theme is the system default theme.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_DEFAULT_THEME = "is_default_theme";
+
+ /**
+ * 1 if this theme is a legacy iconpack. A legacy icon pack is an APK that was written
+ * for Trebuchet or a 3rd party launcher.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_LEGACY_ICONPACK = "is_legacy_iconpack";
+
+ /**
+ * install/update time in millisecs. When the row is inserted this column
+ * is populated by the PackageInfo. It is used for syncing to PM
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String LAST_UPDATE_TIME = "updateTime";
+
+ /**
+ * install time in millisecs. When the row is inserted this column
+ * is populated by the PackageInfo.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String INSTALL_TIME = "install_time";
+
+ /**
+ * The target API this theme supports
+ * is populated by the PackageInfo.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String TARGET_API = "target_api";
+
+ /**
+ * The install state of the theme.
+ * Can be one of the following:
+ * {@link InstallState#UNKNOWN}
+ * {@link InstallState#INSTALLING}
+ * {@link InstallState#UPDATING}
+ * {@link InstallState#INSTALLED}
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String INSTALL_STATE = "install_state";
+
+ public static class InstallState {
+ public static final int UNKNOWN = 0;
+ public static final int INSTALLING = 1;
+ public static final int UPDATING = 2;
+ public static final int INSTALLED = 3;
+ }
+ }
+
+ /**
+ * Key-value table which assigns a component (ex wallpaper) to a theme's package
+ */
+ public static class MixnMatchColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "mixnmatch");
+
+ /**
+ * The unique key for a row. See the KEY_* constants
+ * for valid examples
+ * <P>Type: TEXT</P>
+ */
+ public static final String COL_KEY = "key";
+
+ /**
+ * The package name that corresponds to a given component.
+ * <P>Type: String</P>
+ */
+ public static final String COL_VALUE = "value";
+
+ /**
+ * The package name that corresponds to where this component was applied from previously
+ * <P>Type: String</P>
+ */
+ public static final String COL_PREV_VALUE = "previous_value";
+
+ /**
+ * Time when this entry was last updated
+ * <P>Type: INTEGER</P>
+ */
+ public static final String COL_UPDATE_TIME = "update_time";
+
+ /*
+ * The unique ID for the component within a theme.
+ * Always 0 unless multiples of a component exist.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String COL_COMPONENT_ID = "component_id";
+
+ /**
+ * Valid keys
+ */
+ public static final String KEY_HOMESCREEN = "mixnmatch_homescreen";
+ public static final String KEY_LOCKSCREEN = "mixnmatch_lockscreen";
+ public static final String KEY_ICONS = "mixnmatch_icons";
+ public static final String KEY_STATUS_BAR = "mixnmatch_status_bar";
+ public static final String KEY_BOOT_ANIM = "mixnmatch_boot_anim";
+ public static final String KEY_FONT = "mixnmatch_font";
+ public static final String KEY_ALARM = "mixnmatch_alarm";
+ public static final String KEY_NOTIFICATIONS = "mixnmatch_notifications";
+ public static final String KEY_RINGTONE = "mixnmatch_ringtone";
+ public static final String KEY_OVERLAYS = "mixnmatch_overlays";
+ public static final String KEY_NAVIGATION_BAR = "mixnmatch_navigation_bar";
+ public static final String KEY_LIVE_LOCK_SCREEN = "mixnmatch_live_lock_screen";
+
+ public static final String[] ROWS = { KEY_HOMESCREEN,
+ KEY_LOCKSCREEN,
+ KEY_ICONS,
+ KEY_STATUS_BAR,
+ KEY_BOOT_ANIM,
+ KEY_FONT,
+ KEY_NOTIFICATIONS,
+ KEY_RINGTONE,
+ KEY_ALARM,
+ KEY_OVERLAYS,
+ KEY_NAVIGATION_BAR,
+ KEY_LIVE_LOCK_SCREEN
+ };
+
+ /**
+ * For a given key value in the MixNMatch table, return the column
+ * associated with it in the Themes Table. This is useful for URI based
+ * elements like wallpaper where the caller wishes to determine the
+ * wallpaper URI.
+ */
+ public static String componentToImageColName(String component) {
+ if (component.equals(MixnMatchColumns.KEY_HOMESCREEN)) {
+ return ThemesColumns.HOMESCREEN_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_LOCKSCREEN)) {
+ return ThemesColumns.LOCKSCREEN_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_BOOT_ANIM)) {
+ return ThemesColumns.BOOT_ANIM_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_FONT)) {
+ return ThemesColumns.FONT_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_ICONS)) {
+ return ThemesColumns.ICON_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ return ThemesColumns.STATUSBAR_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) {
+ throw new IllegalArgumentException("Notifications mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_RINGTONE)) {
+ throw new IllegalArgumentException("Ringtone mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_OVERLAYS)) {
+ return ThemesColumns.OVERLAYS_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ throw new IllegalArgumentException(
+ "Status bar mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) {
+ throw new IllegalArgumentException(
+ "Navigation bar mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_LIVE_LOCK_SCREEN)) {
+ throw new IllegalArgumentException(
+ "Live lock screen mixnmatch component does not have a related column");
+ }
+ return null;
+ }
+
+ /**
+ * A component in the themes table (IE "mods_wallpaper") has an
+ * equivalent key in mixnmatch table
+ */
+ public static String componentToMixNMatchKey(String component) {
+ if (component.equals(ThemesColumns.MODIFIES_LAUNCHER)) {
+ return MixnMatchColumns.KEY_HOMESCREEN;
+ } else if (component.equals(ThemesColumns.MODIFIES_ICONS)) {
+ return MixnMatchColumns.KEY_ICONS;
+ } else if (component.equals(ThemesColumns.MODIFIES_LOCKSCREEN)) {
+ return MixnMatchColumns.KEY_LOCKSCREEN;
+ } else if (component.equals(ThemesColumns.MODIFIES_FONTS)) {
+ return MixnMatchColumns.KEY_FONT;
+ } else if (component.equals(ThemesColumns.MODIFIES_BOOT_ANIM)) {
+ return MixnMatchColumns.KEY_BOOT_ANIM;
+ } else if (component.equals(ThemesColumns.MODIFIES_ALARMS)) {
+ return MixnMatchColumns.KEY_ALARM;
+ } else if (component.equals(ThemesColumns.MODIFIES_NOTIFICATIONS)) {
+ return MixnMatchColumns.KEY_NOTIFICATIONS;
+ } else if (component.equals(ThemesColumns.MODIFIES_RINGTONES)) {
+ return MixnMatchColumns.KEY_RINGTONE;
+ } else if (component.equals(ThemesColumns.MODIFIES_OVERLAYS)) {
+ return MixnMatchColumns.KEY_OVERLAYS;
+ } else if (component.equals(ThemesColumns.MODIFIES_STATUS_BAR)) {
+ return MixnMatchColumns.KEY_STATUS_BAR;
+ } else if (component.equals(ThemesColumns.MODIFIES_NAVIGATION_BAR)) {
+ return MixnMatchColumns.KEY_NAVIGATION_BAR;
+ } else if (component.equals(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) {
+ return MixnMatchColumns.KEY_LIVE_LOCK_SCREEN;
+ }
+ return null;
+ }
+
+ /**
+ * A mixnmatch key in has an
+ * equivalent value in the themes table
+ */
+ public static String mixNMatchKeyToComponent(String mixnmatchKey) {
+ if (mixnmatchKey.equals(MixnMatchColumns.KEY_HOMESCREEN)) {
+ return ThemesColumns.MODIFIES_LAUNCHER;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ICONS)) {
+ return ThemesColumns.MODIFIES_ICONS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_LOCKSCREEN)) {
+ return ThemesColumns.MODIFIES_LOCKSCREEN;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_FONT)) {
+ return ThemesColumns.MODIFIES_FONTS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_BOOT_ANIM)) {
+ return ThemesColumns.MODIFIES_BOOT_ANIM;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ALARM)) {
+ return ThemesColumns.MODIFIES_ALARMS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) {
+ return ThemesColumns.MODIFIES_NOTIFICATIONS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_RINGTONE)) {
+ return ThemesColumns.MODIFIES_RINGTONES;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_OVERLAYS)) {
+ return ThemesColumns.MODIFIES_OVERLAYS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ return ThemesColumns.MODIFIES_STATUS_BAR;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) {
+ return ThemesColumns.MODIFIES_NAVIGATION_BAR;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_LIVE_LOCK_SCREEN)) {
+ return ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Table containing cached preview files for a given theme
+ */
+ public static class PreviewColumns {
+ /**
+ * Uri for retrieving the previews table.
+ * Querying the themes provider using this URI will return a cursor with a key and value
+ * columns, and a row for each component.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "previews");
+
+ /**
+ * Uri for retrieving the previews for the currently applied components.
+ * Querying the themes provider using this URI will return a cursor with a single row
+ * containing all the previews for the components that are currently applied.
+ */
+ public static final Uri APPLIED_URI = Uri.withAppendedPath(AUTHORITY_URI,
+ "applied_previews");
+
+ /**
+ * Uri for retrieving the default previews for the theme.
+ * Querying the themes provider using this URI will return a cursor with a single row
+ * containing all the previews for the default components of the current theme.
+ */
+ public static final Uri COMPONENTS_URI = Uri.withAppendedPath(AUTHORITY_URI,
+ "components_previews");
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The unique ID for the theme these previews belong to.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String THEME_ID = "theme_id";
+
+ /**
+ * The unique ID for the component within a theme.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String COMPONENT_ID = "component_id";
+
+ /**
+ * The unique key for a row. See the Valid key constants section below
+ * for valid examples
+ * <P>Type: TEXT</P>
+ */
+ public static final String COL_KEY = "key";
+
+ /**
+ * The package name that corresponds to a given component.
+ * <P>Type: String</P>
+ */
+ public static final String COL_VALUE = "value";
+
+ /**
+ * Valid keys
+ */
+
+ /**
+ * Cached image of the themed status bar background.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BACKGROUND = "statusbar_background";
+
+ /**
+ * Cached image of the themed bluetooth status icon.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BLUETOOTH_ICON = "statusbar_bluetooth_icon";
+
+ /**
+ * Cached image of the themed wifi status icon.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_WIFI_ICON = "statusbar_wifi_icon";
+
+ /**
+ * Cached image of the themed cellular signal status icon.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_SIGNAL_ICON = "statusbar_signal_icon";
+
+ /**
+ * Cached image of the themed battery using portrait style.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BATTERY_PORTRAIT = "statusbar_battery_portrait";
+
+ /**
+ * Cached image of the themed battery using landscape style.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BATTERY_LANDSCAPE = "statusbar_battery_landscape";
+
+ /**
+ * Cached image of the themed battery using circle style.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BATTERY_CIRCLE = "statusbar_battery_circle";
+
+ /**
+ * The themed color used for clock text in the status bar.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String STATUSBAR_CLOCK_TEXT_COLOR = "statusbar_clock_text_color";
+
+ /**
+ * The themed margin value between the wifi and rssi signal icons.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String STATUSBAR_WIFI_COMBO_MARGIN_END = "wifi_combo_margin_end";
+
+ /**
+ * Cached image of the themed navigation bar background.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String NAVBAR_BACKGROUND = "navbar_background";
+
+ /**
+ * Cached image of the themed back button.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String NAVBAR_BACK_BUTTON = "navbar_back_button";
+
+ /**
+ * Cached image of the themed home button.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String NAVBAR_HOME_BUTTON = "navbar_home_button";
+
+ /**
+ * Cached image of the themed recents button.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String NAVBAR_RECENT_BUTTON = "navbar_recent_button";
+
+ /**
+ * Cached image of the 1/3 icons
+ * <P>Type: String (file path)</P>
+ */
+ public static final String ICON_PREVIEW_1 = "icon_preview_1";
+
+ /**
+ * Cached image of the 2/3 icons
+ * <P>Type: String (file path)</P>
+ */
+ public static final String ICON_PREVIEW_2 = "icon_preview_2";
+
+ /**
+ * Cached image of the 3/3 icons
+ * <P>Type: String (file path)</P>
+ */
+ public static final String ICON_PREVIEW_3 = "icon_preview_3";
+
+ /**
+ * Full path to the theme's wallpaper asset.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String WALLPAPER_FULL = "wallpaper_full";
+
+ /**
+ * Cached preview of the theme's wallpaper which is larger than the thumbnail
+ * but smaller than the full sized wallpaper.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String WALLPAPER_PREVIEW = "wallpaper_preview";
+
+ /**
+ * Cached thumbnail of the theme's wallpaper
+ * <P>Type: String (file path)</P>
+ */
+ public static final String WALLPAPER_THUMBNAIL = "wallpaper_thumbnail";
+
+ /**
+ * Cached preview of the theme's lockscreen wallpaper which is larger than the thumbnail
+ * but smaller than the full sized lockscreen wallpaper.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String LOCK_WALLPAPER_PREVIEW = "lock_wallpaper_preview";
+
+ /**
+ * Cached thumbnail of the theme's lockscreen wallpaper
+ * <P>Type: String (file path)</P>
+ */
+ public static final String LOCK_WALLPAPER_THUMBNAIL = "lock_wallpaper_thumbnail";
+
+ /**
+ * Cached preview of UI controls representing the theme's style
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STYLE_PREVIEW = "style_preview";
+
+ /**
+ * Cached thumbnail preview of UI controls representing the theme's style
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STYLE_THUMBNAIL = "style_thumbnail";
+
+ /**
+ * Cached thumbnail of the theme's boot animation
+ * <P>Type: String (file path)</P>
+ */
+ public static final String BOOTANIMATION_THUMBNAIL = "bootanimation_thumbnail";
+
+ /**
+ * Cached preview of live lock screen
+ * <P>Type: String (file path)</P>
+ */
+ public static final String LIVE_LOCK_SCREEN_PREVIEW = "live_lock_screen_preview";
+
+ /**
+ * Cached thumbnail preview of live lock screen
+ * <P>Type: String (file path)</P>
+ */
+ public static final String LIVE_LOCK_SCREEN_THUMBNAIL = "live_lock_screen_thumbnail";
+
+ public static final String[] VALID_KEYS = {
+ STATUSBAR_BACKGROUND,
+ STATUSBAR_BLUETOOTH_ICON,
+ STATUSBAR_WIFI_ICON,
+ STATUSBAR_SIGNAL_ICON,
+ STATUSBAR_BATTERY_PORTRAIT,
+ STATUSBAR_BATTERY_LANDSCAPE,
+ STATUSBAR_BATTERY_CIRCLE,
+ STATUSBAR_CLOCK_TEXT_COLOR,
+ STATUSBAR_WIFI_COMBO_MARGIN_END,
+ NAVBAR_BACKGROUND,
+ NAVBAR_BACK_BUTTON,
+ NAVBAR_HOME_BUTTON,
+ NAVBAR_RECENT_BUTTON,
+ ICON_PREVIEW_1,
+ ICON_PREVIEW_2,
+ ICON_PREVIEW_3,
+ WALLPAPER_FULL,
+ WALLPAPER_PREVIEW,
+ WALLPAPER_THUMBNAIL,
+ LOCK_WALLPAPER_PREVIEW,
+ LOCK_WALLPAPER_THUMBNAIL,
+ STYLE_PREVIEW,
+ STYLE_THUMBNAIL,
+ BOOTANIMATION_THUMBNAIL,
+ LIVE_LOCK_SCREEN_PREVIEW,
+ LIVE_LOCK_SCREEN_THUMBNAIL,
+ };
+ }
+}
diff --git a/src/java/cyanogenmod/themes/IThemeChangeListener.aidl b/src/java/cyanogenmod/themes/IThemeChangeListener.aidl
new file mode 100644
index 0000000..0700eb6
--- /dev/null
+++ b/src/java/cyanogenmod/themes/IThemeChangeListener.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014-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 cyanogenmod.themes;
+
+/** {@hide} */
+oneway interface IThemeChangeListener {
+ void onProgress(int progress);
+ void onFinish(boolean isSuccess);
+}
diff --git a/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl b/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl
new file mode 100644
index 0000000..648e1a9
--- /dev/null
+++ b/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014-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 cyanogenmod.themes;
+
+/** {@hide} */
+oneway interface IThemeProcessingListener {
+ void onFinishedProcessing(String pkgName);
+}
diff --git a/src/java/cyanogenmod/themes/IThemeService.aidl b/src/java/cyanogenmod/themes/IThemeService.aidl
new file mode 100644
index 0000000..fa186e9
--- /dev/null
+++ b/src/java/cyanogenmod/themes/IThemeService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014-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 cyanogenmod.themes;
+
+import cyanogenmod.themes.IThemeChangeListener;
+import cyanogenmod.themes.IThemeProcessingListener;
+import cyanogenmod.themes.ThemeChangeRequest;
+
+import java.util.Map;
+
+/** {@hide} */
+interface IThemeService {
+ oneway void requestThemeChangeUpdates(in IThemeChangeListener listener);
+ oneway void removeUpdates(in IThemeChangeListener listener);
+
+ oneway void requestThemeChange(in ThemeChangeRequest request, boolean removePerAppThemes);
+ oneway void applyDefaultTheme();
+ boolean isThemeApplying();
+ int getProgress();
+
+ boolean processThemeResources(String themePkgName);
+ boolean isThemeBeingProcessed(String themePkgName);
+ oneway void registerThemeProcessingListener(in IThemeProcessingListener listener);
+ oneway void unregisterThemeProcessingListener(in IThemeProcessingListener listener);
+
+ oneway void rebuildResourceCache();
+
+ long getLastThemeChangeTime();
+ int getLastThemeChangeRequestType();
+}
diff --git a/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl b/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl
new file mode 100644
index 0000000..e1d9e4f
--- /dev/null
+++ b/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015-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 cyanogenmod.themes;
+
+parcelable ThemeChangeRequest;
diff --git a/src/java/cyanogenmod/themes/ThemeChangeRequest.java b/src/java/cyanogenmod/themes/ThemeChangeRequest.java
new file mode 100644
index 0000000..5eb497e
--- /dev/null
+++ b/src/java/cyanogenmod/themes/ThemeChangeRequest.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2015-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 cyanogenmod.themes;
+
+import android.content.pm.ThemeUtils;
+import android.content.res.ThemeConfig;
+import android.os.Parcel;
+import android.os.Parcelable;
+import cyanogenmod.os.Build;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static cyanogenmod.providers.ThemesContract.ThemesColumns.*;
+
+public final class ThemeChangeRequest implements Parcelable {
+ public static final int DEFAULT_WALLPAPER_ID = -1;
+
+ private final Map<String, String> mThemeComponents = new HashMap<>();
+ private final Map<String, String> mPerAppOverlays = new HashMap<>();
+ private RequestType mRequestType;
+ private long mWallpaperId = -1;
+
+ public String getOverlayThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_OVERLAYS);
+ }
+
+ public String getStatusBarThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_STATUS_BAR);
+ }
+
+ public String getNavBarThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_NAVIGATION_BAR);
+ }
+
+ public String getFontThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_FONTS);
+ }
+
+ public String getIconsThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_ICONS);
+ }
+
+ public String getBootanimationThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_BOOT_ANIM);
+ }
+
+ public String getWallpaperThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_LAUNCHER);
+ }
+
+ public String getLockWallpaperThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_LOCKSCREEN);
+ }
+
+ public String getAlarmThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_ALARMS);
+ }
+
+ public String getNotificationThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_NOTIFICATIONS);
+ }
+
+ public String getRingtoneThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_RINGTONES);
+ }
+
+ public String getLiveLockScreenThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_LIVE_LOCK_SCREEN);
+ }
+
+ public final Map<String, String> getThemeComponentsMap() {
+ return Collections.unmodifiableMap(mThemeComponents);
+ }
+
+ public long getWallpaperId() {
+ return mWallpaperId;
+ }
+
+ /**
+ * Get the mapping for per app themes
+ * @return A mapping of apps and the theme to apply for each one. or null if none set.
+ */
+ public final Map<String, String> getPerAppOverlays() {
+ return Collections.unmodifiableMap(mPerAppOverlays);
+ }
+
+ public int getNumChangesRequested() {
+ return mThemeComponents.size() + mPerAppOverlays.size();
+ }
+
+ public RequestType getReqeustType() {
+ return mRequestType;
+ }
+
+ private String getThemePackageNameForComponent(String componentName) {
+ return mThemeComponents.get(componentName);
+ }
+
+ private ThemeChangeRequest(Map<String, String> components, Map<String, String> perAppThemes,
+ RequestType requestType, long wallpaperId) {
+ if (components != null) {
+ mThemeComponents.putAll(components);
+ }
+ if (perAppThemes != null) {
+ mPerAppOverlays.putAll(perAppThemes);
+ }
+ mRequestType = requestType;
+ mWallpaperId = wallpaperId;
+ }
+
+ private ThemeChangeRequest(Parcel source) {
+ // Read parcelable version, make sure to define explicit changes
+ // within {@link Build.PARCELABLE_VERSION);
+ int version = source.readInt();
+ int size = source.readInt();
+ int start = source.dataPosition();
+
+ int numComponents = source.readInt();
+ for (int i = 0; i < numComponents; i++) {
+ mThemeComponents.put(source.readString(), source.readString());
+ }
+
+ numComponents = source.readInt();
+ for (int i = 0 ; i < numComponents; i++) {
+ mPerAppOverlays.put(source.readString(), source.readString());
+ }
+ mRequestType = RequestType.values()[source.readInt()];
+ mWallpaperId = source.readLong();
+ source.setDataPosition(start + size);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Write parcelable version, make sure to define explicit changes
+ // within {@link Build.PARCELABLE_VERSION);
+ dest.writeInt(Build.PARCELABLE_VERSION);
+ int sizePos = dest.dataPosition();
+ // Inject a placeholder that will store the parcel size from this point on
+ // (not including the size itself).
+ dest.writeInt(0);
+ int dataStartPos = dest.dataPosition();
+
+ dest.writeInt(mThemeComponents.size());
+ for (String component : mThemeComponents.keySet()) {
+ dest.writeString(component);
+ dest.writeString(mThemeComponents.get(component));
+ }
+ dest.writeInt((mPerAppOverlays.size()));
+ for (String appPkgName : mPerAppOverlays.keySet()) {
+ dest.writeString(appPkgName);
+ dest.writeString(mPerAppOverlays.get(appPkgName));
+ }
+ dest.writeInt(mRequestType.ordinal());
+ dest.writeLong(mWallpaperId);
+
+ // Go back and write size
+ int size = dest.dataPosition() - dataStartPos;
+ dest.setDataPosition(sizePos);
+ dest.writeInt(size);
+ dest.setDataPosition(dataStartPos + size);
+ }
+
+ public static final Parcelable.Creator<ThemeChangeRequest> CREATOR =
+ new Parcelable.Creator<ThemeChangeRequest>() {
+ @Override
+ public ThemeChangeRequest createFromParcel(Parcel source) {
+ return new ThemeChangeRequest(source);
+ }
+
+ @Override
+ public ThemeChangeRequest[] newArray(int size) {
+ return new ThemeChangeRequest[size];
+ }
+ };
+
+ public enum RequestType {
+ USER_REQUEST,
+ USER_REQUEST_MIXNMATCH,
+ THEME_UPDATED,
+ THEME_REMOVED,
+ THEME_RESET
+ }
+
+ public static class Builder {
+ Map<String, String> mThemeComponents = new HashMap<>();
+ Map<String, String> mPerAppOverlays = new HashMap<>();
+ RequestType mRequestType = RequestType.USER_REQUEST;
+ long mWallpaperId;
+
+ public Builder() {}
+
+ public Builder(ThemeConfig themeConfig) {
+ if (themeConfig != null) {
+ buildChangeRequestFromThemeConfig(themeConfig);
+ }
+ }
+
+ public Builder setOverlay(String pkgName) {
+ return setComponent(MODIFIES_OVERLAYS, pkgName);
+ }
+
+ public Builder setStatusBar(String pkgName) {
+ return setComponent(MODIFIES_STATUS_BAR, pkgName);
+ }
+
+ public Builder setNavBar(String pkgName) {
+ return setComponent(MODIFIES_NAVIGATION_BAR, pkgName);
+ }
+
+ public Builder setFont(String pkgName) {
+ return setComponent(MODIFIES_FONTS, pkgName);
+ }
+
+ public Builder setIcons(String pkgName) {
+ return setComponent(MODIFIES_ICONS, pkgName);
+ }
+
+ public Builder setBootanimation(String pkgName) {
+ return setComponent(MODIFIES_BOOT_ANIM, pkgName);
+ }
+
+ public Builder setWallpaper(String pkgName) {
+ return setComponent(MODIFIES_LAUNCHER, pkgName);
+ }
+
+ // Used in the case that more than one wallpaper exists for a given pkg name
+ public Builder setWallpaperId(long id) {
+ mWallpaperId = id;
+ return this;
+ }
+
+ public Builder setLockWallpaper(String pkgName) {
+ return setComponent(MODIFIES_LOCKSCREEN, pkgName);
+ }
+
+ public Builder setAlarm(String pkgName) {
+ return setComponent(MODIFIES_ALARMS, pkgName);
+ }
+
+ public Builder setNotification(String pkgName) {
+ return setComponent(MODIFIES_NOTIFICATIONS, pkgName);
+ }
+
+ public Builder setRingtone(String pkgName) {
+ return setComponent(MODIFIES_RINGTONES, pkgName);
+ }
+
+ public Builder setLiveLockScreen(String pkgName) {
+ return setComponent(MODIFIES_LIVE_LOCK_SCREEN, pkgName);
+ }
+
+ public Builder setComponent(String component, String pkgName) {
+ if (pkgName != null) {
+ mThemeComponents.put(component, pkgName);
+ } else {
+ mThemeComponents.remove(component);
+ }
+ return this;
+ }
+
+ public Builder setAppOverlay(String appPkgName, String themePkgName) {
+ if (appPkgName != null) {
+ if (themePkgName != null) {
+ mPerAppOverlays.put(appPkgName, themePkgName);
+ } else {
+ mPerAppOverlays.remove(appPkgName);
+ }
+ }
+
+ return this;
+ }
+
+ public Builder setRequestType(RequestType requestType) {
+ mRequestType = requestType != null ? requestType : RequestType.USER_REQUEST;
+ return this;
+ }
+
+ public ThemeChangeRequest build() {
+ return new ThemeChangeRequest(mThemeComponents, mPerAppOverlays,
+ mRequestType, mWallpaperId);
+ }
+
+ private void buildChangeRequestFromThemeConfig(ThemeConfig themeConfig) {
+ if (themeConfig.getFontPkgName() != null) {
+ this.setFont(themeConfig.getFontPkgName());
+ }
+ if (themeConfig.getIconPackPkgName() != null) {
+ this.setIcons(themeConfig.getIconPackPkgName());
+ }
+ if (themeConfig.getOverlayPkgName() != null) {
+ this.setOverlay(themeConfig.getOverlayPkgName());
+ }
+ if (themeConfig.getOverlayForStatusBar() != null) {
+ this.setStatusBar(themeConfig.getOverlayForStatusBar());
+ }
+ if (themeConfig.getOverlayForNavBar() != null) {
+ this.setNavBar(themeConfig.getOverlayForNavBar());
+ }
+
+ // Check if there are any per-app overlays using this theme
+ final Map<String, ThemeConfig.AppTheme> themes = themeConfig.getAppThemes();
+ for (String appPkgName : themes.keySet()) {
+ if (ThemeUtils.isPerAppThemeComponent(appPkgName)) {
+ this.setAppOverlay(appPkgName, themes.get(appPkgName).getOverlayPkgName());
+ }
+ }
+ }
+ }
+}
diff --git a/src/java/cyanogenmod/themes/ThemeManager.java b/src/java/cyanogenmod/themes/ThemeManager.java
new file mode 100644
index 0000000..4c575ae
--- /dev/null
+++ b/src/java/cyanogenmod/themes/ThemeManager.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2014-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 cyanogenmod.themes;
+
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import cyanogenmod.app.CMContextConstants;
+import cyanogenmod.themes.ThemeChangeRequest.RequestType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages changing and applying of themes.
+ * <p>Get an instance of this class by calling blah blah blah</p>
+ */
+public class ThemeManager {
+ private static final String TAG = ThemeManager.class.getName();
+ private static IThemeService sService;
+ private static ThemeManager sInstance;
+ private static Handler mHandler;
+
+ private Set<ThemeChangeListener> mChangeListeners = new ArraySet<>();
+
+ private Set<ThemeProcessingListener> mProcessingListeners = new ArraySet<>();
+
+ private ThemeManager() {
+ mHandler = new Handler(Looper.getMainLooper());
+ sService = getService();
+ }
+
+ public static ThemeManager getInstance() {
+ if (sInstance == null) {
+ sInstance = new ThemeManager();
+ }
+
+ return sInstance;
+ }
+
+ private static IThemeService getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_THEME_SERVICE);
+ if (b != null) {
+ sService = IThemeService.Stub.asInterface(b);
+ return sService;
+ }
+ return null;
+ }
+
+ private final IThemeChangeListener mThemeChangeListener = new IThemeChangeListener.Stub() {
+ @Override
+ public void onProgress(final int progress) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mChangeListeners) {
+ List<ThemeChangeListener> listenersToRemove = new ArrayList<>();
+ for (ThemeChangeListener listener : mChangeListeners) {
+ try {
+ listener.onProgress(progress);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change progress", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeChangeListener listener : listenersToRemove) {
+ mChangeListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFinish(final boolean isSuccess) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mChangeListeners) {
+ List<ThemeChangeListener> listenersToRemove = new ArrayList<>();
+ for (ThemeChangeListener listener : mChangeListeners) {
+ try {
+ listener.onFinish(isSuccess);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change listener", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeChangeListener listener : listenersToRemove) {
+ mChangeListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+ private final IThemeProcessingListener mThemeProcessingListener =
+ new IThemeProcessingListener.Stub() {
+ @Override
+ public void onFinishedProcessing(final String pkgName) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mProcessingListeners) {
+ List<ThemeProcessingListener> listenersToRemove = new ArrayList<>();
+ for (ThemeProcessingListener listener : mProcessingListeners) {
+ try {
+ listener.onFinishedProcessing(pkgName);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change progress", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeProcessingListener listener : listenersToRemove) {
+ mProcessingListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+
+ /**
+ * @deprecated Use {@link ThemeManager#registerThemeChangeListener(ThemeChangeListener)} instead
+ */
+ public void addClient(ThemeChangeListener listener) {
+ registerThemeChangeListener(listener);
+ }
+
+ /**
+ * @deprecated Use {@link ThemeManager#unregisterThemeChangeListener(ThemeChangeListener)}
+ * instead
+ */
+ public void removeClient(ThemeChangeListener listener) {
+ unregisterThemeChangeListener(listener);
+ }
+
+ /**
+ * @deprecated Use {@link ThemeManager#unregisterThemeChangeListener(ThemeChangeListener)}
+ * instead
+ */
+ public void onClientPaused(ThemeChangeListener listener) {
+ unregisterThemeChangeListener(listener);
+ }
+
+ /**
+ * @deprecated Use {@link ThemeManager#registerThemeChangeListener(ThemeChangeListener)} instead
+ */
+ public void onClientResumed(ThemeChangeListener listener) {
+ registerThemeChangeListener(listener);
+ }
+
+ /**
+ * @deprecated Use {@link ThemeManager#unregisterThemeChangeListener(ThemeChangeListener)}
+ * instead
+ */
+ public void onClientDestroyed(ThemeChangeListener listener) {
+ unregisterThemeChangeListener(listener);
+ }
+
+ /**
+ * Register a {@link ThemeChangeListener} to be notified when a theme is done being processed.
+ * @param listener {@link ThemeChangeListener} to register
+ */
+ public void registerThemeChangeListener(ThemeChangeListener listener) {
+ synchronized (mChangeListeners) {
+ if (mChangeListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener already registered");
+ }
+ if (mChangeListeners.size() == 0) {
+ try {
+ sService.requestThemeChangeUpdates(mThemeChangeListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register listener", e);
+ }
+ }
+ mChangeListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregister a {@link ThemeChangeListener}
+ * @param listener {@link ThemeChangeListener} to unregister
+ */
+ public void unregisterThemeChangeListener(ThemeChangeListener listener) {
+ synchronized (mChangeListeners) {
+ mChangeListeners.remove(listener);
+ if (mChangeListeners.size() == 0) {
+ try {
+ sService.removeUpdates(mThemeChangeListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to unregister listener", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Register a {@link ThemeProcessingListener} to be notified when a theme is done being
+ * processed.
+ * @param listener {@link ThemeProcessingListener} to register
+ */
+ public void registerProcessingListener(ThemeProcessingListener listener) {
+ synchronized (mProcessingListeners) {
+ if (mProcessingListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener already registered");
+ }
+ if (mProcessingListeners.size() == 0) {
+ try {
+ sService.registerThemeProcessingListener(mThemeProcessingListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register listener", e);
+ }
+ }
+ mProcessingListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregister a {@link ThemeProcessingListener}.
+ * @param listener {@link ThemeProcessingListener} to unregister
+ */
+ public void unregisterProcessingListener(ThemeChangeListener listener) {
+ synchronized (mProcessingListeners) {
+ mProcessingListeners.remove(listener);
+ if (mProcessingListeners.size() == 0) {
+ try {
+ sService.unregisterThemeProcessingListener(mThemeProcessingListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to unregister listener", e);
+ }
+ }
+ }
+ }
+
+ public void requestThemeChange(String pkgName, List<String> components) {
+ requestThemeChange(pkgName, components, true);
+ }
+
+ public void requestThemeChange(String pkgName, List<String> components,
+ boolean removePerAppThemes) {
+ Map<String, String> componentMap = new HashMap<>(components.size());
+ for (String component : components) {
+ componentMap.put(component, pkgName);
+ }
+ requestThemeChange(componentMap, removePerAppThemes);
+ }
+
+ public void requestThemeChange(Map<String, String> componentMap) {
+ requestThemeChange(componentMap, true);
+ }
+
+ public void requestThemeChange(Map<String, String> componentMap, boolean removePerAppThemes) {
+ ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder();
+ for (String component : componentMap.keySet()) {
+ builder.setComponent(component, componentMap.get(component));
+ }
+
+ requestThemeChange(builder.build(), removePerAppThemes);
+ }
+
+ public void requestThemeChange(ThemeChangeRequest request, boolean removePerAppThemes) {
+ try {
+ sService.requestThemeChange(request, removePerAppThemes);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ }
+
+ public void applyDefaultTheme() {
+ try {
+ sService.applyDefaultTheme();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ }
+
+ public boolean isThemeApplying() {
+ try {
+ return sService.isThemeApplying();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+
+ return false;
+ }
+
+ public boolean isThemeBeingProcessed(String themePkgName) {
+ try {
+ return sService.isThemeBeingProcessed(themePkgName);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return false;
+ }
+
+ public int getProgress() {
+ try {
+ return sService.getProgress();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return -1;
+ }
+
+ public boolean processThemeResources(String themePkgName) {
+ try {
+ return sService.processThemeResources(themePkgName);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return false;
+ }
+
+ public long getLastThemeChangeTime() {
+ try {
+ return sService.getLastThemeChangeTime();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return 0;
+ }
+
+ public ThemeChangeRequest.RequestType getLastThemeChangeRequestType() {
+ try {
+ int type = sService.getLastThemeChangeRequestType();
+ return (type >= 0 && type < RequestType.values().length)
+ ? RequestType.values()[type]
+ : null;
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+
+ return null;
+ }
+
+ private void logThemeServiceException(Exception e) {
+ Log.w(TAG, "Unable to access ThemeService", e);
+ }
+
+ public interface ThemeChangeListener {
+ void onProgress(int progress);
+ void onFinish(boolean isSuccess);
+ }
+
+ public interface ThemeProcessingListener {
+ void onFinishedProcessing(String pkgName);
+ }
+}
+
diff --git a/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl b/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl
new file mode 100644
index 0000000..c69e082
--- /dev/null
+++ b/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/src/java/org/cyanogenmod/internal/util/ImageUtils.java b/src/java/org/cyanogenmod/internal/util/ImageUtils.java
new file mode 100644
index 0000000..c67c23c
--- /dev/null
+++ b/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/src/java/org/cyanogenmod/internal/util/ThemeUtils.java b/src/java/org/cyanogenmod/internal/util/ThemeUtils.java
new file mode 100644
index 0000000..ef51ced
--- /dev/null
+++ b/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();
+ }
+ }
+}