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 android.app.admin; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.RequiresPermission; 22 import android.annotation.SuppressLint; 23 import android.annotation.SystemApi; 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.graphics.drawable.Drawable; 27 import android.graphics.drawable.Icon; 28 import android.os.RemoteException; 29 import android.provider.DeviceConfig; 30 import android.util.DisplayMetrics; 31 import android.util.Log; 32 33 import java.util.ArrayList; 34 import java.util.Objects; 35 import java.util.Set; 36 import java.util.function.Supplier; 37 38 /** 39 * Class containing required APIs to set, reset, and get device policy related resources. 40 */ 41 public class DevicePolicyResourcesManager { 42 private static String TAG = "DevicePolicyResourcesManager"; 43 44 private static String DISABLE_RESOURCES_UPDATABILITY_FLAG = "disable_resources_updatability"; 45 private static boolean DEFAULT_DISABLE_RESOURCES_UPDATABILITY = false; 46 47 private final Context mContext; 48 private final IDevicePolicyManager mService; 49 50 /** 51 * @hide 52 */ DevicePolicyResourcesManager(Context context, IDevicePolicyManager service)53 protected DevicePolicyResourcesManager(Context context, IDevicePolicyManager service) { 54 mContext = context; 55 mService = service; 56 } 57 58 /** 59 * For each {@link DevicePolicyDrawableResource} item in {@code drawables}, if 60 * {@link DevicePolicyDrawableResource#getDrawableSource()} is not set, it updates the drawable 61 * resource for the combination of {@link DevicePolicyDrawableResource#getDrawableId()} and 62 * {@link DevicePolicyDrawableResource#getDrawableStyle()} to the drawable with resource ID 63 * {@link DevicePolicyDrawableResource#getResourceIdInCallingPackage()}, 64 * meaning any system UI surface calling {@link #getDrawable} with {@code drawableId} and 65 * {@code drawableStyle} will get the new resource after this API is called. 66 * 67 * <p>Otherwise, if {@link DevicePolicyDrawableResource#getDrawableSource()} is set, it 68 * overrides any drawables that was set for the same {@code drawableId} and 69 * {@code drawableStyle} for the provided source. 70 * 71 * <p>Sends a broadcast with action 72 * {@link DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to registered receivers 73 * when a resource has been updated successfully. 74 * 75 * <p>Important notes to consider when using this API: 76 * <ul> 77 * <li> Updated resources are persisted over reboots. 78 * <li>{@link #getDrawable} references the resource 79 * {@link DevicePolicyDrawableResource#getResourceIdInCallingPackage()} in the 80 * calling package each time it gets called. You have to ensure that the resource is always 81 * available in the calling package as long as it is used as an updated resource. 82 * <li>You still have to re-call {@code setDrawables} even if you only make changes to the 83 * content of the resource with ID 84 * {@link DevicePolicyDrawableResource#getResourceIdInCallingPackage()} as the content might be 85 * cached and would need updating. 86 * </ul> 87 * 88 * @param drawables The list of {@link DevicePolicyDrawableResource} to update. 89 * 90 * @hide 91 */ 92 @SystemApi 93 @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) setDrawables(@onNull Set<DevicePolicyDrawableResource> drawables)94 public void setDrawables(@NonNull Set<DevicePolicyDrawableResource> drawables) { 95 if (mService != null) { 96 try { 97 mService.setDrawables(new ArrayList<>(drawables)); 98 } catch (RemoteException e) { 99 throw e.rethrowFromSystemServer(); 100 } 101 } 102 } 103 104 /** 105 * Removes all updated drawables for the list of {@code drawableIds} that was previously set by 106 * calling {@link #setDrawables}, meaning any subsequent calls to {@link #getDrawable} for the 107 * provided IDs with any {@code drawableStyle} and any {@code drawableSource} will return the 108 * default drawable from {@code defaultDrawableLoader}. 109 * 110 * <p>Sends a broadcast with action 111 * {@link DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to registered receivers 112 * when a resource has been reset successfully. 113 * 114 * @param drawableIds The list of IDs to remove. 115 * 116 * @hide 117 */ 118 @SystemApi 119 @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) resetDrawables(@onNull Set<String> drawableIds)120 public void resetDrawables(@NonNull Set<String> drawableIds) { 121 if (mService != null) { 122 try { 123 mService.resetDrawables(new ArrayList<>(drawableIds)); 124 } catch (RemoteException e) { 125 throw e.rethrowFromSystemServer(); 126 } 127 } 128 } 129 130 /** 131 * Returns the appropriate updated drawable for the {@code drawableId} with style 132 * {@code drawableStyle} if one was set using {@code setDrawables}, otherwise returns the 133 * drawable from {@code defaultDrawableLoader}. 134 * 135 * <p>Also returns the drawable from {@code defaultDrawableLoader} if {@code drawableId} 136 * is {@link DevicePolicyResources#UNDEFINED}. 137 * 138 * <p>Calls to this API will not return {@code null} unless no updated drawable was found 139 * and the call to {@code defaultDrawableLoader} returned {@code null}. 140 * 141 * <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to 142 * set a different value use 143 * {@link #getDrawableForDensity(String, String, int, Supplier)}. 144 * 145 * <p>Callers should register for 146 * {@link DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get notified when a 147 * resource has been updated. 148 * 149 * <p>Note that each call to this API loads the resource from the package that called 150 * {@code setDrawables} to set the updated resource. 151 * 152 * @param drawableId The drawable ID to get the updated resource for. 153 * @param drawableStyle The drawable style to use. 154 * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for 155 * the provided params. 156 */ 157 @Nullable getDrawable( @onNull String drawableId, @NonNull String drawableStyle, @NonNull Supplier<Drawable> defaultDrawableLoader)158 public Drawable getDrawable( 159 @NonNull String drawableId, 160 @NonNull String drawableStyle, 161 @NonNull Supplier<Drawable> defaultDrawableLoader) { 162 return getDrawable( 163 drawableId, drawableStyle, DevicePolicyResources.UNDEFINED, defaultDrawableLoader); 164 } 165 166 /** 167 * Similar to {@link #getDrawable(String, String, Supplier)}, but also accepts 168 * a {@code drawableSource} which could result in returning a different drawable than 169 * {@link #getDrawable(String, String, Supplier)} if an override was set for that specific 170 * source. 171 * 172 * <p> If {@code drawableSource} is {@link DevicePolicyResources#UNDEFINED}, it returns the 173 * appropriate string for {@code drawableId} and {@code drawableStyle} similar to 174 * {@link #getDrawable(String, String, Supplier)}. 175 * 176 * <p>Calls to this API will not return {@code null} unless no updated drawable was found 177 * and the call to {@code defaultDrawableLoader} returned {@code null}. 178 * 179 * <p>Callers should register for 180 * {@link DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get notified when a 181 * resource has been updated. 182 * 183 * @param drawableId The drawable ID to get the updated resource for. 184 * @param drawableStyle The drawable style to use. 185 * @param drawableSource The source for the caller. 186 * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for 187 * the provided params. 188 */ 189 @Nullable getDrawable( @onNull String drawableId, @NonNull String drawableStyle, @NonNull String drawableSource, @NonNull Supplier<Drawable> defaultDrawableLoader)190 public Drawable getDrawable( 191 @NonNull String drawableId, 192 @NonNull String drawableStyle, 193 @NonNull String drawableSource, 194 @NonNull Supplier<Drawable> defaultDrawableLoader) { 195 196 Objects.requireNonNull(drawableId, "drawableId can't be null"); 197 Objects.requireNonNull(drawableStyle, "drawableStyle can't be null"); 198 Objects.requireNonNull(drawableSource, "drawableSource can't be null"); 199 Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); 200 201 if (drawableId.equals(DevicePolicyResources.UNDEFINED) 202 || DeviceConfig.getBoolean( 203 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, 204 DISABLE_RESOURCES_UPDATABILITY_FLAG, 205 DEFAULT_DISABLE_RESOURCES_UPDATABILITY)) { 206 return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); 207 } 208 209 if (mService != null) { 210 try { 211 ParcelableResource resource = mService.getDrawable( 212 drawableId, drawableStyle, drawableSource); 213 if (resource == null) { 214 return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); 215 } 216 return resource.getDrawable( 217 mContext, 218 /* density= */ 0, 219 defaultDrawableLoader); 220 221 } catch (RemoteException e) { 222 Log.e( 223 TAG, 224 "Error getting the updated drawable from DevicePolicyManagerService.", 225 e); 226 return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); 227 } 228 } 229 return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); 230 } 231 232 /** 233 * Similar to {@link #getDrawable(String, String, Supplier)}, but also accepts 234 * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. 235 * 236 * <p>Calls to this API will not return {@code null} unless no updated drawable was found 237 * and the call to {@code defaultDrawableLoader} returned {@code null}. 238 * 239 * <p>Callers should register for 240 * {@link DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get notified when a 241 * resource has been updated. 242 * 243 * @param drawableId The drawable ID to get the updated resource for. 244 * @param drawableStyle The drawable style to use. 245 * @param density The desired screen density indicated by the resource as 246 * found in {@link DisplayMetrics}. A value of 0 means to use the 247 * density returned from {@link Resources#getConfiguration()}. 248 * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for 249 * the provided params. 250 */ 251 @Nullable getDrawableForDensity( @onNull String drawableId, @NonNull String drawableStyle, int density, @NonNull Supplier<Drawable> defaultDrawableLoader)252 public Drawable getDrawableForDensity( 253 @NonNull String drawableId, 254 @NonNull String drawableStyle, 255 int density, 256 @NonNull Supplier<Drawable> defaultDrawableLoader) { 257 return getDrawableForDensity( 258 drawableId, 259 drawableStyle, 260 DevicePolicyResources.UNDEFINED, 261 density, 262 defaultDrawableLoader); 263 } 264 265 /** 266 * Similar to {@link #getDrawable(String, String, String, Supplier)}, but also accepts 267 * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. 268 * 269 * <p>Calls to this API will not return {@code null} unless no updated drawable was found 270 * and the call to {@code defaultDrawableLoader} returned {@code null}. 271 * 272 * <p>Callers should register for 273 * {@link DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get notified when a 274 * resource has been updated. 275 * 276 * @param drawableId The drawable ID to get the updated resource for. 277 * @param drawableStyle The drawable style to use. 278 * @param drawableSource The source for the caller. 279 * @param density The desired screen density indicated by the resource as 280 * found in {@link DisplayMetrics}. A value of 0 means to use the 281 * density returned from {@link Resources#getConfiguration()}. 282 * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for 283 * the provided params. 284 */ 285 @Nullable getDrawableForDensity( @onNull String drawableId, @NonNull String drawableStyle, @NonNull String drawableSource, int density, @NonNull Supplier<Drawable> defaultDrawableLoader)286 public Drawable getDrawableForDensity( 287 @NonNull String drawableId, 288 @NonNull String drawableStyle, 289 @NonNull String drawableSource, 290 int density, 291 @NonNull Supplier<Drawable> defaultDrawableLoader) { 292 293 Objects.requireNonNull(drawableId, "drawableId can't be null"); 294 Objects.requireNonNull(drawableStyle, "drawableStyle can't be null"); 295 Objects.requireNonNull(drawableSource, "drawableSource can't be null"); 296 Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); 297 298 if (drawableId.equals(DevicePolicyResources.UNDEFINED) 299 || DeviceConfig.getBoolean( 300 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, 301 DISABLE_RESOURCES_UPDATABILITY_FLAG, 302 DEFAULT_DISABLE_RESOURCES_UPDATABILITY)) { 303 return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); 304 } 305 306 if (mService != null) { 307 try { 308 ParcelableResource resource = mService.getDrawable( 309 drawableId, drawableStyle, drawableSource); 310 if (resource == null) { 311 return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); 312 } 313 return resource.getDrawable(mContext, density, defaultDrawableLoader); 314 } catch (RemoteException e) { 315 Log.e( 316 TAG, 317 "Error getting the updated drawable from DevicePolicyManagerService.", 318 e); 319 return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); 320 } 321 } 322 return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); 323 } 324 325 /** 326 * Similar to {@link #getDrawable(String, String, String, Supplier)} but returns an 327 * {@link Icon} instead of a {@link Drawable}. 328 * 329 * @param drawableId The drawable ID to get the updated resource for. 330 * @param drawableStyle The drawable style to use. 331 * @param drawableSource The source for the caller. 332 * @param defaultIcon Returned if no updated drawable was set for the provided params. 333 */ 334 @Nullable getDrawableAsIcon( @onNull String drawableId, @NonNull String drawableStyle, @NonNull String drawableSource, @Nullable Icon defaultIcon)335 public Icon getDrawableAsIcon( 336 @NonNull String drawableId, 337 @NonNull String drawableStyle, 338 @NonNull String drawableSource, 339 @Nullable Icon defaultIcon) { 340 Objects.requireNonNull(drawableId, "drawableId can't be null"); 341 Objects.requireNonNull(drawableStyle, "drawableStyle can't be null"); 342 Objects.requireNonNull(drawableSource, "drawableSource can't be null"); 343 Objects.requireNonNull(defaultIcon, "defaultIcon can't be null"); 344 345 if (drawableId.equals(DevicePolicyResources.UNDEFINED) 346 || DeviceConfig.getBoolean( 347 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, 348 DISABLE_RESOURCES_UPDATABILITY_FLAG, 349 DEFAULT_DISABLE_RESOURCES_UPDATABILITY)) { 350 return defaultIcon; 351 } 352 353 if (mService != null) { 354 try { 355 ParcelableResource resource = mService.getDrawable( 356 drawableId, drawableStyle, drawableSource); 357 if (resource == null) { 358 return defaultIcon; 359 } 360 return Icon.createWithResource(resource.getPackageName(), resource.getResourceId()); 361 } catch (RemoteException e) { 362 Log.e( 363 TAG, 364 "Error getting the updated drawable from DevicePolicyManagerService.", 365 e); 366 return defaultIcon; 367 } 368 } 369 return defaultIcon; 370 } 371 372 /** 373 * Similar to {@link #getDrawable(String, String, Supplier)} but returns an {@link Icon} 374 * instead of a {@link Drawable}. 375 * 376 * @param drawableId The drawable ID to get the updated resource for. 377 * @param drawableStyle The drawable style to use. 378 * @param defaultIcon Returned if no updated drawable was set for the provided params. 379 */ 380 @Nullable getDrawableAsIcon( @onNull String drawableId, @NonNull String drawableStyle, @Nullable Icon defaultIcon)381 public Icon getDrawableAsIcon( 382 @NonNull String drawableId, 383 @NonNull String drawableStyle, 384 @Nullable Icon defaultIcon) { 385 return getDrawableAsIcon( 386 drawableId, drawableStyle, DevicePolicyResources.UNDEFINED, defaultIcon); 387 } 388 389 390 /** 391 * For each {@link DevicePolicyStringResource} item in {@code strings}, it updates the string 392 * resource for {@link DevicePolicyStringResource#getStringId()} to the string with ID 393 * {@code callingPackageResourceId}, meaning any system UI surface calling {@link #getString} 394 * with {@code stringId} will get the new resource after this API is called. 395 * 396 * <p>Sends a broadcast with action 397 * {@link DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to registered receivers 398 * when a resource has been updated successfully. 399 * 400 * <p>Important notes to consider when using this API: 401 * <ul> 402 * <li> Updated resources are persisted over reboots. 403 * <li> {@link #getString} references the resource 404 * {@link DevicePolicyStringResource#getResourceIdInCallingPackage()} in the 405 * calling package each time it gets called. You have to ensure that the resource is always 406 * available in the calling package as long as it is used as an updated resource. 407 * <li> You still have to re-call {@code setStrings} even if you only make changes to the 408 * content of the resource with ID {@code callingPackageResourceId} as the content might be 409 * cached and would need updating. 410 * </ul> 411 * 412 * @param strings The list of {@link DevicePolicyStringResource} to update. 413 * 414 * @hide 415 */ 416 @SystemApi 417 @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) setStrings(@onNull Set<DevicePolicyStringResource> strings)418 public void setStrings(@NonNull Set<DevicePolicyStringResource> strings) { 419 if (mService != null) { 420 try { 421 mService.setStrings(new ArrayList<>(strings)); 422 } catch (RemoteException e) { 423 throw e.rethrowFromSystemServer(); 424 } 425 } 426 } 427 428 /** 429 * Removes the updated strings for the list of {@code stringIds} that was previously set by 430 * calling {@link #setStrings}, meaning any subsequent calls to {@link #getString} for the 431 * provided IDs will return the default string from {@code defaultStringLoader}. 432 * 433 * <p>Sends a broadcast with action 434 * {@link DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to registered receivers 435 * when a resource has been reset successfully. 436 * 437 * @param stringIds The list of IDs to remove the updated resources for. 438 * 439 * @hide 440 */ 441 @SystemApi 442 @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) resetStrings(@onNull Set<String> stringIds)443 public void resetStrings(@NonNull Set<String> stringIds) { 444 if (mService != null) { 445 try { 446 mService.resetStrings(new ArrayList<>(stringIds)); 447 } catch (RemoteException e) { 448 throw e.rethrowFromSystemServer(); 449 } 450 } 451 } 452 453 /** 454 * Returns the appropriate updated string for the {@code stringId} (see 455 * {@code DevicePolicyResources.Strings}) if one was set using 456 * {@code setStrings}, otherwise returns the string from {@code defaultStringLoader}. 457 * 458 * <p>Also returns the string from {@code defaultStringLoader} if {@code stringId} is 459 * {@link DevicePolicyResources#UNDEFINED}. 460 * 461 * <p>Calls to this API will not return {@code null} unless no updated drawable was found 462 * and the call to {@code defaultStringLoader} returned {@code null}. 463 * 464 * <p>Callers should register for 465 * {@link DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get notified when a 466 * resource has been updated. 467 * 468 * <p>Note that each call to this API loads the resource from the package that called 469 * {@code setStrings} to set the updated resource. 470 * 471 * @param stringId The IDs to get the updated resource for. 472 * @param defaultStringLoader To get the default string if no updated string was set for 473 * {@code stringId}. 474 */ 475 @Nullable getString( @onNull String stringId, @NonNull Supplier<String> defaultStringLoader)476 public String getString( 477 @NonNull String stringId, 478 @NonNull Supplier<String> defaultStringLoader) { 479 480 Objects.requireNonNull(stringId, "stringId can't be null"); 481 Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); 482 483 if (stringId.equals(DevicePolicyResources.UNDEFINED) || DeviceConfig.getBoolean( 484 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, 485 DISABLE_RESOURCES_UPDATABILITY_FLAG, 486 DEFAULT_DISABLE_RESOURCES_UPDATABILITY)) { 487 return ParcelableResource.loadDefaultString(defaultStringLoader); 488 } 489 if (mService != null) { 490 try { 491 ParcelableResource resource = mService.getString(stringId); 492 if (resource == null) { 493 return ParcelableResource.loadDefaultString(defaultStringLoader); 494 } 495 return resource.getString(mContext, defaultStringLoader); 496 } catch (RemoteException e) { 497 Log.e( 498 TAG, 499 "Error getting the updated string from DevicePolicyManagerService.", 500 e); 501 return ParcelableResource.loadDefaultString(defaultStringLoader); 502 } 503 } 504 return ParcelableResource.loadDefaultString(defaultStringLoader); 505 } 506 507 /** 508 * Similar to {@link #getString(String, Supplier)} but accepts {@code formatArgs} and returns a 509 * localized formatted string, substituting the format arguments as defined in 510 * {@link java.util.Formatter} and {@link java.lang.String#format}, (see 511 * {@link Resources#getString(int, Object...)}). 512 * 513 * <p>Calls to this API will not return {@code null} unless no updated drawable was found 514 * and the call to {@code defaultStringLoader} returned {@code null}. 515 * 516 * @param stringId The IDs to get the updated resource for. 517 * @param defaultStringLoader To get the default string if no updated string was set for 518 * {@code stringId}. 519 * @param formatArgs The format arguments that will be used for substitution. 520 */ 521 @Nullable 522 @SuppressLint("SamShouldBeLast") getString( @onNull String stringId, @NonNull Supplier<String> defaultStringLoader, @NonNull Object... formatArgs)523 public String getString( 524 @NonNull String stringId, 525 @NonNull Supplier<String> defaultStringLoader, 526 @NonNull Object... formatArgs) { 527 528 Objects.requireNonNull(stringId, "stringId can't be null"); 529 Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); 530 531 if (stringId.equals(DevicePolicyResources.UNDEFINED) || DeviceConfig.getBoolean( 532 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, 533 DISABLE_RESOURCES_UPDATABILITY_FLAG, 534 DEFAULT_DISABLE_RESOURCES_UPDATABILITY)) { 535 return ParcelableResource.loadDefaultString(defaultStringLoader); 536 } 537 if (mService != null) { 538 try { 539 ParcelableResource resource = mService.getString(stringId); 540 if (resource == null) { 541 return ParcelableResource.loadDefaultString(defaultStringLoader); 542 } 543 return resource.getString(mContext, defaultStringLoader, formatArgs); 544 } catch (RemoteException e) { 545 Log.e( 546 TAG, 547 "Error getting the updated string from DevicePolicyManagerService.", 548 e); 549 return ParcelableResource.loadDefaultString(defaultStringLoader); 550 } 551 } 552 return ParcelableResource.loadDefaultString(defaultStringLoader); 553 } 554 } 555