1 /* 2 * Copyright (C) 2024 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; 18 19 import static android.Manifest.permission.READ_DEVICE_CONFIG; 20 import static android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG; 21 import static android.Manifest.permission.WRITE_DEVICE_CONFIG; 22 23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 24 25 import static com.android.providers.media.ConfigStore.ConfigStoreImpl.KEY_CLOUD_MEDIA_FEATURE_ENABLED; 26 import static com.android.providers.media.ConfigStore.ConfigStoreImpl.KEY_CLOUD_MEDIA_PROVIDER_ALLOWLIST; 27 28 import static com.google.common.truth.Truth.assertWithMessage; 29 30 import android.os.UserHandle; 31 import android.provider.DeviceConfig; 32 import android.text.TextUtils; 33 import android.util.Log; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 import androidx.test.uiautomator.UiDevice; 38 39 import com.android.modules.utils.build.SdkLevel; 40 41 import java.io.IOException; 42 43 public class PhotoPickerCloudTestUtils { 44 private static final String TAG = PhotoPickerCloudTestUtils.class.getSimpleName(); 45 static final String INVALID_CLOUD_PROVIDER = "Invalid"; 46 47 /** 48 * @return true if cloud is enabled in the device config of the given namespace, 49 * otherwise false. 50 */ isCloudMediaEnabled(@onNull String namespace)51 static boolean isCloudMediaEnabled(@NonNull String namespace) { 52 return Boolean.parseBoolean( 53 readDeviceConfigProp(namespace, KEY_CLOUD_MEDIA_FEATURE_ENABLED)); 54 } 55 56 /** 57 * @return the allowed providers in the device config of the given namespace. 58 */ 59 @Nullable getAllowedProvidersDeviceConfig(@onNull String namespace)60 static String getAllowedProvidersDeviceConfig(@NonNull String namespace) { 61 return readDeviceConfigProp(namespace, KEY_CLOUD_MEDIA_PROVIDER_ALLOWLIST); 62 } 63 64 /** 65 * Enables cloud media and sets the allowed cloud provider in the given namespace. 66 */ enableCloudMediaAndSetAllowedCloudProviders( @onNull String namespace, @NonNull String allowedPackagesJoined)67 public static void enableCloudMediaAndSetAllowedCloudProviders( 68 @NonNull String namespace, @NonNull String allowedPackagesJoined) { 69 writeDeviceConfigProp(namespace, KEY_CLOUD_MEDIA_PROVIDER_ALLOWLIST, allowedPackagesJoined); 70 assertWithMessage("Failed to update the allowed cloud providers device config") 71 .that(getAllowedProvidersDeviceConfig(namespace)) 72 .isEqualTo(allowedPackagesJoined); 73 74 writeDeviceConfigProp(namespace, KEY_CLOUD_MEDIA_FEATURE_ENABLED, true); 75 assertWithMessage("Failed to update the cloud media feature device config") 76 .that(isCloudMediaEnabled(namespace)) 77 .isTrue(); 78 } 79 80 /** 81 * Disable cloud media in the given namespace. 82 */ disableCloudMediaAndClearAllowedCloudProviders(@onNull String namespace)83 static void disableCloudMediaAndClearAllowedCloudProviders(@NonNull String namespace) { 84 writeDeviceConfigProp(namespace, KEY_CLOUD_MEDIA_FEATURE_ENABLED, false); 85 assertWithMessage("Failed to update the cloud media feature device config") 86 .that(isCloudMediaEnabled(namespace)) 87 .isFalse(); 88 89 deleteDeviceConfigProp(namespace, KEY_CLOUD_MEDIA_PROVIDER_ALLOWLIST); 90 assertWithMessage("Failed to delete the allowed cloud providers device config") 91 .that(getAllowedProvidersDeviceConfig(namespace)) 92 .isNull(); 93 } 94 95 @Nullable readDeviceConfigProp(@onNull String namespace, @NonNull String name)96 private static String readDeviceConfigProp(@NonNull String namespace, @NonNull String name) { 97 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(READ_DEVICE_CONFIG); 98 try { 99 return DeviceConfig.getProperty(namespace, name); 100 } finally { 101 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 102 } 103 } 104 writeDeviceConfigProp( @onNull String namespace, @NonNull String key, boolean value)105 private static void writeDeviceConfigProp( 106 @NonNull String namespace, @NonNull String key, boolean value) { 107 writeDeviceConfigProp(namespace, key, Boolean.toString(value)); 108 } 109 writeDeviceConfigProp( @onNull String namespace, @NonNull String name, @NonNull String value)110 private static void writeDeviceConfigProp( 111 @NonNull String namespace, @NonNull String name, @NonNull String value) { 112 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG, 113 WRITE_ALLOWLISTED_DEVICE_CONFIG); 114 115 try { 116 DeviceConfig.setProperty(namespace, name, value, /* makeDefault= */ false); 117 } finally { 118 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 119 } 120 } 121 deleteDeviceConfigProp(@onNull String namespace, @NonNull String name)122 private static void deleteDeviceConfigProp(@NonNull String namespace, @NonNull String name) { 123 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG, 124 WRITE_ALLOWLISTED_DEVICE_CONFIG); 125 try { 126 if (SdkLevel.isAtLeastU()) { 127 DeviceConfig.deleteProperty(namespace, name); 128 } else { 129 // DeviceConfig.deleteProperty API is only available from U onwards. 130 try { 131 UiDevice.getInstance(getInstrumentation()) 132 .executeShellCommand( 133 String.format("device_config delete %s %s", namespace, name)); 134 } catch (IOException e) { 135 Log.e(TAG, String.format("Could not delete device_config %s / %s", 136 namespace, name), e); 137 } 138 } 139 } finally { 140 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 141 } 142 } 143 144 /** 145 * Set cloud provider in the device to the provided authority. 146 * If the provided cloud authority equals {@link #INVALID_CLOUD_PROVIDER}, 147 * the cloud provider will not be updated. 148 */ setCloudProvider(@onNull UiDevice sDevice, @Nullable String authority)149 public static void setCloudProvider(@NonNull UiDevice sDevice, 150 @Nullable String authority) throws IOException { 151 if (INVALID_CLOUD_PROVIDER.equals(authority)) { 152 Log.w(TAG, "Cloud provider is invalid. " 153 + "Ignoring the request to set the cloud provider to invalid"); 154 return; 155 } 156 if (authority == null) { 157 sDevice.executeShellCommand( 158 "content call" 159 + " --user " + UserHandle.myUserId() 160 + " --uri content://media/" 161 + " --method set_cloud_provider" 162 + " --extra cloud_provider:n:null"); 163 } else { 164 sDevice.executeShellCommand( 165 "content call" 166 + " --user " + UserHandle.myUserId() 167 + " --uri content://media/" 168 + " --method set_cloud_provider" 169 + " --extra cloud_provider:s:" + authority); 170 } 171 } 172 173 /** 174 * @return the current cloud provider. 175 */ getCurrentCloudProvider(UiDevice sDevice)176 public static String getCurrentCloudProvider(UiDevice sDevice) throws IOException { 177 final String shellOutput = 178 sDevice.executeShellCommand( 179 "content call" 180 + " --user " + UserHandle.myUserId() 181 + " --uri content://media/" 182 + " --method get_cloud_provider"); 183 return extractCloudProvider(shellOutput); 184 } 185 extractCloudProvider(String shellOutput)186 private static String extractCloudProvider(String shellOutput) { 187 final String[] splitOutput; 188 if (TextUtils.isEmpty(shellOutput) || ((splitOutput = shellOutput.split("=")).length < 2)) { 189 throw new RuntimeException("Could not get current cloud provider. Output: " 190 + shellOutput); 191 } 192 String cloudProvider = splitOutput[1]; 193 cloudProvider = cloudProvider.substring(0, cloudProvider.length() - 3); 194 if (cloudProvider.equals("null")) { 195 return null; 196 } 197 return cloudProvider; 198 } 199 } 200