From 220589b3824cb19d089c7fe0664da40df4bd3b76 Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Thu, 1 Dec 2022 16:44:21 -0800 Subject: Change exact alarm permission to be denied by default The permission SCHEDULE_EXACT_ALARM will change from being allowed by default to denied by default for apps targeting T. However, to reduce user impact, already installed apps will continue having the permission if they did so before this change. To achieve this, app-ops will modify MODE_DEFAULT to MODE_ALLOWED for installed apps that have requested the permission in the same change. Since this upgrade step requires appops service to talk to other system services, moving the upgrade to happen on systemReady. Test: atest FrameworksMockingServicesTests:AppOpsUpgradeTest Test: atest FrameworksMockingServicesTests:AlarmManagerServiceTest Test: atest CtsAlarmManagerTestCases Bug: 226439802 Change-Id: I9c964f8957c6a91deca5035d27d21aa7746d2318 --- .../framework/java/android/app/AlarmManager.java | 4 +- .../android/server/appop/AppOpsServiceImpl.java | 81 ++- .../assets/AppOpsUpgradeTest/appops-version-1.xml | 781 +++++++++++++++++++++ .../android/server/appop/AppOpsUpgradeTest.java | 256 ++++++- 4 files changed, 1071 insertions(+), 51 deletions(-) create mode 100644 services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index dade7c3d84a8..53e81c789d2b 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -27,7 +27,6 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -285,11 +284,10 @@ public class AlarmManager { * The permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will be denied, unless the * user explicitly allows it from Settings. * - * TODO (b/226439802): Either enable it in the next SDK or replace it with a better alternative. * @hide */ @ChangeId - @Disabled + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = 226439802L; @UnsupportedAppUsage diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java index 70f3bcc64ef0..ddcccb527ff4 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java @@ -44,6 +44,7 @@ import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; +import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; import static android.app.AppOpsManager.OP_VIBRATE; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; @@ -130,6 +131,8 @@ import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.LockGuard; import com.android.server.SystemServerInitThreadPool; +import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.component.ParsedAttribution; @@ -163,12 +166,30 @@ class AppOpsServiceImpl implements AppOpsServiceInterface { static final String TAG = "AppOps"; static final boolean DEBUG = false; + /** + * Sentinel integer version to denote that there was no appops.xml found on boot. + * This will happen when a device boots with no existing userdata. + */ + private static final int NO_FILE_VERSION = -2; + + /** + * Sentinel integer version to denote that there was no version in the appops.xml found on boot. + * This means the file is coming from a build before versioning was added. + */ private static final int NO_VERSION = -1; + /** * Increment by one every time and add the corresponding upgrade logic in - * {@link #upgradeLocked(int)} below. The first version was 1 + * {@link #upgradeLocked(int)} below. The first version was 1. */ - private static final int CURRENT_VERSION = 1; + @VisibleForTesting + static final int CURRENT_VERSION = 2; + + /** + * This stores the version of appops.xml seen at boot. If this is smaller than + * {@link #CURRENT_VERSION}, then we will run {@link #upgradeLocked(int)} on startup. + */ + private int mVersionAtBoot = NO_FILE_VERSION; // Write at most every 30 minutes. static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000; @@ -936,6 +957,10 @@ class AppOpsServiceImpl implements AppOpsServiceInterface { @Override public void systemReady() { + synchronized (this) { + upgradeLocked(mVersionAtBoot); + } + mConstants.startMonitoring(mContext.getContentResolver()); mHistoricalRegistry.systemReady(mContext.getContentResolver()); @@ -3191,7 +3216,6 @@ class AppOpsServiceImpl implements AppOpsServiceInterface { @Override public void readState() { - int oldVersion = NO_VERSION; synchronized (mFile) { synchronized (this) { FileInputStream stream; @@ -3216,7 +3240,7 @@ class AppOpsServiceImpl implements AppOpsServiceInterface { throw new IllegalStateException("no start tag found"); } - oldVersion = parser.getAttributeInt(null, "v", NO_VERSION); + mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION); int outerDepth = parser.getDepth(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -3261,12 +3285,11 @@ class AppOpsServiceImpl implements AppOpsServiceInterface { } } } - synchronized (this) { - upgradeLocked(oldVersion); - } } - private void upgradeRunAnyInBackgroundLocked() { + @VisibleForTesting + @GuardedBy("this") + void upgradeRunAnyInBackgroundLocked() { for (int i = 0; i < mUidStates.size(); i++) { final UidState uidState = mUidStates.valueAt(i); if (uidState == null) { @@ -3303,8 +3326,45 @@ class AppOpsServiceImpl implements AppOpsServiceInterface { } } + /** + * The interpretation of the default mode - MODE_DEFAULT - for OP_SCHEDULE_EXACT_ALARM is + * changing. Simultaneously, we want to change this op's mode from MODE_DEFAULT to MODE_ALLOWED + * for already installed apps. For newer apps, it will stay as MODE_DEFAULT. + */ + @VisibleForTesting + @GuardedBy("this") + void upgradeScheduleExactAlarmLocked() { + final PermissionManagerServiceInternal pmsi = LocalServices.getService( + PermissionManagerServiceInternal.class); + final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); + final PackageManagerInternal pmi = getPackageManagerInternal(); + + final String[] packagesDeclaringPermission = pmsi.getAppOpPermissionPackages( + AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM)); + final int[] userIds = umi.getUserIds(); + + for (final String pkg : packagesDeclaringPermission) { + for (int userId : userIds) { + final int uid = pmi.getPackageUid(pkg, 0, userId); + + UidState uidState = mUidStates.get(uid); + if (uidState == null) { + uidState = new UidState(uid); + mUidStates.put(uid, uidState); + } + final int oldMode = uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM); + if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) { + uidState.setUidMode(OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED); + } + } + // This appop is meant to be controlled at a uid level. So we leave package modes as + // they are. + } + } + + @GuardedBy("this") private void upgradeLocked(int oldVersion) { - if (oldVersion >= CURRENT_VERSION) { + if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) { return; } Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION); @@ -3313,6 +3373,9 @@ class AppOpsServiceImpl implements AppOpsServiceInterface { upgradeRunAnyInBackgroundLocked(); // fall through case 1: + upgradeScheduleExactAlarmLocked(); + // fall through + case 2: // for future upgrades } scheduleFastWriteLocked(); diff --git a/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml new file mode 100644 index 000000000000..1dde7dcc4720 --- /dev/null +++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml @@ -0,0 +1,781 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java index 298dbf47d86d..302fa0f0c528 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java @@ -16,23 +16,32 @@ package com.android.server.appop; +import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.res.AssetManager; import android.os.Handler; -import android.os.HandlerThread; +import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; @@ -42,11 +51,20 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.ArrayUtils; import com.android.modules.utils.TypedXmlPullParser; +import com.android.server.LocalServices; +import com.android.server.SystemServerInitThreadPool; +import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.permission.PermissionManagerServiceInternal; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import org.xmlpull.v1.XmlPullParser; import java.io.File; @@ -64,29 +82,39 @@ public class AppOpsUpgradeTest { private static final String TAG = AppOpsUpgradeTest.class.getSimpleName(); private static final String APP_OPS_UNVERSIONED_ASSET_PATH = "AppOpsUpgradeTest/appops-unversioned.xml"; + private static final String APP_OPS_VERSION_1_ASSET_PATH = + "AppOpsUpgradeTest/appops-version-1.xml"; private static final String APP_OPS_FILENAME = "appops-test.xml"; private static final int NON_DEFAULT_OPS_IN_FILE = 4; - private static final int CURRENT_VERSION = 1; - private File mAppOpsFile; - private Context mContext; + private static final Context sContext = InstrumentationRegistry.getTargetContext(); + private static final File sAppOpsFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME); + + private Context mTestContext; + private MockitoSession mMockitoSession; + + @Mock + private PackageManagerInternal mPackageManagerInternal; + @Mock + private PackageManager mPackageManager; + @Mock + private UserManagerInternal mUserManagerInternal; + @Mock + private PermissionManagerServiceInternal mPermissionManagerInternal; + @Mock private Handler mHandler; - private void extractAppOpsFile() { - mAppOpsFile.getParentFile().mkdirs(); - if (mAppOpsFile.exists()) { - mAppOpsFile.delete(); - } - try (FileOutputStream out = new FileOutputStream(mAppOpsFile); - InputStream in = mContext.getAssets().open(APP_OPS_UNVERSIONED_ASSET_PATH, - AssetManager.ACCESS_BUFFER)) { + private static void extractAppOpsFile(String assetPath) { + sAppOpsFile.getParentFile().mkdirs(); + try (FileOutputStream out = new FileOutputStream(sAppOpsFile); + InputStream in = sContext.getAssets().open(assetPath, AssetManager.ACCESS_BUFFER)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) >= 0) { out.write(buffer, 0, bytesRead); } out.flush(); - Log.d(TAG, "Successfully copied xml to " + mAppOpsFile.getAbsolutePath()); + Log.d(TAG, "Successfully copied xml to " + sAppOpsFile.getAbsolutePath()); } catch (IOException exc) { Log.e(TAG, "Exception while copying appops xml", exc); fail(); @@ -98,7 +126,7 @@ public class AppOpsUpgradeTest { int numberOfNonDefaultOps = 0; final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1); final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2); - for(int i = 0; i < uidStates.size(); i++) { + for (int i = 0; i < uidStates.size(); i++) { final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i); SparseIntArray opModes = uidState.getNonDefaultUidModes(); if (opModes != null) { @@ -132,41 +160,191 @@ public class AppOpsUpgradeTest { @Before public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - mAppOpsFile = new File(mContext.getFilesDir(), APP_OPS_FILENAME); - extractAppOpsFile(); - HandlerThread handlerThread = new HandlerThread(TAG); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); + if (sAppOpsFile.exists()) { + sAppOpsFile.delete(); + } + + mMockitoSession = mockitoSession() + .initMocks(this) + .spyStatic(LocalServices.class) + .mockStatic(SystemServerInitThreadPool.class) + .strictness(Strictness.LENIENT) + .startMocking(); + + doReturn(mPermissionManagerInternal).when( + () -> LocalServices.getService(PermissionManagerServiceInternal.class)); + doReturn(mUserManagerInternal).when( + () -> LocalServices.getService(UserManagerInternal.class)); + doReturn(mPackageManagerInternal).when( + () -> LocalServices.getService(PackageManagerInternal.class)); + + mTestContext = spy(sContext); + + // Pretend everybody has all permissions + doNothing().when(mTestContext).enforcePermission(anyString(), anyInt(), anyInt(), + nullable(String.class)); + + doReturn(mPackageManager).when(mTestContext).getPackageManager(); + + // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat + doReturn(null).when(mPackageManager).getPackagesForUid(anyInt()); + } + + @After + public void tearDown() { + mMockitoSession.finishMocking(); } @Test - public void testUpgradeFromNoVersion() throws Exception { - AppOpsDataParser parser = new AppOpsDataParser(mAppOpsFile); + public void upgradeRunAnyInBackground() { + extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH); + + AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext); + + testService.upgradeRunAnyInBackgroundLocked(); + assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND, + AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); + } + + private static int getModeInFile(int uid) { + switch (uid) { + case 10198: + return 0; + case 10200: + return 1; + case 1110200: + case 10267: + case 1110181: + return 2; + default: + return AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM); + } + } + + @Test + public void upgradeScheduleExactAlarm() { + extractAppOpsFile(APP_OPS_VERSION_1_ASSET_PATH); + + String[] packageNames = {"p1", "package2", "pkg3", "package.4", "pkg-5", "pkg.6"}; + int[] appIds = {10267, 10181, 10198, 10199, 10200, 4213}; + int[] userIds = {0, 10, 11}; + + doReturn(userIds).when(mUserManagerInternal).getUserIds(); + + doReturn(packageNames).when(mPermissionManagerInternal).getAppOpPermissionPackages( + AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM)); + + doAnswer(invocation -> { + String pkg = invocation.getArgument(0); + int index = ArrayUtils.indexOf(packageNames, pkg); + if (index < 0) { + return index; + } + int userId = invocation.getArgument(2); + return UserHandle.getUid(userId, appIds[index]); + }).when(mPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt()); + + AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext); + + testService.upgradeScheduleExactAlarmLocked(); + + for (int userId : userIds) { + for (int appId : appIds) { + final int uid = UserHandle.getUid(userId, appId); + final int previousMode = getModeInFile(uid); + + final int expectedMode; + if (previousMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) { + expectedMode = AppOpsManager.MODE_ALLOWED; + } else { + expectedMode = previousMode; + } + final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid); + assertEquals(expectedMode, uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM)); + } + } + + // These uids don't even declare the permission. So should stay as default / empty. + int[] unrelatedUidsInFile = {10225, 10178}; + + for (int uid : unrelatedUidsInFile) { + final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid); + assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM), + uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM)); + } + } + + @Test + public void upgradeFromNoFile() { + assertFalse(sAppOpsFile.exists()); + + AppOpsServiceImpl testService = spy( + new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext)); + + doNothing().when(testService).upgradeRunAnyInBackgroundLocked(); + doNothing().when(testService).upgradeScheduleExactAlarmLocked(); + + // trigger upgrade + testService.systemReady(); + + verify(testService, never()).upgradeRunAnyInBackgroundLocked(); + verify(testService, never()).upgradeScheduleExactAlarmLocked(); + + testService.writeState(); + + assertTrue(sAppOpsFile.exists()); + + AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile); + assertTrue(parser.parse()); + assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion); + } + + @Test + public void upgradeFromNoVersion() { + extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH); + AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile); assertTrue(parser.parse()); assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion); - // Use mock context and package manager to fake permision package manager calls. - Context testContext = spy(mContext); + AppOpsServiceImpl testService = spy( + new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext)); - // Pretent everybody has all permissions - doNothing().when(testContext).enforcePermission(anyString(), anyInt(), anyInt(), - nullable(String.class)); + doNothing().when(testService).upgradeRunAnyInBackgroundLocked(); + doNothing().when(testService).upgradeScheduleExactAlarmLocked(); - PackageManager testPM = mock(PackageManager.class); - when(testContext.getPackageManager()).thenReturn(testPM); + // trigger upgrade + testService.systemReady(); - // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat - when(testPM.getPackagesForUid(anyInt())).thenReturn(null); + verify(testService).upgradeRunAnyInBackgroundLocked(); + verify(testService).upgradeScheduleExactAlarmLocked(); + + testService.writeState(); + assertTrue(parser.parse()); + assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion); + } + + @Test + public void upgradeFromVersion1() { + extractAppOpsFile(APP_OPS_VERSION_1_ASSET_PATH); + AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile); + assertTrue(parser.parse()); + assertEquals(1, parser.mVersion); AppOpsServiceImpl testService = spy( - new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade - assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND, - AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); - mHandler.removeCallbacks(testService.mWriteRunner); + new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext)); + + doNothing().when(testService).upgradeRunAnyInBackgroundLocked(); + doNothing().when(testService).upgradeScheduleExactAlarmLocked(); + + // trigger upgrade + testService.systemReady(); + + verify(testService, never()).upgradeRunAnyInBackgroundLocked(); + verify(testService).upgradeScheduleExactAlarmLocked(); + testService.writeState(); assertTrue(parser.parse()); - assertEquals(CURRENT_VERSION, parser.mVersion); + assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion); } /** @@ -174,7 +352,7 @@ public class AppOpsUpgradeTest { * Other fields may be added as and when required for testing. */ private static final class AppOpsDataParser { - static final int NO_VERSION = -1; + static final int NO_VERSION = -123; int mVersion; private File mFile; -- cgit v1.2.3-59-g8ed1b