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