diff options
| -rw-r--r-- | api/current.txt | 6 | ||||
| -rw-r--r-- | api/system-current.txt | 6 | ||||
| -rw-r--r-- | core/java/android/content/RestrictionEntry.java | 177 | ||||
| -rw-r--r-- | core/java/android/content/RestrictionsManager.java | 130 | ||||
| -rw-r--r-- | core/res/res/values/attrs.xml | 2 | ||||
| -rw-r--r-- | core/tests/coretests/AndroidManifest.xml | 3 | ||||
| -rw-r--r-- | core/tests/coretests/res/values/strings.xml | 4 | ||||
| -rw-r--r-- | core/tests/coretests/res/xml/app_restrictions.xml | 55 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/content/RestrictionsManagerTest.java | 77 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/UserManagerService.java | 207 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java | 102 |
11 files changed, 619 insertions, 150 deletions
diff --git a/api/current.txt b/api/current.txt index 5371ca8..8dd8f20 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/api/system-current.txt b/api/system-current.txt index 3b57333..b1b5cf3 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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); 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.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; @@ -115,6 +130,12 @@ public class RestrictionEntry implements Parcelable { 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 @@ public class RestrictionEntry implements Parcelable { } /** + * 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 @@ public class RestrictionEntry implements Parcelable { } /** + * 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) + + '}'; } } 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.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; + } + } 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..c84cabc --- /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.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 (parser.next() != XmlPullParser.END_DOCUMENT) { + readEntry(restrictions, values, parser); + } + } catch (IOException|XmlPullParserException e) { + Log.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e); + } finally { + IoUtils.closeQuietly(fis); + } + 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); - } + 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--; } } - } - } catch (IOException ioe) { - } catch (XmlPullParserException pe) { - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException e) { + 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); } } } - return restrictions; + } + + 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")); + } + +} |
