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:
Steve Kondik
2016-10-10 01:35:59 -07:00
parent 33fc2d4385
commit c27e31ffd7
5 changed files with 252 additions and 85 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
}
}
};
}

View File

@@ -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];
}
};
}

View File

@@ -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());
}
}
};
}