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.os.Bundle;
22 import android.os.Parcelable;
23 import android.util.Log;
24 
25 import com.android.internal.util.XmlUtils;
26 import com.android.modules.utils.TypedXmlPullParser;
27 import com.android.modules.utils.TypedXmlSerializer;
28 
29 import org.xmlpull.v1.XmlPullParser;
30 import org.xmlpull.v1.XmlPullParserException;
31 
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.Objects;
35 
36 final class BundlePolicySerializer extends PolicySerializer<Bundle> {
37 
38     private static final String TAG = "BundlePolicySerializer";
39 
40     private static final String TAG_ENTRY = "entry";
41     private static final String TAG_VALUE = "value";
42     private static final String ATTR_KEY = "key";
43     private static final String ATTR_VALUE_TYPE = "type";
44     private static final String ATTR_MULTIPLE = "m";
45 
46     private static final String ATTR_TYPE_STRING_ARRAY = "sa";
47     private static final String ATTR_TYPE_STRING = "s";
48     private static final String ATTR_TYPE_BOOLEAN = "b";
49     private static final String ATTR_TYPE_INTEGER = "i";
50     private static final String ATTR_TYPE_BUNDLE = "B";
51     private static final String ATTR_TYPE_BUNDLE_ARRAY = "BA";
52 
53     @Override
saveToXml(TypedXmlSerializer serializer, @NonNull Bundle value)54     void saveToXml(TypedXmlSerializer serializer, @NonNull Bundle value) throws IOException {
55         Objects.requireNonNull(value);
56         writeBundle(value, serializer);
57     }
58 
59     @Override
readFromXml(TypedXmlPullParser parser)60     BundlePolicyValue readFromXml(TypedXmlPullParser parser) {
61         Bundle bundle = new Bundle();
62         ArrayList<String> values = new ArrayList<>();
63         try {
64             final int outerDepth = parser.getDepth();
65             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
66                 readBundle(bundle, values, parser);
67             }
68         } catch (XmlPullParserException | IOException e) {
69             Log.e(TAG, "Error parsing Bundle policy.", e);
70             return null;
71         }
72         return new BundlePolicyValue(bundle);
73     }
74 
readBundle(Bundle restrictions, ArrayList<String> values, TypedXmlPullParser parser)75     private static void readBundle(Bundle restrictions, ArrayList<String> values,
76             TypedXmlPullParser parser) throws XmlPullParserException, IOException {
77         int type = parser.getEventType();
78         if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
79             String key = parser.getAttributeValue(null, ATTR_KEY);
80             String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE);
81             int count = parser.getAttributeInt(null, ATTR_MULTIPLE, -1);
82             if (count != -1) {
83                 values.clear();
84                 while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
85                     if (type == XmlPullParser.START_TAG
86                             && parser.getName().equals(TAG_VALUE)) {
87                         values.add(parser.nextText());
88                         count--;
89                     }
90                 }
91                 String [] valueStrings = new String[values.size()];
92                 values.toArray(valueStrings);
93                 restrictions.putStringArray(key, valueStrings);
94             } else if (ATTR_TYPE_BUNDLE.equals(valType)) {
95                 restrictions.putBundle(key, readBundleEntry(parser, values));
96             } else if (ATTR_TYPE_BUNDLE_ARRAY.equals(valType)) {
97                 final int outerDepth = parser.getDepth();
98                 ArrayList<Bundle> bundleList = new ArrayList<>();
99                 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
100                     Bundle childBundle = readBundleEntry(parser, values);
101                     bundleList.add(childBundle);
102                 }
103                 restrictions.putParcelableArray(key,
104                         bundleList.toArray(new Bundle[bundleList.size()]));
105             } else {
106                 String value = parser.nextText();
107                 if (ATTR_TYPE_BOOLEAN.equals(valType)) {
108                     restrictions.putBoolean(key, Boolean.parseBoolean(value));
109                 } else if (ATTR_TYPE_INTEGER.equals(valType)) {
110                     restrictions.putInt(key, Integer.parseInt(value));
111                 } else {
112                     restrictions.putString(key, value);
113                 }
114             }
115         }
116     }
117 
readBundleEntry(TypedXmlPullParser parser, ArrayList<String> values)118     private static Bundle readBundleEntry(TypedXmlPullParser parser, ArrayList<String> values)
119             throws IOException, XmlPullParserException {
120         Bundle childBundle = new Bundle();
121         int outerDepth = parser.getDepth();
122         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
123             readBundle(childBundle, values, parser);
124         }
125         return childBundle;
126     }
127 
writeBundle(Bundle restrictions, TypedXmlSerializer serializer)128     private static void writeBundle(Bundle restrictions, TypedXmlSerializer serializer)
129             throws IOException {
130         for (String key : restrictions.keySet()) {
131             Object value = restrictions.get(key);
132             serializer.startTag(null, TAG_ENTRY);
133             serializer.attribute(null, ATTR_KEY, key);
134 
135             if (value instanceof Boolean) {
136                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN);
137                 serializer.text(value.toString());
138             } else if (value instanceof Integer) {
139                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER);
140                 serializer.text(value.toString());
141             } else if (value == null || value instanceof String) {
142                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING);
143                 serializer.text(value != null ? (String) value : "");
144             } else if (value instanceof Bundle) {
145                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
146                 writeBundle((Bundle) value, serializer);
147             } else if (value instanceof Parcelable[]) {
148                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE_ARRAY);
149                 Parcelable[] array = (Parcelable[]) value;
150                 for (Parcelable parcelable : array) {
151                     if (!(parcelable instanceof Bundle)) {
152                         throw new IllegalArgumentException("bundle-array can only hold Bundles");
153                     }
154                     serializer.startTag(null, TAG_ENTRY);
155                     serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
156                     writeBundle((Bundle) parcelable, serializer);
157                     serializer.endTag(null, TAG_ENTRY);
158                 }
159             } else {
160                 serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY);
161                 String[] values = (String[]) value;
162                 serializer.attributeInt(null, ATTR_MULTIPLE, values.length);
163                 for (String choice : values) {
164                     serializer.startTag(null, TAG_VALUE);
165                     serializer.text(choice != null ? choice : "");
166                     serializer.endTag(null, TAG_VALUE);
167                 }
168             }
169             serializer.endTag(null, TAG_ENTRY);
170         }
171     }
172 }
173