From c4ed8c84cd787a16c959c2cdafb9ac51520419bc Mon Sep 17 00:00:00 2001 From: d34d Date: Fri, 15 Jul 2016 09:56:19 -0700 Subject: [PATCH] Themes: Broker the theme service Change-Id: I429936f63d52eddcb1653515bc94e82f758b57d6 --- .../internal/IconCacheManagerService.java | 116 -- .../IconCacheManagerServiceBroker.java | 100 ++ .../internal/ThemeManagerService.java | 1301 ----------------- .../internal/ThemeManagerServiceBroker.java | 366 +++++ cm/res/AndroidManifest.xml | 5 + cm/res/res/values/config.xml | 4 +- 6 files changed, 473 insertions(+), 1419 deletions(-) delete mode 100644 cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerService.java create mode 100644 cm/lib/main/java/org/cyanogenmod/platform/internal/IconCacheManagerServiceBroker.java delete mode 100644 cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerService.java create mode 100644 cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerServiceBroker.java 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 mOldestFilesFirstComparator = new Comparator() { - @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 { + + 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 mClients = new RemoteCallbackList<>(); - - private final RemoteCallbackList mProcessingListeners = - new RemoteCallbackList<>(); - - final private ArrayList 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 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 defaults = ThemeUtils.getDefaultComponents(mContext); - ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); - for(Map.Entry 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 components; - if (TextUtils.isEmpty(defaultThemeComponents)) { - components = ThemeUtils.getAllComponents(); - } else { - components = new ArrayList( - 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 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 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 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 infos = mPM.queryIntentActivities(homeIntent, 0); - List 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 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 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 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 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 { + 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 mChangeListeners = + new RemoteCallbackList<>(); + private final RemoteCallbackList 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 @@ + + + org.cyanogenmod.platform.internal.CMHardwareService org.cyanogenmod.platform.internal.AppSuggestManagerService org.cyanogenmod.platform.internal.PerformanceManagerService - org.cyanogenmod.platform.internal.ThemeManagerService - org.cyanogenmod.platform.internal.IconCacheManagerService + org.cyanogenmod.platform.internal.ThemeManagerServiceBroker + org.cyanogenmod.platform.internal.IconCacheManagerServiceBroker org.cyanogenmod.platform.internal.LiveLockScreenServiceBroker org.cyanogenmod.platform.internal.CMWeatherManagerService org.cyanogenmod.platform.internal.display.LiveDisplayService