diff options
Diffstat (limited to 'src/org/cyanogenmod/launcher/dashclock/ExtensionManager.java')
-rw-r--r-- | src/org/cyanogenmod/launcher/dashclock/ExtensionManager.java | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/launcher/dashclock/ExtensionManager.java b/src/org/cyanogenmod/launcher/dashclock/ExtensionManager.java new file mode 100644 index 000000000..a64b0be2e --- /dev/null +++ b/src/org/cyanogenmod/launcher/dashclock/ExtensionManager.java @@ -0,0 +1,390 @@ +/* + * Copyright 2013 Google Inc. + * Modified 2014 for 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.launcher.dashclock; + +import android.app.backup.BackupManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.util.Log; + +import com.google.android.apps.dashclock.api.DashClockExtension; +import com.google.android.apps.dashclock.api.ExtensionData; + +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A singleton class in charge of extension registration, activation (change in user-specified + * 'active' extensions), and data caching. + */ +public class ExtensionManager { + private static final String TAG = "ExtensionManager"; + + private static final String PREF_ACTIVE_EXTENSIONS = "active_extensions"; + + // No default extensions for now. TODO: include dashclock's default extensions + private static final Class[] DEFAULT_EXTENSIONS = {}; + + private final Context mContext; + + private final List<ExtensionWithData> mActiveExtensions = new ArrayList<ExtensionWithData>(); + private Map<ComponentName, ExtensionWithData> mExtensionInfoMap + = new HashMap<ComponentName, ExtensionWithData>(); + private List<OnChangeListener> mOnChangeListeners = new ArrayList<OnChangeListener>(); + + private SharedPreferences mDefaultPreferences; + private SharedPreferences mValuesPreferences; + private Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); + + private static ExtensionManager sInstance; + + public static ExtensionManager getInstance(Context context, Context hostActivityContext) { + if (sInstance == null) { + sInstance = new ExtensionManager(context, hostActivityContext); + } + + return sInstance; + } + + private ExtensionManager(Context context, Context hostActivityContext) { + mContext = context; + mDefaultPreferences = PreferenceManager.getDefaultSharedPreferences(hostActivityContext); + mValuesPreferences = hostActivityContext.getSharedPreferences("extension_data", 0); + loadActiveExtensionList(); + } + + /** + * De-activates active extensions that are unsupported or are no longer installed. + */ + public boolean cleanupExtensions() { + Set<ComponentName> availableExtensions = new HashSet<ComponentName>(); + for (ExtensionListing listing : getAvailableExtensions()) { + // Ensure the extension protocol version is supported. If it isn't, don't allow its use. + if (!ExtensionHost.supportsProtocolVersion(listing.protocolVersion)) { + Log.w(TAG, "Extension '" + listing.title + "' using unsupported protocol version " + + listing.protocolVersion + "."); + continue; + } + availableExtensions.add(listing.componentName); + } + + boolean cleanupRequired = false; + ArrayList<ComponentName> newActiveExtensions = new ArrayList<ComponentName>(); + + synchronized (mActiveExtensions) { + for (ExtensionWithData ewd : mActiveExtensions) { + if (availableExtensions.contains(ewd.listing.componentName)) { + newActiveExtensions.add(ewd.listing.componentName); + } else { + cleanupRequired = true; + } + } + } + + if (cleanupRequired) { + setActiveExtensions(newActiveExtensions); + return true; + } + + return false; + } + + private void loadActiveExtensionList() { + List<ComponentName> activeExtensions = new ArrayList<ComponentName>(); + String extensions; + if (mDefaultPreferences.contains(PREF_ACTIVE_EXTENSIONS)) { + extensions = mDefaultPreferences.getString(PREF_ACTIVE_EXTENSIONS, ""); + } else { + extensions = createDefaultExtensionList(); + } + String[] componentNameStrings = extensions.split(","); + for (String componentNameString : componentNameStrings) { + if (TextUtils.isEmpty(componentNameString)) { + continue; + } + activeExtensions.add(ComponentName.unflattenFromString(componentNameString)); + } + setActiveExtensions(activeExtensions, false); + } + + private String createDefaultExtensionList() { + StringBuilder sb = new StringBuilder(); + + for (Class cls : DEFAULT_EXTENSIONS) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(new ComponentName(mContext, cls).flattenToString()); + } + + return sb.toString(); + } + + private void saveActiveExtensionList() { + StringBuilder sb = new StringBuilder(); + + synchronized (mActiveExtensions) { + for (ExtensionWithData ci : mActiveExtensions) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(ci.listing.componentName.flattenToString()); + } + } + + mDefaultPreferences.edit() + .putString(PREF_ACTIVE_EXTENSIONS, sb.toString()) + .commit(); + new BackupManager(mContext).dataChanged(); + } + + /** + * Replaces the set of active extensions with the given list. + */ + public void setActiveExtensions(List<ComponentName> extensions) { + setActiveExtensions(extensions, true); + } + + private void setActiveExtensions(List<ComponentName> extensionNames, boolean saveAndNotify) { + Map<ComponentName, ExtensionListing> listings + = new HashMap<ComponentName, ExtensionListing>(); + for (ExtensionListing listing : getAvailableExtensions()) { + listings.put(listing.componentName, listing); + } + + List<ComponentName> activeExtensionNames = getActiveExtensionNames(); + if (activeExtensionNames.equals(extensionNames)) { + Log.d(TAG, "No change to list of active extensions."); + return; + } + + // Clear cached data for any no-longer-active extensions. + for (ComponentName cn : activeExtensionNames) { + if (!extensionNames.contains(cn)) { + destroyExtensionData(cn); + } + } + + // Set the new list of active extensions, loading cached data if necessary. + List<ExtensionWithData> newActiveExtensions = new ArrayList<ExtensionWithData>(); + + for (ComponentName cn : extensionNames) { + if (mExtensionInfoMap.containsKey(cn)) { + newActiveExtensions.add(mExtensionInfoMap.get(cn)); + } else { + ExtensionWithData ewd = new ExtensionWithData(); + ewd.listing = listings.get(cn); + if (ewd.listing == null) { + ewd.listing = new ExtensionListing(); + ewd.listing.componentName = cn; + } + ewd.latestData = deserializeExtensionData(ewd.listing.componentName); + newActiveExtensions.add(ewd); + } + } + + mExtensionInfoMap.clear(); + for (ExtensionWithData ewd : newActiveExtensions) { + mExtensionInfoMap.put(ewd.listing.componentName, ewd); + } + + synchronized (mActiveExtensions) { + mActiveExtensions.clear(); + mActiveExtensions.addAll(newActiveExtensions); + } + + if (saveAndNotify) { + Log.d(TAG, "List of active extensions has changed."); + saveActiveExtensionList(); + notifyOnChangeListeners(null); + } + } + + /** + * Updates and caches the user-visible data for a given extension. + */ + public boolean updateExtensionData(ComponentName cn, ExtensionData data) { + data.clean(); + + ExtensionWithData ewd = mExtensionInfoMap.get(cn); + if (ewd != null && !ExtensionData.equals(ewd.latestData, data)) { + ewd.latestData = data; + serializeExtensionData(ewd.listing.componentName, data); + notifyOnChangeListeners(ewd.listing.componentName); + return true; + } + return false; + } + + private ExtensionData deserializeExtensionData(ComponentName componentName) { + ExtensionData extensionData = new ExtensionData(); + String val = mValuesPreferences.getString(componentName.flattenToString(), ""); + if (!TextUtils.isEmpty(val)) { + try { + extensionData.deserialize((JSONObject) new JSONTokener(val).nextValue()); + } catch (JSONException e) { + Log.e(TAG, "Error loading extension data cache for " + componentName + ".", + e); + } + } + return extensionData; + } + + private void serializeExtensionData(ComponentName componentName, ExtensionData extensionData) { + try { + mValuesPreferences.edit() + .putString(componentName.flattenToString(), + extensionData.serialize().toString()) + .apply(); + } catch (JSONException e) { + Log.e(TAG, "Error storing extension data cache for " + componentName + ".", e); + } + } + + private void destroyExtensionData(ComponentName componentName) { + mValuesPreferences.edit() + .remove(componentName.flattenToString()) + .apply(); + } + + public List<ExtensionWithData> getActiveExtensionsWithData() { + ArrayList<ExtensionWithData> activeExtensions; + synchronized (mActiveExtensions) { + activeExtensions = new ArrayList<ExtensionWithData>(mActiveExtensions); + } + return activeExtensions; + } + + public List<ExtensionWithData> getVisibleExtensionsWithData() { + ArrayList<ExtensionWithData> visibleExtensions = new ArrayList<ExtensionWithData>(); + synchronized (mActiveExtensions) { + for (ExtensionManager.ExtensionWithData ewd : mActiveExtensions) { + if (ewd.latestData.visible()) { + visibleExtensions.add(ewd); + } + } + } + return visibleExtensions; + } + + public List<ComponentName> getActiveExtensionNames() { + List<ComponentName> list = new ArrayList<ComponentName>(); + for (ExtensionWithData ci : mActiveExtensions) { + list.add(ci.listing.componentName); + } + return list; + } + + /** + * Returns a listing of all available (installed) extensions. + */ + public List<ExtensionListing> getAvailableExtensions() { + List<ExtensionListing> availableExtensions = new ArrayList<ExtensionListing>(); + PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> resolveInfos = pm.queryIntentServices( + new Intent(DashClockExtension.ACTION_EXTENSION), PackageManager.GET_META_DATA); + for (ResolveInfo resolveInfo : resolveInfos) { + ExtensionListing listing = new ExtensionListing(); + listing.componentName = new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name); + listing.title = resolveInfo.loadLabel(pm).toString(); + Bundle metaData = resolveInfo.serviceInfo.metaData; + if (metaData != null) { + listing.protocolVersion = metaData.getInt("protocolVersion"); + listing.worldReadable = metaData.getBoolean("worldReadable", false); + listing.description = metaData.getString("description"); + String settingsActivity = metaData.getString("settingsActivity"); + if (!TextUtils.isEmpty(settingsActivity)) { + listing.settingsActivity = ComponentName.unflattenFromString( + resolveInfo.serviceInfo.packageName + "/" + settingsActivity); + } + } + + listing.icon = resolveInfo.loadIcon(pm); + availableExtensions.add(listing); + } + + return availableExtensions; + } + + /** + * Registers a listener to be triggered when either the list of active extensions changes or an + * extension's data changes. + */ + public void addOnChangeListener(OnChangeListener onChangeListener) { + mOnChangeListeners.add(onChangeListener); + } + + /** + * Removes a listener previously registered with {@link #addOnChangeListener}. + */ + public void removeOnChangeListener(OnChangeListener onChangeListener) { + mOnChangeListeners.remove(onChangeListener); + } + + private void notifyOnChangeListeners(final ComponentName sourceExtension) { + mMainThreadHandler.post(new Runnable() { + @Override + public void run() { + for (OnChangeListener listener : mOnChangeListeners) { + listener.onExtensionsChanged(sourceExtension); + } + } + }); + } + + public static interface OnChangeListener { + /** + * @param sourceExtension null if not related to any specific extension (e.g. list of + * extensions has changed). + */ + void onExtensionsChanged(ComponentName sourceExtension); + } + + public static class ExtensionWithData { + public ExtensionListing listing; + public ExtensionData latestData; + } + + public static class ExtensionListing { + public ComponentName componentName; + public int protocolVersion; + public boolean worldReadable; + public String title; + public String description; + public Drawable icon; + public ComponentName settingsActivity; + } +} |