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
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index dade7c3..53e81c7 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.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 @@
      * 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 70f3bcc..ddcccb5 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_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.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 @@
     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 @@
 
     @Override
     public void systemReady() {
+        synchronized (this) {
+            upgradeLocked(mVersionAtBoot);
+        }
+
         mConstants.startMonitoring(mContext.getContentResolver());
         mHistoricalRegistry.systemReady(mContext.getContentResolver());
 
@@ -3191,7 +3216,6 @@
 
     @Override
     public void readState() {
-        int oldVersion = NO_VERSION;
         synchronized (mFile) {
             synchronized (this) {
                 FileInputStream stream;
@@ -3216,7 +3240,7 @@
                         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 @@
                 }
             }
         }
-        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 @@
         }
     }
 
+    /**
+     * 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 @@
                 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 0000000..1dde7dc
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml
@@ -0,0 +1,781 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>

+<app-ops v="1">

+  <uid n="1001">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="15" m="0" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="1002">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="15" m="0" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10077">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10079">

+    <op n="116" m="1" />

+  </uid>

+  <uid n="10080">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10081">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10086">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10087">

+    <op n="59" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10090">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+  </uid>

+  <uid n="10096">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+  </uid>

+  <uid n="10112">

+    <op n="11" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="62" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10113">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="51" m="1" />

+    <op n="62" m="1" />

+  </uid>

+  <uid n="10114">

+    <op n="4" m="1" />

+  </uid>

+  <uid n="10115">

+    <op n="0" m="1" />

+  </uid>

+  <uid n="10116">

+    <op n="0" m="1" />

+  </uid>

+  <uid n="10117">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="13" m="1" />

+    <op n="14" m="1" />

+    <op n="16" m="1" />

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+  </uid>

+  <uid n="10118">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10119">

+    <op n="11" m="1" />

+    <op n="77" m="1" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10120">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="6" m="1" />

+    <op n="7" m="1" />

+    <op n="11" m="1" />

+    <op n="13" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="54" m="1" />

+    <op n="59" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10121">

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10122">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10123">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10124">

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+  </uid>

+  <uid n="10125">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10127">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="65" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10129">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+  </uid>

+  <uid n="10130">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10131">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10132">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="69" m="1" />

+    <op n="79" m="1" />

+  </uid>

+  <uid n="10133">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10136">

+    <op n="0" m="1" />

+    <op n="4" m="1" />

+    <op n="77" m="1" />

+    <op n="87" m="1" />

+    <op n="111" m="1" />

+    <op n="114" m="1" />

+  </uid>

+  <uid n="10137">

+    <op n="62" m="1" />

+  </uid>

+  <uid n="10138">

+    <op n="26" m="4" />

+  </uid>

+  <uid n="10140">

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10141">

+    <op n="11" m="1" />

+    <op n="27" m="1" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10142">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10144">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="27" m="4" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10145">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10149">

+    <op n="11" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10150">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10151">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10152">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10154">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="27" m="4" />

+  </uid>

+  <uid n="10155">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+  </uid>

+  <uid n="10157">

+    <op n="13" m="1" />

+  </uid>

+  <uid n="10158">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10160">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10161">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+    <op n="51" m="1" />

+    <op n="77" m="1" />

+    <op n="87" m="1" />

+    <op n="111" m="1" />

+    <op n="114" m="1" />

+  </uid>

+  <uid n="10162">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="15" m="0" />

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+    <op n="89" m="0" />

+  </uid>

+  <uid n="10163">

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="56" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10164">

+    <op n="26" m="1" />

+    <op n="27" m="4" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="69" m="1" />

+    <op n="79" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10169">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10170">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10171">

+    <op n="26" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10172">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10173">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="23" m="0" />

+    <op n="26" m="1" />

+    <op n="51" m="1" />

+    <op n="62" m="1" />

+    <op n="65" m="1" />

+  </uid>

+  <uid n="10175">

+    <op n="0" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+  </uid>

+  <uid n="10178">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+    <op n="27" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10179">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="62" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10180">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10181">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="59" m="1" />

+    <op n="62" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="1110181">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+    <op n="107" m="2" />

+  </uid>

+  <uid n="10182">

+    <op n="27" m="4" />

+  </uid>

+  <uid n="10183">

+    <op n="11" m="1" />

+    <op n="26" m="4" />

+  </uid>

+  <uid n="10184">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="13" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="4" />

+  </uid>

+  <uid n="10185">

+    <op n="8" m="1" />

+    <op n="59" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10187">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10189">

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10190">

+    <op n="0" m="1" />

+    <op n="13" m="1" />

+  </uid>

+  <uid n="10191">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10192">

+    <op n="11" m="1" />

+    <op n="13" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10193">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10197">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10198">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="77" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+    <op n="90" m="1" />

+    <op n="107" m="0" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10199">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="62" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10200">

+    <op n="11" m="1" />

+    <op n="65" m="1" />

+    <op n="107" m="1" />

+  </uid>

+  <uid n="1110200">

+    <op n="11" m="1" />

+    <op n="65" m="1" />

+    <op n="107" m="2" />

+  </uid>

+  <uid n="10201">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="51" m="1" />

+    <op n="62" m="1" />

+    <op n="84" m="0" />

+    <op n="86" m="0" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10206">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="26" m="4" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10209">

+    <op n="11" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10210">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10212">

+    <op n="11" m="1" />

+    <op n="62" m="1" />

+  </uid>

+  <uid n="10214">

+    <op n="26" m="4" />

+  </uid>

+  <uid n="10216">

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10225">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10229">

+    <op n="11" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="62" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10231">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10232">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10234">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+    <op n="13" m="1" />

+    <op n="20" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10235">

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10237">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+  </uid>

+  <uid n="10238">

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10240">

+    <op n="112" m="1" />

+  </uid>

+  <uid n="10241">

+    <op n="59" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10245">

+    <op n="13" m="1" />

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10247">

+    <op n="0" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="0" />

+    <op n="90" m="1" />

+  </uid>

+  <uid n="10254">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10255">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10256">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10258">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10260">

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10262">

+    <op n="15" m="0" />

+  </uid>

+  <uid n="10266">

+    <op n="0" m="4" />

+  </uid>

+  <uid n="10267">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="62" m="1" />

+    <op n="77" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+    <op n="107" m="2" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10268">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="62" m="1" />

+  </uid>

+  <uid n="10269">

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <pkg n="com.google.android.iwlan">

+    <uid n="0">

+      <op n="1" />

+      <op n="75" m="0" />

+    </uid>

+  </pkg>

+  <pkg n="com.android.phone">

+    <uid n="0">

+      <op n="1" />

+      <op n="75" m="0" />

+    </uid>

+  </pkg>

+  <pkg n="android">

+    <uid n="1000">

+      <op n="0">

+        <st n="214748364801" t="1670287941040" />

+      </op>

+      <op n="4">

+        <st n="214748364801" t="1670289665522" />

+      </op>

+      <op n="6">

+        <st n="214748364801" t="1670287946650" />

+      </op>

+      <op n="8">

+        <st n="214748364801" t="1670289624396" />

+      </op>

+      <op n="14">

+        <st n="214748364801" t="1670287951031" />

+      </op>

+      <op n="40">

+        <st n="214748364801" t="1670291786337" d="156" />

+      </op>

+      <op n="41">

+        <st id="SensorNotificationService" n="214748364801" t="1670287585567" d="4251183" />

+        <st id="CountryDetector" n="214748364801" t="1670287583306" d="6700" />

+      </op>

+      <op n="43">

+        <st n="214748364801" r="1670291755062" />

+      </op>

+      <op n="61">

+        <st n="214748364801" r="1670291754997" />

+      </op>

+      <op n="105">

+        <st n="214748364801" r="1670291473903" />

+        <st id="GnssService" n="214748364801" r="1670288044920" />

+      </op>

+      <op n="111">

+        <st n="214748364801" t="1670291441554" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.server.telecom">

+    <uid n="1000">

+      <op n="6">

+        <st n="214748364801" t="1670287609092" />

+      </op>

+      <op n="111">

+        <st n="214748364801" t="1670287583728" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.settings">

+    <uid n="1000">

+      <op n="43">

+        <st n="214748364801" r="1670291447349" />

+      </op>

+      <op n="105">

+        <st n="214748364801" r="1670291399231" />

+      </op>

+      <op n="111">

+        <st n="214748364801" t="1670291756910" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.phone">

+    <uid n="1001">

+      <op n="15">

+        <st n="214748364801" t="1670287951022" />

+      </op>

+      <op n="40">

+        <st n="214748364801" t="1670291786177" />

+      </op>

+      <op n="105">

+        <st n="214748364801" r="1670291756403" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.bluetooth">

+    <uid n="1002">

+      <op n="4">

+        <st n="214748364801" t="1670289671076" />

+      </op>

+      <op n="40">

+        <st n="214748364801" t="1670287585676" d="8" />

+      </op>

+      <op n="43">

+        <st n="214748364801" r="1670287585818" />

+      </op>

+      <op n="77">

+        <st n="214748364801" t="1670288037629" />

+      </op>

+      <op n="111">

+        <st n="214748364801" t="1670287592081" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.vending">

+    <uid n="10136">

+      <op n="40">

+        <st n="429496729601" t="1670289621210" d="114" />

+        <st n="858993459201" t="1670289879730" d="349" />

+        <st n="1288490188801" t="1670287942622" d="937" />

+      </op>

+      <op n="43">

+        <st n="429496729601" r="1670289755305" />

+        <st n="858993459201" r="1670288019246" />

+        <st n="1073741824001" r="1670289571783" />

+        <st n="1288490188801" r="1670289373336" />

+      </op>

+      <op n="76">

+        <st n="429496729601" t="1670289748735" d="15991" />

+        <st n="858993459201" t="1670291395180" d="79201" />

+        <st n="1073741824001" t="1670291395168" d="12" />

+        <st n="1288490188801" t="1670291526029" d="3" />

+        <st n="1503238553601" t="1670291526032" d="310718" />

+      </op>

+      <op n="105">

+        <st n="429496729601" r="1670289538910" />

+        <st n="858993459201" r="1670288054519" />

+        <st n="1073741824001" r="1670287599379" />

+        <st n="1288490188801" r="1670289526854" />

+        <st n="1503238553601" r="1670289528242" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.nfc">

+    <uid n="1027">

+      <op n="40">

+        <st n="214748364801" t="1670291786330" d="22" />

+      </op>

+    </uid>

+  </pkg>

+</app-ops>
\ 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 298dbf4..302fa0f 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.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 @@
     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 @@
         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 @@
 
     @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);
-
-        // Pretent everybody has all permissions
-        doNothing().when(testContext).enforcePermission(anyString(), anyInt(), anyInt(),
-                nullable(String.class));
-
-        PackageManager testPM = mock(PackageManager.class);
-        when(testContext.getPackageManager()).thenReturn(testPM);
-
-        // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
-        when(testPM.getPackagesForUid(anyInt())).thenReturn(null);
-
         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).upgradeRunAnyInBackgroundLocked();
+        verify(testService).upgradeScheduleExactAlarmLocked();
+
         testService.writeState();
         assertTrue(parser.parse());
-        assertEquals(CURRENT_VERSION, parser.mVersion);
+        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(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(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
     }
 
     /**
@@ -174,7 +352,7 @@
      * 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;