1 /* 2 * Copyright (C) 2020 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.server.appop; 18 19 import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; 20 import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT; 21 import static android.app.AppOpsManager._NUM_OP; 22 23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; 24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 27 28 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertFalse; 30 import static org.junit.Assert.assertTrue; 31 import static org.junit.Assert.fail; 32 import static org.mockito.ArgumentMatchers.anyInt; 33 import static org.mockito.ArgumentMatchers.anyLong; 34 import static org.mockito.ArgumentMatchers.anyString; 35 import static org.mockito.ArgumentMatchers.isA; 36 import static org.mockito.ArgumentMatchers.nullable; 37 import static org.mockito.Mockito.doNothing; 38 import static org.mockito.Mockito.never; 39 import static org.mockito.Mockito.spy; 40 41 import android.app.AppOpsManager; 42 import android.content.Context; 43 import android.content.pm.PackageManager; 44 import android.content.pm.PackageManagerInternal; 45 import android.content.pm.UserPackage; 46 import android.content.res.AssetManager; 47 import android.os.Handler; 48 import android.os.UserHandle; 49 import android.permission.PermissionManager; 50 import android.util.ArrayMap; 51 import android.util.Log; 52 import android.util.SparseArray; 53 import android.util.Xml; 54 55 import androidx.test.InstrumentationRegistry; 56 import androidx.test.filters.SmallTest; 57 import androidx.test.runner.AndroidJUnit4; 58 59 import com.android.internal.util.ArrayUtils; 60 import com.android.modules.utils.TypedXmlPullParser; 61 import com.android.server.LocalServices; 62 import com.android.server.SystemServerInitThreadPool; 63 import com.android.server.pm.UserManagerInternal; 64 import com.android.server.pm.permission.PermissionManagerServiceInternal; 65 import com.android.server.pm.pkg.PackageStateInternal; 66 67 import org.junit.After; 68 import org.junit.Before; 69 import org.junit.Test; 70 import org.junit.runner.RunWith; 71 import org.mockito.Mock; 72 import org.mockito.MockitoSession; 73 import org.mockito.quality.Strictness; 74 import org.xmlpull.v1.XmlPullParser; 75 76 import java.io.File; 77 import java.io.FileInputStream; 78 import java.io.FileOutputStream; 79 import java.io.IOException; 80 import java.io.InputStream; 81 82 /** 83 * Tests app ops version upgrades 84 */ 85 @SmallTest 86 @RunWith(AndroidJUnit4.class) 87 public class AppOpsUpgradeTest { 88 private static final String TAG = AppOpsUpgradeTest.class.getSimpleName(); 89 private static final String APP_OPS_UNVERSIONED_ASSET_PATH = 90 "AppOpsUpgradeTest/appops-unversioned.xml"; 91 private static final String APP_OPS_VERSION_1_ASSET_PATH = 92 "AppOpsUpgradeTest/appops-version-1.xml"; 93 94 private static final String APP_OPS_VERSION_3_ASSET_PATH = 95 "AppOpsUpgradeTest/appops-version-3.xml"; 96 97 private static final String APP_OPS_FILENAME = "appops-test.xml"; 98 99 private static final Context sContext = InstrumentationRegistry.getTargetContext(); 100 private static final File sAppOpsFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME); 101 102 private Context mTestContext; 103 private MockitoSession mMockitoSession; 104 105 @Mock 106 private PackageManagerInternal mPackageManagerInternal; 107 @Mock 108 private PackageManager mPackageManager; 109 @Mock 110 private UserManagerInternal mUserManagerInternal; 111 @Mock 112 private PermissionManagerServiceInternal mPermissionManagerInternal; 113 @Mock 114 private Handler mHandler; 115 @Mock 116 private PermissionManager mPermissionManager; 117 118 private Object mLock = new Object(); 119 private SparseArray<int[]> mSwitchedOps; 120 extractAppOpsFile(String assetPath)121 private static void extractAppOpsFile(String assetPath) { 122 sAppOpsFile.getParentFile().mkdirs(); 123 try (FileOutputStream out = new FileOutputStream(sAppOpsFile); 124 InputStream in = sContext.getAssets().open(assetPath, AssetManager.ACCESS_BUFFER)) { 125 byte[] buffer = new byte[4096]; 126 int bytesRead; 127 while ((bytesRead = in.read(buffer)) >= 0) { 128 out.write(buffer, 0, bytesRead); 129 } 130 out.flush(); 131 Log.d(TAG, "Successfully copied xml to " + sAppOpsFile.getAbsolutePath()); 132 } catch (IOException exc) { 133 Log.e(TAG, "Exception while copying appops xml", exc); 134 fail(); 135 } 136 } 137 138 139 @Before setUp()140 public void setUp() { 141 if (sAppOpsFile.exists()) { 142 sAppOpsFile.delete(); 143 } 144 145 mMockitoSession = mockitoSession() 146 .initMocks(this) 147 .spyStatic(LocalServices.class) 148 .mockStatic(SystemServerInitThreadPool.class) 149 .strictness(Strictness.LENIENT) 150 .startMocking(); 151 152 doReturn(mPermissionManagerInternal).when( 153 () -> LocalServices.getService(PermissionManagerServiceInternal.class)); 154 doReturn(mUserManagerInternal).when( 155 () -> LocalServices.getService(UserManagerInternal.class)); 156 doReturn(mPackageManagerInternal).when( 157 () -> LocalServices.getService(PackageManagerInternal.class)); 158 159 mTestContext = spy(sContext); 160 161 // Pretend everybody has all permissions 162 doNothing().when(mTestContext).enforcePermission(anyString(), anyInt(), anyInt(), 163 nullable(String.class)); 164 165 doReturn(mPackageManager).when(mTestContext).getPackageManager(); 166 167 // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat 168 doReturn(null).when(mPackageManager).getPackagesForUid(anyInt()); 169 170 doReturn(new ArrayMap<String, PackageStateInternal>()).when(mPackageManagerInternal) 171 .getPackageStates(); 172 173 doReturn(new int[] {0}).when(mUserManagerInternal).getUserIds(); 174 175 // Build mSwitchedOps 176 mSwitchedOps = buildSwitchedOpsArray(); 177 } 178 buildSwitchedOpsArray()179 private SparseArray<int[]> buildSwitchedOpsArray() { 180 SparseArray<int[]> switchedOps = new SparseArray<>(); 181 for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { 182 int switchCode = AppOpsManager.opToSwitch(switchedCode); 183 switchedOps.put(switchCode, 184 ArrayUtils.appendInt(switchedOps.get(switchCode), switchedCode)); 185 } 186 return switchedOps; 187 } 188 189 @After tearDown()190 public void tearDown() { 191 mMockitoSession.finishMocking(); 192 } 193 194 @Test upgradeRunAnyInBackground()195 public void upgradeRunAnyInBackground() { 196 extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH); 197 198 AppOpsCheckingServiceImpl testService = new AppOpsCheckingServiceImpl(sAppOpsFile, mLock, 199 mHandler, mTestContext, mSwitchedOps); 200 testService.readState(); 201 202 testService.upgradeRunAnyInBackgroundLocked(); 203 204 assertSameModes(testService, AppOpsManager.OP_RUN_IN_BACKGROUND, 205 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); 206 } 207 assertSameModes(AppOpsCheckingServiceImpl testService, int op1, int op2)208 private void assertSameModes(AppOpsCheckingServiceImpl testService, int op1, int op2) { 209 for (int uid : testService.getUidsWithNonDefaultModes()) { 210 assertEquals( 211 testService.getUidMode(uid, op1), 212 testService.getUidMode(uid, op2) 213 ); 214 } 215 for (UserPackage pkg : testService.getPackagesWithNonDefaultModes()) { 216 assertEquals( 217 testService.getPackageMode(pkg.packageName, op1, pkg.userId), 218 testService.getPackageMode(pkg.packageName, op2, pkg.userId) 219 ); 220 } 221 } 222 getModeInFile(int uid, int op)223 private static int getModeInFile(int uid, int op) { 224 switch (uid) { 225 case 10198: 226 return 0; 227 case 10200: 228 return 1; 229 case 1110200: 230 case 10267: 231 case 1110181: 232 return 2; 233 default: 234 return AppOpsManager.opToDefaultMode(op); 235 } 236 } 237 238 @Test upgradeScheduleExactAlarm()239 public void upgradeScheduleExactAlarm() { 240 extractAppOpsFile(APP_OPS_VERSION_1_ASSET_PATH); 241 242 String[] packageNames = {"p1", "package2", "pkg3", "package.4", "pkg-5", "pkg.6"}; 243 int[] appIds = {10267, 10181, 10198, 10199, 10200, 4213}; 244 int[] userIds = {0, 10, 11}; 245 246 doReturn(userIds).when(mUserManagerInternal).getUserIds(); 247 248 doReturn(packageNames).when(mPermissionManagerInternal).getAppOpPermissionPackages( 249 AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM)); 250 251 doAnswer(invocation -> { 252 String pkg = invocation.getArgument(0); 253 int index = ArrayUtils.indexOf(packageNames, pkg); 254 if (index < 0) { 255 return index; 256 } 257 int userId = invocation.getArgument(2); 258 return UserHandle.getUid(userId, appIds[index]); 259 }).when(mPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt()); 260 261 AppOpsCheckingServiceImpl testService = new AppOpsCheckingServiceImpl(sAppOpsFile, mLock, 262 mHandler, mTestContext, mSwitchedOps); 263 testService.readState(); 264 265 testService.upgradeScheduleExactAlarmLocked(); 266 267 for (int userId : userIds) { 268 for (int appId : appIds) { 269 final int uid = UserHandle.getUid(userId, appId); 270 final int previousMode = getModeInFile(uid, OP_SCHEDULE_EXACT_ALARM); 271 272 final int expectedMode; 273 if (previousMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) { 274 expectedMode = AppOpsManager.MODE_ALLOWED; 275 } else { 276 expectedMode = previousMode; 277 } 278 int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM); 279 assertEquals(expectedMode, mode); 280 } 281 } 282 283 // These uids don't even declare the permission. So should stay as default / empty. 284 int[] unrelatedUidsInFile = {10225, 10178}; 285 286 for (int uid : unrelatedUidsInFile) { 287 int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM); 288 assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM), mode); 289 } 290 } 291 292 @Test resetUseFullScreenIntent()293 public void resetUseFullScreenIntent() { 294 extractAppOpsFile(APP_OPS_VERSION_3_ASSET_PATH); 295 296 String[] packageNames = {"p1", "package2", "pkg3", "package.4", "pkg-5", "pkg.6"}; 297 int[] appIds = {10267, 10181, 10198, 10199, 10200, 4213}; 298 int[] userIds = {0, 10, 11}; 299 int flag = 0; 300 301 doReturn(userIds).when(mUserManagerInternal).getUserIds(); 302 303 doReturn(packageNames).when(mPermissionManagerInternal).getAppOpPermissionPackages( 304 AppOpsManager.opToPermission(OP_USE_FULL_SCREEN_INTENT)); 305 306 doReturn(mPermissionManager).when(mTestContext).getSystemService(PermissionManager.class); 307 308 doReturn(flag).when(mPackageManager).getPermissionFlags( 309 anyString(), anyString(), isA(UserHandle.class)); 310 311 doAnswer(invocation -> { 312 String pkg = invocation.getArgument(0); 313 int index = ArrayUtils.indexOf(packageNames, pkg); 314 if (index < 0) { 315 return index; 316 } 317 int userId = invocation.getArgument(2); 318 return UserHandle.getUid(userId, appIds[index]); 319 }).when(mPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt()); 320 321 AppOpsCheckingServiceImpl testService = new AppOpsCheckingServiceImpl(sAppOpsFile, mLock, 322 mHandler, mTestContext, mSwitchedOps); 323 testService.readState(); 324 325 synchronized (testService) { 326 testService.resetUseFullScreenIntentLocked(); 327 } 328 329 for (int userId : userIds) { 330 for (int appId : appIds) { 331 final int uid = UserHandle.getUid(userId, appId); 332 final int expectedMode = AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT); 333 synchronized (testService) { 334 int mode = testService.getUidMode(uid, OP_USE_FULL_SCREEN_INTENT); 335 assertEquals(expectedMode, mode); 336 } 337 } 338 } 339 } 340 341 @Test upgradeFromNoFile()342 public void upgradeFromNoFile() { 343 assertFalse(sAppOpsFile.exists()); 344 345 AppOpsCheckingServiceImpl testService = spy(new AppOpsCheckingServiceImpl(sAppOpsFile, 346 mLock, mHandler, mTestContext, mSwitchedOps)); 347 testService.readState(); 348 349 doNothing().when(testService).upgradeRunAnyInBackgroundLocked(); 350 doNothing().when(testService).upgradeScheduleExactAlarmLocked(); 351 doNothing().when(testService).resetUseFullScreenIntentLocked(); 352 353 // trigger upgrade 354 testService.systemReady(); 355 356 verify(testService, never()).upgradeRunAnyInBackgroundLocked(); 357 verify(testService, never()).upgradeScheduleExactAlarmLocked(); 358 verify(testService, never()).resetUseFullScreenIntentLocked(); 359 360 testService.writeState(); 361 362 assertTrue(sAppOpsFile.exists()); 363 364 AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile); 365 assertTrue(parser.parse()); 366 assertEquals(AppOpsCheckingServiceImpl.CURRENT_VERSION, parser.mVersion); 367 } 368 369 @Test upgradeFromNoVersion()370 public void upgradeFromNoVersion() { 371 extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH); 372 AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile); 373 assertTrue(parser.parse()); 374 assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion); 375 376 AppOpsCheckingServiceImpl testService = spy(new AppOpsCheckingServiceImpl(sAppOpsFile, 377 mLock, mHandler, mTestContext, mSwitchedOps)); 378 testService.readState(); 379 380 doNothing().when(testService).upgradeRunAnyInBackgroundLocked(); 381 doNothing().when(testService).upgradeScheduleExactAlarmLocked(); 382 doNothing().when(testService).resetUseFullScreenIntentLocked(); 383 384 // trigger upgrade 385 testService.systemReady(); 386 387 verify(testService).upgradeRunAnyInBackgroundLocked(); 388 verify(testService).upgradeScheduleExactAlarmLocked(); 389 verify(testService).resetUseFullScreenIntentLocked(); 390 391 testService.writeState(); 392 assertTrue(parser.parse()); 393 assertEquals(AppOpsCheckingServiceImpl.CURRENT_VERSION, parser.mVersion); 394 } 395 396 @Test upgradeFromVersion1()397 public void upgradeFromVersion1() { 398 extractAppOpsFile(APP_OPS_VERSION_1_ASSET_PATH); 399 AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile); 400 assertTrue(parser.parse()); 401 assertEquals(1, parser.mVersion); 402 403 AppOpsCheckingServiceImpl testService = spy(new AppOpsCheckingServiceImpl(sAppOpsFile, 404 mLock, mHandler, mTestContext, mSwitchedOps)); 405 testService.readState(); 406 407 doNothing().when(testService).upgradeRunAnyInBackgroundLocked(); 408 doNothing().when(testService).upgradeScheduleExactAlarmLocked(); 409 doNothing().when(testService).resetUseFullScreenIntentLocked(); 410 411 // trigger upgrade 412 testService.systemReady(); 413 414 verify(testService, never()).upgradeRunAnyInBackgroundLocked(); 415 verify(testService).upgradeScheduleExactAlarmLocked(); 416 verify(testService).resetUseFullScreenIntentLocked(); 417 418 testService.writeState(); 419 assertTrue(parser.parse()); 420 assertEquals(AppOpsCheckingServiceImpl.CURRENT_VERSION, parser.mVersion); 421 } 422 423 @Test resetFromVersion3()424 public void resetFromVersion3() { 425 extractAppOpsFile(APP_OPS_VERSION_3_ASSET_PATH); 426 AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile); 427 assertTrue(parser.parse()); 428 assertEquals(3, parser.mVersion); 429 430 AppOpsCheckingServiceImpl testService = spy(new AppOpsCheckingServiceImpl(sAppOpsFile, 431 mLock, mHandler, mTestContext, mSwitchedOps)); 432 testService.readState(); 433 434 doNothing().when(testService).upgradeRunAnyInBackgroundLocked(); 435 doNothing().when(testService).upgradeScheduleExactAlarmLocked(); 436 doNothing().when(testService).resetUseFullScreenIntentLocked(); 437 438 testService.systemReady(); 439 440 verify(testService, never()).upgradeRunAnyInBackgroundLocked(); 441 verify(testService, never()).upgradeScheduleExactAlarmLocked(); 442 verify(testService).resetUseFullScreenIntentLocked(); 443 444 testService.writeState(); 445 assertTrue(parser.parse()); 446 assertEquals(AppOpsCheckingServiceImpl.CURRENT_VERSION, parser.mVersion); 447 } 448 449 /** 450 * Class to parse data from the appops xml. Currently only parses and holds the version number. 451 * Other fields may be added as and when required for testing. 452 */ 453 private static final class AppOpsDataParser { 454 static final int NO_VERSION = -123; 455 int mVersion; 456 private File mFile; 457 AppOpsDataParser(File file)458 AppOpsDataParser(File file) { 459 mFile = file; 460 mVersion = NO_VERSION; 461 } 462 parse()463 boolean parse() { 464 try (FileInputStream stream = new FileInputStream(mFile)) { 465 TypedXmlPullParser parser = Xml.resolvePullParser(stream); 466 int type; 467 while ((type = parser.next()) != XmlPullParser.START_TAG 468 && type != XmlPullParser.END_DOCUMENT) { 469 ; 470 } 471 if (type != XmlPullParser.START_TAG) { 472 throw new IllegalStateException("no start tag found"); 473 } 474 final String versionString = parser.getAttributeValue(null, "v"); 475 if (versionString != null) { 476 mVersion = Integer.parseInt(versionString); 477 } 478 } catch (Exception e) { 479 Log.e(TAG, "Failed while parsing test appops xml", e); 480 return false; 481 } 482 return true; 483 } 484 } 485 } 486