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.providers.media; 18 19 import static android.os.Build.VERSION_CODES.TIRAMISU; 20 import static android.provider.DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT; 21 22 import static java.util.Objects.requireNonNull; 23 24 import android.content.res.Resources; 25 import android.os.Binder; 26 import android.os.Build; 27 import android.os.SystemProperties; 28 import android.provider.DeviceConfig; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.annotation.VisibleForTesting; 33 import androidx.core.util.Supplier; 34 35 import com.android.modules.utils.build.SdkLevel; 36 import com.android.providers.media.util.StringUtils; 37 38 import java.io.PrintWriter; 39 import java.util.Arrays; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.concurrent.Executor; 43 44 /** 45 * Interface for retrieving MediaProvider configs. 46 * The configs are usually stored to and read from {@link android.provider.DeviceConfig} provider, 47 * however having this interface provides an easy way to mock out configs in tests (which don't 48 * always have permissions for accessing the {@link android.provider.DeviceConfig}). 49 */ 50 public interface ConfigStore { 51 52 // TODO(b/288066342): Remove and replace after new constant definition in 53 // {@link android.provider.DeviceConfig}. 54 String NAMESPACE_MEDIAPROVIDER = "mediaprovider"; 55 boolean DEFAULT_TAKE_OVER_GET_CONTENT = false; 56 boolean DEFAULT_USER_SELECT_FOR_APP = true; 57 boolean DEFAULT_STABILISE_VOLUME_INTERNAL = false; 58 boolean DEFAULT_STABILIZE_VOLUME_EXTERNAL = false; 59 boolean DEFAULT_STABILIZE_VOLUME_PUBLIC = false; 60 61 boolean DEFAULT_TRANSCODE_ENABLED = true; 62 boolean DEFAULT_TRANSCODE_OPT_OUT_STRATEGY_ENABLED = false; 63 int DEFAULT_TRANSCODE_MAX_DURATION = 60 * 1000; // 1 minute 64 65 boolean DEFAULT_MODERN_PICKER_ENABLED = false; 66 67 boolean DEFAULT_PICKER_GET_CONTENT_PRELOAD = true; 68 boolean DEFAULT_PICKER_PICK_IMAGES_PRELOAD = true; 69 boolean DEFAULT_PICKER_PICK_IMAGES_RESPECT_PRELOAD_ARG = false; 70 71 boolean DEFAULT_CLOUD_MEDIA_IN_PHOTO_PICKER_ENABLED = true; 72 boolean DEFAULT_ENFORCE_CLOUD_PROVIDER_ALLOWLIST = true; 73 boolean DEFAULT_PICKER_CHOICE_MANAGED_SELECTION_ENABLED = true; 74 75 76 /** 77 * @return if the modern photopicker experience is enabled. 78 */ isModernPickerEnabled()79 default boolean isModernPickerEnabled() { 80 return DEFAULT_MODERN_PICKER_ENABLED; 81 } 82 83 /** 84 * @return if the Cloud-Media-in-Photo-Picker enabled (e.g. platform will recognize and 85 * "plug-in" {@link android.provider.CloudMediaProvider}s. 86 */ isCloudMediaInPhotoPickerEnabled()87 default boolean isCloudMediaInPhotoPickerEnabled() { 88 return DEFAULT_CLOUD_MEDIA_IN_PHOTO_PICKER_ENABLED; 89 } 90 91 /** 92 * @return if the Private-Space-in-Photo-Picker enabled 93 */ 94 // TODO(b/322093140) The method below is needed to support existing espresso tests running 95 // on 'UserIdManager' and needs to be cleared again after refactoring the espresso tests 96 // as per UserManagerState isPrivateSpaceInPhotoPickerEnabled()97 default boolean isPrivateSpaceInPhotoPickerEnabled() { 98 return true; 99 } 100 101 /** 102 * @return if the Picker-Choice_Managed_selection is enabled. 103 */ isPickerChoiceManagedSelectionEnabled()104 default boolean isPickerChoiceManagedSelectionEnabled() { 105 return DEFAULT_PICKER_CHOICE_MANAGED_SELECTION_ENABLED; 106 } 107 108 109 /** 110 * @return package name of the pre-configured "system default" 111 * {@link android.provider.CloudMediaProvider}. 112 * @see #isCloudMediaInPhotoPickerEnabled() 113 */ 114 @Nullable getDefaultCloudProviderPackage()115 default String getDefaultCloudProviderPackage() { 116 return null; 117 } 118 119 /** 120 * @return a non-null list of names of packages that are allowed to serve as the system 121 * Cloud Media Provider. 122 * @see #isCloudMediaInPhotoPickerEnabled() 123 * @see #shouldEnforceCloudProviderAllowlist() 124 */ 125 @NonNull getAllowedCloudProviderPackages()126 default List<String> getAllowedCloudProviderPackages() { 127 return Collections.emptyList(); 128 } 129 130 /** 131 * @return if the Cloud Media Provider allowlist should be enforced. 132 * @see #isCloudMediaInPhotoPickerEnabled() 133 * @see #getAllowedCloudProviderPackages() 134 */ shouldEnforceCloudProviderAllowlist()135 default boolean shouldEnforceCloudProviderAllowlist() { 136 return DEFAULT_ENFORCE_CLOUD_PROVIDER_ALLOWLIST; 137 } 138 139 /** 140 * @return if {@link com.android.providers.media.photopicker.PhotoPickerActivity} should preload 141 * selected media items before "returning" 142 * ({@link com.android.providers.media.photopicker.PhotoPickerActivity#setResultAndFinishSelf()}) 143 * back to the calling application, in the case when the PhotoPicker was launched via 144 * {@link android.content.Intent#ACTION_GET_CONTENT ACTION_GET_CONTENT}. 145 * @see com.android.providers.media.photopicker.PhotoPickerActivity#shouldPreloadSelectedItems() 146 * @see com.android.providers.media.photopicker.SelectedMediaPreloader 147 */ shouldPickerPreloadForGetContent()148 default boolean shouldPickerPreloadForGetContent() { 149 return DEFAULT_PICKER_GET_CONTENT_PRELOAD; 150 } 151 152 /** 153 * @return if {@link com.android.providers.media.photopicker.PhotoPickerActivity} should preload 154 * selected media items before "returning" 155 * ({@link com.android.providers.media.photopicker.PhotoPickerActivity#setResultAndFinishSelf()}) 156 * back to the calling application, in the case when the PhotoPicker was launched via 157 * {@link android.provider.MediaStore#ACTION_PICK_IMAGES ACTION_PICK_IMAGES}. 158 * @see com.android.providers.media.photopicker.PhotoPickerActivity#shouldPreloadSelectedItems() 159 * @see com.android.providers.media.photopicker.SelectedMediaPreloader 160 */ shouldPickerPreloadForPickImages()161 default boolean shouldPickerPreloadForPickImages() { 162 return DEFAULT_PICKER_PICK_IMAGES_PRELOAD; 163 } 164 165 /** 166 * @return if {@link com.android.providers.media.photopicker.PhotoPickerActivity} should respect 167 * {@code EXTRA_PRELOAD_SELECTED} {@code Intent} "argument" when making a 168 * decision whether to preload selected media items before "returning" 169 * ({@link com.android.providers.media.photopicker.PhotoPickerActivity#setResultAndFinishSelf()}) 170 * back to the calling application, in the case when the PhotoPicker was launched via 171 * {@link android.provider.MediaStore#ACTION_PICK_IMAGES ACTION_PICK_IMAGES}. 172 * @see com.android.providers.media.photopicker.PhotoPickerActivity#shouldPreloadSelectedItems() 173 * @see com.android.providers.media.photopicker.SelectedMediaPreloader 174 */ shouldPickerRespectPreloadArgumentForPickImages()175 default boolean shouldPickerRespectPreloadArgumentForPickImages() { 176 return DEFAULT_PICKER_PICK_IMAGES_RESPECT_PRELOAD_ARG; 177 } 178 179 /** 180 * @return if PhotoPicker should handle {@link android.content.Intent#ACTION_GET_CONTENT}. 181 */ isGetContentTakeOverEnabled()182 default boolean isGetContentTakeOverEnabled() { 183 return DEFAULT_TAKE_OVER_GET_CONTENT; 184 } 185 186 /** 187 * @return if PhotoPickerUserSelectActivity should be enabled 188 */ isUserSelectForAppEnabled()189 default boolean isUserSelectForAppEnabled() { 190 return DEFAULT_USER_SELECT_FOR_APP; 191 } 192 193 /** 194 * @return if stable URI are enabled for the internal volume. 195 */ isStableUrisForInternalVolumeEnabled()196 default boolean isStableUrisForInternalVolumeEnabled() { 197 return DEFAULT_STABILISE_VOLUME_INTERNAL; 198 } 199 200 /** 201 * @return if stable URI are enabled for the external volume. 202 */ isStableUrisForExternalVolumeEnabled()203 default boolean isStableUrisForExternalVolumeEnabled() { 204 return DEFAULT_STABILIZE_VOLUME_EXTERNAL; 205 } 206 207 /** 208 * @return if stable URI are enabled for public volumes. 209 */ isStableUrisForPublicVolumeEnabled()210 default boolean isStableUrisForPublicVolumeEnabled() { 211 return DEFAULT_STABILIZE_VOLUME_PUBLIC; 212 } 213 214 /** 215 * @return if transcoding is enabled. 216 */ isTranscodeEnabled()217 default boolean isTranscodeEnabled() { 218 return DEFAULT_TRANSCODE_ENABLED; 219 } 220 221 /** 222 * @return if transcoding is the default option. 223 */ shouldTranscodeDefault()224 default boolean shouldTranscodeDefault() { 225 return DEFAULT_TRANSCODE_OPT_OUT_STRATEGY_ENABLED; 226 } 227 228 /** 229 * @return max transcode duration (in milliseconds). 230 */ getTranscodeMaxDurationMs()231 default int getTranscodeMaxDurationMs() { 232 return DEFAULT_TRANSCODE_MAX_DURATION; 233 } 234 235 @NonNull getTranscodeCompatManifest()236 List<String> getTranscodeCompatManifest(); 237 238 @NonNull getTranscodeCompatStale()239 List<String> getTranscodeCompatStale(); 240 241 /** 242 * Add a listener for changes. 243 */ addOnChangeListener(@onNull Executor executor, @NonNull Runnable listener)244 void addOnChangeListener(@NonNull Executor executor, @NonNull Runnable listener); 245 246 /** 247 * Print the {@link ConfigStore} state into the given stream. 248 */ dump(PrintWriter writer)249 default void dump(PrintWriter writer) { 250 writer.println("Config store state:"); 251 writer.println(" isCloudMediaInPhotoPickerEnabled=" + isCloudMediaInPhotoPickerEnabled()); 252 writer.println(" defaultCloudProviderPackage=" + getDefaultCloudProviderPackage()); 253 writer.println(" allowedCloudProviderPackages=" + getAllowedCloudProviderPackages()); 254 writer.println(" shouldEnforceCloudProviderAllowlist=" 255 + shouldEnforceCloudProviderAllowlist()); 256 writer.println(" shouldPickerPreloadForGetContent=" + shouldPickerPreloadForGetContent()); 257 writer.println(" shouldPickerPreloadForPickImages=" + shouldPickerPreloadForPickImages()); 258 writer.println(" shouldPickerRespectPreloadArgumentForPickImages=" 259 + shouldPickerRespectPreloadArgumentForPickImages()); 260 writer.println(" isGetContentTakeOverEnabled=" + isGetContentTakeOverEnabled()); 261 writer.println(" isUserSelectForAppEnabled=" + isUserSelectForAppEnabled()); 262 writer.println(" isStableUrisForInternalVolumeEnabled=" 263 + isStableUrisForInternalVolumeEnabled()); 264 writer.println(" isStableUrisForExternalVolumeEnabled=" 265 + isStableUrisForExternalVolumeEnabled()); 266 writer.println(" isTranscodeEnabled=" + isTranscodeEnabled()); 267 writer.println(" shouldTranscodeDefault=" + shouldTranscodeDefault()); 268 writer.println(" transcodeMaxDurationMs=" + getTranscodeMaxDurationMs()); 269 writer.println(" transcodeCompatManifest=" + getTranscodeCompatManifest()); 270 writer.println(" transcodeCompatStale=" + getTranscodeCompatStale()); 271 } 272 getDefaultConfigStore()273 static ConfigStore getDefaultConfigStore() { 274 return new ConfigStore() { 275 @NonNull 276 @Override 277 public List<String> getTranscodeCompatManifest() { 278 return Collections.emptyList(); 279 } 280 281 @NonNull 282 @Override 283 public List<String> getTranscodeCompatStale() { 284 return Collections.emptyList(); 285 } 286 287 @Override 288 public void addOnChangeListener(@NonNull Executor executor, 289 @NonNull Runnable listener) { 290 // Do nothing 291 } 292 }; 293 } 294 295 /** 296 * Implementation of the {@link ConfigStore} that reads "real" configs from 297 * {@link android.provider.DeviceConfig}. Meant to be used by the "production" code. 298 */ 299 class ConfigStoreImpl implements ConfigStore { 300 private static final String KEY_TAKE_OVER_GET_CONTENT = "take_over_get_content"; 301 private static final String KEY_USER_SELECT_FOR_APP = "user_select_for_app"; 302 303 @VisibleForTesting 304 public static final String KEY_STABILIZE_VOLUME_INTERNAL = "stabilize_volume_internal"; 305 @VisibleForTesting 306 public static final String KEY_STABILIZE_VOLUME_EXTERNAL = "stabilize_volume_external"; 307 @VisibleForTesting 308 public static final String KEY_STABILIZE_VOLUME_PUBLIC = "stabilize_volume_public"; 309 310 private static final String KEY_TRANSCODE_ENABLED = "transcode_enabled"; 311 private static final String KEY_TRANSCODE_OPT_OUT_STRATEGY_ENABLED = "transcode_default"; 312 private static final String KEY_TRANSCODE_MAX_DURATION = "transcode_max_duration_ms"; 313 private static final String KEY_TRANSCODE_COMPAT_MANIFEST = "transcode_compat_manifest"; 314 private static final String KEY_TRANSCODE_COMPAT_STALE = "transcode_compat_stale"; 315 316 private static final String SYSPROP_TRANSCODE_MAX_DURATION = 317 "persist.sys.fuse.transcode_max_file_duration_ms"; 318 private static final int TRANSCODE_MAX_DURATION_INVALID = 0; 319 320 private static final String KEY_MODERN_PICKER_ENABLED = "enable_modern_picker"; 321 322 private static final String KEY_PICKER_GET_CONTENT_PRELOAD = 323 "picker_get_content_preload_selected"; 324 private static final String KEY_PICKER_PICK_IMAGES_PRELOAD = 325 "picker_pick_images_preload_selected"; 326 private static final String KEY_PICKER_PICK_IMAGES_RESPECT_PRELOAD_ARG = 327 "picker_pick_images_respect_preload_selected_arg"; 328 329 @VisibleForTesting 330 public static final String KEY_CLOUD_MEDIA_FEATURE_ENABLED = "cloud_media_feature_enabled"; 331 332 private static final String KEY_PICKER_CHOICE_MANAGED_SELECTION_ENABLED = 333 "picker_choice_managed_selection_enabled"; 334 @VisibleForTesting 335 public static final String KEY_CLOUD_MEDIA_PROVIDER_ALLOWLIST = "allowed_cloud_providers"; 336 private static final String KEY_CLOUD_MEDIA_ENFORCE_PROVIDER_ALLOWLIST = 337 "cloud_media_enforce_provider_allowlist"; 338 339 private static final boolean sCanReadDeviceConfig = SdkLevel.isAtLeastS(); 340 341 @NonNull 342 private final Resources mResources; 343 344 ConfigStoreImpl(@NonNull Resources resources) { 345 mResources = requireNonNull(resources); 346 } 347 348 @Override 349 public boolean isModernPickerEnabled() { 350 351 // The modern photopicker can only be enabled on T+ such that it can acquire all 352 // of the necessary runtime permissions it needs. For devices running a platform 353 // prior to T, the modern picker is always disabled. 354 if (SdkLevel.isAtLeastT()) { 355 return getBooleanDeviceConfig( 356 NAMESPACE_MEDIAPROVIDER, 357 KEY_MODERN_PICKER_ENABLED, 358 DEFAULT_MODERN_PICKER_ENABLED); 359 } else { 360 return false; 361 } 362 } 363 364 @Override 365 public boolean isCloudMediaInPhotoPickerEnabled() { 366 Boolean isEnabled = 367 getBooleanDeviceConfig( 368 NAMESPACE_MEDIAPROVIDER, 369 KEY_CLOUD_MEDIA_FEATURE_ENABLED, 370 DEFAULT_CLOUD_MEDIA_IN_PHOTO_PICKER_ENABLED); 371 372 List<String> allowList = 373 getStringArrayDeviceConfig( 374 NAMESPACE_MEDIAPROVIDER, KEY_CLOUD_MEDIA_PROVIDER_ALLOWLIST); 375 376 // Only consider the feature enabled when the enabled flag is on AND when the allowlist 377 // of permitted cloud media providers is not empty. 378 return isEnabled && !allowList.isEmpty(); 379 } 380 381 @Override 382 public boolean isPickerChoiceManagedSelectionEnabled() { 383 return getBooleanDeviceConfig( 384 NAMESPACE_MEDIAPROVIDER, 385 KEY_PICKER_CHOICE_MANAGED_SELECTION_ENABLED, 386 DEFAULT_PICKER_CHOICE_MANAGED_SELECTION_ENABLED); 387 } 388 389 @Nullable 390 @Override 391 public String getDefaultCloudProviderPackage() { 392 String pkg = mResources.getString(R.string.config_default_cloud_media_provider_package); 393 if (pkg == null && Build.VERSION.SDK_INT <= TIRAMISU) { 394 // We are on Android T or below and do not have 395 // config_default_cloud_media_provider_package: let's see if we have now deprecated 396 // config_default_cloud_provider_authority. 397 final String authority = 398 mResources.getString(R.string.config_default_cloud_provider_authority); 399 if (authority != null) { 400 pkg = maybeExtractPackageNameFromCloudProviderAuthority(authority); 401 } 402 } 403 return pkg; 404 } 405 406 @NonNull 407 @Override 408 public List<String> getAllowedCloudProviderPackages() { 409 final List<String> allowlist = 410 getStringArrayDeviceConfig(NAMESPACE_MEDIAPROVIDER, 411 KEY_CLOUD_MEDIA_PROVIDER_ALLOWLIST); 412 413 // BACKWARD COMPATIBILITY WORKAROUND. 414 // See javadoc to maybeExtractPackageNameFromCloudProviderAuthority() below for more 415 // details. 416 for (int i = 0; i < allowlist.size(); i++) { 417 final String pkg = 418 maybeExtractPackageNameFromCloudProviderAuthority(allowlist.get(i)); 419 if (pkg != null) { 420 allowlist.set(i, pkg); 421 } 422 } 423 424 return allowlist; 425 } 426 427 @Override 428 public boolean shouldEnforceCloudProviderAllowlist() { 429 return getBooleanDeviceConfig( 430 NAMESPACE_MEDIAPROVIDER, 431 KEY_CLOUD_MEDIA_ENFORCE_PROVIDER_ALLOWLIST, 432 DEFAULT_ENFORCE_CLOUD_PROVIDER_ALLOWLIST); 433 } 434 435 @Override 436 public boolean shouldPickerPreloadForGetContent() { 437 return getBooleanDeviceConfig(KEY_PICKER_GET_CONTENT_PRELOAD, 438 DEFAULT_PICKER_GET_CONTENT_PRELOAD); 439 } 440 441 @Override 442 public boolean shouldPickerPreloadForPickImages() { 443 return getBooleanDeviceConfig(KEY_PICKER_PICK_IMAGES_PRELOAD, 444 DEFAULT_PICKER_PICK_IMAGES_PRELOAD); 445 } 446 447 @Override 448 public boolean shouldPickerRespectPreloadArgumentForPickImages() { 449 return getBooleanDeviceConfig(KEY_PICKER_PICK_IMAGES_RESPECT_PRELOAD_ARG, 450 DEFAULT_PICKER_PICK_IMAGES_RESPECT_PRELOAD_ARG); 451 } 452 453 @Override 454 public boolean isGetContentTakeOverEnabled() { 455 return getBooleanDeviceConfig(NAMESPACE_MEDIAPROVIDER, KEY_TAKE_OVER_GET_CONTENT, 456 DEFAULT_TAKE_OVER_GET_CONTENT); 457 } 458 459 @Override 460 public boolean isUserSelectForAppEnabled() { 461 return getBooleanDeviceConfig(KEY_USER_SELECT_FOR_APP, DEFAULT_USER_SELECT_FOR_APP); 462 } 463 464 @Override 465 public boolean isStableUrisForInternalVolumeEnabled() { 466 return getBooleanDeviceConfig(NAMESPACE_MEDIAPROVIDER, KEY_STABILIZE_VOLUME_INTERNAL, 467 DEFAULT_STABILISE_VOLUME_INTERNAL); 468 } 469 470 @Override 471 public boolean isStableUrisForExternalVolumeEnabled() { 472 return getBooleanDeviceConfig(NAMESPACE_MEDIAPROVIDER, KEY_STABILIZE_VOLUME_EXTERNAL, 473 DEFAULT_STABILIZE_VOLUME_EXTERNAL); 474 } 475 476 @Override 477 public boolean isStableUrisForPublicVolumeEnabled() { 478 return getBooleanDeviceConfig(NAMESPACE_MEDIAPROVIDER, KEY_STABILIZE_VOLUME_PUBLIC, 479 DEFAULT_STABILIZE_VOLUME_PUBLIC); 480 } 481 482 @Override 483 public boolean isTranscodeEnabled() { 484 return getBooleanDeviceConfig( 485 KEY_TRANSCODE_ENABLED, DEFAULT_TRANSCODE_ENABLED); 486 } 487 488 @Override 489 public boolean shouldTranscodeDefault() { 490 return getBooleanDeviceConfig(KEY_TRANSCODE_OPT_OUT_STRATEGY_ENABLED, 491 DEFAULT_TRANSCODE_OPT_OUT_STRATEGY_ENABLED); 492 } 493 494 @Override 495 public int getTranscodeMaxDurationMs() { 496 // First check if OEMs overwrite default duration via system property. 497 int maxDurationMs = SystemProperties.getInt( 498 SYSPROP_TRANSCODE_MAX_DURATION, TRANSCODE_MAX_DURATION_INVALID); 499 500 // Give priority to OEM value if set. Only accept larger values, which can be desired 501 // for more performant devices. Lower values may result in unexpected behaviour 502 // (a value of 0 would mean transcoding is actually disabled) or break CTS tests (a 503 // value small enough to prevent transcoding the videos under test). 504 // Otherwise, fallback to device config / default values. 505 if (maxDurationMs != TRANSCODE_MAX_DURATION_INVALID 506 && maxDurationMs > DEFAULT_TRANSCODE_MAX_DURATION) { 507 return maxDurationMs; 508 } 509 return getIntDeviceConfig(KEY_TRANSCODE_MAX_DURATION, DEFAULT_TRANSCODE_MAX_DURATION); 510 } 511 512 @Override 513 @NonNull 514 public List<String> getTranscodeCompatManifest() { 515 return getStringArrayDeviceConfig(KEY_TRANSCODE_COMPAT_MANIFEST); 516 } 517 518 @Override 519 @NonNull 520 public List<String> getTranscodeCompatStale() { 521 return getStringArrayDeviceConfig(KEY_TRANSCODE_COMPAT_STALE); 522 } 523 524 @Override 525 public void addOnChangeListener(@NonNull Executor executor, @NonNull Runnable listener) { 526 if (!sCanReadDeviceConfig) { 527 return; 528 } 529 530 // TODO(b/246590468): Follow best naming practices for namespaces of device config flags 531 // that make changes to this package independent of reboot 532 DeviceConfig.addOnPropertiesChangedListener( 533 NAMESPACE_STORAGE_NATIVE_BOOT, executor, unused -> listener.run()); 534 DeviceConfig.addOnPropertiesChangedListener( 535 NAMESPACE_MEDIAPROVIDER, executor, unused -> listener.run()); 536 } 537 538 private static boolean getBooleanDeviceConfig(@NonNull String key, boolean defaultValue) { 539 if (!sCanReadDeviceConfig) { 540 return defaultValue; 541 } 542 return withCleanCallingIdentity(() -> 543 DeviceConfig.getBoolean(NAMESPACE_STORAGE_NATIVE_BOOT, key, defaultValue)); 544 } 545 546 private static boolean getBooleanDeviceConfig(@NonNull String namespace, 547 @NonNull String key, boolean defaultValue) { 548 if (!sCanReadDeviceConfig) { 549 return defaultValue; 550 } 551 return withCleanCallingIdentity( 552 () -> DeviceConfig.getBoolean(namespace, key, defaultValue)); 553 } 554 555 private static int getIntDeviceConfig(@NonNull String key, int defaultValue) { 556 if (!sCanReadDeviceConfig) { 557 return defaultValue; 558 } 559 return withCleanCallingIdentity(() -> 560 DeviceConfig.getInt(NAMESPACE_STORAGE_NATIVE_BOOT, key, defaultValue)); 561 } 562 563 private static String getStringDeviceConfig(@NonNull String key) { 564 if (!sCanReadDeviceConfig) { 565 return null; 566 } 567 return withCleanCallingIdentity(() -> 568 DeviceConfig.getString(NAMESPACE_STORAGE_NATIVE_BOOT, key, null)); 569 } 570 571 private static String getStringDeviceConfig(@NonNull String namespace, 572 @NonNull String key) { 573 if (!sCanReadDeviceConfig) { 574 return null; 575 } 576 return withCleanCallingIdentity(() -> 577 DeviceConfig.getString(namespace, key, null)); 578 } 579 580 private static List<String> getStringArrayDeviceConfig(@NonNull String key) { 581 final String items = getStringDeviceConfig(key); 582 if (StringUtils.isNullOrEmpty(items)) { 583 return Collections.emptyList(); 584 } 585 return Arrays.asList(items.split(",")); 586 } 587 588 private static List<String> getStringArrayDeviceConfig(@NonNull String namespace, 589 @NonNull String key) { 590 final String items = getStringDeviceConfig(namespace, key); 591 if (StringUtils.isNullOrEmpty(items)) { 592 return Collections.emptyList(); 593 } 594 return Arrays.asList(items.split(",")); 595 } 596 597 private static <T> T withCleanCallingIdentity(@NonNull Supplier<T> action) { 598 final long callingIdentity = Binder.clearCallingIdentity(); 599 try { 600 return action.get(); 601 } finally { 602 Binder.restoreCallingIdentity(callingIdentity); 603 } 604 } 605 606 /** 607 * BACKWARD COMPATIBILITY WORKAROUND 608 * Initially, instead of using package names when allow-listing and setting the system 609 * default CloudMediaProviders we used authorities. 610 * This, however, introduced a vulnerability, so we switched to using package names. 611 * But, by then, we had been allow-listing and setting default CMPs using authorities. 612 * Luckily for us, all of those CMPs had authorities in one the following formats: 613 * "${package-name}.cloudprovider" or "${package-name}.picker", 614 * e.g. "com.hooli.android.photos" package would implement a CMP with 615 * "com.hooli.android.photos.cloudpicker" authority. 616 * So in order for the old allow-listings and defaults to work now, we try to extract 617 * package names from authorities by removing the ".cloudprovider" and ".cloudpicker" 618 * suffixes. 619 */ 620 @Nullable 621 private static String maybeExtractPackageNameFromCloudProviderAuthority( 622 @NonNull String authority) { 623 if (authority.endsWith(".cloudprovider")) { 624 return authority.substring(0, authority.length() - ".cloudprovider".length()); 625 } else if (authority.endsWith(".cloudpicker")) { 626 return authority.substring(0, authority.length() - ".cloudpicker".length()); 627 } else { 628 return null; 629 } 630 } 631 } 632 } 633