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