diff options
Diffstat (limited to 'cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerServiceBroker.java')
-rw-r--r-- | cm/lib/main/java/org/cyanogenmod/platform/internal/ThemeManagerServiceBroker.java | 366 |
1 files changed, 366 insertions, 0 deletions
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); + } + } + } +} |