cmsdk: Add support for PartsRefresher to CMPartsPreference
* Allow CMParts to update the summary (or any detail) of a preference so it can be shown correctly in the UI. * Also some cleanup and refactoring around the parts list. Change-Id: Idc16a7eb1a1e4170671fd068bd240b88fadfb577
This commit is contained in:
@@ -26,8 +26,12 @@
|
||||
<protected-broadcast android:name="cyanogenmod.intent.action.INITIALIZE_CM_HARDWARE" />
|
||||
<protected-broadcast android:name="cyanogenmod.intent.action.ACTION_AUDIO_SESSIONS_CHANGED"
|
||||
android:permission="cyanogenmod.permission.MANAGE_AUDIO_SESSIONS" />
|
||||
<protected-broadcast android:name="cyanogenmod.platform.intent.action.PROFILE_SELECTED" />
|
||||
<protected-broadcast android:name="com.cyanogenmod.intent.action.HOTWORD_INPUT_CHANGED" />
|
||||
|
||||
<protected-broadcast android:name="org.cyanogenmod.settings.REFRESH_SUMMARY" />
|
||||
<protected-broadcast android:name="org.cyanogenmod.cmparts.REFRESH_PART" />
|
||||
|
||||
<!-- Must be required by an, to ensure that only the system can bind to it.
|
||||
@hide -->
|
||||
<permission android:name="cyanogenmod.permission.BIND_CUSTOM_TILE_LISTENER_SERVICE"
|
||||
@@ -275,4 +279,11 @@
|
||||
android:description="@string/permdesc_dataUsageWrite"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<!-- Permission for managing remote parts -->
|
||||
<permission android:name="cyanogenmod.permission.MANAGE_PARTS"
|
||||
android:label="@string/permlab_manageParts"
|
||||
android:description="@string/permdesc_manageParts"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -229,4 +229,8 @@
|
||||
<!-- Privacy Guard -->
|
||||
<string name="privacy_guard_manager_title">Privacy Guard</string>
|
||||
|
||||
<!-- Permissions used by CMParts -->
|
||||
<string name="permlab_manageParts">manage remote settings</string>
|
||||
<string name="permdesc_manageParts">Allows an app to manage CyanogenMod settings</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -15,28 +15,27 @@
|
||||
*/
|
||||
package org.cyanogenmod.internal.cmparts;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import cyanogenmod.preference.SelfRemovingPreference;
|
||||
|
||||
import static org.cyanogenmod.internal.cmparts.PartsList.ACTION_PART_CHANGED;
|
||||
import static org.cyanogenmod.internal.cmparts.PartsList.EXTRA_PART;
|
||||
import static org.cyanogenmod.internal.cmparts.PartsList.EXTRA_PART_KEY;
|
||||
|
||||
public class CMPartsPreference extends SelfRemovingPreference {
|
||||
/**
|
||||
* A link to a remote preference screen which can be used with a minimum amount
|
||||
* of information. Supports summary updates asynchronously.
|
||||
*/
|
||||
public class CMPartsPreference extends SelfRemovingPreference implements PartInfo.RemotePart {
|
||||
|
||||
private static final String TAG = "CMPartsPreference";
|
||||
|
||||
private final PartInfo mPart;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public CMPartsPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs, com.android.internal.R.attr.preferenceScreenStyle);
|
||||
|
||||
mPart = PartsList.getPartInfo(context, getKey());
|
||||
mContext = context;
|
||||
mPart = PartsList.get(context).getPartInfo(getKey());
|
||||
if (mPart == null) {
|
||||
throw new RuntimeException("Part not found: " + getKey());
|
||||
}
|
||||
@@ -46,34 +45,25 @@ public class CMPartsPreference extends SelfRemovingPreference {
|
||||
}
|
||||
|
||||
setIntent(mPart.getIntentForActivity());
|
||||
update();
|
||||
|
||||
onRefresh(context, mPart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttached() {
|
||||
super.onAttached();
|
||||
getContext().registerReceiver(mPartChangedReceiver, new IntentFilter(ACTION_PART_CHANGED));
|
||||
mPart.registerRemote(mContext, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetached() {
|
||||
super.onDetached();
|
||||
getContext().unregisterReceiver(mPartChangedReceiver);
|
||||
mPart.unregisterRemote(mContext, this);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
@Override
|
||||
public void onRefresh(Context context, PartInfo info) {
|
||||
setTitle(mPart.getTitle());
|
||||
setSummary((CharSequence) mPart.getSummary());
|
||||
}
|
||||
|
||||
private final BroadcastReceiver mPartChangedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (ACTION_PART_CHANGED.equals(intent.getAction()) &&
|
||||
mPart.getName().equals(intent.getStringExtra(EXTRA_PART_KEY))) {
|
||||
mPart.updateFrom((PartInfo) intent.getParcelableExtra(EXTRA_PART));
|
||||
update();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,15 +15,19 @@
|
||||
*/
|
||||
package org.cyanogenmod.internal.cmparts;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import cyanogenmod.os.Concierge;
|
||||
|
||||
public class PartInfo implements Parcelable {
|
||||
|
||||
private static final String TAG = PartInfo.class.getSimpleName();
|
||||
|
||||
private final String mName;
|
||||
|
||||
private String mTitle;
|
||||
@@ -82,28 +86,44 @@ public class PartInfo implements Parcelable {
|
||||
return mSummary;
|
||||
}
|
||||
|
||||
public String getFragmentClass() { return mFragmentClass; }
|
||||
public String getFragmentClass() {
|
||||
return mFragmentClass;
|
||||
}
|
||||
|
||||
public void setFragmentClass(String fragmentClass) { mFragmentClass = fragmentClass; };
|
||||
public void setFragmentClass(String fragmentClass) {
|
||||
mFragmentClass = fragmentClass;
|
||||
}
|
||||
|
||||
public int getIconRes() { return mIconRes; }
|
||||
public int getIconRes() {
|
||||
return mIconRes;
|
||||
}
|
||||
|
||||
public void setIconRes(int iconRes) { mIconRes = iconRes; }
|
||||
public void setIconRes(int iconRes) {
|
||||
mIconRes = iconRes;
|
||||
}
|
||||
|
||||
public boolean isAvailable() { return mAvailable; }
|
||||
public boolean isAvailable() {
|
||||
return mAvailable;
|
||||
}
|
||||
|
||||
public void setAvailable(boolean available) { mAvailable = available; }
|
||||
public void setAvailable(boolean available) {
|
||||
mAvailable = available;
|
||||
}
|
||||
|
||||
public int getXmlRes() { return mXmlRes; }
|
||||
public int getXmlRes() {
|
||||
return mXmlRes;
|
||||
}
|
||||
|
||||
public void setXmlRes(int xmlRes) { mXmlRes = xmlRes; }
|
||||
public void setXmlRes(int xmlRes) {
|
||||
mXmlRes = xmlRes;
|
||||
}
|
||||
|
||||
public void updateFrom(PartInfo other) {
|
||||
public boolean updateFrom(PartInfo other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (other.getName().equals(getName())) {
|
||||
return;
|
||||
if (other.equals(this)) {
|
||||
return false;
|
||||
}
|
||||
setTitle(other.getTitle());
|
||||
setSummary(other.getSummary());
|
||||
@@ -111,6 +131,7 @@ public class PartInfo implements Parcelable {
|
||||
setIconRes(other.getIconRes());
|
||||
setAvailable(other.isAvailable());
|
||||
setXmlRes(other.getXmlRes());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,6 +159,22 @@ public class PartInfo implements Parcelable {
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
PartInfo o = (PartInfo) other;
|
||||
return Objects.equals(mName, o.mName) && Objects.equals(mTitle, o.mTitle) &&
|
||||
Objects.equals(mSummary, o.mSummary) && Objects.equals(mFragmentClass, o.mFragmentClass) &&
|
||||
Objects.equals(mIconRes, o.mIconRes) && Objects.equals(mAvailable, o.mAvailable) &&
|
||||
Objects.equals(mXmlRes, o.mXmlRes);
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return PartsList.PARTS_ACTION_PREFIX + "." + mName;
|
||||
}
|
||||
@@ -148,16 +185,28 @@ public class PartInfo implements Parcelable {
|
||||
return i;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<PartInfo> CREATOR =
|
||||
new Parcelable.Creator<PartInfo>() {
|
||||
@Override
|
||||
public PartInfo createFromParcel(Parcel in) {
|
||||
return new PartInfo(in);
|
||||
}
|
||||
public interface RemotePart {
|
||||
public void onRefresh(Context context, PartInfo info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartInfo[] newArray(int size) {
|
||||
return new PartInfo[size];
|
||||
}
|
||||
};
|
||||
public void registerRemote(Context context, final RemotePart remote) {
|
||||
PartsList.get(context).registerRemotePart(mName, remote);
|
||||
}
|
||||
|
||||
public void unregisterRemote(Context context, final RemotePart remote) {
|
||||
PartsList.get(context).unregisterRemotePart(mName, remote);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<PartInfo> CREATOR = new Parcelable.Creator<PartInfo>() {
|
||||
@Override
|
||||
public PartInfo createFromParcel(Parcel in) {
|
||||
return new PartInfo(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartInfo[] newArray(int size) {
|
||||
return new PartInfo[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,22 @@
|
||||
*/
|
||||
package org.cyanogenmod.internal.cmparts;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.util.Xml;
|
||||
|
||||
@@ -34,7 +42,8 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import cyanogenmod.platform.Manifest;
|
||||
|
||||
import static com.android.internal.R.styleable.Preference;
|
||||
import static com.android.internal.R.styleable.Preference_fragment;
|
||||
@@ -42,34 +51,58 @@ import static com.android.internal.R.styleable.Preference_icon;
|
||||
import static com.android.internal.R.styleable.Preference_key;
|
||||
import static com.android.internal.R.styleable.Preference_summary;
|
||||
import static com.android.internal.R.styleable.Preference_title;
|
||||
|
||||
import static cyanogenmod.platform.R.styleable.cm_Searchable;
|
||||
import static cyanogenmod.platform.R.styleable.cm_Searchable_xmlRes;
|
||||
|
||||
public class PartsList {
|
||||
|
||||
private static final String TAG = PartsList.class.getSimpleName();
|
||||
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
|
||||
|
||||
public static final String ACTION_PART_CHANGED = "org.cyanogenmod.cmparts.PART_CHANGED";
|
||||
public static final String ACTION_REFRESH_PART = "org.cyanogenmod.cmparts.REFRESH_PART";
|
||||
|
||||
public static final String EXTRA_PART = "part";
|
||||
public static final String EXTRA_PART_KEY = "key";
|
||||
public static final String EXTRA_PART_SUMMARY = "summary";
|
||||
|
||||
public static final String CMPARTS_PACKAGE = "org.cyanogenmod.cmparts";
|
||||
|
||||
public static final ComponentName CMPARTS_ACTIVITY = new ComponentName(
|
||||
CMPARTS_PACKAGE, CMPARTS_PACKAGE + ".PartsActivity");
|
||||
|
||||
public static final ComponentName CMPARTS_REFRESHER = new ComponentName(
|
||||
CMPARTS_PACKAGE, CMPARTS_PACKAGE + ".RefreshReceiver");
|
||||
|
||||
public static final String PARTS_ACTION_PREFIX = CMPARTS_PACKAGE + ".parts";
|
||||
|
||||
private static final Map<String, PartInfo> sParts = new ArrayMap<>();
|
||||
private final Map<String, PartInfo> mParts = new ArrayMap<>();
|
||||
|
||||
private static final AtomicBoolean sCatalogLoaded = new AtomicBoolean(false);
|
||||
private final Map<String, Set<PartInfo.RemotePart>> mRemotes = new ArrayMap<>();
|
||||
|
||||
public static void loadParts(Context context) {
|
||||
synchronized (sParts) {
|
||||
if (sCatalogLoaded.get()) {
|
||||
return;
|
||||
private final Context mContext;
|
||||
|
||||
private static PartsList sInstance;
|
||||
private static final Object sInstanceLock = new Object();
|
||||
|
||||
private PartsList(Context context) {
|
||||
mContext = context;
|
||||
loadParts();
|
||||
}
|
||||
|
||||
public static PartsList get(Context context) {
|
||||
synchronized (sInstanceLock) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new PartsList(context);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
}
|
||||
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
private void loadParts() {
|
||||
synchronized (mParts) {
|
||||
final PackageManager pm = mContext.getPackageManager();
|
||||
try {
|
||||
final Resources r = pm.getResourcesForApplication(CMPARTS_PACKAGE);
|
||||
if (r == null) {
|
||||
@@ -77,7 +110,7 @@ public class PartsList {
|
||||
}
|
||||
int resId = r.getIdentifier("parts_catalog", "xml", CMPARTS_PACKAGE);
|
||||
if (resId > 0) {
|
||||
loadPartsFromResourceLocked(r, resId, sParts);
|
||||
loadPartsFromResourceLocked(r, resId, mParts);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// no cmparts installed
|
||||
@@ -85,30 +118,21 @@ public class PartsList {
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> getPartsList(Context context) {
|
||||
synchronized (sParts) {
|
||||
if (!sCatalogLoaded.get()) {
|
||||
loadParts(context);
|
||||
}
|
||||
return sParts.keySet();
|
||||
public Set<String> getPartsList() {
|
||||
synchronized (mParts) {
|
||||
return mParts.keySet();
|
||||
}
|
||||
}
|
||||
|
||||
public static PartInfo getPartInfo(Context context, String key) {
|
||||
synchronized (sParts) {
|
||||
if (!sCatalogLoaded.get()) {
|
||||
loadParts(context);
|
||||
}
|
||||
return sParts.get(key);
|
||||
public PartInfo getPartInfo(String key) {
|
||||
synchronized (mParts) {
|
||||
return mParts.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
public static final PartInfo getPartInfoForClass(Context context, String clazz) {
|
||||
synchronized (sParts) {
|
||||
if (!sCatalogLoaded.get()) {
|
||||
loadParts(context);
|
||||
}
|
||||
for (PartInfo info : sParts.values()) {
|
||||
public final PartInfo getPartInfoForClass(String clazz) {
|
||||
synchronized (mParts) {
|
||||
for (PartInfo info : mParts.values()) {
|
||||
if (info.getFragmentClass() != null && info.getFragmentClass().equals(clazz)) {
|
||||
return info;
|
||||
}
|
||||
@@ -117,12 +141,8 @@ public class PartsList {
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadPartsFromResourceLocked(Resources res, int resid,
|
||||
Map<String, PartInfo> target) {
|
||||
if (sCatalogLoaded.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void loadPartsFromResourceLocked(Resources res, int resid,
|
||||
Map<String, PartInfo> target) {
|
||||
XmlResourceParser parser = null;
|
||||
|
||||
try {
|
||||
@@ -138,7 +158,7 @@ public class PartsList {
|
||||
String nodeName = parser.getName();
|
||||
if (!"parts-catalog".equals(nodeName)) {
|
||||
throw new RuntimeException(
|
||||
"XML document must start with <parts-catalog> tag; found"
|
||||
"XML document must start with <parts-catalog> tag; found "
|
||||
+ nodeName + " at " + parser.getPositionDescription());
|
||||
}
|
||||
|
||||
@@ -207,6 +227,99 @@ public class PartsList {
|
||||
} finally {
|
||||
if (parser != null) parser.close();
|
||||
}
|
||||
sCatalogLoaded.set(true);
|
||||
}
|
||||
|
||||
public void registerRemotePart(final String key, final PartInfo.RemotePart remote) {
|
||||
synchronized (mParts) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "registerRemotePart part=" + key + " remote=" + remote.toString());
|
||||
}
|
||||
if (mRemotes.size() == 0) {
|
||||
final IntentFilter filter = new IntentFilter(ACTION_PART_CHANGED);
|
||||
mContext.registerReceiver(mPartChangedReceiver, filter,
|
||||
Manifest.permission.MANAGE_PARTS, null);
|
||||
}
|
||||
|
||||
Set<PartInfo.RemotePart> remotes = mRemotes.get(key);
|
||||
if (remotes == null) {
|
||||
remotes = new ArraySet<PartInfo.RemotePart>();
|
||||
mRemotes.put(key, remotes);
|
||||
}
|
||||
remotes.add(remote);
|
||||
|
||||
final Intent i = new Intent(ACTION_REFRESH_PART);
|
||||
i.setComponent(PartsList.CMPARTS_REFRESHER);
|
||||
|
||||
i.putExtra(EXTRA_PART_KEY, key);
|
||||
|
||||
// Send an ordered broadcast to request a refresh and receive the reply
|
||||
// on the BroadcastReceiver.
|
||||
mContext.sendOrderedBroadcastAsUser(i, UserHandle.CURRENT,
|
||||
Manifest.permission.MANAGE_PARTS,
|
||||
new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
synchronized (mParts) {
|
||||
refreshPartFromBundleLocked(getResultExtras(true));
|
||||
}
|
||||
}
|
||||
}, null, Activity.RESULT_OK, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void refreshPartFromBundleLocked(Bundle result) {
|
||||
PartInfo info = mParts.get(result.getString(EXTRA_PART_KEY));
|
||||
if (info != null) {
|
||||
PartInfo updatedPart = (PartInfo) result.getParcelable(EXTRA_PART);
|
||||
if (updatedPart != null) {
|
||||
if (info.updateFrom(updatedPart)) {
|
||||
Set<PartInfo.RemotePart> remotes = mRemotes.get(info.getName());
|
||||
if (remotes != null && remotes.size() > 0) {
|
||||
for (PartInfo.RemotePart remote : remotes) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "refresh remote=" + remote.toString() +
|
||||
" info=" + info.toString());
|
||||
}
|
||||
remote.onRefresh(mContext, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterRemotePart(String key, final PartInfo.RemotePart remote) {
|
||||
synchronized (mParts) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "unregisterRemotePart: " + key + " remote=" + remote.toString());
|
||||
}
|
||||
Set<PartInfo.RemotePart> remotes = mRemotes.get(key);
|
||||
if (remotes != null) {
|
||||
remotes.remove(remote);
|
||||
if (remotes.size() == 0) {
|
||||
mRemotes.remove(key);
|
||||
if (mRemotes.size() == 0) {
|
||||
mContext.unregisterReceiver(mPartChangedReceiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver for asynchronous updates
|
||||
*/
|
||||
private final BroadcastReceiver mPartChangedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
synchronized (mParts) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "PART_CHANGED: " + intent.toString() +
|
||||
" bundle: " + intent.getExtras().toString());
|
||||
}
|
||||
refreshPartFromBundleLocked(intent.getExtras());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user