1 /* 2 * Copyright (C) 2023 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 android.settings.cts; 18 19 import static androidx.test.InstrumentationRegistry.getInstrumentation; 20 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assume.assumeFalse; 23 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.PackageManager; 27 import android.os.UserManager; 28 import android.platform.test.annotations.RequiresFlagsEnabled; 29 import android.platform.test.flag.junit.CheckFlagsRule; 30 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 31 import android.widget.Switch; 32 33 import androidx.annotation.Nullable; 34 import androidx.recyclerview.widget.RecyclerView; 35 import androidx.test.InstrumentationRegistry; 36 import androidx.test.runner.AndroidJUnit4; 37 import androidx.test.uiautomator.By; 38 import androidx.test.uiautomator.BySelector; 39 import androidx.test.uiautomator.Condition; 40 import androidx.test.uiautomator.Direction; 41 import androidx.test.uiautomator.UiDevice; 42 import androidx.test.uiautomator.UiObject2; 43 import androidx.test.uiautomator.Until; 44 45 import com.android.bedstead.harrier.annotations.RequireRunNotOnVisibleBackgroundNonProfileUser; 46 import com.android.compatibility.common.util.CddTest; 47 import com.android.modules.utils.build.SdkLevel; 48 49 import org.junit.Before; 50 import org.junit.Rule; 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 54 import java.util.List; 55 56 @RunWith(AndroidJUnit4.class) 57 public class AutoPrivacySettingsTest { 58 private static final int TIMEOUT_MS = 10000; 59 private static final String ACTION_SETTINGS = "android.settings.SETTINGS"; 60 private static final String PRIVACY = "Privacy"; 61 private static final String MICROPHONE = "Microphone"; 62 private static final String USE_MICROPHONE = "Use microphone"; 63 private static final String MICROPHONE_ACCESS = "Microphone access"; 64 private static final String[] EXPECTED_MICROPHONE_ENABLED_SETTINGS = { 65 USE_MICROPHONE, "Recently accessed", "Manage microphone permissions"}; 66 private static final String[] EXPECTED_MICROPHONE_ENABLED_SETTINGS_V2 = { 67 MICROPHONE_ACCESS, "Recently accessed", "Manage microphone permissions"}; 68 69 // For the camera privacy setting test 70 private static final String CAMERA = "Camera"; 71 72 private static final String CAMERA_ACCESS = "Camera access"; 73 private static final String INFOTAINMENT_APPS = "Infotainment apps"; 74 private static final String USE_CAMERA = "Use camera"; 75 private static final String[] EXPECTED_CAMERA_ENABLED_ITEMS_V1 = { 76 USE_CAMERA, "Recently accessed", "Manage camera permissions"}; 77 private static final String[] EXPECTED_CAMERA_ENABLED_ITEMS_V2 = { 78 CAMERA_ACCESS, "Recently accessed", "Manage camera permissions"}; 79 80 // To support dual panes in AAOS S 81 private static final int MAX_NUM_SCROLLABLES = 2; 82 83 private final Context mContext = InstrumentationRegistry.getContext(); 84 private final UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); 85 private int mDisplayId; 86 87 private Condition mSearchCondition = new Condition<UiDevice, Boolean>() { 88 89 @Override 90 public Boolean apply(UiDevice device) { 91 return device.findObjects(By.clazz(RecyclerView.class)).size() > 1; 92 } 93 }; 94 95 @Rule 96 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 97 98 99 @Before setUp()100 public void setUp() { 101 assumeFalse("Skipping test: Requirements only apply to Auto", 102 !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)); 103 UserManager userManager = mContext.getSystemService(UserManager.class); 104 mDisplayId = userManager.getMainDisplayIdAssignedToUser(); 105 } 106 107 /** 108 * MUST provide a user affordance to do microphone toggle in the following location:Settings > 109 * Privacy. 110 * 111 * This test is not enabled on visible background users. 112 */ 113 @CddTest(requirement = "9.8.2/A-1-3") 114 @Test 115 @RequiresFlagsEnabled(com.android.car.settings.Flags.FLAG_MICROPHONE_PRIVACY_UPDATES) 116 @RequireRunNotOnVisibleBackgroundNonProfileUser testPrivacyMicrophoneSettings()117 public void testPrivacyMicrophoneSettings() throws Exception { 118 PackageManager pm = mContext.getPackageManager(); 119 if (!pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) { 120 // Skip this test if the system does not have a microphone. 121 return; 122 } 123 goHome(); 124 125 launchActionSettings(); 126 mDevice.waitForIdle(); 127 128 UiObject2 privacyObj = assertScrollToAndFind(PRIVACY); 129 privacyObj.click(); 130 mDevice.waitForIdle(); 131 132 UiObject2 micObj = assertScrollToAndFind(MICROPHONE); 133 micObj.click(); 134 mDevice.waitForIdle(); 135 136 // verify state when mic is enabled 137 disableCameraMicPrivacy(); 138 if (SdkLevel.isAtLeastV()) { 139 for (String setting : EXPECTED_MICROPHONE_ENABLED_SETTINGS_V2) { 140 assertScrollToAndFind(setting); 141 } 142 UiObject2 micAccessObj = scrollToText(MICROPHONE_ACCESS); 143 micAccessObj.click(); 144 mDevice.waitForIdle(); 145 assertScrollToAndFind(INFOTAINMENT_APPS); 146 } else { 147 for (String setting : EXPECTED_MICROPHONE_ENABLED_SETTINGS) { 148 assertScrollToAndFind(setting); 149 } 150 } 151 152 goHome(); 153 } 154 155 /** 156 * MUST provide a user affordance to do camera toggle in the following location:Settings > 157 * Privacy. 158 * 159 * This test is not enabled on visible background users 160 */ 161 @CddTest(requirement = "9.8.2/A-2-3") 162 @Test 163 @RequiresFlagsEnabled(com.android.internal.camera.flags.Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) 164 @RequireRunNotOnVisibleBackgroundNonProfileUser testPrivacyCameraSettings()165 public void testPrivacyCameraSettings() throws Exception { 166 assumeFalse( 167 "Skipping test: Enabling/Disabling Camera is not supported in Wear", 168 SettingsTestUtils.isWatch()); 169 170 PackageManager pm = mContext.getPackageManager(); 171 if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { 172 // Skip this test if the system does not have a camera. 173 return; 174 } 175 176 goHome(); 177 178 launchActionSettings(); 179 mDevice.waitForIdle(); 180 181 UiObject2 privacyObj = assertScrollToAndFind(PRIVACY); 182 privacyObj.click(); 183 mDevice.waitForIdle(); 184 185 UiObject2 camObj = assertScrollToAndFind(CAMERA); 186 camObj.click(); 187 mDevice.waitForIdle(); 188 189 // verify state when camera is enabled 190 disableCameraMicPrivacy(); 191 if (SdkLevel.isAtLeastV()) { 192 for (String item : EXPECTED_CAMERA_ENABLED_ITEMS_V2) { 193 assertScrollToAndFind(item); 194 } 195 UiObject2 camAccessObj = scrollToText(CAMERA_ACCESS); 196 camAccessObj.click(); 197 mDevice.waitForIdle(); 198 assertScrollToAndFind(INFOTAINMENT_APPS); 199 } else { 200 for (String item : EXPECTED_CAMERA_ENABLED_ITEMS_V1) { 201 assertScrollToAndFind(item); 202 } 203 } 204 goHome(); 205 } 206 207 /** 208 * Find the specified text. 209 */ assertFind(String text)210 private UiObject2 assertFind(String text) { 211 UiObject2 obj = mDevice.findObject(By.text(text).displayId(mDisplayId)); 212 assertNotNull("Failed to find '" + text + "'.", obj); 213 return obj; 214 } 215 216 /** 217 * Scroll to find the specified text. 218 */ assertScrollToAndFind(String text)219 private UiObject2 assertScrollToAndFind(String text) { 220 scrollToText(text); 221 return assertFind(text); 222 } 223 224 /** 225 * Scroll to text, which might be at the bottom of a scrollable list. 226 */ 227 @Nullable scrollToText(String text)228 private UiObject2 scrollToText(String text) { 229 UiObject2 foundObject = null; 230 // RecyclerViews take longer to load and aren't found even after waiting for idle 231 mDevice.wait(mSearchCondition, TIMEOUT_MS); 232 // Car-ui-lib replaces settings recyclerviews dynamically so cannot find by resource 233 List<UiObject2> recyclerViews = 234 mDevice.findObjects(By.clazz(RecyclerView.class).displayId(mDisplayId)); 235 for (UiObject2 recyclerView : recyclerViews) { 236 // Make sure reclerview starts at the top 237 recyclerView.scroll(Direction.UP, 1); 238 recyclerView.scrollUntil(Direction.DOWN, Until.findObject(By.textContains(text))); 239 foundObject = mDevice.findObject(By.text(text).displayId(mDisplayId)); 240 if (foundObject != null) { 241 // No need to look at other recyclerviews. 242 break; 243 } 244 } 245 246 mDevice.waitForIdle(); 247 return foundObject; 248 } 249 250 251 /** 252 * Launch the action settings screen. 253 */ launchActionSettings()254 private void launchActionSettings() { 255 final Intent intent = new Intent(ACTION_SETTINGS); 256 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 257 mContext.startActivityAsUser(intent, mContext.getUser()); 258 // wait for settings UI to come into focus, test is auto specific 259 mDevice.wait( 260 Until.hasObject( 261 By.res("com.android.car.settings:id/car_settings_activity_wrapper") 262 .focused(true)), 263 10000L); 264 } 265 goHome()266 private void goHome() { 267 final Intent home = new Intent(Intent.ACTION_MAIN); 268 home.addCategory(Intent.CATEGORY_HOME); 269 home.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 270 mContext.startActivityAsUser(home, mContext.getUser()); 271 mDevice.waitForIdle(); 272 } 273 disableCameraMicPrivacy()274 private void disableCameraMicPrivacy() { 275 BySelector switchSelector = By.clazz(Switch.class); 276 UiObject2 switchButton = mDevice.findObject(switchSelector); 277 if (switchButton != null && !switchButton.isChecked()) { 278 switchButton.click(); 279 mDevice.waitForIdle(); 280 switchButton.wait(Until.checked(true), TIMEOUT_MS); 281 } 282 } 283 } 284