• 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.appwidget;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.appwidget.AppWidgetProviderInfo;
22 import android.content.ComponentName;
23 import android.os.Build;
24 import android.text.TextUtils;
25 import android.util.ArraySet;
26 import android.util.Log;
27 import android.util.SizeF;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 
31 import com.android.modules.utils.TypedXmlPullParser;
32 import com.android.modules.utils.TypedXmlSerializer;
33 
34 import org.xmlpull.v1.XmlPullParser;
35 import org.xmlpull.v1.XmlPullParserException;
36 
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.Set;
43 import java.util.stream.Collectors;
44 
45 /**
46  * @hide
47  */
48 public class AppWidgetXmlUtil {
49 
50     private static final String TAG = "AppWidgetXmlUtil";
51 
52     private static final String ATTR_MIN_WIDTH = "min_width";
53     private static final String ATTR_MIN_HEIGHT = "min_height";
54     private static final String ATTR_MIN_RESIZE_WIDTH = "min_resize_width";
55     private static final String ATTR_MIN_RESIZE_HEIGHT = "min_resize_height";
56     private static final String ATTR_MAX_RESIZE_WIDTH = "max_resize_width";
57     private static final String ATTR_MAX_RESIZE_HEIGHT = "max_resize_height";
58     private static final String ATTR_TARGET_CELL_WIDTH = "target_cell_width";
59     private static final String ATTR_TARGET_CELL_HEIGHT = "target_cell_height";
60     private static final String ATTR_UPDATE_PERIOD_MILLIS = "update_period_millis";
61     private static final String ATTR_INITIAL_LAYOUT = "initial_layout";
62     private static final String ATTR_INITIAL_KEYGUARD_LAYOUT = "initial_keyguard_layout";
63     private static final String ATTR_CONFIGURE = "configure";
64     private static final String ATTR_LABEL = "label";
65     private static final String ATTR_ICON = "icon";
66     private static final String ATTR_PREVIEW_IMAGE = "preview_image";
67     private static final String ATTR_PREVIEW_LAYOUT = "preview_layout";
68     private static final String ATTR_AUTO_ADVANCED_VIEW_ID = "auto_advance_view_id";
69     private static final String ATTR_RESIZE_MODE = "resize_mode";
70     private static final String ATTR_WIDGET_CATEGORY = "widget_category";
71     private static final String ATTR_WIDGET_FEATURES = "widget_features";
72     private static final String ATTR_DESCRIPTION_RES = "description_res";
73     private static final String ATTR_PROVIDER_INHERITANCE = "provider_inheritance";
74     private static final String ATTR_OS_FINGERPRINT = "os_fingerprint";
75     static final String TAG_BACKUP_RESTORE_CONTROLLER_STATE = "br";
76     private static final String TAG_PRUNED_APPS = "pruned_apps";
77     private static final String ATTR_TAG = "tag";
78     private static final String ATTR_PACKAGE_NAMES = "pkgs";
79     private static final String TAG_PROVIDER_UPDATES = "provider_updates";
80     private static final String TAG_HOST_UPDATES = "host_updates";
81     private static final String TAG_RECORD = "record";
82     private static final String ATTR_OLD_ID = "old_id";
83     private static final String ATTR_NEW_ID = "new_id";
84     private static final String ATTR_NOTIFIED = "notified";
85     private static final String SIZE_SEPARATOR = ",";
86 
87     /**
88      * @hide
89      */
writeAppWidgetProviderInfoLocked(@onNull final TypedXmlSerializer out, @NonNull final AppWidgetProviderInfo info)90     public static void writeAppWidgetProviderInfoLocked(@NonNull final TypedXmlSerializer out,
91             @NonNull final AppWidgetProviderInfo info) throws IOException {
92         Objects.requireNonNull(out);
93         Objects.requireNonNull(info);
94         out.attributeInt(null, ATTR_MIN_WIDTH, info.minWidth);
95         out.attributeInt(null, ATTR_MIN_HEIGHT, info.minHeight);
96         out.attributeInt(null, ATTR_MIN_RESIZE_WIDTH, info.minResizeWidth);
97         out.attributeInt(null, ATTR_MIN_RESIZE_HEIGHT, info.minResizeHeight);
98         out.attributeInt(null, ATTR_MAX_RESIZE_WIDTH, info.maxResizeWidth);
99         out.attributeInt(null, ATTR_MAX_RESIZE_HEIGHT, info.maxResizeHeight);
100         out.attributeInt(null, ATTR_TARGET_CELL_WIDTH, info.targetCellWidth);
101         out.attributeInt(null, ATTR_TARGET_CELL_HEIGHT, info.targetCellHeight);
102         out.attributeInt(null, ATTR_UPDATE_PERIOD_MILLIS, info.updatePeriodMillis);
103         out.attributeInt(null, ATTR_INITIAL_LAYOUT, info.initialLayout);
104         out.attributeInt(null, ATTR_INITIAL_KEYGUARD_LAYOUT, info.initialKeyguardLayout);
105         if (info.configure != null) {
106             out.attribute(null, ATTR_CONFIGURE, info.configure.flattenToShortString());
107         }
108         if (info.label != null) {
109             out.attribute(null, ATTR_LABEL, info.label);
110         }
111         out.attributeInt(null, ATTR_ICON, info.icon);
112         out.attributeInt(null, ATTR_PREVIEW_IMAGE, info.previewImage);
113         out.attributeInt(null, ATTR_PREVIEW_LAYOUT, info.previewLayout);
114         out.attributeInt(null, ATTR_AUTO_ADVANCED_VIEW_ID, info.autoAdvanceViewId);
115         out.attributeInt(null, ATTR_RESIZE_MODE, info.resizeMode);
116         out.attributeInt(null, ATTR_WIDGET_CATEGORY, info.widgetCategory);
117         out.attributeInt(null, ATTR_WIDGET_FEATURES, info.widgetFeatures);
118         out.attributeInt(null, ATTR_DESCRIPTION_RES, info.descriptionRes);
119         out.attributeBoolean(null, ATTR_PROVIDER_INHERITANCE, info.isExtendedFromAppWidgetProvider);
120         out.attribute(null, ATTR_OS_FINGERPRINT, Build.FINGERPRINT);
121     }
122 
123     /**
124      * @hide
125      */
126     @Nullable
readAppWidgetProviderInfoLocked( @onNull final TypedXmlPullParser parser)127     public static AppWidgetProviderInfo readAppWidgetProviderInfoLocked(
128             @NonNull final TypedXmlPullParser parser) {
129         Objects.requireNonNull(parser);
130         final String fingerprint = parser.getAttributeValue(null, ATTR_OS_FINGERPRINT);
131         if (!Build.FINGERPRINT.equals(fingerprint)) {
132             return null;
133         }
134         final AppWidgetProviderInfo info = new AppWidgetProviderInfo();
135         info.minWidth = parser.getAttributeInt(null, ATTR_MIN_WIDTH, 0);
136         info.minHeight = parser.getAttributeInt(null, ATTR_MIN_HEIGHT, 0);
137         info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_WIDTH, 0);
138         info.minResizeHeight = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
139         info.maxResizeWidth = parser.getAttributeInt(null, ATTR_MAX_RESIZE_WIDTH, 0);
140         info.maxResizeHeight = parser.getAttributeInt(null, ATTR_MAX_RESIZE_HEIGHT, 0);
141         info.targetCellWidth = parser.getAttributeInt(null, ATTR_TARGET_CELL_WIDTH, 0);
142         info.targetCellHeight = parser.getAttributeInt(null, ATTR_TARGET_CELL_HEIGHT, 0);
143         info.updatePeriodMillis = parser.getAttributeInt(null, ATTR_UPDATE_PERIOD_MILLIS, 0);
144         info.initialLayout = parser.getAttributeInt(null, ATTR_INITIAL_LAYOUT, 0);
145         info.initialKeyguardLayout = parser.getAttributeInt(
146                 null, ATTR_INITIAL_KEYGUARD_LAYOUT, 0);
147         final String configure = parser.getAttributeValue(null, ATTR_CONFIGURE);
148         if (!TextUtils.isEmpty(configure)) {
149             info.configure = ComponentName.unflattenFromString(configure);
150         }
151         info.label = parser.getAttributeValue(null, ATTR_LABEL);
152         info.icon = parser.getAttributeInt(null, ATTR_ICON, 0);
153         info.previewImage = parser.getAttributeInt(null, ATTR_PREVIEW_IMAGE, 0);
154         info.previewLayout = parser.getAttributeInt(null, ATTR_PREVIEW_LAYOUT, 0);
155         info.autoAdvanceViewId = parser.getAttributeInt(null, ATTR_AUTO_ADVANCED_VIEW_ID, 0);
156         info.resizeMode = parser.getAttributeInt(null, ATTR_RESIZE_MODE, 0);
157         info.widgetCategory = parser.getAttributeInt(null, ATTR_WIDGET_CATEGORY, 0);
158         info.widgetFeatures = parser.getAttributeInt(null, ATTR_WIDGET_FEATURES, 0);
159         info.descriptionRes = parser.getAttributeInt(null, ATTR_DESCRIPTION_RES, 0);
160         info.isExtendedFromAppWidgetProvider = parser.getAttributeBoolean(null,
161             ATTR_PROVIDER_INHERITANCE, false);
162         return info;
163     }
164 
165     @NonNull
serializeWidgetSizes(@onNull List<SizeF> sizes)166     static String serializeWidgetSizes(@NonNull List<SizeF> sizes) {
167         return sizes.stream().map(SizeF::toString)
168                 .collect(Collectors.joining(SIZE_SEPARATOR));
169     }
170 
171     @Nullable
deserializeWidgetSizesStr(@ullable String sizesStr)172     static ArrayList<SizeF> deserializeWidgetSizesStr(@Nullable String sizesStr) {
173         if (sizesStr == null || sizesStr.isEmpty()) {
174             return null;
175         }
176         try {
177             return Arrays.stream(sizesStr.split(SIZE_SEPARATOR))
178                     .map(SizeF::parseSizeF)
179                     .collect(Collectors.toCollection(ArrayList::new));
180         } catch (NumberFormatException e) {
181             Slog.e(TAG, "Error parsing widget sizes", e);
182             return null;
183         }
184     }
185 
186     /**
187      * Persists {@link AppWidgetServiceImpl.BackupRestoreController.State} to disk as XML.
188      * See {@link #readBackupRestoreControllerState(TypedXmlPullParser)} for example XML.
189      *
190      * @param out XML serializer
191      * @param state {@link AppWidgetServiceImpl.BackupRestoreController.State} of
192      *      intermediate states to be persisted as xml to resume restore after reboot.
193      */
writeBackupRestoreControllerState( @onNull final TypedXmlSerializer out, @NonNull final AppWidgetServiceImpl.BackupRestoreController.State state)194     static void writeBackupRestoreControllerState(
195             @NonNull final TypedXmlSerializer out,
196             @NonNull final AppWidgetServiceImpl.BackupRestoreController.State state)
197             throws IOException {
198         Objects.requireNonNull(out);
199         Objects.requireNonNull(state);
200         out.startTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE);
201         final Set<String> prunedApps = state.getPrunedApps();
202         if (prunedApps != null && !prunedApps.isEmpty()) {
203             out.startTag(null, TAG_PRUNED_APPS);
204             out.attribute(null, ATTR_PACKAGE_NAMES, String.join(",", prunedApps));
205             out.endTag(null, TAG_PRUNED_APPS);
206         }
207         final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
208                 updatesByProvider = state.getUpdatesByProvider();
209         if (updatesByProvider != null) {
210             writeUpdateRecords(out, TAG_PROVIDER_UPDATES, updatesByProvider);
211         }
212         final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
213                 updatesByHost = state.getUpdatesByHost();
214         if (updatesByHost != null) {
215             writeUpdateRecords(out, TAG_HOST_UPDATES, updatesByHost);
216         }
217         out.endTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE);
218     }
219 
writeUpdateRecords(@onNull final TypedXmlSerializer out, @NonNull final String outerTag, @NonNull final SparseArray<List< AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> records)220     private static void writeUpdateRecords(@NonNull final TypedXmlSerializer out,
221             @NonNull final String outerTag, @NonNull final SparseArray<List<
222                     AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> records)
223             throws IOException {
224         for (int i = 0; i < records.size(); i++) {
225             final int tag = records.keyAt(i);
226             out.startTag(null, outerTag);
227             out.attributeInt(null, ATTR_TAG, tag);
228             final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> entries =
229                     records.get(tag);
230             for (AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord entry : entries) {
231                 out.startTag(null, TAG_RECORD);
232                 out.attributeInt(null, ATTR_OLD_ID, entry.oldId);
233                 out.attributeInt(null, ATTR_NEW_ID, entry.newId);
234                 out.attributeBoolean(null, ATTR_NOTIFIED, entry.notified);
235                 out.endTag(null, TAG_RECORD);
236             }
237             out.endTag(null, outerTag);
238         }
239     }
240 
241     /**
242      * Parses {@link AppWidgetServiceImpl.BackupRestoreController.State} from xml.
243      *
244      * <pre>
245      * {@code
246      *     <?xml version="1.0"?>
247      *     <br>
248      *         <pruned_apps pkgs="com.example.app1,com.example.app2,com.example.app3" />
249      *         <provider_updates tag="0">
250      *             <record old_id="10" new_id="0" notified="false" />
251      *         </provider_updates>
252      *         <provider_updates tag="1">
253      *             <record old_id="9" new_id="1" notified="true" />
254      *         </provider_updates>
255      *         <provider_updates tag="2">
256      *             <record old_id="8" new_id="2" notified="false" />
257      *         </provider_updates>
258      *         <host_updates tag="0">
259      *             <record old_id="10" new_id="0" notified="false" />
260      *         </host_updates>
261      *         <host_updates tag="1">
262      *             <record old_id="9" new_id="1" notified="true" />
263      *         </host_updates>
264      *         <host_updates tag="2">
265      *             <record old_id="8" new_id="2" notified="false" />
266      *         </host_updates>
267      *     </br>
268      * }
269      * </pre>
270      *
271      * @param parser XML parser
272      * @return {@link AppWidgetServiceImpl.BackupRestoreController.State} of intermediate states
273      * in {@link AppWidgetServiceImpl.BackupRestoreController}, so that backup & restore can be
274      * resumed after reboot.
275      */
276     @Nullable
277     static AppWidgetServiceImpl.BackupRestoreController.State
readBackupRestoreControllerState(@onNull final TypedXmlPullParser parser)278             readBackupRestoreControllerState(@NonNull final TypedXmlPullParser parser) {
279         Objects.requireNonNull(parser);
280         int type;
281         String tag = null;
282         final Set<String> prunedApps = new ArraySet<>(1);
283         final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
284                 updatesByProviders = new SparseArray<>();
285         final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
286                 updatesByHosts = new SparseArray<>();
287 
288         try {
289             do {
290                 type = parser.next();
291                 if (type != XmlPullParser.START_TAG) {
292                     continue;
293                 }
294                 tag = parser.getName();
295                 switch (tag) {
296                     case TAG_PRUNED_APPS:
297                         final String packages =
298                                 parser.getAttributeValue(null, ATTR_PACKAGE_NAMES);
299                         prunedApps.addAll(Arrays.asList(packages.split(",")));
300                         break;
301                     case TAG_PROVIDER_UPDATES:
302                         updatesByProviders.put(parser.getAttributeInt(null, ATTR_TAG),
303                                 parseRestoreUpdateRecords(parser));
304                         break;
305                     case TAG_HOST_UPDATES:
306                         updatesByHosts.put(parser.getAttributeInt(null, ATTR_TAG),
307                                 parseRestoreUpdateRecords(parser));
308                         break;
309                     default:
310                         break;
311                 }
312             } while (type != XmlPullParser.END_DOCUMENT
313                     && (!TAG_BACKUP_RESTORE_CONTROLLER_STATE.equals(tag)
314                             || type != XmlPullParser.END_TAG));
315         } catch (IOException | XmlPullParserException e) {
316             Log.e(TAG, "error parsing state", e);
317             return null;
318         }
319         return new AppWidgetServiceImpl.BackupRestoreController.State(
320                 prunedApps, updatesByProviders, updatesByHosts);
321     }
322 
323     @NonNull
324     private static List<
325             AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord
parseRestoreUpdateRecords(@onNull final TypedXmlPullParser parser)326             > parseRestoreUpdateRecords(@NonNull final TypedXmlPullParser parser)
327             throws XmlPullParserException, IOException {
328         int type;
329         String tag;
330         final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> ret =
331                 new ArrayList<>();
332         do {
333             type = parser.next();
334             tag = parser.getName();
335             if (tag.equals(TAG_RECORD) && type == XmlPullParser.START_TAG) {
336                 final int oldId = parser.getAttributeInt(null, ATTR_OLD_ID);
337                 final int newId = parser.getAttributeInt(null, ATTR_NEW_ID);
338                 final boolean notified = parser.getAttributeBoolean(
339                         null, ATTR_NOTIFIED);
340                 final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord record =
341                         new AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord(
342                                 oldId, newId);
343                 record.notified = notified;
344                 ret.add(record);
345             }
346         } while (tag.equals(TAG_RECORD));
347         return ret;
348     }
349 }
350