• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static java.util.Objects.requireNonNull;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.admin.DevicePolicyDrawableResource;
24 import android.app.admin.DevicePolicyResources;
25 import android.app.admin.DevicePolicyStringResource;
26 import android.app.admin.ParcelableResource;
27 import android.os.Environment;
28 import android.util.AtomicFile;
29 import android.util.Log;
30 import android.util.TypedXmlPullParser;
31 import android.util.TypedXmlSerializer;
32 import android.util.Xml;
33 
34 import libcore.io.IoUtils;
35 
36 import org.xmlpull.v1.XmlPullParserException;
37 
38 import java.io.File;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Objects;
46 
47 /**
48  * A helper class for {@link DevicePolicyManagerService} to store/retrieve updated device
49  * management resources.
50  */
51 class DeviceManagementResourcesProvider {
52     private static final String TAG = "DevicePolicyManagerService";
53 
54     private static final String UPDATED_RESOURCES_XML = "updated_resources.xml";
55     private static final String TAG_ROOT = "root";
56     private static final String TAG_DRAWABLE_STYLE_ENTRY = "drawable-style-entry";
57     private static final String TAG_DRAWABLE_SOURCE_ENTRY = "drawable-source-entry";
58     private static final String ATTR_DRAWABLE_STYLE = "drawable-style";
59     private static final String ATTR_DRAWABLE_SOURCE = "drawable-source";
60     private static final String ATTR_DRAWABLE_ID = "drawable-id";
61     private static final String TAG_STRING_ENTRY = "string-entry";
62     private static final String ATTR_SOURCE_ID = "source-id";
63 
64     /**
65      * Map of <drawable_id, <style_id, resource_value>>
66      */
67     private final Map<String, Map<String, ParcelableResource>>
68             mUpdatedDrawablesForStyle = new HashMap<>();
69 
70     /**
71      * Map of <drawable_id, <source_id, <style_id, resource_value>>>
72      */
73     private final Map<String, Map<String, Map<String, ParcelableResource>>>
74             mUpdatedDrawablesForSource = new HashMap<>();
75 
76     /**
77      * Map of <string_id, resource_value>
78      */
79     private final Map<String, ParcelableResource> mUpdatedStrings = new HashMap<>();
80 
81     private final Object mLock = new Object();
82     private final Injector mInjector;
83 
DeviceManagementResourcesProvider()84     DeviceManagementResourcesProvider() {
85         this(new Injector());
86     }
87 
DeviceManagementResourcesProvider(Injector injector)88     DeviceManagementResourcesProvider(Injector injector) {
89         mInjector = requireNonNull(injector);
90     }
91 
92     /**
93      * Returns {@code false} if no resources were updated.
94      */
updateDrawables(@onNull List<DevicePolicyDrawableResource> drawables)95     boolean updateDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
96         boolean updated = false;
97         for (int i = 0; i < drawables.size(); i++) {
98             String drawableId = drawables.get(i).getDrawableId();
99             String drawableStyle = drawables.get(i).getDrawableStyle();
100             String drawableSource = drawables.get(i).getDrawableSource();
101             ParcelableResource resource = drawables.get(i).getResource();
102 
103             Objects.requireNonNull(drawableId, "drawableId must be provided.");
104             Objects.requireNonNull(drawableStyle, "drawableStyle must be provided.");
105             Objects.requireNonNull(drawableSource, "drawableSource must be provided.");
106             Objects.requireNonNull(resource, "ParcelableResource must be provided.");
107 
108             if (DevicePolicyResources.UNDEFINED.equals(drawableSource)) {
109                 updated |= updateDrawable(drawableId, drawableStyle, resource);
110             } else {
111                 updated |= updateDrawableForSource(
112                         drawableId, drawableSource, drawableStyle, resource);
113             }
114         }
115         if (!updated) {
116             return false;
117         }
118         synchronized (mLock) {
119             write();
120             return true;
121         }
122     }
123 
updateDrawable( String drawableId, String drawableStyle, ParcelableResource updatableResource)124     private boolean updateDrawable(
125             String drawableId, String drawableStyle, ParcelableResource updatableResource) {
126         synchronized (mLock) {
127             if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) {
128                 mUpdatedDrawablesForStyle.put(drawableId, new HashMap<>());
129             }
130             ParcelableResource current = mUpdatedDrawablesForStyle.get(drawableId).get(
131                     drawableStyle);
132             if (updatableResource.equals(current)) {
133                 return false;
134             }
135             mUpdatedDrawablesForStyle.get(drawableId).put(drawableStyle, updatableResource);
136             return true;
137         }
138     }
139 
updateDrawableForSource( String drawableId, String drawableSource, String drawableStyle, ParcelableResource updatableResource)140     private boolean updateDrawableForSource(
141             String drawableId, String drawableSource, String drawableStyle,
142             ParcelableResource updatableResource) {
143         synchronized (mLock) {
144             if (!mUpdatedDrawablesForSource.containsKey(drawableId)) {
145                 mUpdatedDrawablesForSource.put(drawableId, new HashMap<>());
146             }
147             Map<String, Map<String, ParcelableResource>> drawablesForId =
148                     mUpdatedDrawablesForSource.get(drawableId);
149             if (!drawablesForId.containsKey(drawableSource)) {
150                 mUpdatedDrawablesForSource.get(drawableId).put(drawableSource, new HashMap<>());
151             }
152             ParcelableResource current = drawablesForId.get(drawableSource).get(drawableStyle);
153             if (updatableResource.equals(current)) {
154                 return false;
155             }
156             drawablesForId.get(drawableSource).put(drawableStyle, updatableResource);
157             return true;
158         }
159     }
160 
161     /**
162      * Returns {@code false} if no resources were removed.
163      */
removeDrawables(@onNull List<String> drawableIds)164     boolean removeDrawables(@NonNull List<String> drawableIds) {
165         synchronized (mLock) {
166             boolean removed = false;
167             for (int i = 0; i < drawableIds.size(); i++) {
168                 String drawableId = drawableIds.get(i);
169                 removed |= mUpdatedDrawablesForStyle.remove(drawableId) != null
170                         || mUpdatedDrawablesForSource.remove(drawableId) != null;
171             }
172             if (!removed) {
173                 return false;
174             }
175             write();
176             return true;
177         }
178     }
179 
180     @Nullable
getDrawable(String drawableId, String drawableStyle, String drawableSource)181     ParcelableResource getDrawable(String drawableId, String drawableStyle, String drawableSource) {
182         synchronized (mLock) {
183             ParcelableResource resource = getDrawableForSourceLocked(
184                     drawableId, drawableStyle, drawableSource);
185             if (resource != null) {
186                 return resource;
187             }
188             if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) {
189                 return null;
190             }
191             return mUpdatedDrawablesForStyle.get(drawableId).get(drawableStyle);
192         }
193     }
194 
195     @Nullable
getDrawableForSourceLocked( String drawableId, String drawableStyle, String drawableSource)196     ParcelableResource getDrawableForSourceLocked(
197             String drawableId, String drawableStyle, String drawableSource) {
198         if (!mUpdatedDrawablesForSource.containsKey(drawableId)) {
199             return null;
200         }
201         if (!mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) {
202             return null;
203         }
204         return mUpdatedDrawablesForSource.get(drawableId).get(drawableSource).get(drawableStyle);
205     }
206 
207     /**
208      * Returns {@code false} if no resources were updated.
209      */
updateStrings(@onNull List<DevicePolicyStringResource> strings)210     boolean updateStrings(@NonNull List<DevicePolicyStringResource> strings) {
211         boolean updated = false;
212         for (int i = 0; i < strings.size(); i++) {
213             String stringId = strings.get(i).getStringId();
214             ParcelableResource resource = strings.get(i).getResource();
215 
216             Objects.requireNonNull(stringId, "stringId must be provided.");
217             Objects.requireNonNull(resource, "ParcelableResource must be provided.");
218 
219             updated |= updateString(stringId, resource);
220         }
221         if (!updated) {
222             return false;
223         }
224         synchronized (mLock) {
225             write();
226             return true;
227         }
228     }
229 
updateString(String stringId, ParcelableResource updatableResource)230     private boolean updateString(String stringId, ParcelableResource updatableResource) {
231         synchronized (mLock) {
232             ParcelableResource current = mUpdatedStrings.get(stringId);
233             if (updatableResource.equals(current)) {
234                 return false;
235             }
236             mUpdatedStrings.put(stringId, updatableResource);
237             return true;
238         }
239     }
240 
241     /**
242      * Returns {@code false} if no resources were removed.
243      */
removeStrings(@onNull List<String> stringIds)244     boolean removeStrings(@NonNull List<String> stringIds) {
245         synchronized (mLock) {
246             boolean removed = false;
247             for (int i = 0; i < stringIds.size(); i++) {
248                 String stringId = stringIds.get(i);
249                 removed |= mUpdatedStrings.remove(stringId) != null;
250             }
251             if (!removed) {
252                 return false;
253             }
254             write();
255             return true;
256         }
257     }
258 
259     @Nullable
getString(String stringId)260     ParcelableResource getString(String stringId) {
261         synchronized (mLock) {
262             return mUpdatedStrings.get(stringId);
263         }
264     }
265 
write()266     private void write() {
267         Log.d(TAG, "Writing updated resources to file.");
268         new ResourcesReaderWriter().writeToFileLocked();
269     }
270 
load()271     void load() {
272         synchronized (mLock) {
273             new ResourcesReaderWriter().readFromFileLocked();
274         }
275     }
276 
getResourcesFile()277     private File getResourcesFile() {
278         return new File(mInjector.environmentGetDataSystemDirectory(), UPDATED_RESOURCES_XML);
279     }
280 
281     private class ResourcesReaderWriter {
282         private final File mFile;
ResourcesReaderWriter()283         private ResourcesReaderWriter() {
284             mFile = getResourcesFile();
285         }
286 
writeToFileLocked()287         void writeToFileLocked() {
288             Log.d(TAG, "Writing to " + mFile);
289 
290             AtomicFile f = new AtomicFile(mFile);
291             FileOutputStream outputStream = null;
292             try {
293                 outputStream = f.startWrite();
294                 TypedXmlSerializer out = Xml.resolveSerializer(outputStream);
295 
296                 // Root tag
297                 out.startDocument(null, true);
298                 out.startTag(null, TAG_ROOT);
299 
300                 // Actual content
301                 writeInner(out);
302 
303                 // Close root
304                 out.endTag(null, TAG_ROOT);
305                 out.endDocument();
306                 out.flush();
307 
308                 // Commit the content.
309                 f.finishWrite(outputStream);
310                 outputStream = null;
311 
312             } catch (IOException e) {
313                 Log.e(TAG, "Exception when writing", e);
314                 if (outputStream != null) {
315                     f.failWrite(outputStream);
316                 }
317             }
318         }
319 
readFromFileLocked()320         void readFromFileLocked() {
321             if (!mFile.exists()) {
322                 Log.d(TAG, "" + mFile + " doesn't exist");
323                 return;
324             }
325 
326             Log.d(TAG, "Reading from " + mFile);
327             AtomicFile f = new AtomicFile(mFile);
328             InputStream input = null;
329             try {
330                 input = f.openRead();
331                 TypedXmlPullParser parser = Xml.resolvePullParser(input);
332 
333                 int type;
334                 int depth = 0;
335                 while ((type = parser.next()) != TypedXmlPullParser.END_DOCUMENT) {
336                     switch (type) {
337                         case TypedXmlPullParser.START_TAG:
338                             depth++;
339                             break;
340                         case TypedXmlPullParser.END_TAG:
341                             depth--;
342                             // fallthrough
343                         default:
344                             continue;
345                     }
346                     // Check the root tag
347                     String tag = parser.getName();
348                     if (depth == 1) {
349                         if (!TAG_ROOT.equals(tag)) {
350                             Log.e(TAG, "Invalid root tag: " + tag);
351                             return;
352                         }
353                         continue;
354                     }
355                     // readInner() will only see START_TAG at depth >= 2.
356                     if (!readInner(parser, depth, tag)) {
357                         return; // Error
358                     }
359                 }
360             } catch (XmlPullParserException | IOException e) {
361                 Log.e(TAG, "Error parsing resources file", e);
362             } finally {
363                 IoUtils.closeQuietly(input);
364             }
365         }
366 
writeInner(TypedXmlSerializer out)367         void writeInner(TypedXmlSerializer out) throws IOException {
368             writeDrawablesForStylesInner(out);
369             writeDrawablesForSourcesInner(out);
370             writeStringsInner(out);
371         }
372 
writeDrawablesForStylesInner(TypedXmlSerializer out)373         private void writeDrawablesForStylesInner(TypedXmlSerializer out) throws IOException {
374             if (mUpdatedDrawablesForStyle != null && !mUpdatedDrawablesForStyle.isEmpty()) {
375                 for (Map.Entry<String, Map<String, ParcelableResource>> drawableEntry
376                         : mUpdatedDrawablesForStyle.entrySet()) {
377                     for (Map.Entry<String, ParcelableResource> styleEntry
378                             : drawableEntry.getValue().entrySet()) {
379                         out.startTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY);
380                         out.attribute(
381                                 /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey());
382                         out.attribute(
383                                 /* namespace= */ null,
384                                 ATTR_DRAWABLE_STYLE,
385                                 styleEntry.getKey());
386                         styleEntry.getValue().writeToXmlFile(out);
387                         out.endTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY);
388                     }
389                 }
390             }
391         }
392 
writeDrawablesForSourcesInner(TypedXmlSerializer out)393         private void writeDrawablesForSourcesInner(TypedXmlSerializer out) throws IOException {
394             if (mUpdatedDrawablesForSource != null && !mUpdatedDrawablesForSource.isEmpty()) {
395                 for (Map.Entry<String, Map<String, Map<String, ParcelableResource>>> drawableEntry
396                         : mUpdatedDrawablesForSource.entrySet()) {
397                     for (Map.Entry<String, Map<String, ParcelableResource>> sourceEntry
398                             : drawableEntry.getValue().entrySet()) {
399                         for (Map.Entry<String, ParcelableResource> styleEntry
400                                 : sourceEntry.getValue().entrySet()) {
401                             out.startTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY);
402                             out.attribute(/* namespace= */ null, ATTR_DRAWABLE_ID,
403                                     drawableEntry.getKey());
404                             out.attribute(/* namespace= */ null, ATTR_DRAWABLE_SOURCE,
405                                     sourceEntry.getKey());
406                             out.attribute(/* namespace= */ null, ATTR_DRAWABLE_STYLE,
407                                     styleEntry.getKey());
408                             styleEntry.getValue().writeToXmlFile(out);
409                             out.endTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY);
410                         }
411                     }
412                 }
413             }
414         }
415 
writeStringsInner(TypedXmlSerializer out)416         private void writeStringsInner(TypedXmlSerializer out) throws IOException {
417             if (mUpdatedStrings != null && !mUpdatedStrings.isEmpty()) {
418                 for (Map.Entry<String, ParcelableResource> entry
419                         : mUpdatedStrings.entrySet()) {
420                     out.startTag(/* namespace= */ null, TAG_STRING_ENTRY);
421                     out.attribute(
422                             /* namespace= */ null,
423                             ATTR_SOURCE_ID,
424                             entry.getKey());
425                     entry.getValue().writeToXmlFile(out);
426                     out.endTag(/* namespace= */ null, TAG_STRING_ENTRY);
427                 }
428             }
429         }
430 
readInner(TypedXmlPullParser parser, int depth, String tag)431         private boolean readInner(TypedXmlPullParser parser, int depth, String tag)
432                 throws XmlPullParserException, IOException {
433             if (depth > 2) {
434                 return true; // Ignore
435             }
436             switch (tag) {
437                 case TAG_DRAWABLE_STYLE_ENTRY: {
438                     String id = parser.getAttributeValue(/* namespace= */ null, ATTR_DRAWABLE_ID);
439                     String style = parser.getAttributeValue(
440                             /* namespace= */ null, ATTR_DRAWABLE_STYLE);
441                     ParcelableResource resource = ParcelableResource.createFromXml(parser);
442                     if (!mUpdatedDrawablesForStyle.containsKey(id)) {
443                         mUpdatedDrawablesForStyle.put(id, new HashMap<>());
444                     }
445                     mUpdatedDrawablesForStyle.get(id).put(style, resource);
446                     break;
447                 }
448                 case TAG_DRAWABLE_SOURCE_ENTRY: {
449                     String id = parser.getAttributeValue(/* namespace= */ null, ATTR_DRAWABLE_ID);
450                     String source = parser.getAttributeValue(
451                             /* namespace= */ null, ATTR_DRAWABLE_SOURCE);
452                     String style = parser.getAttributeValue(
453                             /* namespace= */ null, ATTR_DRAWABLE_STYLE);
454                     ParcelableResource resource = ParcelableResource.createFromXml(parser);
455                     if (!mUpdatedDrawablesForSource.containsKey(id)) {
456                         mUpdatedDrawablesForSource.put(id, new HashMap<>());
457                     }
458                     if (!mUpdatedDrawablesForSource.get(id).containsKey(source)) {
459                         mUpdatedDrawablesForSource.get(id).put(source, new HashMap<>());
460                     }
461                     mUpdatedDrawablesForSource.get(id).get(source).put(style, resource);
462                     break;
463                 }
464                 case TAG_STRING_ENTRY: {
465                     String id = parser.getAttributeValue(/* namespace= */ null, ATTR_SOURCE_ID);
466                     mUpdatedStrings.put(id, ParcelableResource.createFromXml(parser));
467                     break;
468                 }
469                 default: {
470                     Log.e(TAG, "Unexpected tag: " + tag);
471                     return false;
472                 }
473             }
474             return true;
475         }
476     }
477 
478     public static class Injector {
environmentGetDataSystemDirectory()479         File environmentGetDataSystemDirectory() {
480             return Environment.getDataSystemDirectory();
481         }
482     }
483 }
484