1 /* 2 * Copyright (C) 2017 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 package com.android.settings.applications; 17 18 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; 19 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 20 21 import android.app.AppOpsManager; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.UserInfo; 27 import android.net.Uri; 28 import android.os.UserHandle; 29 import android.os.UserManager; 30 import android.support.test.InstrumentationRegistry; 31 import android.support.test.uiautomator.By; 32 import android.support.test.uiautomator.BySelector; 33 import android.support.test.uiautomator.Direction; 34 import android.support.test.uiautomator.UiDevice; 35 import android.support.test.uiautomator.UiObject2; 36 import android.support.test.uiautomator.Until; 37 import android.support.v7.widget.RecyclerView; 38 import android.widget.Switch; 39 import android.widget.TextView; 40 41 import org.junit.After; 42 import org.junit.Before; 43 import org.junit.Test; 44 45 import java.util.List; 46 47 import static android.app.AppOpsManager.MODE_ALLOWED; 48 import static android.app.AppOpsManager.MODE_DEFAULT; 49 import static android.app.AppOpsManager.MODE_ERRORED; 50 import static org.junit.Assert.assertEquals; 51 import static org.junit.Assert.assertNotNull; 52 import static org.junit.Assert.assertTrue; 53 54 /** 55 * An abstract parent for testing settings activities that manage an AppOps permission. 56 */ 57 abstract public class AppOpsSettingsTest { 58 private static final String WM_DISMISS_KEYGUARD_COMMAND = "wm dismiss-keyguard"; 59 private static final long START_ACTIVITY_TIMEOUT = 5000; 60 61 private Context mContext; 62 private UiDevice mUiDevice; 63 private PackageManager mPackageManager; 64 private AppOpsManager mAppOpsManager; 65 private List<UserInfo> mProfiles; 66 private String mPackageName; 67 68 // These depend on which app op's settings UI is being tested. 69 private final String mActivityAction; 70 private final int mAppOpCode; 71 72 protected AppOpsSettingsTest(String activityAction, int appOpCode) { 73 mActivityAction = activityAction; 74 mAppOpCode = appOpCode; 75 } 76 77 @Before 78 public void setUp() throws Exception { 79 mContext = InstrumentationRegistry.getTargetContext(); 80 mPackageName = InstrumentationRegistry.getContext().getPackageName(); 81 mPackageManager = mContext.getPackageManager(); 82 mAppOpsManager = mContext.getSystemService(AppOpsManager.class); 83 mProfiles = mContext.getSystemService(UserManager.class).getProfiles(UserHandle.myUserId()); 84 resetAppOpModeForAllProfiles(); 85 mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 86 mUiDevice.wakeUp(); 87 mUiDevice.executeShellCommand(WM_DISMISS_KEYGUARD_COMMAND); 88 } 89 90 private void resetAppOpModeForAllProfiles() throws Exception { 91 for (UserInfo user : mProfiles) { 92 final int uid = mPackageManager.getPackageUidAsUser(mPackageName, user.id); 93 mAppOpsManager.setMode(mAppOpCode, uid, mPackageName, MODE_DEFAULT); 94 } 95 } 96 97 /** 98 * Creates an intent for showing the permission settings for all apps. 99 */ 100 private Intent createManageAllAppsIntent() { 101 final Intent intent = new Intent(mActivityAction); 102 intent.addFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); 103 return intent; 104 } 105 106 /** 107 * Creates an intent for showing the permission setting for a single app. 108 */ 109 private Intent createManageSingleAppIntent(String packageName) { 110 final Intent intent = createManageAllAppsIntent(); 111 intent.setData(Uri.parse("package:" + packageName)); 112 return intent; 113 } 114 115 private String getApplicationLabel(String packageName) throws Exception { 116 final ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 0); 117 return mPackageManager.getApplicationLabel(info).toString(); 118 } 119 120 private UiObject2 findAndVerifySwitchState(boolean checked) { 121 final BySelector switchSelector = By.clazz(Switch.class).res("android:id/switch_widget"); 122 final UiObject2 switchPref = mUiDevice.wait(Until.findObject(switchSelector), 123 START_ACTIVITY_TIMEOUT); 124 assertNotNull("Switch not shown", switchPref); 125 assertTrue("Switch in invalid state", switchPref.isChecked() == checked); 126 return switchPref; 127 } 128 129 @Test 130 public void testAppList() throws Exception { 131 final String testAppLabel = getApplicationLabel(mPackageName); 132 133 mContext.startActivity(createManageAllAppsIntent()); 134 final BySelector preferenceListSelector = 135 By.clazz(RecyclerView.class).res("com.android.settings:id/apps_list"); 136 final UiObject2 preferenceList = mUiDevice.wait(Until.findObject(preferenceListSelector), 137 START_ACTIVITY_TIMEOUT); 138 assertNotNull("App list not shown", preferenceList); 139 140 final BySelector appLabelTextViewSelector = By.clazz(TextView.class) 141 .res("android:id/title") 142 .text(testAppLabel); 143 List<UiObject2> listOfMatchingTextViews; 144 do { 145 listOfMatchingTextViews = preferenceList.findObjects(appLabelTextViewSelector); 146 // assuming the number of profiles will be sufficiently small so that all the entries 147 // for the same package will fit in one screen at some time during the scroll. 148 } while (listOfMatchingTextViews.size() != mProfiles.size() && 149 preferenceList.scroll(Direction.DOWN, 0.2f)); 150 assertEquals("Test app not listed for each profile", mProfiles.size(), 151 listOfMatchingTextViews.size()); 152 153 for (UiObject2 matchingObject : listOfMatchingTextViews) { 154 matchingObject.click(); 155 findAndVerifySwitchState(true); 156 mUiDevice.pressBack(); 157 } 158 } 159 160 private void testAppDetailScreenForAppOp(int appOpMode, int userId) throws Exception { 161 final String testAppLabel = getApplicationLabel(mPackageName); 162 final BySelector appDetailTitleSelector = By.clazz(TextView.class) 163 .res("com.android.settings:id/app_detail_title") 164 .text(testAppLabel); 165 166 mAppOpsManager.setMode(mAppOpCode, 167 mPackageManager.getPackageUidAsUser(mPackageName, userId), mPackageName, appOpMode); 168 mContext.startActivityAsUser(createManageSingleAppIntent(mPackageName), 169 UserHandle.of(userId)); 170 mUiDevice.wait(Until.findObject(appDetailTitleSelector), START_ACTIVITY_TIMEOUT); 171 findAndVerifySwitchState(appOpMode == MODE_ALLOWED || appOpMode == MODE_DEFAULT); 172 mUiDevice.pressBack(); 173 } 174 175 @Test 176 public void testSingleApp() throws Exception { 177 // App op MODE_DEFAULT is already tested in #testAppList 178 for (UserInfo user : mProfiles) { 179 testAppDetailScreenForAppOp(MODE_ALLOWED, user.id); 180 testAppDetailScreenForAppOp(MODE_ERRORED, user.id); 181 } 182 } 183 184 private void testSwitchToggle(int fromAppOp, int toAppOp) throws Exception { 185 final int packageUid = mPackageManager.getPackageUid(mPackageName, 0); 186 final boolean initialState = (fromAppOp == MODE_ALLOWED || fromAppOp == MODE_DEFAULT); 187 188 mAppOpsManager.setMode(mAppOpCode, packageUid, mPackageName, fromAppOp); 189 mContext.startActivity(createManageSingleAppIntent(mPackageName)); 190 final UiObject2 switchPref = findAndVerifySwitchState(initialState); 191 switchPref.click(); 192 Thread.sleep(1000); 193 assertEquals("Toggling switch did not change app op", toAppOp, 194 mAppOpsManager.checkOpNoThrow(mAppOpCode, packageUid, 195 mPackageName)); 196 mUiDevice.pressBack(); 197 } 198 199 @Test 200 public void testIfSwitchTogglesAppOp() throws Exception { 201 testSwitchToggle(MODE_ALLOWED, MODE_ERRORED); 202 testSwitchToggle(MODE_ERRORED, MODE_ALLOWED); 203 } 204 205 @After 206 public void tearDown() throws Exception { 207 mUiDevice.pressHome(); 208 resetAppOpModeForAllProfiles(); 209 } 210 } 211