Support for nested bundles in setApplicationRestrictions
Added new restriction types - bundle and bundle-array. Modified RestrictionsManager.getManifestRestrictions to support new hierarchical restrictions. Added RestrictionsManager.convertRestrictionsToBundle, which enables programmatic conversion from a list of RestrictionEntries to a Bundle. Modified read/write methods for application restrictions in UserManagerService. Added unit tests. Bug: 19540606 Change-Id: I32b264e04d5d177ea5b4c39a8ace5ee0ce907970
This commit is contained in:
@@ -8377,6 +8377,7 @@ package android.content {
|
||||
ctor public RestrictionEntry(java.lang.String, boolean);
|
||||
ctor public RestrictionEntry(java.lang.String, java.lang.String[]);
|
||||
ctor public RestrictionEntry(java.lang.String, int);
|
||||
ctor public RestrictionEntry(java.lang.String, android.content.RestrictionEntry[], boolean);
|
||||
ctor public RestrictionEntry(android.os.Parcel);
|
||||
method public int describeContents();
|
||||
method public java.lang.String[] getAllSelectedStrings();
|
||||
@@ -8385,6 +8386,7 @@ package android.content {
|
||||
method public java.lang.String getDescription();
|
||||
method public int getIntValue();
|
||||
method public java.lang.String getKey();
|
||||
method public android.content.RestrictionEntry[] getRestrictions();
|
||||
method public boolean getSelectedState();
|
||||
method public java.lang.String getSelectedString();
|
||||
method public java.lang.String getTitle();
|
||||
@@ -8396,6 +8398,7 @@ package android.content {
|
||||
method public void setChoiceValues(android.content.Context, int);
|
||||
method public void setDescription(java.lang.String);
|
||||
method public void setIntValue(int);
|
||||
method public void setRestrictions(android.content.RestrictionEntry[]);
|
||||
method public void setSelectedState(boolean);
|
||||
method public void setSelectedString(java.lang.String);
|
||||
method public void setTitle(java.lang.String);
|
||||
@@ -8403,6 +8406,8 @@ package android.content {
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator<android.content.RestrictionEntry> CREATOR;
|
||||
field public static final int TYPE_BOOLEAN = 1; // 0x1
|
||||
field public static final int TYPE_BUNDLE = 7; // 0x7
|
||||
field public static final int TYPE_BUNDLE_ARRAY = 8; // 0x8
|
||||
field public static final int TYPE_CHOICE = 2; // 0x2
|
||||
field public static final int TYPE_INTEGER = 5; // 0x5
|
||||
field public static final int TYPE_MULTI_SELECT = 4; // 0x4
|
||||
@@ -8411,6 +8416,7 @@ package android.content {
|
||||
}
|
||||
|
||||
public class RestrictionsManager {
|
||||
method public static android.os.Bundle convertRestrictionsToBundle(java.util.List<android.content.RestrictionEntry>);
|
||||
method public android.content.Intent createLocalApprovalIntent();
|
||||
method public android.os.Bundle getApplicationRestrictions();
|
||||
method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(java.lang.String);
|
||||
|
||||
@@ -8598,6 +8598,7 @@ package android.content {
|
||||
ctor public RestrictionEntry(java.lang.String, boolean);
|
||||
ctor public RestrictionEntry(java.lang.String, java.lang.String[]);
|
||||
ctor public RestrictionEntry(java.lang.String, int);
|
||||
ctor public RestrictionEntry(java.lang.String, android.content.RestrictionEntry[], boolean);
|
||||
ctor public RestrictionEntry(android.os.Parcel);
|
||||
method public int describeContents();
|
||||
method public java.lang.String[] getAllSelectedStrings();
|
||||
@@ -8606,6 +8607,7 @@ package android.content {
|
||||
method public java.lang.String getDescription();
|
||||
method public int getIntValue();
|
||||
method public java.lang.String getKey();
|
||||
method public android.content.RestrictionEntry[] getRestrictions();
|
||||
method public boolean getSelectedState();
|
||||
method public java.lang.String getSelectedString();
|
||||
method public java.lang.String getTitle();
|
||||
@@ -8617,6 +8619,7 @@ package android.content {
|
||||
method public void setChoiceValues(android.content.Context, int);
|
||||
method public void setDescription(java.lang.String);
|
||||
method public void setIntValue(int);
|
||||
method public void setRestrictions(android.content.RestrictionEntry[]);
|
||||
method public void setSelectedState(boolean);
|
||||
method public void setSelectedString(java.lang.String);
|
||||
method public void setTitle(java.lang.String);
|
||||
@@ -8624,6 +8627,8 @@ package android.content {
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator<android.content.RestrictionEntry> CREATOR;
|
||||
field public static final int TYPE_BOOLEAN = 1; // 0x1
|
||||
field public static final int TYPE_BUNDLE = 7; // 0x7
|
||||
field public static final int TYPE_BUNDLE_ARRAY = 8; // 0x8
|
||||
field public static final int TYPE_CHOICE = 2; // 0x2
|
||||
field public static final int TYPE_INTEGER = 5; // 0x5
|
||||
field public static final int TYPE_MULTI_SELECT = 4; // 0x4
|
||||
@@ -8632,6 +8637,7 @@ package android.content {
|
||||
}
|
||||
|
||||
public class RestrictionsManager {
|
||||
method public static android.os.Bundle convertRestrictionsToBundle(java.util.List<android.content.RestrictionEntry>);
|
||||
method public android.content.Intent createLocalApprovalIntent();
|
||||
method public android.os.Bundle getApplicationRestrictions();
|
||||
method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(java.lang.String);
|
||||
|
||||
@@ -20,6 +20,9 @@ import android.annotation.ArrayRes;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Applications can expose restrictions for a restricted user on a
|
||||
* multiuser device. The administrator can configure these restrictions that will then be
|
||||
@@ -33,19 +36,19 @@ import android.os.Parcelable;
|
||||
public class RestrictionEntry implements Parcelable {
|
||||
|
||||
/**
|
||||
* A type of restriction. Use this type for information that needs to be transferred across
|
||||
* but shouldn't be presented to the user in the UI. Stores a single String value.
|
||||
* Hidden restriction type. Use this type for information that needs to be transferred
|
||||
* across but shouldn't be presented to the user in the UI. Stores a single String value.
|
||||
*/
|
||||
public static final int TYPE_NULL = 0;
|
||||
|
||||
/**
|
||||
* A type of restriction. Use this for storing a boolean value, typically presented as
|
||||
* Restriction of type "bool". Use this for storing a boolean value, typically presented as
|
||||
* a checkbox in the UI.
|
||||
*/
|
||||
public static final int TYPE_BOOLEAN = 1;
|
||||
|
||||
/**
|
||||
* A type of restriction. Use this for storing a string value, typically presented as
|
||||
* Restriction of type "choice". Use this for storing a string value, typically presented as
|
||||
* a single-select list. Call {@link #setChoiceEntries(String[])} and
|
||||
* {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
|
||||
* and the corresponding values, respectively.
|
||||
@@ -53,7 +56,7 @@ public class RestrictionEntry implements Parcelable {
|
||||
public static final int TYPE_CHOICE = 2;
|
||||
|
||||
/**
|
||||
* A type of restriction. Use this for storing a string value, typically presented as
|
||||
* Internal restriction type. Use this for storing a string value, typically presented as
|
||||
* a single-select list. Call {@link #setChoiceEntries(String[])} and
|
||||
* {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
|
||||
* and the corresponding values, respectively.
|
||||
@@ -64,8 +67,8 @@ public class RestrictionEntry implements Parcelable {
|
||||
public static final int TYPE_CHOICE_LEVEL = 3;
|
||||
|
||||
/**
|
||||
* A type of restriction. Use this for presenting a multi-select list where more than one
|
||||
* entry can be selected, such as for choosing specific titles to white-list.
|
||||
* Restriction of type "multi-select". Use this for presenting a multi-select list where more
|
||||
* than one entry can be selected, such as for choosing specific titles to white-list.
|
||||
* Call {@link #setChoiceEntries(String[])} and
|
||||
* {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
|
||||
* and the corresponding values, respectively.
|
||||
@@ -75,18 +78,30 @@ public class RestrictionEntry implements Parcelable {
|
||||
public static final int TYPE_MULTI_SELECT = 4;
|
||||
|
||||
/**
|
||||
* A type of restriction. Use this for storing an integer value. The range of values
|
||||
* Restriction of type "integer". Use this for storing an integer value. The range of values
|
||||
* is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}.
|
||||
*/
|
||||
public static final int TYPE_INTEGER = 5;
|
||||
|
||||
/**
|
||||
* A type of restriction. Use this for storing a string value.
|
||||
* Restriction of type "string". Use this for storing a string value.
|
||||
* @see #setSelectedString
|
||||
* @see #getSelectedString
|
||||
*/
|
||||
public static final int TYPE_STRING = 6;
|
||||
|
||||
/**
|
||||
* Restriction of type "bundle". Use this for storing {@link android.os.Bundle bundles} of
|
||||
* restrictions
|
||||
*/
|
||||
public static final int TYPE_BUNDLE = 7;
|
||||
|
||||
/**
|
||||
* Restriction of type "bundle_array". Use this for storing arrays of
|
||||
* {@link android.os.Bundle bundles} of restrictions
|
||||
*/
|
||||
public static final int TYPE_BUNDLE_ARRAY = 8;
|
||||
|
||||
/** The type of restriction. */
|
||||
private int mType;
|
||||
|
||||
@@ -100,13 +115,13 @@ public class RestrictionEntry implements Parcelable {
|
||||
private String mDescription;
|
||||
|
||||
/** The user-visible set of choices used for single-select and multi-select lists. */
|
||||
private String [] mChoiceEntries;
|
||||
private String[] mChoiceEntries;
|
||||
|
||||
/** The values corresponding to the user-visible choices. The value(s) of this entry will
|
||||
* one or more of these, returned by {@link #getAllSelectedStrings()} and
|
||||
* {@link #getSelectedString()}.
|
||||
*/
|
||||
private String [] mChoiceValues;
|
||||
private String[] mChoiceValues;
|
||||
|
||||
/* The chosen value, whose content depends on the type of the restriction. */
|
||||
private String mCurrentValue;
|
||||
@@ -114,6 +129,12 @@ public class RestrictionEntry implements Parcelable {
|
||||
/* List of selected choices in the multi-select case. */
|
||||
private String[] mCurrentValues;
|
||||
|
||||
/**
|
||||
* List of nested restrictions. Used by {@link #TYPE_BUNDLE bundle} and
|
||||
* {@link #TYPE_BUNDLE_ARRAY bundle_array} restrictions.
|
||||
*/
|
||||
private RestrictionEntry[] mRestrictions;
|
||||
|
||||
/**
|
||||
* Constructor for specifying the type and key, with no initial value;
|
||||
*
|
||||
@@ -169,6 +190,35 @@ public class RestrictionEntry implements Parcelable {
|
||||
setIntValue(selectedInt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for {@link #TYPE_BUNDLE}/{@link #TYPE_BUNDLE_ARRAY} type.
|
||||
* @param key the unique key for this restriction
|
||||
* @param restrictionEntries array of nested restriction entries. If the entry, being created
|
||||
* represents a {@link #TYPE_BUNDLE_ARRAY bundle-array}, {@code restrictionEntries} array may
|
||||
* only contain elements of type {@link #TYPE_BUNDLE bundle}.
|
||||
* @param isBundleArray true if this restriction represents
|
||||
* {@link #TYPE_BUNDLE_ARRAY bundle-array} type, otherwise the type will be set to
|
||||
* {@link #TYPE_BUNDLE bundle}.
|
||||
*/
|
||||
public RestrictionEntry(String key, RestrictionEntry[] restrictionEntries,
|
||||
boolean isBundleArray) {
|
||||
mKey = key;
|
||||
if (isBundleArray) {
|
||||
mType = TYPE_BUNDLE_ARRAY;
|
||||
if (restrictionEntries != null) {
|
||||
for (RestrictionEntry restriction : restrictionEntries) {
|
||||
if (restriction.getType() != TYPE_BUNDLE) {
|
||||
throw new IllegalArgumentException("bundle_array restriction can only have "
|
||||
+ "nested restriction entries of type bundle");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mType = TYPE_BUNDLE;
|
||||
}
|
||||
setRestrictions(restrictionEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type for this restriction.
|
||||
* @param type the type for this restriction.
|
||||
@@ -282,6 +332,22 @@ public class RestrictionEntry implements Parcelable {
|
||||
mChoiceValues = context.getResources().getStringArray(stringArrayResId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of possible restriction entries that this entry may contain.
|
||||
*/
|
||||
public RestrictionEntry[] getRestrictions() {
|
||||
return mRestrictions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an array of possible restriction entries, that this entry may contain.
|
||||
* <p>This method is only relevant for types {@link #TYPE_BUNDLE} and
|
||||
* {@link #TYPE_BUNDLE_ARRAY}
|
||||
*/
|
||||
public void setRestrictions(RestrictionEntry[] restrictions) {
|
||||
mRestrictions = restrictions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of possible string values set earlier.
|
||||
* @return the list of possible values.
|
||||
@@ -362,27 +428,30 @@ public class RestrictionEntry implements Parcelable {
|
||||
this.mTitle = title;
|
||||
}
|
||||
|
||||
private boolean equalArrays(String[] one, String[] other) {
|
||||
if (one.length != other.length) return false;
|
||||
for (int i = 0; i < one.length; i++) {
|
||||
if (!one[i].equals(other[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof RestrictionEntry)) return false;
|
||||
final RestrictionEntry other = (RestrictionEntry) o;
|
||||
// Make sure that either currentValue matches or currentValues matches.
|
||||
return mType == other.mType && mKey.equals(other.mKey)
|
||||
&&
|
||||
((mCurrentValues == null && other.mCurrentValues == null
|
||||
&& mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue))
|
||||
||
|
||||
(mCurrentValue == null && other.mCurrentValue == null
|
||||
&& mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues)));
|
||||
if (mType != other.mType || mKey.equals(other.mKey)) {
|
||||
return false;
|
||||
}
|
||||
if (mCurrentValues == null && other.mCurrentValues == null
|
||||
&& mRestrictions == null && other.mRestrictions == null
|
||||
&& Objects.equals(mCurrentValue, other.mCurrentValue)) {
|
||||
return true;
|
||||
}
|
||||
if (mCurrentValue == null && other.mCurrentValue == null
|
||||
&& mRestrictions == null && other.mRestrictions == null
|
||||
&& Arrays.equals(mCurrentValues, other.mCurrentValues)) {
|
||||
return true;
|
||||
}
|
||||
if (mCurrentValue == null && other.mCurrentValue == null
|
||||
&& mCurrentValue == null && other.mCurrentValue == null
|
||||
&& Arrays.equals(mRestrictions, other.mRestrictions)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -397,28 +466,28 @@ public class RestrictionEntry implements Parcelable {
|
||||
result = 31 * result + value.hashCode();
|
||||
}
|
||||
}
|
||||
} else if (mRestrictions != null) {
|
||||
result = 31 * result + Arrays.hashCode(mRestrictions);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String[] readArray(Parcel in) {
|
||||
int count = in.readInt();
|
||||
String[] values = new String[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
values[i] = in.readString();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public RestrictionEntry(Parcel in) {
|
||||
mType = in.readInt();
|
||||
mKey = in.readString();
|
||||
mTitle = in.readString();
|
||||
mDescription = in.readString();
|
||||
mChoiceEntries = readArray(in);
|
||||
mChoiceValues = readArray(in);
|
||||
mChoiceEntries = in.readStringArray();
|
||||
mChoiceValues = in.readStringArray();
|
||||
mCurrentValue = in.readString();
|
||||
mCurrentValues = readArray(in);
|
||||
mCurrentValues = in.readStringArray();
|
||||
Parcelable[] parcelables = in.readParcelableArray(null);
|
||||
if (parcelables != null) {
|
||||
mRestrictions = new RestrictionEntry[parcelables.length];
|
||||
for (int i = 0; i < parcelables.length; i++) {
|
||||
mRestrictions[i] = (RestrictionEntry) parcelables[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -426,27 +495,17 @@ public class RestrictionEntry implements Parcelable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void writeArray(Parcel dest, String[] values) {
|
||||
if (values == null) {
|
||||
dest.writeInt(0);
|
||||
} else {
|
||||
dest.writeInt(values.length);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
dest.writeString(values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mType);
|
||||
dest.writeString(mKey);
|
||||
dest.writeString(mTitle);
|
||||
dest.writeString(mDescription);
|
||||
writeArray(dest, mChoiceEntries);
|
||||
writeArray(dest, mChoiceValues);
|
||||
dest.writeStringArray(mChoiceEntries);
|
||||
dest.writeStringArray(mChoiceValues);
|
||||
dest.writeString(mCurrentValue);
|
||||
writeArray(dest, mCurrentValues);
|
||||
dest.writeStringArray(mCurrentValues);
|
||||
dest.writeParcelableArray(mRestrictions, 0);
|
||||
}
|
||||
|
||||
public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
|
||||
@@ -461,6 +520,16 @@ public class RestrictionEntry implements Parcelable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}";
|
||||
return "RestrictionEntry{" +
|
||||
"mType=" + mType +
|
||||
", mKey='" + mKey + '\'' +
|
||||
", mTitle='" + mTitle + '\'' +
|
||||
", mDescription='" + mDescription + '\'' +
|
||||
", mChoiceEntries=" + Arrays.toString(mChoiceEntries) +
|
||||
", mChoiceValues=" + Arrays.toString(mChoiceValues) +
|
||||
", mCurrentValue='" + mCurrentValue + '\'' +
|
||||
", mCurrentValues=" + Arrays.toString(mCurrentValues) +
|
||||
", mRestrictions=" + Arrays.toString(mRestrictions) +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,14 @@ import android.util.Log;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -71,12 +73,15 @@ import java.util.List;
|
||||
* android:key="string"
|
||||
* android:title="string resource"
|
||||
* android:restrictionType=["bool" | "string" | "integer"
|
||||
* | "choice" | "multi-select" | "hidden"]
|
||||
* | "choice" | "multi-select" | "hidden"
|
||||
* | "bundle" | "bundle_array"]
|
||||
* android:description="string resource"
|
||||
* android:entries="string-array resource"
|
||||
* android:entryValues="string-array resource"
|
||||
* android:defaultValue="reference"
|
||||
* />
|
||||
* android:defaultValue="reference" >
|
||||
* <restriction ... />
|
||||
* ...
|
||||
* </restriction>
|
||||
* <restriction ... />
|
||||
* ...
|
||||
* </restrictions>
|
||||
@@ -97,6 +102,9 @@ import java.util.List;
|
||||
* administrator controlling the values, if the title is not sufficient.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Only restrictions of type {@code bundle} and {@code bundle_array} can have one or multiple nested
|
||||
* restriction elements.
|
||||
* <p>
|
||||
* In your manifest's <code>application</code> section, add the meta-data tag to point to
|
||||
* the restrictions XML file as shown below:
|
||||
* <pre>
|
||||
@@ -537,9 +545,7 @@ public class RestrictionsManager {
|
||||
|
||||
XmlResourceParser xml =
|
||||
appInfo.loadXmlMetaData(mContext.getPackageManager(), META_DATA_APP_RESTRICTIONS);
|
||||
List<RestrictionEntry> restrictions = loadManifestRestrictions(packageName, xml);
|
||||
|
||||
return restrictions;
|
||||
return loadManifestRestrictions(packageName, xml);
|
||||
}
|
||||
|
||||
private List<RestrictionEntry> loadManifestRestrictions(String packageName,
|
||||
@@ -550,23 +556,16 @@ public class RestrictionsManager {
|
||||
} catch (NameNotFoundException nnfe) {
|
||||
return null;
|
||||
}
|
||||
ArrayList<RestrictionEntry> restrictions = new ArrayList<RestrictionEntry>();
|
||||
ArrayList<RestrictionEntry> restrictions = new ArrayList<>();
|
||||
RestrictionEntry restriction;
|
||||
|
||||
try {
|
||||
int tagType = xml.next();
|
||||
while (tagType != XmlPullParser.END_DOCUMENT) {
|
||||
if (tagType == XmlPullParser.START_TAG) {
|
||||
if (xml.getName().equals(TAG_RESTRICTION)) {
|
||||
AttributeSet attrSet = Xml.asAttributeSet(xml);
|
||||
if (attrSet != null) {
|
||||
TypedArray a = appContext.obtainStyledAttributes(attrSet,
|
||||
com.android.internal.R.styleable.RestrictionEntry);
|
||||
restriction = loadRestriction(appContext, a);
|
||||
if (restriction != null) {
|
||||
restrictions.add(restriction);
|
||||
}
|
||||
}
|
||||
restriction = loadRestrictionElement(appContext, xml);
|
||||
if (restriction != null) {
|
||||
restrictions.add(restriction);
|
||||
}
|
||||
}
|
||||
tagType = xml.next();
|
||||
@@ -582,7 +581,21 @@ public class RestrictionsManager {
|
||||
return restrictions;
|
||||
}
|
||||
|
||||
private RestrictionEntry loadRestriction(Context appContext, TypedArray a) {
|
||||
private RestrictionEntry loadRestrictionElement(Context appContext, XmlResourceParser xml)
|
||||
throws IOException, XmlPullParserException {
|
||||
if (xml.getName().equals(TAG_RESTRICTION)) {
|
||||
AttributeSet attrSet = Xml.asAttributeSet(xml);
|
||||
if (attrSet != null) {
|
||||
TypedArray a = appContext.obtainStyledAttributes(attrSet,
|
||||
com.android.internal.R.styleable.RestrictionEntry);
|
||||
return loadRestriction(appContext, a, xml);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private RestrictionEntry loadRestriction(Context appContext, TypedArray a, XmlResourceParser xml)
|
||||
throws IOException, XmlPullParserException {
|
||||
String key = a.getString(R.styleable.RestrictionEntry_key);
|
||||
int restrictionType = a.getInt(
|
||||
R.styleable.RestrictionEntry_restrictionType, -1);
|
||||
@@ -633,9 +646,90 @@ public class RestrictionsManager {
|
||||
restriction.setSelectedState(
|
||||
a.getBoolean(R.styleable.RestrictionEntry_defaultValue, false));
|
||||
break;
|
||||
case RestrictionEntry.TYPE_BUNDLE:
|
||||
case RestrictionEntry.TYPE_BUNDLE_ARRAY:
|
||||
final int outerDepth = xml.getDepth();
|
||||
List<RestrictionEntry> restrictionEntries = new ArrayList<>();
|
||||
while (XmlUtils.nextElementWithin(xml, outerDepth)) {
|
||||
RestrictionEntry childEntry = loadRestrictionElement(appContext, xml);
|
||||
if (childEntry == null) {
|
||||
Log.w(TAG, "Child entry cannot be loaded for bundle restriction " + key);
|
||||
} else {
|
||||
restrictionEntries.add(childEntry);
|
||||
if (restrictionType == RestrictionEntry.TYPE_BUNDLE_ARRAY
|
||||
&& childEntry.getType() != RestrictionEntry.TYPE_BUNDLE) {
|
||||
Log.w(TAG, "bundle_array " + key
|
||||
+ " can only contain entries of type bundle");
|
||||
}
|
||||
}
|
||||
}
|
||||
restriction.setRestrictions(restrictionEntries.toArray(new RestrictionEntry[
|
||||
restrictionEntries.size()]));
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Unknown restriction type " + restrictionType);
|
||||
}
|
||||
return restriction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of restrictions to the corresponding bundle, using the following mapping:
|
||||
* <table>
|
||||
* <tr><th>RestrictionEntry</th><th>Bundle</th></tr>
|
||||
* <tr><td>{@link RestrictionEntry#TYPE_BOOLEAN}</td><td>{@link Bundle#putBoolean}</td></tr>
|
||||
* <tr><td>{@link RestrictionEntry#TYPE_CHOICE}, {@link RestrictionEntry#TYPE_CHOICE}</td>
|
||||
* <td>{@link Bundle#putStringArray}</td></tr>
|
||||
* <tr><td>{@link RestrictionEntry#TYPE_INTEGER}</td><td>{@link Bundle#putInt}</td></tr>
|
||||
* <tr><td>{@link RestrictionEntry#TYPE_STRING}</td><td>{@link Bundle#putString}</td></tr>
|
||||
* <tr><td>{@link RestrictionEntry#TYPE_BUNDLE}</td><td>{@link Bundle#putBundle}</td></tr>
|
||||
* <tr><td>{@link RestrictionEntry#TYPE_BUNDLE_ARRAY}</td>
|
||||
* <td>{@link Bundle#putParcelableArray}</td></tr>
|
||||
* </table>
|
||||
* @param entries list of restrictions
|
||||
*/
|
||||
public static Bundle convertRestrictionsToBundle(List<RestrictionEntry> entries) {
|
||||
final Bundle bundle = new Bundle();
|
||||
for (RestrictionEntry entry : entries) {
|
||||
addRestrictionToBundle(bundle, entry);
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private static Bundle addRestrictionToBundle(Bundle bundle, RestrictionEntry entry) {
|
||||
switch (entry.getType()) {
|
||||
case RestrictionEntry.TYPE_BOOLEAN:
|
||||
bundle.putBoolean(entry.getKey(), entry.getSelectedState());
|
||||
break;
|
||||
case RestrictionEntry.TYPE_CHOICE:
|
||||
case RestrictionEntry.TYPE_CHOICE_LEVEL:
|
||||
case RestrictionEntry.TYPE_MULTI_SELECT:
|
||||
bundle.putStringArray(entry.getKey(), entry.getAllSelectedStrings());
|
||||
break;
|
||||
case RestrictionEntry.TYPE_INTEGER:
|
||||
bundle.putInt(entry.getKey(), entry.getIntValue());
|
||||
break;
|
||||
case RestrictionEntry.TYPE_STRING:
|
||||
case RestrictionEntry.TYPE_NULL:
|
||||
bundle.putString(entry.getKey(), entry.getSelectedString());
|
||||
break;
|
||||
case RestrictionEntry.TYPE_BUNDLE:
|
||||
RestrictionEntry[] restrictions = entry.getRestrictions();
|
||||
Bundle childBundle = convertRestrictionsToBundle(Arrays.asList(restrictions));
|
||||
bundle.putBundle(entry.getKey(), childBundle);
|
||||
break;
|
||||
case RestrictionEntry.TYPE_BUNDLE_ARRAY:
|
||||
restrictions = entry.getRestrictions();
|
||||
Bundle[] bundleArray = new Bundle[restrictions.length];
|
||||
for (int i = 0; i < restrictions.length; i++) {
|
||||
bundleArray[i] = addRestrictionToBundle(new Bundle(), restrictions[i]);
|
||||
}
|
||||
bundle.putParcelableArray(entry.getKey(), bundleArray);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported restrictionEntry type: " + entry.getType());
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7759,6 +7759,8 @@
|
||||
<enum name="multi-select" value="4" />
|
||||
<enum name="integer" value="5" />
|
||||
<enum name="string" value="6" />
|
||||
<enum name="bundle" value="7" />
|
||||
<enum name="bundle_array" value="8" />
|
||||
</attr>
|
||||
<attr name="title" />
|
||||
<attr name="description" />
|
||||
|
||||
@@ -113,6 +113,9 @@
|
||||
<application android:theme="@style/Theme">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
<uses-library android:name="org.apache.http.legacy" android:required="false" />
|
||||
<meta-data
|
||||
android:name="android.content.APP_RESTRICTIONS"
|
||||
android:resource="@xml/app_restrictions" />
|
||||
<activity android:name="android.view.ViewAttachTestActivity" android:label="View Attach Test">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -135,4 +135,8 @@
|
||||
<string name="first">Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.</string>
|
||||
<string name="actor">Abe Lincoln</string>
|
||||
<string name="caption">Lincoln adressing the crowd at Gettysburgh</string>
|
||||
|
||||
<!-- RestrictionsManagerTest -->
|
||||
<string name="restrictionManager_title">Title</string>
|
||||
<string name="restrictionManager_desc">Description</string>
|
||||
</resources>
|
||||
|
||||
55
core/tests/coretests/res/xml/app_restrictions.xml
Normal file
55
core/tests/coretests/res/xml/app_restrictions.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<restriction android:key="hidden_key"
|
||||
android:restrictionType="hidden"/>
|
||||
<restriction
|
||||
android:defaultValue="true"
|
||||
android:description="@string/restrictionManager_desc"
|
||||
android:key="bool_key"
|
||||
android:restrictionType="bool"
|
||||
android:title="@string/restrictionManager_title"/>
|
||||
<restriction
|
||||
android:defaultValue="test"
|
||||
android:key="string_key"
|
||||
android:restrictionType="string"/>
|
||||
|
||||
<restriction android:key="int_key"
|
||||
android:restrictionType="integer"
|
||||
android:defaultValue="15"/>
|
||||
<restriction android:key="bundle_key"
|
||||
android:restrictionType="bundle">
|
||||
<restriction
|
||||
android:key="bundle_string_key"
|
||||
android:restrictionType="string"/>
|
||||
<restriction
|
||||
android:defaultValue="true"
|
||||
android:key="bundle_bool_key"
|
||||
android:restrictionType="bool"/>
|
||||
|
||||
</restriction>
|
||||
<restriction android:key="bundle_array_key"
|
||||
android:restrictionType="bundle_array">
|
||||
<restriction android:key="bundle_array_int"
|
||||
android:restrictionType="integer"/>
|
||||
<restriction android:key="bundle_array_bundle_key"
|
||||
android:restrictionType="bundle">
|
||||
<restriction android:key="bundle_array_bundle_int_key"
|
||||
android:restrictionType="integer"/>
|
||||
</restriction>
|
||||
</restriction>
|
||||
</restrictions>
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
package android.content;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class RestrictionsManagerTest extends AndroidTestCase {
|
||||
private RestrictionsManager mRm;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mRm = (RestrictionsManager) mContext.getSystemService(Context.RESTRICTIONS_SERVICE);
|
||||
}
|
||||
|
||||
public void testGetManifestRestrictions() {
|
||||
String packageName = getContext().getPackageName();
|
||||
List<RestrictionEntry> manifestRestrictions = mRm.getManifestRestrictions(packageName);
|
||||
assertEquals(6, manifestRestrictions.size());
|
||||
Set<String> verifiedKeys = new HashSet<>(Arrays.asList("bundle_key", "bundle_array_key",
|
||||
"bundle_array_bundle_key"));
|
||||
for (RestrictionEntry entry : manifestRestrictions) {
|
||||
if ("bundle_key".equals(entry.getKey())) {
|
||||
assertEquals("bundle_key entry should have 2 children entries",
|
||||
2, entry.getRestrictions().length);
|
||||
verifiedKeys.remove(entry.getKey());
|
||||
} else if ("bundle_array_key".equals(entry.getKey())) {
|
||||
assertEquals("bundle_array_key should have 2 children entries",
|
||||
2, entry.getRestrictions().length);
|
||||
assertNotNull(entry.getRestrictions());
|
||||
for (RestrictionEntry childEntry : entry.getRestrictions()) {
|
||||
if ("bundle_array_bundle_key".equals(childEntry.getKey())) {
|
||||
assertNotNull(childEntry.getRestrictions());
|
||||
assertEquals("bundle_array_bundle_key should have 1 child entry",
|
||||
1, childEntry.getRestrictions().length);
|
||||
verifiedKeys.remove(childEntry.getKey());
|
||||
}
|
||||
}
|
||||
verifiedKeys.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
assertTrue("Entries" + verifiedKeys + " were not found", verifiedKeys.isEmpty());
|
||||
}
|
||||
|
||||
public void testConvertRestrictionsToBundle() {
|
||||
String packageName = getContext().getPackageName();
|
||||
List<RestrictionEntry> manifestRestrictions = mRm.getManifestRestrictions(packageName);
|
||||
Bundle bundle = RestrictionsManager.convertRestrictionsToBundle(manifestRestrictions);
|
||||
assertEquals(6, bundle.size());
|
||||
Bundle childBundle = bundle.getBundle("bundle_key");
|
||||
assertNotNull(childBundle);
|
||||
assertEquals(2, childBundle.size());
|
||||
Parcelable[] childBundleArray = bundle.getParcelableArray("bundle_array_key");
|
||||
assertEquals(2, childBundleArray.length);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import android.os.Handler;
|
||||
import android.os.IUserManager;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
@@ -49,9 +50,11 @@ import android.util.SparseBooleanArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.IAppOpsService;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
@@ -71,6 +74,8 @@ import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
public class UserManagerService extends IUserManager.Stub {
|
||||
|
||||
private static final String LOG_TAG = "UserManagerService";
|
||||
@@ -107,6 +112,8 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
private static final String ATTR_TYPE_STRING = "s";
|
||||
private static final String ATTR_TYPE_BOOLEAN = "b";
|
||||
private static final String ATTR_TYPE_INTEGER = "i";
|
||||
private static final String ATTR_TYPE_BUNDLE = "B";
|
||||
private static final String ATTR_TYPE_BUNDLE_ARRAY = "BA";
|
||||
|
||||
private static final String USER_INFO_DIR = "system" + File.separator + "users";
|
||||
private static final String USER_LIST_FILENAME = "userlist.xml";
|
||||
@@ -1672,124 +1679,168 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
|
||||
private Bundle readApplicationRestrictionsLocked(String packageName,
|
||||
int userId) {
|
||||
AtomicFile restrictionsFile =
|
||||
new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
|
||||
packageToRestrictionsFileName(packageName)));
|
||||
return readApplicationRestrictionsLocked(restrictionsFile);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static Bundle readApplicationRestrictionsLocked(AtomicFile restrictionsFile) {
|
||||
final Bundle restrictions = new Bundle();
|
||||
final ArrayList<String> values = new ArrayList<String>();
|
||||
final ArrayList<String> values = new ArrayList<>();
|
||||
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
AtomicFile restrictionsFile =
|
||||
new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
|
||||
packageToRestrictionsFileName(packageName)));
|
||||
fis = restrictionsFile.openRead();
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(fis, null);
|
||||
int type;
|
||||
while ((type = parser.next()) != XmlPullParser.START_TAG
|
||||
&& type != XmlPullParser.END_DOCUMENT) {
|
||||
;
|
||||
}
|
||||
|
||||
if (type != XmlPullParser.START_TAG) {
|
||||
XmlUtils.nextElement(parser);
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG) {
|
||||
Slog.e(LOG_TAG, "Unable to read restrictions file "
|
||||
+ restrictionsFile.getBaseFile());
|
||||
return restrictions;
|
||||
}
|
||||
|
||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
|
||||
String key = parser.getAttributeValue(null, ATTR_KEY);
|
||||
String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE);
|
||||
String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE);
|
||||
if (multiple != null) {
|
||||
values.clear();
|
||||
int count = Integer.parseInt(multiple);
|
||||
while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
if (type == XmlPullParser.START_TAG
|
||||
&& parser.getName().equals(TAG_VALUE)) {
|
||||
values.add(parser.nextText().trim());
|
||||
count--;
|
||||
}
|
||||
}
|
||||
String [] valueStrings = new String[values.size()];
|
||||
values.toArray(valueStrings);
|
||||
restrictions.putStringArray(key, valueStrings);
|
||||
} else {
|
||||
String value = parser.nextText().trim();
|
||||
if (ATTR_TYPE_BOOLEAN.equals(valType)) {
|
||||
restrictions.putBoolean(key, Boolean.parseBoolean(value));
|
||||
} else if (ATTR_TYPE_INTEGER.equals(valType)) {
|
||||
restrictions.putInt(key, Integer.parseInt(value));
|
||||
} else {
|
||||
restrictions.putString(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
readEntry(restrictions, values, parser);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
} catch (XmlPullParserException pe) {
|
||||
} catch (IOException|XmlPullParserException e) {
|
||||
Log.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
IoUtils.closeQuietly(fis);
|
||||
}
|
||||
return restrictions;
|
||||
}
|
||||
|
||||
private static void readEntry(Bundle restrictions, ArrayList<String> values,
|
||||
XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
int type = parser.getEventType();
|
||||
if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
|
||||
String key = parser.getAttributeValue(null, ATTR_KEY);
|
||||
String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE);
|
||||
String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE);
|
||||
if (multiple != null) {
|
||||
values.clear();
|
||||
int count = Integer.parseInt(multiple);
|
||||
while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
if (type == XmlPullParser.START_TAG
|
||||
&& parser.getName().equals(TAG_VALUE)) {
|
||||
values.add(parser.nextText().trim());
|
||||
count--;
|
||||
}
|
||||
}
|
||||
String [] valueStrings = new String[values.size()];
|
||||
values.toArray(valueStrings);
|
||||
restrictions.putStringArray(key, valueStrings);
|
||||
} else if (ATTR_TYPE_BUNDLE.equals(valType)) {
|
||||
restrictions.putBundle(key, readBundleEntry(parser, values));
|
||||
} else if (ATTR_TYPE_BUNDLE_ARRAY.equals(valType)) {
|
||||
final int outerDepth = parser.getDepth();
|
||||
ArrayList<Bundle> bundleList = new ArrayList<>();
|
||||
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
|
||||
Bundle childBundle = readBundleEntry(parser, values);
|
||||
bundleList.add(childBundle);
|
||||
}
|
||||
restrictions.putParcelableArray(key,
|
||||
bundleList.toArray(new Bundle[bundleList.size()]));
|
||||
} else {
|
||||
String value = parser.nextText().trim();
|
||||
if (ATTR_TYPE_BOOLEAN.equals(valType)) {
|
||||
restrictions.putBoolean(key, Boolean.parseBoolean(value));
|
||||
} else if (ATTR_TYPE_INTEGER.equals(valType)) {
|
||||
restrictions.putInt(key, Integer.parseInt(value));
|
||||
} else {
|
||||
restrictions.putString(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Bundle readBundleEntry(XmlPullParser parser, ArrayList<String> values)
|
||||
throws IOException, XmlPullParserException {
|
||||
Bundle childBundle = new Bundle();
|
||||
final int outerDepth = parser.getDepth();
|
||||
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
|
||||
readEntry(childBundle, values, parser);
|
||||
}
|
||||
return childBundle;
|
||||
}
|
||||
|
||||
private void writeApplicationRestrictionsLocked(String packageName,
|
||||
Bundle restrictions, int userId) {
|
||||
FileOutputStream fos = null;
|
||||
AtomicFile restrictionsFile = new AtomicFile(
|
||||
new File(Environment.getUserSystemDirectory(userId),
|
||||
packageToRestrictionsFileName(packageName)));
|
||||
writeApplicationRestrictionsLocked(restrictions, restrictionsFile);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void writeApplicationRestrictionsLocked(Bundle restrictions,
|
||||
AtomicFile restrictionsFile) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = restrictionsFile.startWrite();
|
||||
final BufferedOutputStream bos = new BufferedOutputStream(fos);
|
||||
|
||||
// XmlSerializer serializer = XmlUtils.serializerInstance();
|
||||
final XmlSerializer serializer = new FastXmlSerializer();
|
||||
serializer.setOutput(bos, "utf-8");
|
||||
serializer.startDocument(null, true);
|
||||
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
|
||||
serializer.startTag(null, TAG_RESTRICTIONS);
|
||||
|
||||
for (String key : restrictions.keySet()) {
|
||||
Object value = restrictions.get(key);
|
||||
serializer.startTag(null, TAG_ENTRY);
|
||||
serializer.attribute(null, ATTR_KEY, key);
|
||||
|
||||
if (value instanceof Boolean) {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN);
|
||||
serializer.text(value.toString());
|
||||
} else if (value instanceof Integer) {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER);
|
||||
serializer.text(value.toString());
|
||||
} else if (value == null || value instanceof String) {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING);
|
||||
serializer.text(value != null ? (String) value : "");
|
||||
} else {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY);
|
||||
String[] values = (String[]) value;
|
||||
serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length));
|
||||
for (String choice : values) {
|
||||
serializer.startTag(null, TAG_VALUE);
|
||||
serializer.text(choice != null ? choice : "");
|
||||
serializer.endTag(null, TAG_VALUE);
|
||||
}
|
||||
}
|
||||
serializer.endTag(null, TAG_ENTRY);
|
||||
}
|
||||
|
||||
writeBundle(restrictions, serializer);
|
||||
serializer.endTag(null, TAG_RESTRICTIONS);
|
||||
|
||||
serializer.endDocument();
|
||||
restrictionsFile.finishWrite(fos);
|
||||
} catch (Exception e) {
|
||||
restrictionsFile.failWrite(fos);
|
||||
Slog.e(LOG_TAG, "Error writing application restrictions list");
|
||||
Slog.e(LOG_TAG, "Error writing application restrictions list", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeBundle(Bundle restrictions, XmlSerializer serializer)
|
||||
throws IOException {
|
||||
for (String key : restrictions.keySet()) {
|
||||
Object value = restrictions.get(key);
|
||||
serializer.startTag(null, TAG_ENTRY);
|
||||
serializer.attribute(null, ATTR_KEY, key);
|
||||
|
||||
if (value instanceof Boolean) {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN);
|
||||
serializer.text(value.toString());
|
||||
} else if (value instanceof Integer) {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER);
|
||||
serializer.text(value.toString());
|
||||
} else if (value == null || value instanceof String) {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING);
|
||||
serializer.text(value != null ? (String) value : "");
|
||||
} else if (value instanceof Bundle) {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
|
||||
writeBundle((Bundle) value, serializer);
|
||||
} else if (value instanceof Parcelable[]) {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE_ARRAY);
|
||||
Parcelable[] array = (Parcelable[]) value;
|
||||
for (Parcelable parcelable : array) {
|
||||
if (!(parcelable instanceof Bundle)) {
|
||||
throw new IllegalArgumentException("bundle-array can only hold Bundles");
|
||||
}
|
||||
serializer.startTag(null, TAG_ENTRY);
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
|
||||
writeBundle((Bundle) parcelable, serializer);
|
||||
serializer.endTag(null, TAG_ENTRY);
|
||||
}
|
||||
} else {
|
||||
serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY);
|
||||
String[] values = (String[]) value;
|
||||
serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length));
|
||||
for (String choice : values) {
|
||||
serializer.startTag(null, TAG_VALUE);
|
||||
serializer.text(choice != null ? choice : "");
|
||||
serializer.endTag(null, TAG_VALUE);
|
||||
}
|
||||
}
|
||||
serializer.endTag(null, TAG_ENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.android.server.pm;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.FileUtils;
|
||||
import android.os.Parcelable;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.AtomicFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class UserManagerServiceTest extends AndroidTestCase {
|
||||
private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["};
|
||||
private File restrictionsFile;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml");
|
||||
restrictionsFile.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
restrictionsFile.delete();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testWriteReadApplicationRestrictions() throws IOException {
|
||||
AtomicFile atomicFile = new AtomicFile(restrictionsFile);
|
||||
Bundle bundle = createBundle();
|
||||
UserManagerService.writeApplicationRestrictionsLocked(bundle, atomicFile);
|
||||
assertTrue(atomicFile.getBaseFile().exists());
|
||||
String s = FileUtils.readTextFile(restrictionsFile, 10000, "");
|
||||
System.out.println("restrictionsFile: " + s);
|
||||
bundle = UserManagerService.readApplicationRestrictionsLocked(atomicFile);
|
||||
System.out.println("readApplicationRestrictionsLocked bundle: " + bundle);
|
||||
assertBundle(bundle);
|
||||
}
|
||||
|
||||
private Bundle createBundle() {
|
||||
Bundle result = new Bundle();
|
||||
// Tests for 6 allowed types: Integer, Boolean, String, String[], Bundle and Parcelable[]
|
||||
result.putBoolean("boolean_0", false);
|
||||
result.putBoolean("boolean_1", true);
|
||||
result.putInt("integer", 100);
|
||||
result.putString("empty", "");
|
||||
result.putString("string", "text");
|
||||
result.putStringArray("string[]", STRING_ARRAY);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("bundle_string", "bundle_string");
|
||||
bundle.putInt("bundle_int", 1);
|
||||
result.putBundle("bundle", bundle);
|
||||
|
||||
Bundle[] bundleArray = new Bundle[2];
|
||||
bundleArray[0] = new Bundle();
|
||||
bundleArray[0].putString("bundle_array_string", "bundle_array_string");
|
||||
bundleArray[0].putBundle("bundle_array_bundle", bundle);
|
||||
bundleArray[1] = new Bundle();
|
||||
bundleArray[1].putString("bundle_array_string2", "bundle_array_string2");
|
||||
result.putParcelableArray("bundle_array", bundleArray);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void assertBundle(Bundle bundle) {
|
||||
assertFalse(bundle.getBoolean("boolean_0"));
|
||||
assertTrue(bundle.getBoolean("boolean_1"));
|
||||
assertEquals(100, bundle.getInt("integer"));
|
||||
assertEquals("", bundle.getString("empty"));
|
||||
assertEquals("text", bundle.getString("string"));
|
||||
assertEquals(Arrays.asList(STRING_ARRAY), Arrays.asList(bundle.getStringArray("string[]")));
|
||||
Parcelable[] bundle_array = bundle.getParcelableArray("bundle_array");
|
||||
assertEquals(2, bundle_array.length);
|
||||
Bundle bundle1 = (Bundle) bundle_array[0];
|
||||
assertEquals("bundle_array_string", bundle1.getString("bundle_array_string"));
|
||||
assertNotNull(bundle1.getBundle("bundle_array_bundle"));
|
||||
Bundle bundle2 = (Bundle) bundle_array[1];
|
||||
assertEquals("bundle_array_string2", bundle2.getString("bundle_array_string2"));
|
||||
Bundle childBundle = bundle.getBundle("bundle");
|
||||
assertEquals("bundle_string", childBundle.getString("bundle_string"));
|
||||
assertEquals(1, childBundle.getInt("bundle_int"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user