aboutsummaryrefslogtreecommitdiffstats
path: root/cm
diff options
context:
space:
mode:
authord34d <clark@cyngn.com>2016-07-15 09:56:19 -0700
committerGerrit Code Review <gerrit@cyanogenmod.org>2016-08-02 14:40:34 -0700
commitc4ed8c84cd787a16c959c2cdafb9ac51520419bc (patch)
tree32249d18c5ea6301b5f394c131563af8f707ce2e /cm
parent4cf18a3e36ec98391734ac0f901b4f54a1e2a04f (diff)
downloadvendor_cmsdk-c4ed8c84cd787a16c959c2cdafb9ac51520419bc.zip
vendor_cmsdk-c4ed8c84cd787a16c959c2cdafb9ac51520419bc.tar.gz
vendor_cmsdk-c4ed8c84cd787a16c959c2cdafb9ac51520419bc.tar.bz2
Themes: Broker the theme service
Change-Id: I429936f63d52eddcb1653515bc94e82f758b57d6
Diffstat (limited to 'cm')
-rw-r--r--cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerService.java116
-rw-r--r--cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerServiceBroker.java100
-rw-r--r--cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerService.java1301
-rw-r--r--cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerServiceBroker.java366
-rw-r--r--cm/res/AndroidManifest.xml5
-rw-r--r--cm/res/res/values/config.xml4
6 files changed, 473 insertions, 1419 deletions
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerService.java
deleted file mode 100644
index c8c6d3e..0000000
--- a/cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerService.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.platform.internal;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.os.Binder;
-import android.os.FileUtils;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import com.android.server.SystemService;
-import cyanogenmod.app.CMContextConstants;
-
-import org.cyanogenmod.internal.themes.IIconCacheManager;
-import org.cyanogenmod.internal.util.ThemeUtils;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.util.Arrays;
-import java.util.Comparator;
-
-/** @hide */
-public class IconCacheManagerService extends CMSystemService {
- private static final String TAG = IconCacheManagerService.class.getSimpleName();
-
- private static final long MAX_ICON_CACHE_SIZE = 33554432L; // 32MB
- private static final long PURGED_ICON_CACHE_SIZE = 25165824L; // 24 MB
-
- private long mIconCacheSize = 0L;
- private Context mContext;
-
- public IconCacheManagerService(Context context) {
- super(context);
- mContext = context;
- }
-
- @Override
- public String getFeatureDeclaration() {
- return CMContextConstants.Features.THEMES;
- }
-
- @Override
- public void onStart() {
- Log.d(TAG, "registerIconCache cmiconcache: " + this);
- publishBinderService(CMContextConstants.CM_ICON_CACHE_SERVICE, mService);
- }
-
- private void purgeIconCache() {
- Log.d(TAG, "Purging icon cahe of size " + mIconCacheSize);
- File cacheDir = new File(ThemeUtils.SYSTEM_THEME_ICON_CACHE_DIR);
- File[] files = cacheDir.listFiles();
- Arrays.sort(files, mOldestFilesFirstComparator);
- for (File f : files) {
- if (!f.isDirectory()) {
- final long size = f.length();
- if(f.delete()) mIconCacheSize -= size;
- }
- if (mIconCacheSize <= PURGED_ICON_CACHE_SIZE) break;
- }
- }
-
- private Comparator<File> mOldestFilesFirstComparator = new Comparator<File>() {
- @Override
- public int compare(File lhs, File rhs) {
- return (int) (lhs.lastModified() - rhs.lastModified());
- }
- };
-
- private IBinder mService = new IIconCacheManager.Stub() {
- @Override
- public boolean cacheComposedIcon(Bitmap icon, String fileName) throws RemoteException {
- final long token = Binder.clearCallingIdentity();
- boolean success;
- FileOutputStream os;
- final File cacheDir = new File(ThemeUtils.SYSTEM_THEME_ICON_CACHE_DIR);
- if (cacheDir.listFiles().length == 0) {
- mIconCacheSize = 0;
- }
- try {
- File outFile = new File(cacheDir, fileName);
- os = new FileOutputStream(outFile);
- icon.compress(Bitmap.CompressFormat.PNG, 90, os);
- os.close();
- FileUtils.setPermissions(outFile,
- FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH,
- -1, -1);
- mIconCacheSize += outFile.length();
- if (mIconCacheSize > MAX_ICON_CACHE_SIZE) {
- purgeIconCache();
- }
- success = true;
- } catch (Exception e) {
- success = false;
- Log.w(TAG, "Unable to cache icon " + fileName, e);
- }
- Binder.restoreCallingIdentity(token);
- return success;
- }
-
- };
-}
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerServiceBroker.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerServiceBroker.java
new file mode 100644
index 0000000..044449a
--- /dev/null
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerServiceBroker.java
@@ -0,0 +1,100 @@
+/*
+ * 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.platform.internal;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import cyanogenmod.app.CMContextConstants;
+
+import org.cyanogenmod.internal.themes.IIconCacheManager;
+import org.cyanogenmod.platform.internal.common.BrokeredServiceConnection;
+
+/**
+ * Icon cache service broker for connecting clients to a backing icon cache manager service.
+ *
+ * @hide
+ */
+public class IconCacheManagerServiceBroker extends BrokerableCMSystemService<IIconCacheManager> {
+
+ private static final ComponentName SERVICE_COMPONENT =
+ new ComponentName("org.cyanogenmod.themeservice",
+ "org.cyanogenmod.themeservice.IconCacheManagerService");
+
+ private final IIconCacheManager mServiceStubForFailure = new IIconCacheManager.Stub() {
+ @Override
+ public boolean cacheComposedIcon(Bitmap icon, String path) throws RemoteException {
+ return false;
+ }
+ };
+
+ private BrokeredServiceConnection mServiceConnection = new BrokeredServiceConnection() {
+ @Override
+ public void onBrokeredServiceConnected() {
+ }
+
+ @Override
+ public void onBrokeredServiceDisconnected() {
+ }
+ };
+
+ private final class BinderService extends IIconCacheManager.Stub {
+ @Override
+ public boolean cacheComposedIcon(Bitmap icon, String path) throws RemoteException {
+ return getBrokeredService().cacheComposedIcon(icon, path);
+ }
+ }
+
+ public IconCacheManagerServiceBroker(Context context) {
+ super(context);
+ setBrokeredServiceConnection(mServiceConnection);
+ }
+
+ @Override
+ public String getFeatureDeclaration() {
+ return CMContextConstants.Features.THEMES;
+ }
+
+ @Override
+ protected IIconCacheManager getIBinderAsIInterface(@NonNull IBinder service) {
+ return IIconCacheManager.Stub.asInterface(service);
+ }
+
+ @Override
+ protected IIconCacheManager getDefaultImplementation() {
+ return mServiceStubForFailure;
+ }
+
+ @Override
+ protected ComponentName getServiceComponent() {
+ return SERVICE_COMPONENT;
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(CMContextConstants.CM_ICON_CACHE_SERVICE, new BinderService());
+ }
+
+ @Override
+ protected String getComponentFilteringPermission() {
+ return cyanogenmod.platform.Manifest.permission.ACCESS_THEME_MANAGER;
+ }
+}
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerService.java
deleted file mode 100644
index 08273e3..0000000
--- a/cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerService.java
+++ /dev/null
@@ -1,1301 +0,0 @@
-/*
- * 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 org.cyanogenmod.platform.internal;
-
-import android.app.ActivityManager;
-import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.WallpaperManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.AssetManager;
-import android.content.res.Configuration;
-import android.content.res.ThemeConfig;
-import android.media.RingtoneManager;
-import android.os.Binder;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.server.SystemService;
-
-import cyanogenmod.app.CMContextConstants;
-import cyanogenmod.app.CMStatusBarManager;
-import cyanogenmod.app.CustomTile;
-import cyanogenmod.providers.CMSettings;
-import cyanogenmod.providers.ThemesContract.MixnMatchColumns;
-import cyanogenmod.providers.ThemesContract.ThemesColumns;
-import cyanogenmod.themes.IThemeChangeListener;
-import cyanogenmod.themes.IThemeProcessingListener;
-import cyanogenmod.themes.IThemeService;
-import cyanogenmod.themes.ThemeChangeRequest;
-
-import org.cyanogenmod.internal.util.ImageUtils;
-import org.cyanogenmod.internal.util.QSConstants;
-import org.cyanogenmod.internal.util.QSUtils;
-import org.cyanogenmod.internal.util.ThemeUtils;
-import org.cyanogenmod.platform.internal.AppsFailureReceiver;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import libcore.io.IoUtils;
-
-import static android.content.res.ThemeConfig.SYSTEM_DEFAULT;
-import static cyanogenmod.platform.Manifest.permission.ACCESS_THEME_MANAGER;
-import static org.cyanogenmod.internal.util.ThemeUtils.SYSTEM_THEME_PATH;
-import static org.cyanogenmod.internal.util.ThemeUtils.THEME_BOOTANIMATION_PATH;
-
-public class ThemeManagerService extends CMSystemService {
-
- private static final String TAG = ThemeManagerService.class.getName();
-
- //Constant to set Component_id in case of mismatch with mixnmatch_homescreen
- private static final int DEFAULT_COMPONENT_ID = 0;
-
- private static final boolean DEBUG = false;
-
- private static final String GOOGLE_SETUPWIZARD_PACKAGE = "com.google.android.setupwizard";
- private static final String CM_SETUPWIZARD_PACKAGE = "com.cyanogenmod.setupwizard";
- private static final String MANAGED_PROVISIONING_PACKAGE = "com.android.managedprovisioning";
-
- private static final String CATEGORY_THEME_CHOOSER = "cyanogenmod.intent.category.APP_THEMES";
-
- // Defines a min and max compatible api level for themes on this system.
- private static final int MIN_COMPATIBLE_VERSION = 21;
-
- private HandlerThread mWorker;
- private ThemeWorkerHandler mHandler;
- private ResourceProcessingHandler mResourceProcessingHandler;
- private Context mContext;
- private PackageManager mPM;
- private int mProgress;
- private boolean mWallpaperChangedByUs = false;
- private int mCurrentUserId = UserHandle.USER_OWNER;
-
- private boolean mIsThemeApplying = false;
-
- private final RemoteCallbackList<IThemeChangeListener> mClients = new RemoteCallbackList<>();
-
- private final RemoteCallbackList<IThemeProcessingListener> mProcessingListeners =
- new RemoteCallbackList<>();
-
- final private ArrayList<String> mThemesToProcessQueue = new ArrayList<>();
-
- private long mLastThemeChangeTime = 0;
- private int mLastThemeChangeRequestType;
-
- private class ThemeWorkerHandler extends Handler {
- private static final int MESSAGE_CHANGE_THEME = 1;
- private static final int MESSAGE_APPLY_DEFAULT_THEME = 2;
- private static final int MESSAGE_REBUILD_RESOURCE_CACHE = 3;
-
- public ThemeWorkerHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_CHANGE_THEME:
- final ThemeChangeRequest request = (ThemeChangeRequest) msg.obj;
- doApplyTheme(request, msg.arg1 == 1);
- break;
- case MESSAGE_APPLY_DEFAULT_THEME:
- doApplyDefaultTheme();
- break;
- case MESSAGE_REBUILD_RESOURCE_CACHE:
- doRebuildResourceCache();
- break;
- default:
- Log.w(TAG, "Unknown message " + msg.what);
- break;
- }
- }
- }
-
- private class ResourceProcessingHandler extends Handler {
- private static final int MESSAGE_QUEUE_THEME_FOR_PROCESSING = 3;
- private static final int MESSAGE_DEQUEUE_AND_PROCESS_THEME = 4;
-
- public ResourceProcessingHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_QUEUE_THEME_FOR_PROCESSING:
- String pkgName = (String) msg.obj;
- synchronized (mThemesToProcessQueue) {
- if (!mThemesToProcessQueue.contains(pkgName)) {
- if (DEBUG) Log.d(TAG, "Adding " + pkgName + " for processing");
- mThemesToProcessQueue.add(pkgName);
- if (mThemesToProcessQueue.size() == 1) {
- this.sendEmptyMessage(MESSAGE_DEQUEUE_AND_PROCESS_THEME);
- }
- }
- }
- break;
- case MESSAGE_DEQUEUE_AND_PROCESS_THEME:
- synchronized (mThemesToProcessQueue) {
- pkgName = mThemesToProcessQueue.get(0);
- }
- if (pkgName != null) {
- if (DEBUG) Log.d(TAG, "Processing " + pkgName);
- String name;
- try {
- PackageInfo pi = mPM.getPackageInfo(pkgName, 0);
- name = getThemeName(pi);
- } catch (PackageManager.NameNotFoundException e) {
- name = null;
- }
-
- int result = mPM.processThemeResources(pkgName);
- if (result < 0) {
- postFailedThemeInstallNotification(name != null ? name : pkgName);
- }
- sendThemeResourcesCachedBroadcast(pkgName, result);
-
- synchronized (mThemesToProcessQueue) {
- mThemesToProcessQueue.remove(0);
- if (mThemesToProcessQueue.size() > 0 &&
- !hasMessages(MESSAGE_DEQUEUE_AND_PROCESS_THEME)) {
- this.sendEmptyMessage(MESSAGE_DEQUEUE_AND_PROCESS_THEME);
- }
- }
- postFinishedProcessing(pkgName);
- }
- break;
- default:
- Log.w(TAG, "Unknown message " + msg.what);
- break;
- }
- }
- }
-
-
- public ThemeManagerService(Context context) {
- super(context);
- mContext = context;
- mWorker = new HandlerThread("ThemeServiceWorker", Process.THREAD_PRIORITY_BACKGROUND);
- mWorker.start();
- mHandler = new ThemeWorkerHandler(mWorker.getLooper());
- Log.i(TAG, "Spawned worker thread");
-
- HandlerThread processingThread = new HandlerThread("ResourceProcessingThread",
- Process.THREAD_PRIORITY_BACKGROUND);
- processingThread.start();
- mResourceProcessingHandler =
- new ResourceProcessingHandler(processingThread.getLooper());
-
- // create the theme directories if they do not exist
- ThemeUtils.createThemeDirIfNotExists();
- ThemeUtils.createFontDirIfNotExists();
- ThemeUtils.createAlarmDirIfNotExists();
- ThemeUtils.createNotificationDirIfNotExists();
- ThemeUtils.createRingtoneDirIfNotExists();
- ThemeUtils.createIconCacheDirIfNotExists();
- }
-
- @Override
- public String getFeatureDeclaration() {
- return CMContextConstants.Features.THEMES;
- }
-
- @Override
- public void onStart() {
- publishBinderService(CMContextConstants.CM_THEME_SERVICE, mService);
- // listen for wallpaper changes
- IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
- mContext.registerReceiver(mWallpaperChangeReceiver, filter);
-
- filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(mUserChangeReceiver, filter);
-
- mPM = mContext.getPackageManager();
- }
-
- @Override
- public void onBootPhase(int phase) {
- super.onBootPhase(phase);
- if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
- if (!isThemeApiUpToDate()) {
- Log.d(TAG, "The system has been upgraded to a theme new api, " +
- "checking if currently set theme is compatible");
- removeObsoleteThemeOverlayIfExists();
- updateThemeApi();
- }
- registerAppsFailureReceiver();
- processInstalledThemes();
- } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
- publishThemesTile();
- }
- }
-
- private void registerAppsFailureReceiver() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(cyanogenmod.content.Intent.ACTION_APP_FAILURE);
- filter.addAction(ThemeUtils.ACTION_THEME_CHANGED);
- mContext.registerReceiver(new AppsFailureReceiver(), filter);
- }
-
- private void removeObsoleteThemeOverlayIfExists() {
- // Get the current overlay theme so we can see it it's overlay should be unapplied
- final IActivityManager am = ActivityManagerNative.getDefault();
- ThemeConfig config = null;
- try {
- if (am != null) {
- config = am.getConfiguration().themeConfig;
- } else {
- Log.e(TAG, "ActivityManager getDefault() " +
- "returned null, cannot remove obsolete theme");
- }
- } catch(RemoteException e) {
- Log.e(TAG, "Failed to get the theme config ", e);
- }
- if (config == null) return; // No need to unapply a theme if one isn't set
-
- // Populate the currentTheme map for the components we care about, we'll look
- // at the compatibility of each pkg below.
- HashMap<String, String> currentThemeMap = new HashMap<>();
- currentThemeMap.put(ThemesColumns.MODIFIES_STATUS_BAR, config.getOverlayForStatusBar());
- currentThemeMap.put(ThemesColumns.MODIFIES_NAVIGATION_BAR,
- config.getOverlayForNavBar());
- currentThemeMap.put(ThemesColumns.MODIFIES_OVERLAYS, config.getOverlayPkgName());
-
- // Look at each component's theme (that we care about at least) and check compatibility
- // of the pkg with the system. If it is not compatible then we will add it to a theme
- // change request.
- Map<String, String> defaults = ThemeUtils.getDefaultComponents(mContext);
- ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder();
- for(Map.Entry<String, String> entry : currentThemeMap.entrySet()) {
- String component = entry.getKey();
- String pkgName = entry.getValue();
- String defaultPkg = defaults.get(component);
-
- if (defaultPkg == null) {
- Log.d(TAG, "Default package is null, skipping " + component);
- continue;
- }
-
- // Check that the default overlay theme is not currently set
- if (defaultPkg.equals(pkgName)) {
- Log.d(TAG, "Current overlay theme is same as default. " +
- "Not doing anything for " + component);
- continue;
- }
-
- // No need to unapply a system theme since it is always compatible
- if (ThemeConfig.SYSTEM_DEFAULT.equals(pkgName)) {
- Log.d(TAG, "Current overlay theme for "
- + component + " was system. no need to unapply");
- continue;
- }
-
- if (!isThemeCompatibleWithUpgradedApi(pkgName)) {
- Log.d(TAG, pkgName + "is incompatible with latest theme api for component " +
- component + ", Applying " + defaultPkg);
- builder.setComponent(component, pkgName);
- }
- }
-
- // Now actually unapply the incompatible themes
- ThemeChangeRequest request = builder.build();
- if (!request.getThemeComponentsMap().isEmpty()) {
- try {
- ((IThemeService) mService).requestThemeChange(request, true);
- } catch(RemoteException e) {
- // This cannot happen
- }
- } else {
- Log.d(TAG, "Current theme is compatible with the system. Not unapplying anything");
- }
- }
-
- private boolean isThemeCompatibleWithUpgradedApi(String pkgName) {
- // Note this function does not cover the case of a downgrade. That case is out of scope and
- // would require predicting whether the future API levels will be compatible or not.
- boolean compatible = false;
- try {
- PackageInfo pi = mPM.getPackageInfo(pkgName, 0);
- Log.d(TAG, "Comparing theme target: " + pi.applicationInfo.targetSdkVersion +
- "to " + android.os.Build.VERSION.SDK_INT);
- compatible = pi.applicationInfo.targetSdkVersion >= MIN_COMPATIBLE_VERSION;
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Unable to get package info for " + pkgName, e);
- }
- return compatible;
- }
-
- private boolean isThemeApiUpToDate() {
- // We can't be 100% sure its an upgrade. If the field is undefined it
- // could have been a factory reset.
- final ContentResolver resolver = mContext.getContentResolver();
- int recordedApiLevel = android.os.Build.VERSION.SDK_INT;
- try {
- recordedApiLevel = CMSettings.Secure.getInt(resolver,
- CMSettings.Secure.THEME_PREV_BOOT_API_LEVEL);
- } catch (CMSettings.CMSettingNotFoundException e) {
- recordedApiLevel = -1;
- Log.d(TAG, "Previous api level not found. First time booting?");
- }
- Log.d(TAG, "Prev api level was: " + recordedApiLevel
- + ", api is now: " + android.os.Build.VERSION.SDK_INT);
-
- return recordedApiLevel == android.os.Build.VERSION.SDK_INT;
- }
-
- private void updateThemeApi() {
- final ContentResolver resolver = mContext.getContentResolver();
- boolean success = CMSettings.Secure.putInt(resolver,
- CMSettings.Secure.THEME_PREV_BOOT_API_LEVEL, android.os.Build.VERSION.SDK_INT);
- if (!success) {
- Log.e(TAG, "Unable to store latest API level to secure settings");
- }
- }
-
- private void doApplyTheme(ThemeChangeRequest request, boolean removePerAppTheme) {
- synchronized(this) {
- mProgress = 0;
- }
-
- if (request == null || request.getNumChangesRequested() == 0) {
- postFinish(true, request, 0);
- return;
- }
- mIsThemeApplying = true;
- mLastThemeChangeTime = System.currentTimeMillis();
- mLastThemeChangeRequestType = request.getReqeustType().ordinal();
-
- incrementProgress(5);
-
- // TODO: provide progress updates that reflect the time needed for each component
- final int progressIncrement = 75 / request.getNumChangesRequested();
-
- if (request.getIconsThemePackageName() != null) {
- updateIcons(request.getIconsThemePackageName());
- incrementProgress(progressIncrement);
- }
-
- if (request.getWallpaperThemePackageName() != null) {
- if (updateWallpaper(request.getWallpaperThemePackageName(),
- request.getWallpaperId())) {
- mWallpaperChangedByUs = true;
- }
- incrementProgress(progressIncrement);
- }
-
- if (request.getLockWallpaperThemePackageName() != null) {
- updateLockscreen(request.getLockWallpaperThemePackageName());
- incrementProgress(progressIncrement);
- }
-
- Environment.setUserRequired(false);
- if (request.getNotificationThemePackageName() != null) {
- updateNotifications(request.getNotificationThemePackageName());
- incrementProgress(progressIncrement);
- }
-
- if (request.getAlarmThemePackageName() != null) {
- updateAlarms(request.getAlarmThemePackageName());
- incrementProgress(progressIncrement);
- }
-
- if (request.getRingtoneThemePackageName() != null) {
- updateRingtones(request.getRingtoneThemePackageName());
- incrementProgress(progressIncrement);
- }
- Environment.setUserRequired(true);
-
- if (request.getBootanimationThemePackageName() != null) {
- updateBootAnim(request.getBootanimationThemePackageName());
- incrementProgress(progressIncrement);
- }
-
- if (request.getFontThemePackageName() != null) {
- updateFonts(request.getFontThemePackageName());
- incrementProgress(progressIncrement);
- }
-
- if (request.getLiveLockScreenThemePackageName() != null) {
- updateLiveLockScreen(request.getLiveLockScreenThemePackageName());
- incrementProgress(progressIncrement);
- }
-
- try {
- updateProvider(request, mLastThemeChangeTime);
- } catch(IllegalArgumentException e) {
- // Safeguard against provider not being ready yet.
- Log.e(TAG, "Not updating the theme provider since it is unavailable");
- }
-
- if (shouldUpdateConfiguration(request)) {
- updateConfiguration(request, removePerAppTheme);
- }
-
- killLaunchers(request);
-
- postFinish(true, request, mLastThemeChangeTime);
- mIsThemeApplying = false;
- }
-
- private void doApplyDefaultTheme() {
- final ContentResolver resolver = mContext.getContentResolver();
- final String defaultThemePkg = ThemeUtils.getDefaultThemePackageName(mContext);
- if (!TextUtils.isEmpty(defaultThemePkg)) {
- String defaultThemeComponents = CMSettings.Secure.getString(resolver,
- CMSettings.Secure.DEFAULT_THEME_COMPONENTS);
- List<String> components;
- if (TextUtils.isEmpty(defaultThemeComponents)) {
- components = ThemeUtils.getAllComponents();
- } else {
- components = new ArrayList<String>(
- Arrays.asList(defaultThemeComponents.split("\\|")));
- }
- ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder();
- for (String component : components) {
- builder.setComponent(component, defaultThemePkg);
- }
- try {
- ((IThemeService) mService).requestThemeChange(builder.build(), true);
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to set default theme", e);
- }
- }
- }
-
- private void doRebuildResourceCache() {
- FileUtils.deleteContents(new File(ThemeUtils.RESOURCE_CACHE_DIR));
- processInstalledThemes();
- }
-
- private void updateProvider(ThemeChangeRequest request, long updateTime) {
- ContentValues values = new ContentValues();
- values.put(MixnMatchColumns.COL_UPDATE_TIME, updateTime);
- Map<String, String> componentMap = request.getThemeComponentsMap();
- for (String component : componentMap.keySet()) {
- values.put(MixnMatchColumns.COL_VALUE, componentMap.get(component));
- String where = MixnMatchColumns.COL_KEY + "=?";
- String[] selectionArgs = { MixnMatchColumns.componentToMixNMatchKey(component) };
- if (selectionArgs[0] == null) {
- continue; // No equivalence between mixnmatch and theme
- }
-
- // Add component ID for multiwallpaper
- if (ThemesColumns.MODIFIES_LAUNCHER.equals(component)) {
- values.put(MixnMatchColumns.COL_COMPONENT_ID, request.getWallpaperId());
- } else {
- values.put(MixnMatchColumns.COL_COMPONENT_ID, DEFAULT_COMPONENT_ID);
- }
-
- mContext.getContentResolver().update(MixnMatchColumns.CONTENT_URI, values, where,
- selectionArgs);
- }
- }
-
- private boolean updateIcons(String pkgName) {
- ThemeUtils.clearIconCache();
- try {
- if (pkgName.equals(SYSTEM_DEFAULT)) {
- mPM.updateIconMaps(null);
- } else {
- mPM.updateIconMaps(pkgName);
- }
- } catch (Exception e) {
- Log.w(TAG, "Changing icons failed", e);
- return false;
- }
- return true;
- }
-
- private boolean updateFonts(String pkgName) {
- //Clear the font dir
- FileUtils.deleteContents(new File(ThemeUtils.SYSTEM_THEME_FONT_PATH));
-
- if (!pkgName.equals(SYSTEM_DEFAULT)) {
- //Get Font Assets
- Context themeCtx;
- String[] assetList;
- try {
- themeCtx = mContext.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY);
- AssetManager assetManager = themeCtx.getAssets();
- assetList = assetManager.list("fonts");
- } catch (Exception e) {
- Log.e(TAG, "There was an error getting assets for pkg " + pkgName, e);
- return false;
- }
- if (assetList == null || assetList.length == 0) {
- Log.e(TAG, "Could not find any font assets");
- return false;
- }
-
- //Copy font assets to font dir
- for(String asset : assetList) {
- InputStream is = null;
- OutputStream os = null;
- try {
- is = ThemeUtils.getInputStreamFromAsset(themeCtx,
- "file:///android_asset/fonts/" + asset);
- File outFile = new File(ThemeUtils.SYSTEM_THEME_FONT_PATH, asset);
- FileUtils.copyToFile(is, outFile);
- FileUtils.setPermissions(outFile,
- FileUtils.S_IRWXU|FileUtils.S_IRGRP|FileUtils.S_IRWXO, -1, -1);
- } catch (Exception e) {
- Log.e(TAG, "There was an error installing the new fonts for pkg " + pkgName, e);
- return false;
- } finally {
- IoUtils.closeQuietly(is);
- IoUtils.closeQuietly(os);
- }
- }
- }
-
- //Notify zygote that themes need a refresh
- SystemProperties.set("sys.refresh_theme", "1");
- return true;
- }
-
- private boolean updateBootAnim(String pkgName) {
- if (TextUtils.isEmpty(pkgName) || SYSTEM_DEFAULT.equals(pkgName)) {
- clearBootAnimation();
- return true;
- }
-
- try {
- final ApplicationInfo ai = mPM.getApplicationInfo(pkgName, 0);
- applyBootAnimation(ai.sourceDir);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Changing boot animation failed", e);
- return false;
- }
- return true;
- }
-
- private boolean updateAlarms(String pkgName) {
- return updateAudible(ThemeUtils.SYSTEM_THEME_ALARM_PATH, "alarms",
- RingtoneManager.TYPE_ALARM, pkgName);
- }
-
- private boolean updateNotifications(String pkgName) {
- return updateAudible(ThemeUtils.SYSTEM_THEME_NOTIFICATION_PATH, "notifications",
- RingtoneManager.TYPE_NOTIFICATION, pkgName);
- }
-
- private boolean updateRingtones(String pkgName) {
- return updateAudible(ThemeUtils.SYSTEM_THEME_RINGTONE_PATH, "ringtones",
- RingtoneManager.TYPE_RINGTONE, pkgName);
- }
-
- private boolean updateAudible(String dirPath, String assetPath, int type, String pkgName) {
- //Clear the dir
- ThemeUtils.clearAudibles(mContext, dirPath);
- if (TextUtils.isEmpty(pkgName) || pkgName.equals(SYSTEM_DEFAULT)) {
- if (!ThemeUtils.setDefaultAudible(mContext, type)) {
- Log.e(TAG, "There was an error installing the default audio file");
- return false;
- }
- return true;
- }
-
- PackageInfo pi = null;
- try {
- pi = mPM.getPackageInfo(pkgName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Unable to update audible " + dirPath, e);
- return false;
- }
-
- //Get theme Assets
- Context themeCtx;
- String[] assetList;
- try {
- themeCtx = mContext.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY);
- AssetManager assetManager = themeCtx.getAssets();
- assetList = assetManager.list(assetPath);
- } catch (Exception e) {
- Log.e(TAG, "There was an error getting assets for pkg " + pkgName, e);
- return false;
- }
- if (assetList == null || assetList.length == 0) {
- Log.e(TAG, "Could not find any audio assets");
- return false;
- }
-
- // TODO: right now we just load the first file but this will need to be changed
- // in the future if multiple audio files are supported.
- final String asset = assetList[0];
- if (!ThemeUtils.isValidAudible(asset)) return false;
-
- InputStream is = null;
- OutputStream os = null;
- try {
- is = ThemeUtils.getInputStreamFromAsset(themeCtx, "file:///android_asset/"
- + assetPath + File.separator + asset);
- File outFile = new File(dirPath, asset);
- FileUtils.copyToFile(is, outFile);
- FileUtils.setPermissions(outFile,
- FileUtils.S_IRWXU|FileUtils.S_IRGRP|FileUtils.S_IRWXO,-1, -1);
- ThemeUtils.setAudible(mContext, outFile, type, pi.themeInfo.name);
- } catch (Exception e) {
- Log.e(TAG, "There was an error installing the new audio file for pkg " + pkgName, e);
- return false;
- } finally {
- IoUtils.closeQuietly(is);
- IoUtils.closeQuietly(os);
- }
- return true;
- }
-
- private boolean updateLockscreen(String pkgName) {
- boolean success;
- success = setCustomLockScreenWallpaper(pkgName);
-
- if (success) {
- mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED),
- UserHandle.ALL);
- }
- return success;
- }
-
- private boolean setCustomLockScreenWallpaper(String pkgName) {
- WallpaperManager wm = WallpaperManager.getInstance(mContext);
- try {
- if (SYSTEM_DEFAULT.equals(pkgName) || TextUtils.isEmpty(pkgName)) {
- wm.clearKeyguardWallpaper();
- } else {
- InputStream in = ImageUtils.getCroppedKeyguardStream(pkgName, mContext);
- if (in != null) {
- wm.setKeyguardStream(in);
- IoUtils.closeQuietly(in);
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "There was an error setting lockscreen wp for pkg " + pkgName, e);
- return false;
- }
- return true;
- }
-
- private boolean updateWallpaper(String pkgName, long id) {
- WallpaperManager wm = WallpaperManager.getInstance(mContext);
- if (SYSTEM_DEFAULT.equals(pkgName)) {
- try {
- wm.clear();
- } catch (IOException e) {
- return false;
- }
- } else if (TextUtils.isEmpty(pkgName)) {
- try {
- wm.clear(false);
- } catch (IOException e) {
- return false;
- }
- } else {
- InputStream in = null;
- try {
- in = ImageUtils.getCroppedWallpaperStream(pkgName, id, mContext);
- if (in != null)
- wm.setStream(in);
- } catch (Exception e) {
- return false;
- } finally {
- IoUtils.closeQuietly(in);
- }
- }
- return true;
- }
-
- private boolean updateLiveLockScreen(String pkgName) {
- // TODO: do something meaningful here once ready
- return true;
- }
-
- private boolean updateConfiguration(ThemeChangeRequest request,
- boolean removePerAppThemes) {
- final IActivityManager am = ActivityManagerNative.getDefault();
- if (am != null) {
- final long token = Binder.clearCallingIdentity();
- try {
- Configuration config = am.getConfiguration();
- ThemeConfig.Builder themeBuilder = createBuilderFrom(config, request, null,
- removePerAppThemes);
- ThemeConfig newConfig = themeBuilder.build();
-
- config.themeConfig = newConfig;
- am.updateConfiguration(config);
- } catch (RemoteException e) {
- return false;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- return true;
- }
-
- private boolean updateConfiguration(ThemeConfig themeConfig) {
- final IActivityManager am = ActivityManagerNative.getDefault();
- if (am != null) {
- final long token = Binder.clearCallingIdentity();
- try {
- Configuration config = am.getConfiguration();
-
- config.themeConfig = themeConfig;
- am.updateConfiguration(config);
- } catch (RemoteException e) {
- return false;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- return true;
- }
-
- private boolean shouldUpdateConfiguration(ThemeChangeRequest request) {
- return request.getOverlayThemePackageName() != null ||
- request.getFontThemePackageName() != null ||
- request.getIconsThemePackageName() != null ||
- request.getStatusBarThemePackageName() != null ||
- request.getNavBarThemePackageName() != null ||
- request.getPerAppOverlays().size() > 0;
- }
-
- private static ThemeConfig.Builder createBuilderFrom(Configuration config,
- ThemeChangeRequest request, String pkgName, boolean removePerAppThemes) {
- ThemeConfig.Builder builder = new ThemeConfig.Builder(config.themeConfig);
-
- if (removePerAppThemes) removePerAppThemesFromConfig(builder, config.themeConfig);
-
- if (request.getIconsThemePackageName() != null) {
- builder.defaultIcon(pkgName == null ? request.getIconsThemePackageName() : pkgName);
- }
-
- if (request.getOverlayThemePackageName() != null) {
- builder.defaultOverlay(pkgName == null ?
- request.getOverlayThemePackageName() : pkgName);
- }
-
- if (request.getFontThemePackageName() != null) {
- builder.defaultFont(pkgName == null ? request.getFontThemePackageName() : pkgName);
- }
-
- if (request.getStatusBarThemePackageName() != null) {
- builder.overlay(ThemeConfig.SYSTEMUI_STATUS_BAR_PKG, pkgName == null ?
- request.getStatusBarThemePackageName() : pkgName);
- }
-
- if (request.getNavBarThemePackageName() != null) {
- builder.overlay(ThemeConfig.SYSTEMUI_NAVBAR_PKG, pkgName == null ?
- request.getNavBarThemePackageName() : pkgName);
- }
-
- // check for any per app overlays being applied
- Map<String, String> appOverlays = request.getPerAppOverlays();
- for (String appPkgName : appOverlays.keySet()) {
- if (appPkgName != null) {
- builder.overlay(appPkgName, appOverlays.get(appPkgName));
- }
- }
-
- return builder;
- }
-
- private static void removePerAppThemesFromConfig(ThemeConfig.Builder builder,
- ThemeConfig themeConfig) {
- if (themeConfig != null) {
- Map<String, ThemeConfig.AppTheme> themes = themeConfig.getAppThemes();
- for (String appPkgName : themes.keySet()) {
- if (ThemeUtils.isPerAppThemeComponent(appPkgName)) {
- builder.overlay(appPkgName, null);
- }
- }
- }
- }
-
- // Kill the current Home process, they tend to be evil and cache
- // drawable references in all apps
- private void killLaunchers(ThemeChangeRequest request) {
- if (request.getOverlayThemePackageName() == null
- && request.getIconsThemePackageName() == null) {
- return;
- }
-
- final ActivityManager am =
- (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-
- Intent homeIntent = new Intent();
- homeIntent.setAction(Intent.ACTION_MAIN);
- homeIntent.addCategory(Intent.CATEGORY_HOME);
-
- List<ResolveInfo> infos = mPM.queryIntentActivities(homeIntent, 0);
- List<ResolveInfo> themeChangeInfos = mPM.queryBroadcastReceivers(
- new Intent(ThemeUtils.ACTION_THEME_CHANGED), 0);
- for(ResolveInfo info : infos) {
- if (info.activityInfo != null && info.activityInfo.applicationInfo != null &&
- !isSetupActivity(info) && !handlesThemeChanges(
- info.activityInfo.applicationInfo.packageName, themeChangeInfos)) {
- String pkgToStop = info.activityInfo.applicationInfo.packageName;
- Log.d(TAG, "Force stopping " + pkgToStop + " for theme change");
- try {
- am.forceStopPackage(pkgToStop);
- } catch(Exception e) {
- Log.e(TAG, "Unable to force stop package, did you forget platform signature?",
- e);
- }
- }
- }
- }
-
- private boolean isSetupActivity(ResolveInfo info) {
- return GOOGLE_SETUPWIZARD_PACKAGE.equals(info.activityInfo.packageName) ||
- MANAGED_PROVISIONING_PACKAGE.equals(info.activityInfo.packageName) ||
- CM_SETUPWIZARD_PACKAGE.equals(info.activityInfo.packageName);
- }
-
- private boolean handlesThemeChanges(String pkgName, List<ResolveInfo> infos) {
- if (infos != null && infos.size() > 0) {
- for (ResolveInfo info : infos) {
- if (info.activityInfo.applicationInfo.packageName.equals(pkgName)) {
- return true;
- }
- }
- }
- return false;
- }
-
- private void postProgress() {
- int N = mClients.beginBroadcast();
- for(int i=0; i < N; i++) {
- IThemeChangeListener listener = mClients.getBroadcastItem(0);
- try {
- listener.onProgress(mProgress);
- } catch(RemoteException e) {
- Log.w(TAG, "Unable to post progress to client listener", e);
- }
- }
- mClients.finishBroadcast();
- }
-
- private void postFinish(boolean isSuccess, ThemeChangeRequest request, long updateTime) {
- synchronized(this) {
- mProgress = 0;
- }
-
- int N = mClients.beginBroadcast();
- for(int i=0; i < N; i++) {
- IThemeChangeListener listener = mClients.getBroadcastItem(0);
- try {
- listener.onFinish(isSuccess);
- } catch(RemoteException e) {
- Log.w(TAG, "Unable to post progress to client listener", e);
- }
- }
- mClients.finishBroadcast();
-
- // if successful, broadcast that the theme changed
- if (isSuccess) {
- broadcastThemeChange(request, updateTime);
- }
- }
-
- private void postFinishedProcessing(String pkgName) {
- int N = mProcessingListeners.beginBroadcast();
- for(int i=0; i < N; i++) {
- IThemeProcessingListener listener = mProcessingListeners.getBroadcastItem(0);
- try {
- listener.onFinishedProcessing(pkgName);
- } catch(RemoteException e) {
- Log.w(TAG, "Unable to post progress to listener", e);
- }
- }
- mProcessingListeners.finishBroadcast();
- }
-
- private void broadcastThemeChange(ThemeChangeRequest request, long updateTime) {
- Map<String, String> componentMap = request.getThemeComponentsMap();
- if (componentMap == null || componentMap.size() == 0) return;
-
- final Intent intent = new Intent(ThemeUtils.ACTION_THEME_CHANGED);
- ArrayList componentsArrayList = new ArrayList(componentMap.keySet());
- intent.putStringArrayListExtra(ThemeUtils.EXTRA_COMPONENTS, componentsArrayList);
- intent.putExtra(ThemeUtils.EXTRA_REQUEST_TYPE, request.getReqeustType().ordinal());
- intent.putExtra(ThemeUtils.EXTRA_UPDATE_TIME, updateTime);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- }
-
- private void incrementProgress(int increment) {
- synchronized(this) {
- mProgress += increment;
- if (mProgress > 100) mProgress = 100;
- }
- postProgress();
- }
-
- private boolean applyBootAnimation(String themePath) {
- boolean success = false;
- try {
- ZipFile zip = new ZipFile(new File(themePath));
- ZipEntry ze = zip.getEntry(THEME_BOOTANIMATION_PATH);
- if (ze != null) {
- clearBootAnimation();
- BufferedInputStream is = new BufferedInputStream(zip.getInputStream(ze));
- final String bootAnimationPath = SYSTEM_THEME_PATH + File.separator
- + "bootanimation.zip";
- ThemeUtils.copyAndScaleBootAnimation(mContext, is, bootAnimationPath);
- FileUtils.setPermissions(bootAnimationPath,
- FileUtils.S_IRWXU|FileUtils.S_IRGRP|FileUtils.S_IROTH, -1, -1);
- }
- zip.close();
- success = true;
- } catch (Exception e) {
- Log.w(TAG, "Unable to load boot animation for " + themePath, e);
- }
-
- return success;
- }
-
- private void clearBootAnimation() {
- File anim = new File(SYSTEM_THEME_PATH + File.separator + "bootanimation.zip");
- if (anim.exists())
- anim.delete();
- }
-
- private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!mWallpaperChangedByUs) {
- // In case the mixnmatch table has a mods_launcher entry, we'll clear it
- ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder();
- builder.setWallpaper("");
- updateProvider(builder.build(), System.currentTimeMillis());
- } else {
- mWallpaperChangedByUs = false;
- }
- }
- };
-
- private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userHandle >= 0 && userHandle != mCurrentUserId) {
- mCurrentUserId = userHandle;
- ThemeConfig config = ThemeConfig.getBootThemeForUser(mContext.getContentResolver(),
- userHandle);
- if (DEBUG) {
- Log.d(TAG,
- "Changing theme for user " + userHandle + " to " + config.toString());
- }
- ThemeChangeRequest request = new ThemeChangeRequest.Builder(config).build();
- try {
- ((IThemeService) mService).requestThemeChange(request, true);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to change theme for user change", e);
- }
- }
- }
- };
-
- private void processInstalledThemes() {
- final String defaultTheme = getDefaultThemePackageName(mContext);
- Message msg;
- // Make sure the default theme is the first to get processed!
- if (!ThemeConfig.SYSTEM_DEFAULT.equals(defaultTheme)) {
- msg = mHandler.obtainMessage(
- ResourceProcessingHandler.MESSAGE_QUEUE_THEME_FOR_PROCESSING,
- 0, 0, defaultTheme);
- mResourceProcessingHandler.sendMessage(msg);
- }
- // Iterate over all installed packages and queue up the ones that are themes or icon packs
- List<PackageInfo> packages = mPM.getInstalledPackages(0);
- for (PackageInfo info : packages) {
- if (!defaultTheme.equals(info.packageName) &&
- (info.isThemeApk || info.isLegacyIconPackApk)) {
- msg = mHandler.obtainMessage(
- ResourceProcessingHandler.MESSAGE_QUEUE_THEME_FOR_PROCESSING,
- 0, 0, info.packageName);
- mResourceProcessingHandler.sendMessage(msg);
- }
- }
- }
-
- private void sendThemeResourcesCachedBroadcast(String themePkgName, int resultCode) {
- final Intent intent = new Intent(Intent.ACTION_THEME_RESOURCES_CACHED);
- intent.putExtra(Intent.EXTRA_THEME_PACKAGE_NAME, themePkgName);
- intent.putExtra(Intent.EXTRA_THEME_RESULT, resultCode);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- }
-
- /**
- * Posts a notification to let the user know the theme was not installed.
- * @param name
- */
- private void postFailedThemeInstallNotification(String name) {
- NotificationManager nm =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- Notification notice = new Notification.Builder(mContext)
- .setAutoCancel(true)
- .setOngoing(false)
- .setContentTitle(
- mContext.getString(R.string.theme_install_error_title))
- .setContentText(String.format(
- mContext.getString(R.string.theme_install_error_message), name))
- .setSmallIcon(android.R.drawable.stat_notify_error)
- .setWhen(System.currentTimeMillis())
- .build();
- nm.notify(name.hashCode(), notice);
- }
-
- private String getThemeName(PackageInfo pi) {
- if (pi.themeInfo != null) {
- return pi.themeInfo.name;
- } else if (pi.isLegacyIconPackApk) {
- return pi.applicationInfo.name;
- }
-
- return null;
- }
-
- /**
- * Get the default theme package name
- * Historically this was done using {@link ThemeUtils#getDefaultThemePackageName(Context)} but
- * the setting that is queried in that method uses the AOSP settings provider but the setting
- * is now in CMSettings. Since {@link ThemeUtils} is in the core framework we cannot access
- * CMSettings.
- * @param context
- * @return Default theme package name
- */
- private 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;
- }
-
- private void publishThemesTile() {
- // This action should be performed as system
- final int userId = UserHandle.myUserId();
- long token = Binder.clearCallingIdentity();
- try {
- final UserHandle user = new UserHandle(userId);
- final Context resourceContext = QSUtils.getQSTileContext(mContext, userId);
-
- CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(mContext);
- final PendingIntent chooserIntent = getThemeChooserPendingIntent();
- CustomTile tile = new CustomTile.Builder(resourceContext)
- .setLabel(R.string.qs_themes_label)
- .setContentDescription(R.string.qs_themes_content_description)
- .setIcon(R.drawable.ic_qs_themes)
- .setOnClickIntent(chooserIntent)
- .setOnLongClickIntent(chooserIntent)
- .shouldCollapsePanel(true)
- .build();
- statusBarManager.publishTileAsUser(QSConstants.DYNAMIC_TILE_THEMES,
- ThemeManagerService.class.hashCode(), tile, user);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private PendingIntent getThemeChooserPendingIntent() {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(CATEGORY_THEME_CHOOSER);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- return PendingIntent.getActivity(mContext, ThemeManagerService.class.hashCode(),
- intent, 0);
- }
-
- private final IBinder mService = new IThemeService.Stub() {
- @Override
- public void requestThemeChangeUpdates(IThemeChangeListener listener)
- throws RemoteException {
- enforcePermission();
- mClients.register(listener);
- }
-
- @Override
- public void removeUpdates(IThemeChangeListener listener) throws RemoteException {
- enforcePermission();
- mClients.unregister(listener);
- }
-
- @Override
- public void requestThemeChange(ThemeChangeRequest request, boolean removePerAppThemes)
- throws RemoteException {
- enforcePermission();
- Message msg;
-
- /**
- * Since the ThemeService handles compiling theme resource we need to make sure that any
- * of the components we are trying to apply are either already processed or put to the
- * front of the queue and handled before the theme change takes place.
- *
- * TODO: create a callback that can be sent to any ThemeChangeListeners to notify them
- * that the theme will be applied once the processing is done.
- */
- synchronized (mThemesToProcessQueue) {
- Map<String, String> componentMap = request.getThemeComponentsMap();
- for (Object key : componentMap.keySet()) {
- if (ThemesColumns.MODIFIES_OVERLAYS.equals(key) ||
- ThemesColumns.MODIFIES_NAVIGATION_BAR.equals(key) ||
- ThemesColumns.MODIFIES_STATUS_BAR.equals(key) ||
- ThemesColumns.MODIFIES_ICONS.equals(key)) {
- String pkgName = componentMap.get(key);
- if (mThemesToProcessQueue.indexOf(pkgName) > 0) {
- mThemesToProcessQueue.remove(pkgName);
- mThemesToProcessQueue.add(0, pkgName);
- // We want to make sure these resources are taken care of first so
- // send the dequeue message and place it in the front of the queue
- msg = mResourceProcessingHandler.obtainMessage(
- ResourceProcessingHandler.MESSAGE_DEQUEUE_AND_PROCESS_THEME);
- mResourceProcessingHandler.sendMessageAtFrontOfQueue(msg);
- }
- }
- }
- }
- msg = Message.obtain();
- msg.what = ThemeWorkerHandler.MESSAGE_CHANGE_THEME;
- msg.obj = request;
- msg.arg1 = removePerAppThemes ? 1 : 0;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void applyDefaultTheme() {
- enforcePermission();
- Message msg = Message.obtain();
- msg.what = ThemeWorkerHandler.MESSAGE_APPLY_DEFAULT_THEME;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public boolean isThemeApplying() throws RemoteException {
- enforcePermission();
- return mIsThemeApplying;
- }
-
- @Override
- public int getProgress() throws RemoteException {
- enforcePermission();
- synchronized(this) {
- return mProgress;
- }
- }
-
- @Override
- public boolean processThemeResources(String themePkgName) throws RemoteException {
- enforcePermission();
- try {
- mPM.getPackageInfo(themePkgName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- // Package doesn't exist so nothing to process
- return false;
- }
- // Obtain a message and send it to the handler to process this theme
- Message msg = mResourceProcessingHandler.obtainMessage(
- ResourceProcessingHandler.MESSAGE_QUEUE_THEME_FOR_PROCESSING, 0, 0,
- themePkgName);
- mResourceProcessingHandler.sendMessage(msg);
- return true;
- }
-
- @Override
- public boolean isThemeBeingProcessed(String themePkgName) throws RemoteException {
- enforcePermission();
- synchronized (mThemesToProcessQueue) {
- return mThemesToProcessQueue.contains(themePkgName);
- }
- }
-
- @Override
- public void registerThemeProcessingListener(IThemeProcessingListener listener)
- throws RemoteException {
- enforcePermission();
- mProcessingListeners.register(listener);
- }
-
- @Override
- public void unregisterThemeProcessingListener(IThemeProcessingListener listener)
- throws RemoteException {
- enforcePermission();
- mProcessingListeners.unregister(listener);
- }
-
- @Override
- public void rebuildResourceCache() throws RemoteException {
- enforcePermission();
- mHandler.sendEmptyMessage(ThemeWorkerHandler.MESSAGE_REBUILD_RESOURCE_CACHE);
- }
-
- @Override
- public long getLastThemeChangeTime() {
- return mLastThemeChangeTime;
- }
-
- @Override
- public int getLastThemeChangeRequestType() {
- return mLastThemeChangeRequestType;
- }
-
- private void enforcePermission() {
- mContext.enforceCallingOrSelfPermission(ACCESS_THEME_MANAGER, null);
- }
- };
-}
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerServiceBroker.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerServiceBroker.java
new file mode 100644
index 0000000..771c537
--- /dev/null
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerServiceBroker.java
@@ -0,0 +1,366 @@
+/*
+ * 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.platform.internal;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.util.Log;
+import android.util.Slog;
+
+import cyanogenmod.app.CMContextConstants;
+import cyanogenmod.platform.Manifest;
+import cyanogenmod.themes.IThemeChangeListener;
+import cyanogenmod.themes.IThemeProcessingListener;
+import cyanogenmod.themes.IThemeService;
+import cyanogenmod.themes.ThemeChangeRequest;
+
+import org.cyanogenmod.internal.util.ThemeUtils;
+import org.cyanogenmod.platform.internal.common.BrokeredServiceConnection;
+
+import java.io.File;
+
+import static cyanogenmod.platform.Manifest.permission.ACCESS_THEME_MANAGER;
+
+/**
+ * Theme service broker for connecting clients to a backing theme manager service.
+ *
+ * @hide
+ */
+public class ThemeManagerServiceBroker extends BrokerableCMSystemService<IThemeService> {
+ private static final String TAG = ThemeManagerServiceBroker.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final ComponentName SERVICE_COMPONENT =
+ new ComponentName("org.cyanogenmod.themeservice",
+ "org.cyanogenmod.themeservice.ThemeManagerService");
+
+ // Cached change listeners
+ private final RemoteCallbackList<IThemeChangeListener> mChangeListeners =
+ new RemoteCallbackList<>();
+ private final RemoteCallbackList<IThemeProcessingListener> mProcessingListeners =
+ new RemoteCallbackList<>();
+
+ private Context mContext;
+
+ private final IThemeService mServiceStubForFailure = new IThemeService.Stub() {
+ @Override
+ public void requestThemeChangeUpdates(IThemeChangeListener listener) throws RemoteException {
+ }
+
+ @Override
+ public void removeUpdates(IThemeChangeListener listener) throws RemoteException {
+ }
+
+ @Override
+ public void requestThemeChange(ThemeChangeRequest request,
+ boolean removePerAppThemes) throws RemoteException {
+ }
+
+ @Override
+ public void applyDefaultTheme() throws RemoteException {
+ }
+
+ @Override
+ public boolean isThemeApplying() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int getProgress() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean processThemeResources(String themePkgName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean isThemeBeingProcessed(String themePkgName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void registerThemeProcessingListener(
+ IThemeProcessingListener listener) throws RemoteException {
+ }
+
+ @Override
+ public void unregisterThemeProcessingListener(
+ IThemeProcessingListener listener) throws RemoteException {
+ }
+
+ @Override
+ public void rebuildResourceCache() throws RemoteException {
+ }
+
+ @Override
+ public long getLastThemeChangeTime() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int getLastThemeChangeRequestType() throws RemoteException {
+ return 0;
+ }
+ };
+
+ private final class BinderService extends IThemeService.Stub {
+
+ @Override
+ public void requestThemeChangeUpdates(IThemeChangeListener listener)
+ throws RemoteException {
+ enforcePermission();
+ getBrokeredService().requestThemeChangeUpdates(listener);
+ mChangeListeners.register(listener);
+ }
+
+ @Override
+ public void removeUpdates(IThemeChangeListener listener) throws RemoteException {
+ enforcePermission();
+ getBrokeredService().removeUpdates(listener);
+ mChangeListeners.unregister(listener);
+ }
+
+ @Override
+ public void requestThemeChange(ThemeChangeRequest request,
+ boolean removePerAppThemes) throws RemoteException {
+ enforcePermission();
+ getBrokeredService().requestThemeChange(request, removePerAppThemes);
+ }
+
+ @Override
+ public void applyDefaultTheme() throws RemoteException {
+ enforcePermission();
+ getBrokeredService().applyDefaultTheme();
+ }
+
+ @Override
+ public boolean isThemeApplying() throws RemoteException {
+ enforcePermission();
+ return getBrokeredService().isThemeApplying();
+ }
+
+ @Override
+ public int getProgress() throws RemoteException {
+ enforcePermission();
+ return getBrokeredService().getProgress();
+ }
+
+ @Override
+ public boolean processThemeResources(String themePkgName) throws RemoteException {
+ enforcePermission();
+ return getBrokeredService().processThemeResources(themePkgName);
+ }
+
+ @Override
+ public boolean isThemeBeingProcessed(String themePkgName) throws RemoteException {
+ enforcePermission();
+ return getBrokeredService().isThemeBeingProcessed(themePkgName);
+ }
+
+ @Override
+ public void registerThemeProcessingListener(
+ IThemeProcessingListener listener) throws RemoteException {
+ enforcePermission();
+ getBrokeredService().registerThemeProcessingListener(listener);
+ mProcessingListeners.register(listener);
+ }
+
+ @Override
+ public void unregisterThemeProcessingListener(
+ IThemeProcessingListener listener) throws RemoteException {
+ enforcePermission();
+ getBrokeredService().unregisterThemeProcessingListener(listener);
+ mProcessingListeners.unregister(listener);
+ }
+
+ @Override
+ public void rebuildResourceCache() throws RemoteException {
+ enforcePermission();
+ getBrokeredService().rebuildResourceCache();
+ }
+
+ @Override
+ public long getLastThemeChangeTime() throws RemoteException {
+ enforcePermission();
+ return getBrokeredService().getLastThemeChangeTime();
+ }
+
+ @Override
+ public int getLastThemeChangeRequestType() throws RemoteException {
+ enforcePermission();
+ return getBrokeredService().getLastThemeChangeRequestType();
+ }
+ }
+
+ public ThemeManagerServiceBroker(Context context) {
+ super(context);
+ mContext = context;
+ setBrokeredServiceConnection(mServiceConnection);
+ }
+
+ @Override
+ protected IThemeService getIBinderAsIInterface(@NonNull IBinder service) {
+ return IThemeService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected IThemeService getDefaultImplementation() {
+ return mServiceStubForFailure;
+ }
+
+ @Override
+ protected ComponentName getServiceComponent() {
+ return SERVICE_COMPONENT;
+ }
+
+ @Override
+ public String getFeatureDeclaration() {
+ return CMContextConstants.Features.THEMES;
+ }
+
+ @Override
+ public void onStart() {
+ if (DEBUG) Slog.d(TAG, "service started");
+ publishBinderService(CMContextConstants.CM_THEME_SERVICE, new BinderService());
+ }
+
+ @Override
+ protected String getComponentFilteringPermission() {
+ return Manifest.permission.ACCESS_THEME_MANAGER;
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ // create the main theme directory for brokered service
+ createDirIfNotExists(ThemeUtils.SYSTEM_THEME_PATH);
+
+ if (shouldMigrateFilePermissions()) {
+ migrateFilePermissions();
+ }
+ } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
+ tryConnecting();
+ }
+ super.onBootPhase(phase);
+ }
+
+ private BrokeredServiceConnection mServiceConnection = new BrokeredServiceConnection() {
+ @Override
+ public void onBrokeredServiceConnected() {
+ // If any change listeners are cached, register them with the newly connected
+ // service.
+ IThemeService themeService =
+ getBrokeredService();
+ try {
+ int N = mChangeListeners.beginBroadcast();
+ if (themeService != null && N > 0) {
+ for (int i = 0; i < N; i++) {
+ themeService.requestThemeChangeUpdates(
+ mChangeListeners.getBroadcastItem(i));
+ }
+ }
+ } catch (RemoteException e) {
+ /* ignore */
+ } finally {
+ mChangeListeners.finishBroadcast();
+ }
+
+ try {
+ int N = mProcessingListeners.beginBroadcast();
+ if (themeService != null && N > 0) {
+ for (int i = 0; i < N; i++) {
+ themeService.registerThemeProcessingListener(
+ mProcessingListeners.getBroadcastItem(i));
+ }
+ }
+ } catch (RemoteException e) {
+ /* ignore */
+ } finally {
+ mProcessingListeners.finishBroadcast();
+ }
+ }
+
+ @Override
+ public void onBrokeredServiceDisconnected() {
+ }
+ };
+
+ private void enforcePermission() {
+ mContext.enforceCallingOrSelfPermission(ACCESS_THEME_MANAGER, null);
+ }
+
+ /**
+ * @return True if {@link ThemeUtils#SYSTEM_THEME_ALARM_PATH} is not writable by other users
+ */
+ private boolean shouldMigrateFilePermissions() {
+ return isUserWritable(ThemeUtils.SYSTEM_THEME_ALARM_PATH);
+ }
+
+ /**
+ * Ensures other users can write files in /data/system/theme/* which is necessary for the
+ * brokered service to write theme data files. SELinux policies will not allow random third
+ * party apps to write to this location.
+ */
+ private void migrateFilePermissions() {
+ File[] files = new File(ThemeUtils.SYSTEM_THEME_PATH).listFiles();
+ for (File file : files) {
+ setAllUsersWritable(file, true);
+ }
+ }
+
+ private void setAllUsersWritable(File file, boolean recursive) {
+ if (file.isDirectory() && recursive) {
+ File[] files = file.listFiles();
+ for (File childFile : files) {
+ setAllUsersWritable(childFile, recursive);
+ }
+ }
+ if (!isUserWritable(file.getAbsolutePath())) {
+ file.setWritable(true, false);
+ }
+ }
+
+ private boolean isUserWritable(String path) {
+ try {
+ StructStat stat = Os.stat(path);
+ return (stat.st_mode & 2) == 0;
+ } catch (ErrnoException e) {
+ Log.w(TAG, "Cannot stat " + path);
+ }
+ return false;
+ }
+
+ private static void createDirIfNotExists(String dirPath) {
+ final File dir = new File(dirPath);
+ if (!dir.exists()) {
+ if (dir.mkdir()) {
+ FileUtils.setPermissions(dir, FileUtils.S_IRWXU |
+ FileUtils.S_IRWXG| FileUtils.S_IRWXO, -1, -1);
+ }
+ }
+ }
+}
diff --git a/cm/res/AndroidManifest.xml b/cm/res/AndroidManifest.xml
index c0e0e94..66fc804 100644
--- a/cm/res/AndroidManifest.xml
+++ b/cm/res/AndroidManifest.xml
@@ -248,6 +248,11 @@
<permission android:name="cyanogenmod.permission.BIND_CORE_SERVICE"
android:protectionLevel="signature|privileged" />
+ <!-- Not meant for third parties.
+ @hide -->
+ <permission android:name="cyanogenmod.permission.SEND_PROTECTED_THEME_BROADCAST"
+ android:protectionLevel="signature|privileged" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/cm/res/res/values/config.xml b/cm/res/res/values/config.xml
index f83d4a6..92e72cb 100644
--- a/cm/res/res/values/config.xml
+++ b/cm/res/res/values/config.xml
@@ -107,8 +107,8 @@
<item>org.cyanogenmod.platform.internal.CMHardwareService</item>
<item>org.cyanogenmod.platform.internal.AppSuggestManagerService</item>
<item>org.cyanogenmod.platform.internal.PerformanceManagerService</item>
- <item>org.cyanogenmod.platform.internal.ThemeManagerService</item>
- <item>org.cyanogenmod.platform.internal.IconCacheManagerService</item>
+ <item>org.cyanogenmod.platform.internal.ThemeManagerServiceBroker</item>
+ <item>org.cyanogenmod.platform.internal.IconCacheManagerServiceBroker</item>
<item>org.cyanogenmod.platform.internal.LiveLockScreenServiceBroker</item>
<item>org.cyanogenmod.platform.internal.CMWeatherManagerService</item>
<item>org.cyanogenmod.platform.internal.display.LiveDisplayService</item>