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