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