From 78079eacb8a6b8970ae22e6eb01c2eb34b7ee3fb Mon Sep 17 00:00:00 2001 From: Steve Kondik Date: Tue, 11 Oct 2016 08:16:38 -0700 Subject: [PATCH] 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 --- Android.mk | 1 + api/cm_current.txt | 37 ++- sdk/res/res/values/attrs.xml | 3 + sdk/res/res/values/public.xml | 9 +- sdk/res/res/values/symbols.xml | 3 + .../preference/ConstraintsHelper.java | 260 +++++++++++++++--- .../SelfRemovingListPreference.java | 13 +- .../preference/SelfRemovingPreference.java | 25 +- .../SelfRemovingSwitchPreference.java | 8 +- 9 files changed, 280 insertions(+), 79 deletions(-) diff --git a/Android.mk b/Android.mk index 71f37dfb..dc2b7142 100644 --- a/Android.mk +++ b/Android.mk @@ -39,6 +39,7 @@ LOCAL_MODULE_TAGS := optional cmsdk_LOCAL_JAVA_LIBRARIES := \ android-support-v7-preference \ + android-support-v7-recyclerview \ android-support-v14-preference LOCAL_JAVA_LIBRARIES := \ diff --git a/api/cm_current.txt b/api/cm_current.txt index 3d77c1b3..3dc18c7e 100644 --- a/api/cm_current.txt +++ b/api/cm_current.txt @@ -738,6 +738,9 @@ package cyanogenmod.platform { public static final class R.attr { ctor public R.attr(); + field public static final int minSummaryLines = 1057030154; // 0x3f01000a + field public static final int replacesKey = 1057030153; // 0x3f010009 + field public static final int requiresAction = 1057030152; // 0x3f010008 field public static final int requiresConfig = 1057030148; // 0x3f010004 field public static final int requiresFeature = 1057030147; // 0x3f010003 field public static final int requiresOwner = 1057030150; // 0x3f010006 @@ -855,38 +858,41 @@ package cyanogenmod.preference { method protected boolean persistBoolean(boolean); } - public class ConstraintsHelper { - ctor public ConstraintsHelper(android.content.Context, android.util.AttributeSet, Preference); - method public void applyConstraints(); - method public static boolean hasSystemFeature(android.content.Context, java.lang.String); - method public static boolean isDozeAvailable(android.content.Context); - method public static boolean isPackageInstalled(android.content.Context, java.lang.String, boolean); - method public static boolean isVoiceCapable(android.content.Context); - method public void setAvailable(boolean); + public class SecureSettingSwitchPreference extends cyanogenmod.preference.SelfRemovingSwitchPreference { + ctor public SecureSettingSwitchPreference(android.content.Context, android.util.AttributeSet, int); + ctor public SecureSettingSwitchPreference(android.content.Context, android.util.AttributeSet); + ctor public SecureSettingSwitchPreference(android.content.Context); + method protected boolean getPersistedBoolean(boolean); + method protected boolean isPersisted(); + method protected boolean persistBoolean(boolean); } public class SelfRemovingListPreference extends ListPreference { ctor public SelfRemovingListPreference(android.content.Context, android.util.AttributeSet, int); ctor public SelfRemovingListPreference(android.content.Context, android.util.AttributeSet); ctor public SelfRemovingListPreference(android.content.Context); + method public boolean isAvailable(); method public void onBindViewHolder(PreferenceViewHolder); - method protected void setAvailable(boolean); + method public void setAvailable(boolean); } public class SelfRemovingPreference extends Preference { + ctor public SelfRemovingPreference(android.content.Context, android.util.AttributeSet, int, int); ctor public SelfRemovingPreference(android.content.Context, android.util.AttributeSet, int); ctor public SelfRemovingPreference(android.content.Context, android.util.AttributeSet); ctor public SelfRemovingPreference(android.content.Context); + method public boolean isAvailable(); method public void onBindViewHolder(PreferenceViewHolder); - method protected void setAvailable(boolean); + method public void setAvailable(boolean); } public class SelfRemovingSwitchPreference extends SwitchPreference { ctor public SelfRemovingSwitchPreference(android.content.Context, android.util.AttributeSet, int); ctor public SelfRemovingSwitchPreference(android.content.Context, android.util.AttributeSet); ctor public SelfRemovingSwitchPreference(android.content.Context); + method public boolean isAvailable(); method public void onBindViewHolder(PreferenceViewHolder); - method protected void setAvailable(boolean); + method public void setAvailable(boolean); } public class SystemSettingSwitchPreference extends cyanogenmod.preference.SelfRemovingSwitchPreference { @@ -898,15 +904,6 @@ package cyanogenmod.preference { method protected boolean persistBoolean(boolean); } - public class SecureSettingSwitchPreference extends cyanogenmod.preference.SelfRemovingSwitchPreference { - ctor public SecureSettingSwitchPreference(android.content.Context, android.util.AttributeSet, int); - ctor public SecureSettingSwitchPreference(android.content.Context, android.util.AttributeSet); - ctor public SecureSettingSwitchPreference(android.content.Context); - method protected boolean getPersistedBoolean(boolean); - method protected boolean isPersisted(); - method protected boolean persistBoolean(boolean); - } - } package cyanogenmod.profiles { diff --git a/sdk/res/res/values/attrs.xml b/sdk/res/res/values/attrs.xml index c82cb39e..36cfeb84 100644 --- a/sdk/res/res/values/attrs.xml +++ b/sdk/res/res/values/attrs.xml @@ -29,6 +29,9 @@ + + + diff --git a/sdk/res/res/values/public.xml b/sdk/res/res/values/public.xml index 6fc39133..33e8c044 100644 --- a/sdk/res/res/values/public.xml +++ b/sdk/res/res/values/public.xml @@ -1,16 +1,13 @@ - - - - - - + + + diff --git a/sdk/res/res/values/symbols.xml b/sdk/res/res/values/symbols.xml index 8b9e2639..1262bd09 100644 --- a/sdk/res/res/values/symbols.xml +++ b/sdk/res/res/values/symbols.xml @@ -5,7 +5,10 @@ + + + diff --git a/sdk/src/java/cyanogenmod/preference/ConstraintsHelper.java b/sdk/src/java/cyanogenmod/preference/ConstraintsHelper.java index c4e8195f..4b926053 100644 --- a/sdk/src/java/cyanogenmod/preference/ConstraintsHelper.java +++ b/sdk/src/java/cyanogenmod/preference/ConstraintsHelper.java @@ -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 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 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); } } diff --git a/sdk/src/java/cyanogenmod/preference/SelfRemovingListPreference.java b/sdk/src/java/cyanogenmod/preference/SelfRemovingListPreference.java index a4a59304..ca3469f7 100644 --- a/sdk/src/java/cyanogenmod/preference/SelfRemovingListPreference.java +++ b/sdk/src/java/cyanogenmod/preference/SelfRemovingListPreference.java @@ -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(); + } + } diff --git a/sdk/src/java/cyanogenmod/preference/SelfRemovingPreference.java b/sdk/src/java/cyanogenmod/preference/SelfRemovingPreference.java index ff3de3b6..4169ff9b 100644 --- a/sdk/src/java/cyanogenmod/preference/SelfRemovingPreference.java +++ b/sdk/src/java/cyanogenmod/preference/SelfRemovingPreference.java @@ -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(); + } } diff --git a/sdk/src/java/cyanogenmod/preference/SelfRemovingSwitchPreference.java b/sdk/src/java/cyanogenmod/preference/SelfRemovingSwitchPreference.java index 771dc047..3815ff02 100644 --- a/sdk/src/java/cyanogenmod/preference/SelfRemovingSwitchPreference.java +++ b/sdk/src/java/cyanogenmod/preference/SelfRemovingSwitchPreference.java @@ -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(); + } }