cmsdk: Add support for more preference constraints
* Add support for negation- prefix with ! * Add requiresAction constraint, which checks if an application is available to resolve the given action * Fix issues with operations order * Add flag to remove a preference if it's intent can't be resolved * Add helper to allow setting minLines of a preference, since it's common for things to get weird with the kinds of behavior we are introducing everywhere * Add support for nuking *other* preferences. For example with Doze, many devices have custom features and a extra_settings panel which is redundant with the switch. Now we can trivially override the one we don't want. Change-Id: Ibb14b05add56b403013e908db1105dce9d34faad
This commit is contained in:
@@ -16,18 +16,28 @@
|
||||
package cyanogenmod.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Build;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceGroup;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import cyanogenmod.hardware.CMHardwareManager;
|
||||
import cyanogenmod.platform.R;
|
||||
@@ -35,10 +45,14 @@ import cyanogenmod.platform.R;
|
||||
|
||||
/**
|
||||
* Helpers for checking if a device supports various features.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class ConstraintsHelper {
|
||||
|
||||
private static final String TAG = "Constraints";
|
||||
private static final String TAG = "ConstraintsHelper";
|
||||
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
@@ -48,43 +62,52 @@ public class ConstraintsHelper {
|
||||
|
||||
private boolean mAvailable = true;
|
||||
|
||||
private boolean mVerifyIntent = true;
|
||||
|
||||
private int mSummaryMinLines = -1;
|
||||
|
||||
private String mReplacesKey = null;
|
||||
|
||||
public ConstraintsHelper(Context context, AttributeSet attrs, Preference pref) {
|
||||
mContext = context;
|
||||
mAttrs = attrs;
|
||||
mPref = pref;
|
||||
|
||||
mAvailable = checkConstraints();
|
||||
TypedArray a = context.getResources().obtainAttributes(attrs,
|
||||
R.styleable.cm_SelfRemovingPreference);
|
||||
mSummaryMinLines = a.getInteger(R.styleable.cm_SelfRemovingPreference_minSummaryLines, -1);
|
||||
mReplacesKey = a.getString(R.styleable.cm_SelfRemovingPreference_replacesKey);
|
||||
setAvailable(checkConstraints());
|
||||
|
||||
Log.d(TAG, "construct key=" + mPref.getKey() + " available=" + mAvailable);
|
||||
}
|
||||
|
||||
public void setAvailable(boolean available) {
|
||||
mAvailable = available;
|
||||
}
|
||||
|
||||
public void applyConstraints() {
|
||||
if (!mAvailable) {
|
||||
final PreferenceGroup group = getParent(mPref);
|
||||
group.removePreference(mPref);
|
||||
if (group.getPreferenceCount() == 0) {
|
||||
getParent(group).removePreference(group);
|
||||
}
|
||||
if (!available) {
|
||||
Graveyard.get(mContext).addTombstone(mPref.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
private PreferenceGroup getParent(Preference preference)
|
||||
{
|
||||
public boolean isAvailable() {
|
||||
return mAvailable;
|
||||
}
|
||||
|
||||
public void setVerifyIntent(boolean verifyIntent) {
|
||||
mVerifyIntent = verifyIntent;
|
||||
}
|
||||
|
||||
private PreferenceGroup getParent(Preference preference) {
|
||||
return getParent(mPref.getPreferenceManager().getPreferenceScreen(), preference);
|
||||
}
|
||||
|
||||
private PreferenceGroup getParent(PreferenceGroup root, Preference preference)
|
||||
{
|
||||
for (int i = 0; i < root.getPreferenceCount(); i++)
|
||||
{
|
||||
private PreferenceGroup getParent(PreferenceGroup root, Preference preference) {
|
||||
for (int i = 0; i < root.getPreferenceCount(); i++) {
|
||||
Preference p = root.getPreference(i);
|
||||
if (p == preference)
|
||||
return root;
|
||||
if (PreferenceGroup.class.isInstance(p))
|
||||
{
|
||||
PreferenceGroup parent = getParent((PreferenceGroup)p, preference);
|
||||
if (PreferenceGroup.class.isInstance(p)) {
|
||||
PreferenceGroup parent = getParent((PreferenceGroup) p, preference);
|
||||
if (parent != null)
|
||||
return parent;
|
||||
}
|
||||
@@ -92,6 +115,20 @@ public class ConstraintsHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isNegated(String key) {
|
||||
return key != null && key.startsWith("!");
|
||||
}
|
||||
|
||||
private void checkIntent() {
|
||||
Intent i = mPref.getIntent();
|
||||
if (i != null) {
|
||||
if (!resolveIntent(mContext, i)) {
|
||||
Graveyard.get(mContext).addTombstone(mPref.getKey());
|
||||
mAvailable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkConstraints() {
|
||||
if (mAttrs == null) {
|
||||
return true;
|
||||
@@ -110,19 +147,42 @@ public class ConstraintsHelper {
|
||||
|
||||
// Check if a specific package is installed
|
||||
String rPackage = a.getString(R.styleable.cm_SelfRemovingPreference_requiresPackage);
|
||||
if (rPackage != null && !isPackageInstalled(mContext, rPackage, false)) {
|
||||
return false;
|
||||
if (rPackage != null) {
|
||||
boolean negated = isNegated(rPackage);
|
||||
if (negated) {
|
||||
rPackage = rPackage.substring(1);
|
||||
}
|
||||
boolean available = isPackageInstalled(mContext, rPackage, false);
|
||||
if (available == negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if an intent can be resolved to handle the given action
|
||||
String rAction = a.getString(R.styleable.cm_SelfRemovingPreference_requiresAction);
|
||||
if (rAction != null) {
|
||||
boolean negated = isNegated(rAction);
|
||||
if (negated) {
|
||||
rAction = rAction.substring(1);
|
||||
}
|
||||
boolean available = resolveIntent(mContext, rAction);
|
||||
if (available == negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a system feature is available
|
||||
String rFeature = a.getString(R.styleable.cm_SelfRemovingPreference_requiresFeature);
|
||||
if (rFeature != null) {
|
||||
if (rFeature.startsWith("cmhardware:")) {
|
||||
if (!CMHardwareManager.getInstance(mContext).isSupported(
|
||||
rFeature.substring("cmhardware:".length()))) {
|
||||
return false;
|
||||
}
|
||||
} else if (!hasSystemFeature(mContext, rFeature)) {
|
||||
boolean negated = isNegated(rFeature);
|
||||
if (negated) {
|
||||
rFeature = rFeature.substring(1);
|
||||
}
|
||||
boolean available = rFeature.startsWith("cmhardware:") ?
|
||||
CMHardwareManager.getInstance(mContext).isSupported(
|
||||
rFeature.substring("cmhardware:".length())) :
|
||||
hasSystemFeature(mContext, rFeature);
|
||||
if (available == negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -130,15 +190,20 @@ public class ConstraintsHelper {
|
||||
// Check a boolean system property
|
||||
String rProperty = a.getString(R.styleable.cm_SelfRemovingPreference_requiresProperty);
|
||||
if (rProperty != null) {
|
||||
boolean negated = isNegated(rProperty);
|
||||
if (negated) {
|
||||
rProperty = rFeature.substring(1);
|
||||
}
|
||||
String value = SystemProperties.get(rProperty);
|
||||
if (value == null || !Boolean.parseBoolean(value)) {
|
||||
boolean available = value != null && Boolean.parseBoolean(value);
|
||||
if (available == negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check a config resource. This can be a bool or a string. A null string
|
||||
// fails the constraint.
|
||||
TypedValue tv = a.peekValue(R.styleable.cm_SelfRemovingPreference_requiresConfig);
|
||||
TypedValue tv = a.peekValue(R.styleable.cm_SelfRemovingPreference_requiresConfig);
|
||||
if (tv != null) {
|
||||
if (tv.type == TypedValue.TYPE_STRING) {
|
||||
if (tv.resourceId != 0) {
|
||||
@@ -167,6 +232,7 @@ public class ConstraintsHelper {
|
||||
public static boolean hasSystemFeature(Context context, String feature) {
|
||||
return context.getPackageManager().hasSystemFeature(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the device is voice-capable (meaning, it is also a phone).
|
||||
*/
|
||||
@@ -196,16 +262,128 @@ public class ConstraintsHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the device support Doze?
|
||||
* @param context
|
||||
* @return
|
||||
* Checks if a package is available to handle the given action.
|
||||
*/
|
||||
public static boolean isDozeAvailable(Context context) {
|
||||
String name = Build.IS_DEBUGGABLE ? SystemProperties.get("debug.doze.component") : null;
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
name = context.getResources().getString(
|
||||
com.android.internal.R.string.config_dozeComponent);
|
||||
public static boolean resolveIntent(Context context, Intent intent) {
|
||||
if (DEBUG) Log.d(TAG, "resolveIntent " + Objects.toString(intent));
|
||||
// check whether the target handler exist in system
|
||||
PackageManager pm = context.getPackageManager();
|
||||
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
|
||||
PackageManager.MATCH_SYSTEM_ONLY,
|
||||
UserHandle.myUserId());
|
||||
for (ResolveInfo resolveInfo : results) {
|
||||
// check is it installed in system.img, exclude the application
|
||||
// installed by user
|
||||
if (DEBUG) Log.d(TAG, "resolveInfo: " + Objects.toString(resolveInfo));
|
||||
if ((resolveInfo.activityInfo.applicationInfo.flags &
|
||||
ApplicationInfo.FLAG_SYSTEM) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean resolveIntent(Context context, String action) {
|
||||
return resolveIntent(context, new Intent(action));
|
||||
}
|
||||
|
||||
public static int getAttr(Context context, int attr, int fallbackAttr) {
|
||||
TypedValue value = new TypedValue();
|
||||
context.getTheme().resolveAttribute(attr, value, true);
|
||||
if (value.resourceId != 0) {
|
||||
return attr;
|
||||
}
|
||||
return fallbackAttr;
|
||||
}
|
||||
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
checkIntent();
|
||||
|
||||
if (isAvailable() && mReplacesKey != null) {
|
||||
Graveyard.get(mContext).addTombstone(mReplacesKey);
|
||||
}
|
||||
|
||||
Graveyard.get(mContext).summonReaper(mPref.getPreferenceManager());
|
||||
|
||||
if (!isAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSummaryMinLines > 0) {
|
||||
TextView textView = (TextView) holder.itemView.findViewById(android.R.id.summary);
|
||||
if (textView != null) {
|
||||
textView.setMinLines(mSummaryMinLines);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we want to keep this at the preference level vs the fragment level, we need to
|
||||
* collate all the preferences that need to be removed when attached to the
|
||||
* hierarchy, then purge them all when loading is complete. The Graveyard keeps track
|
||||
* of this, and will reap the dead during the first call to onBindViewHolder.
|
||||
*/
|
||||
private static class Graveyard {
|
||||
|
||||
private final Set<String> mDeathRow = new ArraySet<>();
|
||||
|
||||
private static Graveyard sInstance;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private Graveyard(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public synchronized static Graveyard get(Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new Graveyard(context);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void addTombstone(String pref) {
|
||||
synchronized (mDeathRow) {
|
||||
mDeathRow.add(pref);
|
||||
}
|
||||
}
|
||||
|
||||
private PreferenceGroup getParent(Preference p1, Preference p2) {
|
||||
return getParent(p1.getPreferenceManager().getPreferenceScreen(), p2);
|
||||
}
|
||||
|
||||
private PreferenceGroup getParent(PreferenceGroup root, Preference preference) {
|
||||
for (int i = 0; i < root.getPreferenceCount(); i++) {
|
||||
Preference p = root.getPreference(i);
|
||||
if (p == preference)
|
||||
return root;
|
||||
if (PreferenceGroup.class.isInstance(p)) {
|
||||
PreferenceGroup parent = getParent((PreferenceGroup) p, preference);
|
||||
if (parent != null)
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void removePreference(PreferenceManager mgr, Preference pref) {
|
||||
final PreferenceGroup group = getParent(pref, pref);
|
||||
group.removePreference(pref);
|
||||
if (group.getPreferenceCount() == 0) {
|
||||
getParent(pref, group).removePreference(group);
|
||||
}
|
||||
}
|
||||
|
||||
public void summonReaper(PreferenceManager mgr) {
|
||||
synchronized (mDeathRow) {
|
||||
for (String dead : mDeathRow) {
|
||||
Preference deadPref = mgr.findPreference(dead);
|
||||
if (deadPref != null) {
|
||||
removePreference(mgr, deadPref);
|
||||
}
|
||||
}
|
||||
mDeathRow.clear();
|
||||
}
|
||||
}
|
||||
return !TextUtils.isEmpty(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* A Preference which can automatically remove itself from the hierarchy
|
||||
* based on constraints set in XML.
|
||||
*/
|
||||
public class SelfRemovingListPreference extends ListPreference {
|
||||
|
||||
private final ConstraintsHelper mConstraints;
|
||||
@@ -42,10 +46,15 @@ public class SelfRemovingListPreference extends ListPreference {
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
mConstraints.applyConstraints();
|
||||
mConstraints.onBindViewHolder(holder);
|
||||
}
|
||||
|
||||
protected void setAvailable(boolean available) {
|
||||
public void setAvailable(boolean available) {
|
||||
mConstraints.setAvailable(available);
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return mConstraints.isAvailable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package cyanogenmod.preference;
|
||||
import android.content.Context;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.support.v7.preference.R;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
@@ -28,28 +29,36 @@ public class SelfRemovingPreference extends Preference {
|
||||
|
||||
private final ConstraintsHelper mConstraints;
|
||||
|
||||
public SelfRemovingPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
public SelfRemovingPreference(Context context, AttributeSet attrs,
|
||||
int defStyle, int defStyleRes) {
|
||||
super(context, attrs, defStyle, defStyleRes);
|
||||
mConstraints = new ConstraintsHelper(context, attrs, this);
|
||||
}
|
||||
|
||||
public SelfRemovingPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
this(context, attrs, defStyle, 0);
|
||||
}
|
||||
|
||||
public SelfRemovingPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mConstraints = new ConstraintsHelper(context, attrs, this);
|
||||
this(context, attrs, ConstraintsHelper.getAttr(
|
||||
context, R.attr.preferenceStyle, android.R.attr.preferenceStyle));
|
||||
}
|
||||
|
||||
public SelfRemovingPreference(Context context) {
|
||||
super(context);
|
||||
mConstraints = new ConstraintsHelper(context, null, this);
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
mConstraints.applyConstraints();
|
||||
mConstraints.onBindViewHolder(holder);
|
||||
}
|
||||
|
||||
protected void setAvailable(boolean available) {
|
||||
public void setAvailable(boolean available) {
|
||||
mConstraints.setAvailable(available);
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return mConstraints.isAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,14 @@ public class SelfRemovingSwitchPreference extends SwitchPreference {
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
mConstraints.applyConstraints();
|
||||
mConstraints.onBindViewHolder(holder);
|
||||
}
|
||||
|
||||
protected void setAvailable(boolean available) {
|
||||
public void setAvailable(boolean available) {
|
||||
mConstraints.setAvailable(available);
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return mConstraints.isAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user