1 /*
2  * Copyright (C) 2018 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.permission.cts;
18 
19 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
20 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
21 import static android.content.pm.PackageManager.GET_PERMISSIONS;
22 
23 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
24 
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.fail;
27 
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.PackageInfo;
32 import android.content.pm.PackageManager;
33 import android.content.res.Resources;
34 import android.platform.test.annotations.AppModeFull;
35 import android.platform.test.annotations.AsbSecurityTest;
36 import android.support.test.uiautomator.By;
37 import android.support.test.uiautomator.UiDevice;
38 import android.support.test.uiautomator.UiScrollable;
39 import android.support.test.uiautomator.UiSelector;
40 import android.widget.ScrollView;
41 
42 import androidx.test.InstrumentationRegistry;
43 
44 import com.android.compatibility.common.util.SystemUtil;
45 
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.Test;
49 
50 import java.util.concurrent.TimeUnit;
51 import java.util.regex.Pattern;
52 
53 public class PermissionGroupChange {
54     private static final String APP_PKG_NAME = "android.permission.cts.appthatrequestpermission";
55     private static final long EXPECTED_BEHAVIOR_TIMEOUT_SEC = 15;
56     private static final long UNEXPECTED_BEHAVIOR_TIMEOUT_SEC = 2;
57 
58     private Context mContext;
59     private UiDevice mUiDevice;
60     private String mAllowButtonText = null;
61 
62     @Before
setContextAndUiDevice()63     public void setContextAndUiDevice() {
64         mContext = InstrumentationRegistry.getTargetContext();
65         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
66     }
67 
68     @Before
wakeUpScreen()69     public void wakeUpScreen() {
70         SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP");
71     }
72 
73     /**
74      * Retry for a time until the runnable stops throwing.
75      *
76      * @param runnable The condition to execute
77      * @param timeoutSec The time to try
78      */
eventually(ThrowingRunnable runnable, long timeoutSec)79     private void eventually(ThrowingRunnable runnable, long timeoutSec) throws Throwable {
80         long startTime = System.nanoTime();
81         while (true) {
82             try {
83                 runnable.run();
84                 return;
85             } catch (Throwable t) {
86                 if (System.nanoTime() - startTime < TimeUnit.SECONDS.toNanos(timeoutSec)) {
87                     Thread.sleep(100);
88                     continue;
89                 }
90 
91                 throw t;
92             }
93         }
94     }
95 
96 
scrollToBottomIfWatch()97     private void scrollToBottomIfWatch() throws Exception {
98         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
99             UiScrollable scrollable = new UiScrollable(
100                     new UiSelector().className(ScrollView.class));
101             if (scrollable.exists()) {
102                 scrollable.flingToEnd(10);
103             }
104         }
105     }
106 
clickAllowButton()107     protected void clickAllowButton() throws Exception {
108         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
109             if (mAllowButtonText == null) {
110                 mAllowButtonText = getPermissionControllerString("grant_dialog_button_allow");
111             }
112             mUiDevice.findObject(By.text(Pattern.compile(Pattern.quote(mAllowButtonText),
113                     Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE))).click();
114         } else {
115             mUiDevice.findObject(By.res(
116                     "com.android.permissioncontroller:id/permission_allow_button")).click();
117         }
118     }
119 
grantPermissionViaUi()120     private void grantPermissionViaUi() throws Throwable {
121         eventually(() -> {
122             scrollToBottomIfWatch();
123             clickAllowButton();
124         }, EXPECTED_BEHAVIOR_TIMEOUT_SEC);
125     }
126 
waitUntilPermissionGranted(String permName, long timeoutSec)127     private void waitUntilPermissionGranted(String permName, long timeoutSec) throws Throwable {
128         eventually(() -> {
129             PackageInfo appInfo = mContext.getPackageManager().getPackageInfo(APP_PKG_NAME,
130                     GET_PERMISSIONS);
131 
132             for (int i = 0; i < appInfo.requestedPermissions.length; i++) {
133                 if (appInfo.requestedPermissions[i].equals(permName)
134                         && ((appInfo.requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED)
135                         != 0)) {
136                     return;
137                 }
138             }
139 
140             fail(permName + " not granted");
141         }, timeoutSec);
142     }
143 
installApp(String apk)144     private void installApp(String apk) {
145         String installResult = SystemUtil.runShellCommand(
146                 "pm install -r data/local/tmp/cts/permissions/" + apk + ".apk");
147         assertEquals("Success", installResult.trim());
148     }
149 
150     /**
151      * Start the app. The app will request the permissions.
152      */
startApp()153     private void startApp() {
154         Intent startApp = new Intent();
155         startApp.setComponent(new ComponentName(APP_PKG_NAME, APP_PKG_NAME + ".RequestPermission"));
156         startApp.setFlags(FLAG_ACTIVITY_NEW_TASK);
157 
158         mContext.startActivity(startApp);
159     }
160 
161     @After
uninstallTestApp()162     public void uninstallTestApp() {
163         runShellCommand("pm uninstall android.permission.cts.appthatrequestpermission");
164     }
165 
166     @Test
167     @AppModeFull
168     @AsbSecurityTest(cveBugId = 72710897)
permissionGroupShouldNotBeAutoGrantedIfNewMember()169     public void permissionGroupShouldNotBeAutoGrantedIfNewMember() throws Throwable {
170         installApp("CtsAppThatRequestsPermissionAandB");
171 
172         startApp();
173         grantPermissionViaUi();
174         waitUntilPermissionGranted("android.permission.cts.appthatrequestpermission.A",
175                 EXPECTED_BEHAVIOR_TIMEOUT_SEC);
176 
177         // Update app which changes the permission group of "android.permission.cts
178         // .appthatrequestpermission.A" to the same as "android.permission.cts.C"
179         installApp("CtsAppThatRequestsPermissionAandC");
180 
181         startApp();
182         try {
183             // The permission should not be auto-granted
184             waitUntilPermissionGranted("android.permission.cts.C", UNEXPECTED_BEHAVIOR_TIMEOUT_SEC);
185             fail("android.permission.cts.C was auto-granted");
186         } catch (Throwable expected) {
187             assertEquals("android.permission.cts.C not granted", expected.getMessage());
188         }
189     }
190 
getPermissionControllerString(String res)191     private String getPermissionControllerString(String res)
192             throws PackageManager.NameNotFoundException {
193         Resources permissionControllerResources = mContext.createPackageContext(
194                 mContext.getPackageManager().getPermissionControllerPackageName(), 0)
195                 .getResources();
196         return permissionControllerResources.getString(permissionControllerResources
197                 .getIdentifier(res, "string", "com.android.permissioncontroller"));
198     }
199 
200     private interface ThrowingRunnable {
run()201         void run() throws Throwable;
202     }
203 }
204