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