• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.providers.media.photopicker.util;
18 
19 import static android.provider.CloudMediaProviderContract.MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION;
20 import static android.provider.CloudMediaProviderContract.METHOD_GET_MEDIA_COLLECTION_INFO;
21 import static android.provider.CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME;
22 import static android.provider.MediaStore.EXTRA_CLOUD_PROVIDER;
23 import static android.provider.MediaStore.GET_CLOUD_PROVIDER_CALL;
24 import static android.provider.MediaStore.GET_CLOUD_PROVIDER_RESULT;
25 import static android.provider.MediaStore.SET_CLOUD_PROVIDER_CALL;
26 
27 import static java.util.Collections.emptyList;
28 
29 import android.content.ContentProviderClient;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.PackageManager;
34 import android.content.pm.ProviderInfo;
35 import android.content.pm.ResolveInfo;
36 import android.os.Bundle;
37 import android.os.Process;
38 import android.os.RemoteException;
39 import android.os.UserHandle;
40 import android.provider.CloudMediaProvider;
41 import android.provider.CloudMediaProviderContract;
42 import android.util.Log;
43 
44 import androidx.annotation.NonNull;
45 import androidx.annotation.Nullable;
46 
47 import com.android.providers.media.ConfigStore;
48 import com.android.providers.media.photopicker.data.CloudProviderInfo;
49 import com.android.providers.media.photopicker.data.model.UserId;
50 
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.Objects;
54 
55 /**
56  * Utility methods for retrieving available and/or allowlisted Cloud Providers.
57  *
58  * @see CloudMediaProvider
59  */
60 public class CloudProviderUtils {
61     private static final String TAG = "CloudProviderUtils";
62     /**
63      * @return list of available <b>and</b> allowlisted {@link CloudMediaProvider}-s for the current
64      * user.
65      */
getAvailableCloudProviders( @onNull Context context, @NonNull ConfigStore configStore)66     public static List<CloudProviderInfo> getAvailableCloudProviders(
67             @NonNull Context context, @NonNull ConfigStore configStore) {
68         return getAvailableCloudProviders(
69                 context, configStore, Process.myUserHandle());
70     }
71 
72     /**
73      * @return list of available <b>and</b> allowlisted {@link CloudMediaProvider}-s for the given
74      * userId.
75      */
getAvailableCloudProviders( @onNull Context context, @NonNull ConfigStore configStore, @NonNull UserHandle userHandle)76     public static List<CloudProviderInfo> getAvailableCloudProviders(
77             @NonNull Context context, @NonNull ConfigStore configStore,
78             @NonNull UserHandle userHandle) {
79         return getAvailableCloudProvidersInternal(
80                 context, configStore, /* ignoreAllowList */ false, userHandle);
81     }
82 
83     /**
84      * @return list of <b>all</b> available {@link CloudMediaProvider}-s (<b>ignoring</b> the
85      *         allowlist) for the current user.
86      */
getAllAvailableCloudProviders( @onNull Context context, @NonNull ConfigStore configStore)87     public static List<CloudProviderInfo> getAllAvailableCloudProviders(
88             @NonNull Context context, @NonNull ConfigStore configStore) {
89         return getAllAvailableCloudProviders(context, configStore, Process.myUserHandle());
90     }
91 
92     /**
93      * @return list of <b>all</b> available {@link CloudMediaProvider}-s (<b>ignoring</b> the
94      *         allowlist) for the given userId.
95      */
getAllAvailableCloudProviders( @onNull Context context, @NonNull ConfigStore configStore, @NonNull UserHandle userHandle)96     public static List<CloudProviderInfo> getAllAvailableCloudProviders(
97             @NonNull Context context, @NonNull ConfigStore configStore,
98             @NonNull UserHandle userHandle) {
99         return getAvailableCloudProvidersInternal(context, configStore, /* ignoreAllowList */ true,
100                 userHandle);
101     }
102 
getAvailableCloudProvidersInternal( @onNull Context context, @NonNull ConfigStore configStore, boolean ignoreAllowlist, @NonNull UserHandle userHandle)103     private static List<CloudProviderInfo> getAvailableCloudProvidersInternal(
104             @NonNull Context context, @NonNull ConfigStore configStore, boolean ignoreAllowlist,
105             @NonNull UserHandle userHandle) {
106         if (!configStore.isCloudMediaInPhotoPickerEnabled()) {
107             Log.i(TAG, "Returning an empty list of available cloud providers since the "
108                     + "Cloud-Media-in-Photo-Picker feature is disabled.");
109             return emptyList();
110         }
111 
112         Objects.requireNonNull(context);
113 
114         ignoreAllowlist = ignoreAllowlist || !configStore.shouldEnforceCloudProviderAllowlist();
115 
116         final List<CloudProviderInfo> providers = new ArrayList<>();
117 
118         // We do not need to get the allowlist from the ConfigStore if we are going to skip
119         // if-allowlisted check below.
120         final List<String> allowlistedPackages =
121                 ignoreAllowlist ? null : configStore.getAllowedCloudProviderPackages();
122 
123         final Intent intent = new Intent(CloudMediaProviderContract.PROVIDER_INTERFACE);
124         final List<ResolveInfo> allAvailableProviders = getAllCloudProvidersForUser(context,
125                 intent, userHandle);
126 
127         for (ResolveInfo info : allAvailableProviders) {
128             final ProviderInfo providerInfo = info.providerInfo;
129             if (providerInfo.authority == null) {
130                 // Provider does NOT declare an authority.
131                 continue;
132             }
133 
134             if (!MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION.equals(providerInfo.readPermission)) {
135                 // Provider does NOT have the right read permission.
136                 continue;
137             }
138 
139             if (!ignoreAllowlist && !allowlistedPackages.contains(providerInfo.packageName)) {
140                 // Provider is not allowlisted.
141                 continue;
142             }
143 
144             final CloudProviderInfo cloudProvider = new CloudProviderInfo(
145                     providerInfo.authority,
146                     providerInfo.applicationInfo.packageName,
147                     providerInfo.applicationInfo.uid);
148             providers.add(cloudProvider);
149         }
150 
151         Log.d(TAG, (ignoreAllowlist ? "All (ignoring allowlist)" : "")
152                 + "Available CloudMediaProvider-s: " + providers);
153         return providers;
154     }
155 
156     /**
157      * Returns a list of all available providers with the given intent for a userId. If userId is
158      * null, results are returned for the current user.
159      */
getAllCloudProvidersForUser(@onNull Context context, @NonNull Intent intent, @NonNull UserHandle userHandle)160     private static List<ResolveInfo> getAllCloudProvidersForUser(@NonNull Context context,
161             @NonNull Intent intent, @NonNull UserHandle userHandle) {
162         return context.getPackageManager()
163                 .queryIntentContentProvidersAsUser(intent, 0, userHandle);
164     }
165 
166     /**
167      * Request content provider to change cloud provider.
168      */
persistSelectedProvider( @onNull ContentProviderClient client, @Nullable String newCloudProvider)169     public static boolean persistSelectedProvider(
170             @NonNull ContentProviderClient client,
171             @Nullable String newCloudProvider) throws RemoteException {
172         final Bundle input = new Bundle();
173         input.putString(EXTRA_CLOUD_PROVIDER, newCloudProvider);
174         client.call(SET_CLOUD_PROVIDER_CALL, /* arg */ null, /* extras */ input);
175         return true;
176     }
177 
178     /**
179      * Fetch selected cloud provider from content provider.
180      * @param defaultAuthority is the default returned in case query result is null.
181      * @return fetched cloud provider authority if it is non-null.
182      *               Otherwise return defaultAuthority.
183      */
184     @Nullable
fetchProviderAuthority( @onNull ContentProviderClient client, @NonNull String defaultAuthority)185     public static String fetchProviderAuthority(
186             @NonNull ContentProviderClient client,
187             @NonNull String defaultAuthority) throws RemoteException {
188         final Bundle result = client.call(GET_CLOUD_PROVIDER_CALL, /* arg */ null,
189                 /* extras */ null);
190         return result.getString(GET_CLOUD_PROVIDER_RESULT, defaultAuthority);
191     }
192 
193     /**
194      * @return the label for the {@link ProviderInfo} with {@code authority} for the given
195      *         {@link UserHandle}.
196      */
197     @Nullable
getProviderLabelForUser(@onNull Context context, @NonNull UserHandle user, @Nullable String authority)198     public static String getProviderLabelForUser(@NonNull Context context, @NonNull UserHandle user,
199             @Nullable String authority) throws PackageManager.NameNotFoundException {
200         if (authority == null) {
201             return null;
202         }
203 
204         final PackageManager packageManager = UserId.of(user).getPackageManager(context);
205         return getProviderLabel(packageManager, authority);
206     }
207 
208     /**
209      * @return the label for the {@link ProviderInfo} with {@code authority}.
210      */
211     @NonNull
getProviderLabel(@onNull PackageManager packageManager, @NonNull String authority)212     public static String getProviderLabel(@NonNull PackageManager packageManager,
213             @NonNull String authority) {
214         final ProviderInfo providerInfo = packageManager.resolveContentProvider(
215                 authority, /* flags */ 0);
216         return getProviderLabel(packageManager, providerInfo);
217     }
218 
219     /**
220      * @return the label for the given {@link ProviderInfo}.
221      */
222     @NonNull
getProviderLabel(@onNull PackageManager packageManager, @NonNull ProviderInfo providerInfo)223     public static String getProviderLabel(@NonNull PackageManager packageManager,
224             @NonNull ProviderInfo providerInfo) {
225         return String.valueOf(providerInfo.loadLabel(packageManager));
226     }
227 
228     /**
229      * @return the current cloud media account name for the {@link CloudMediaProvider} with the
230      *         given {@code cloudMediaProviderAuthority}.
231      */
232     @Nullable
getCloudMediaAccountName(@onNull ContentResolver resolver, @Nullable String cloudMediaProviderAuthority)233     public static String getCloudMediaAccountName(@NonNull ContentResolver resolver,
234             @Nullable String cloudMediaProviderAuthority) {
235         if (cloudMediaProviderAuthority == null) {
236             return null;
237         }
238 
239         try (ContentProviderClient client =
240                      resolver.acquireContentProviderClient(cloudMediaProviderAuthority)) {
241             final Bundle out = client.call(METHOD_GET_MEDIA_COLLECTION_INFO, /* arg */ null,
242                     /* extras */ null);
243             return out.getString(ACCOUNT_NAME);
244         } catch (RemoteException e) {
245             throw e.rethrowAsRuntimeException();
246         }
247     }
248 }
249