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:
Fyodor Kupolov
2015-03-23 18:55:11 -07:00
parent 607f1f09c3
commit 262f9952e6
11 changed files with 621 additions and 152 deletions

View File

@@ -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);

View File

@@ -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);

View File

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

View File

@@ -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"
* /&gt;
* android:defaultValue="reference" &gt;
* &lt;restriction ... /&gt;
* ...
* &lt;/restriction&gt;
* &lt;restriction ... /&gt;
* ...
* &lt;/restrictions&gt;
@@ -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;
}
}

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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>

View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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"));
}
}