1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.devicepolicy;
18 
19 import android.annotation.NonNull;
20 import android.app.admin.BundlePolicyValue;
21 import android.app.admin.PackagePolicyKey;
22 import android.app.admin.PolicyKey;
23 import android.os.Bundle;
24 import android.os.Parcelable;
25 import android.util.Log;
26 
27 import com.android.internal.util.XmlUtils;
28 import com.android.modules.utils.TypedXmlPullParser;
29 import com.android.modules.utils.TypedXmlSerializer;
30 
31 import org.xmlpull.v1.XmlPullParser;
32 import org.xmlpull.v1.XmlPullParserException;
33 
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.Objects;
37 
38 final class BundlePolicySerializer extends PolicySerializer<Bundle> {
39 
40     private static final String TAG = "BundlePolicySerializer";
41 
42     private static final String TAG_ENTRY = "entry";
43     private static final String TAG_VALUE = "value";
44     private static final String ATTR_KEY = "key";
45     private static final String ATTR_VALUE_TYPE = "type";
46     private static final String ATTR_MULTIPLE = "m";
47 
48     private static final String ATTR_TYPE_STRING_ARRAY = "sa";
49     private static final String ATTR_TYPE_STRING = "s";
50     private static final String ATTR_TYPE_BOOLEAN = "b";
51     private static final String ATTR_TYPE_INTEGER = "i";
52     private static final String ATTR_TYPE_BUNDLE = "B";
53     private static final String ATTR_TYPE_BUNDLE_ARRAY = "BA";
54 
55     @Override
saveToXml(@onNull PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Bundle value)56     void saveToXml(@NonNull PolicyKey policyKey, TypedXmlSerializer serializer,
57             @NonNull Bundle value) throws IOException {
58         Objects.requireNonNull(value);
59         Objects.requireNonNull(policyKey);
60         if (!(policyKey instanceof PackagePolicyKey)) {
61             throw new IllegalArgumentException("policyKey is not of type "
62                     + "PackagePolicyKey");
63         }
64         writeBundle(value, serializer);
65     }
66 
67     @Override
readFromXml(TypedXmlPullParser parser)68     BundlePolicyValue readFromXml(TypedXmlPullParser parser) {
69         Bundle bundle = new Bundle();
70         ArrayList<String> values = new ArrayList<>();
71         try {
72             final int outerDepth = parser.getDepth();
73             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
74                 readBundle(bundle, values, parser);
75             }
76         } catch (XmlPullParserException | IOException e) {
77             Log.e(TAG, "Error parsing Bundle policy.", e);
78             return null;
79         }
80         return new BundlePolicyValue(bundle);
81     }
82 
readBundle(Bundle restrictions, ArrayList<String> values, TypedXmlPullParser parser)83     private static void readBundle(Bundle restrictions, ArrayList<String> values,
84             TypedXmlPullParser parser) throws XmlPullParserException, IOException {
85         int type = parser.getEventType();
86         if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
87             String key = parser.getAttributeValue(null, ATTR_KEY);
88             String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE);
89             int count = parser.getAttributeInt(null, ATTR_MULTIPLE, -1);
90             if (count != -1) {
91                 values.clear();
92                 while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
93                     if (type == XmlPullParser.START_TAG
94                             && parser.getName().equals(TAG_VALUE)) {
95                         values.add(parser.nextText().trim());
96                         count--;
97                     }
98                 }
99                 String [] valueStrings = new String[values.size()];
100                 values.toArray(valueStrings);
101                 restrictions.putStringArray(key, valueStrings);
102             } else if (ATTR_TYPE_BUNDLE.equals(valType)) {
103                 restrictions.putBundle(key, readBundleEntry(parser, values));
104             } else if (ATTR_TYPE_BUNDLE_ARRAY.equals(valType)) {
105                 final int outerDepth = parser.getDepth();
106                 ArrayList<Bundle> bundleList = new ArrayList<>();
107                 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
108                     Bundle childBundle = readBundleEntry(parser, values);
109                     bundleList.add(childBundle);
110                 }
111                 restrictions.putParcelableArray(key,
112                         bundleList.toArray(new Bundle[bundleList.size()]));
113             } else {
114                 String value = parser.nextText().trim();
115                 if (ATTR_TYPE_BOOLEAN.equals(valType)) {
116                     restrictions.putBoolean(key, Boolean.parseBoolean(value));
117                 } else if (ATTR_TYPE_INTEGER.equals(valType)) {
118                     restrictions.putInt(key, Integer.parseInt(value));
119                 } else {
120                     restrictions.putString(key, value);
121                 }
122             }
123         }
124     }
125 
readBundleEntry(TypedXmlPullParser parser, ArrayList<String> values)126     private static Bundle readBundleEntry(TypedXmlPullParser parser, ArrayList<String> values)
127             throws IOException, XmlPullParserException {
128         Bundle childBundle = new Bundle();
129         int outerDepth = parser.getDepth();
130         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
131             readBundle(childBundle, values, parser);
132         }
133         return childBundle;
134     }
135 
writeBundle(Bundle restrictions, TypedXmlSerializer serializer)136     private static void writeBundle(Bundle restrictions, TypedXmlSerializer serializer)
137             throws IOException {
138         for (String key : restrictions.keySet()) {
139             Object value = restrictions.get(key);
140             serializer.startTag(null, TAG_ENTRY);
141             serializer.attribute(null, ATTR_KEY, key);
142 
143             if (value instanceof Boolean) {
144                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN);
145                 serializer.text(value.toString());
146             } else if (value instanceof Integer) {
147                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER);
148                 serializer.text(value.toString());
149             } else if (value == null || value instanceof String) {
150                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING);
151                 serializer.text(value != null ? (String) value : "");
152             } else if (value instanceof Bundle) {
153                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
154                 writeBundle((Bundle) value, serializer);
155             } else if (value instanceof Parcelable[]) {
156                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE_ARRAY);
157                 Parcelable[] array = (Parcelable[]) value;
158                 for (Parcelable parcelable : array) {
159                     if (!(parcelable instanceof Bundle)) {
160                         throw new IllegalArgumentException("bundle-array can only hold Bundles");
161                     }
162                     serializer.startTag(null, TAG_ENTRY);
163                     serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
164                     writeBundle((Bundle) parcelable, serializer);
165                     serializer.endTag(null, TAG_ENTRY);
166                 }
167             } else {
168                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY);
169                 String[] values = (String[]) value;
170                 serializer.attributeInt(null, ATTR_MULTIPLE, values.length);
171                 for (String choice : values) {
172                     serializer.startTag(null, TAG_VALUE);
173                     serializer.text(choice != null ? choice : "");
174                     serializer.endTag(null, TAG_VALUE);
175                 }
176             }
177             serializer.endTag(null, TAG_ENTRY);
178         }
179     }
180 }
181