Merge "Support for nested bundles in setApplicationRestrictions"
diff --git a/api/current.txt b/api/current.txt
index e2592aa..b3d5fd0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8379,6 +8379,7 @@
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();
@@ -8387,6 +8388,7 @@
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();
@@ -8398,6 +8400,7 @@
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);
@@ -8405,6 +8408,8 @@
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
@@ -8413,6 +8418,7 @@
}
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);
diff --git a/api/system-current.txt b/api/system-current.txt
index 13575c0..9462054 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8600,6 +8600,7 @@
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();
@@ -8608,6 +8609,7 @@
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();
@@ -8619,6 +8621,7 @@
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);
@@ -8626,6 +8629,8 @@
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
@@ -8634,6 +8639,7 @@
}
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);
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 6d79626..342ee38 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -20,6 +20,9 @@
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 @@
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 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 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 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 @@
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;
@@ -115,6 +130,12 @@
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;
*
* @param type the restriction type.
@@ -170,6 +191,35 @@
}
/**
+ * 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.
*/
@@ -283,6 +333,22 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 @@
@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) +
+ '}';
}
}
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 21a6a0d..1fac06e 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -32,12 +32,14 @@
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 @@
* 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 @@
* 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 @@
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 @@
} 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 @@
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 @@
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;
+ }
+
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index dcb4b9e..b6d32b2 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -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" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index bfaea8f..b07d338 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -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" />
diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml
index ce0d9a2..04b0478 100644
--- a/core/tests/coretests/res/values/strings.xml
+++ b/core/tests/coretests/res/values/strings.xml
@@ -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>
diff --git a/core/tests/coretests/res/xml/app_restrictions.xml b/core/tests/coretests/res/xml/app_restrictions.xml
new file mode 100644
index 0000000..c84cabcc
--- /dev/null
+++ b/core/tests/coretests/res/xml/app_restrictions.xml
@@ -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>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
new file mode 100644
index 0000000..8921924
--- /dev/null
+++ b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
@@ -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);
+ }
+
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8cc9d19..5e58cd9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -36,6 +36,7 @@
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.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.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 @@
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 @@
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);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
new file mode 100644
index 0000000..eb7eb15
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -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"));
+ }
+
+}