Merge "Add dynamic coloring documentation to FORMAT.md"
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index 4053bdd..8c8d2bf 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -285,7 +285,7 @@
     /** @hide */
     public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT = 0;
     /** @hide */
-    public static final double DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING = 0.01;
+    public static final float DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING = 0.01f;
     /** @hide */
     public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX = 500;
     /** @hide */
@@ -372,7 +372,7 @@
     /** @hide */
     public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT = 0;
     /** @hide */
-    public static final double DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING = 0.5;
+    public static final float DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING = 0.5f;
     /** @hide */
     public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX = 15000;
     /** @hide */
diff --git a/apex/jobscheduler/framework/java/com/android/server/AppStateTracker.java b/apex/jobscheduler/framework/java/com/android/server/AppStateTracker.java
index b0b9abc..fc27a79 100644
--- a/apex/jobscheduler/framework/java/com/android/server/AppStateTracker.java
+++ b/apex/jobscheduler/framework/java/com/android/server/AppStateTracker.java
@@ -25,19 +25,25 @@
     String TAG = "AppStateTracker";
 
     /**
-     * Register a {@link ServiceStateListener} to listen for forced-app-standby changes that should
-     * affect services.
+     * Register a {@link BackgroundRestrictedAppListener} to listen for background restricted mode
+     * changes that should affect services etc.
      */
-    void addServiceStateListener(@NonNull ServiceStateListener listener);
+    void addBackgroundRestrictedAppListener(@NonNull BackgroundRestrictedAppListener listener);
 
     /**
-     * A listener to listen to forced-app-standby changes that should affect services.
+     * @return {code true} if the given UID/package has been in background restricted mode,
+     * it does NOT include the case where the "force app background restricted" is enabled.
      */
-    interface ServiceStateListener {
+    boolean isAppBackgroundRestricted(int uid, @NonNull String packageName);
+
+    /**
+     * A listener to listen to background restricted mode changes that should affect services etc.
+     */
+    interface BackgroundRestrictedAppListener {
         /**
-         * Called when an app goes into forced app standby and its foreground
-         * services need to be removed from that state.
+         * Called when an app goes in/out of background restricted mode.
          */
-        void stopForegroundServicesForUidPackage(int uid, String packageName);
+        void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
+                boolean restricted);
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index c332a59..d0a155d 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -60,8 +60,10 @@
 
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Class to keep track of the information related to "force app standby", which includes:
@@ -160,16 +162,34 @@
     @GuardedBy("mLock")
     boolean mForcedAppStandbyEnabled;
 
+    /**
+     * A lock-free set of (uid, packageName) pairs in background restricted mode.
+     *
+     * <p>
+     * It's bascially shadowing the {@link #mRunAnyRestrictedPackages} together with
+     * the {@link #mForcedAppStandbyEnabled} - mutations on them would result in copy-on-write.
+     * </p>
+     */
+    volatile Set<Pair<Integer, String>> mBackgroundRestrictedUidPackages = Collections.emptySet();
+
     @Override
-    public void addServiceStateListener(@NonNull ServiceStateListener listener) {
+    public void addBackgroundRestrictedAppListener(
+            @NonNull BackgroundRestrictedAppListener listener) {
         addListener(new Listener() {
             @Override
-            public void stopForegroundServicesForUidPackage(int uid, String packageName) {
-                listener.stopForegroundServicesForUidPackage(uid, packageName);
+            public void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
+                    boolean restricted) {
+                listener.updateBackgroundRestrictedForUidPackage(uid, packageName, restricted);
             }
         });
     }
 
+    @Override
+    public boolean isAppBackgroundRestricted(int uid, @NonNull String packageName) {
+        final Set<Pair<Integer, String>> bgRestrictedUidPkgs = mBackgroundRestrictedUidPackages;
+        return bgRestrictedUidPkgs.contains(Pair.create(uid, packageName));
+    }
+
     interface Stats {
         int UID_FG_STATE_CHANGED = 0;
         int UID_ACTIVE_STATE_CHANGED = 1;
@@ -233,6 +253,7 @@
                         return;
                     }
                     mForcedAppStandbyEnabled = enabled;
+                    updateBackgroundRestrictedUidPackagesLocked();
                     if (DEBUG) {
                         Slog.d(TAG, "Forced app standby feature flag changed: "
                                 + mForcedAppStandbyEnabled);
@@ -277,7 +298,11 @@
             if (!sender.isRunAnyInBackgroundAppOpsAllowed(uid, packageName)) {
                 Slog.v(TAG, "Package " + packageName + "/" + uid
                         + " toggled into fg service restriction");
-                stopForegroundServicesForUidPackage(uid, packageName);
+                updateBackgroundRestrictedForUidPackage(uid, packageName, true);
+            } else {
+                Slog.v(TAG, "Package " + packageName + "/" + uid
+                        + " toggled out of fg service restriction");
+                updateBackgroundRestrictedForUidPackage(uid, packageName, false);
             }
         }
 
@@ -366,10 +391,10 @@
         }
 
         /**
-         * Called when an app goes into forced app standby and its foreground
-         * services need to be removed from that state.
+         * Called when an app goes in/out of background restricted mode.
          */
-        public void stopForegroundServicesForUidPackage(int uid, String packageName) {
+        public void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
+                boolean restricted) {
         }
 
         /**
@@ -438,9 +463,12 @@
                         final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                         // No need to notify for state change as all the alarms and jobs should be
                         // removed too.
-                        mExemptedBucketPackages.remove(userId, pkgName);
-                        mRunAnyRestrictedPackages.remove(Pair.create(uid, pkgName));
-                        mActiveUids.delete(uid);
+                        synchronized (mLock) {
+                            mExemptedBucketPackages.remove(userId, pkgName);
+                            mRunAnyRestrictedPackages.remove(Pair.create(uid, pkgName));
+                            updateBackgroundRestrictedUidPackagesLocked();
+                            mActiveUids.delete(uid);
+                        }
                     }
                     break;
             }
@@ -580,6 +608,28 @@
                 }
             }
         }
+        updateBackgroundRestrictedUidPackagesLocked();
+    }
+
+    /**
+     * Update the {@link #mBackgroundRestrictedUidPackages} upon mutations on
+     * {@link #mRunAnyRestrictedPackages} or {@link #mForcedAppStandbyEnabled}.
+     */
+    @GuardedBy("mLock")
+    private void updateBackgroundRestrictedUidPackagesLocked() {
+        if (!mForcedAppStandbyEnabled) {
+            mBackgroundRestrictedUidPackages = Collections.emptySet();
+            return;
+        }
+        if (mForceAllAppsStandby) {
+            mBackgroundRestrictedUidPackages = null;
+            return;
+        }
+        Set<Pair<Integer, String>> fasUidPkgs = new ArraySet<>();
+        for (int i = 0, size = mRunAnyRestrictedPackages.size(); i < size; i++) {
+            fasUidPkgs.add(mRunAnyRestrictedPackages.valueAt(i));
+        }
+        mBackgroundRestrictedUidPackages = Collections.unmodifiableSet(fasUidPkgs);
     }
 
     private void updateForceAllAppStandbyState() {
@@ -645,6 +695,7 @@
         } else {
             mRunAnyRestrictedPackages.removeAt(index);
         }
+        updateBackgroundRestrictedUidPackagesLocked();
         return true;
     }
 
@@ -966,6 +1017,7 @@
                     mRunAnyRestrictedPackages.removeAt(i);
                 }
             }
+            updateBackgroundRestrictedUidPackagesLocked();
             cleanUpArrayForUser(mActiveUids, removedUserId);
             mExemptedBucketPackages.remove(removedUserId);
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index acdf689..6ef9456 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -16,6 +16,79 @@
 
 package com.android.server.tare;
 
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_CIRCULATION;
+import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE;
+import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALARMCLOCK_CTP;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_EXACT_WAKEUP_CTP;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP;
+import static android.app.tare.EconomyManager.KEY_AM_MAX_CIRCULATION;
+import static android.app.tare.EconomyManager.KEY_AM_MAX_SATIATED_BALANCE;
+import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_SEEN_INSTANT;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_SEEN_MAX;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_SEEN_ONGOING;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_OTHER_USER_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_OTHER_USER_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_OTHER_USER_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_TOP_ACTIVITY_INSTANT;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_TOP_ACTIVITY_MAX;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_TOP_ACTIVITY_ONGOING;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_WIDGET_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING;
+import static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS;
+
 import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING;
 import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE;
 import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE;
@@ -24,6 +97,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Slog;
 import android.util.SparseArray;
 
 /**
@@ -31,6 +111,8 @@
  * AlarmManager.
  */
 public class AlarmManagerEconomicPolicy extends EconomicPolicy {
+    private static final String TAG = "TARE- " + AlarmManagerEconomicPolicy.class.getSimpleName();
+
     public static final int ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE =
             TYPE_ACTION | POLICY_AM | 0;
     public static final int ACTION_ALARM_WAKEUP_EXACT =
@@ -57,30 +139,50 @@
             COST_MODIFIER_PROCESS_STATE
     };
 
+    private long mMinSatiatedBalance;
+    private long mMaxSatiatedBalance;
+    private long mMaxSatiatedCirculation;
+
+    private final KeyValueListParser mParser = new KeyValueListParser(',');
+    private final SettingsObserver mSettingsObserver;
+    private final InternalResourceService mInternalResourceService;
+
     private final SparseArray<Action> mActions = new SparseArray<>();
     private final SparseArray<Reward> mRewards = new SparseArray<>();
 
     AlarmManagerEconomicPolicy(InternalResourceService irs) {
         super(irs);
-        loadActions();
-        loadRewards();
+        mInternalResourceService = irs;
+        mSettingsObserver = new SettingsObserver(TareHandlerThread.getHandler());
+        loadConstants("");
+    }
+
+    @Override
+    void setup() {
+        super.setup();
+        ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+        resolver.registerContentObserver(
+                Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS),
+                false, mSettingsObserver, UserHandle.USER_ALL);
+        loadConstants(Settings.Global.getString(
+                mInternalResourceService.getContext().getContentResolver(),
+                TARE_ALARM_MANAGER_CONSTANTS));
     }
 
     @Override
     long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
         // TODO: take exemption into account
-        return arcToNarc(160);
+        return mMinSatiatedBalance;
     }
 
     @Override
     long getMaxSatiatedBalance() {
-        return arcToNarc(1440);
+        return mMaxSatiatedBalance;
     }
 
-
     @Override
     long getMaxSatiatedCirculation() {
-        return arcToNarc(52000);
+        return mMaxSatiatedCirculation;
     }
 
     @NonNull
@@ -101,43 +203,162 @@
         return mRewards.get(rewardId);
     }
 
-    private void loadActions() {
+    private void loadConstants(String policyValuesString) {
+        mActions.clear();
+        mRewards.clear();
+
+        try {
+            mParser.setString(policyValuesString);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Global setting key incorrect: ", e);
+        }
+
+        mMinSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP,
+                        DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP));
+        mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MAX_SATIATED_BALANCE,
+                DEFAULT_AM_MAX_SATIATED_BALANCE));
+        mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_AM_MAX_CIRCULATION,
+                DEFAULT_AM_MAX_CIRCULATION));
+
+        final long exactAllowWhileIdleWakeupBasePrice = arcToNarc(
+                mParser.getInt(KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
+                        DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE));
+
         mActions.put(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
-                new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE, arcToNarc(3), arcToNarc(5)));
+                new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP,
+                                DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP)),
+                        exactAllowWhileIdleWakeupBasePrice));
         mActions.put(ACTION_ALARM_WAKEUP_EXACT,
-                new Action(ACTION_ALARM_WAKEUP_EXACT, arcToNarc(3), arcToNarc(4)));
+                new Action(ACTION_ALARM_WAKEUP_EXACT,
+                        arcToNarc(mParser.getInt(KEY_AM_ACTION_ALARM_EXACT_WAKEUP_CTP,
+                                DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP)),
+                        arcToNarc(mParser.getInt(KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE,
+                                DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE))));
+
+        final long inexactAllowWhileIdleWakeupBasePrice =
+                arcToNarc(mParser.getInt(
+                        KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE,
+                        DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE));
+
         mActions.put(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE,
-                        arcToNarc(3), arcToNarc(4)));
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP,
+                                DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP)),
+                        inexactAllowWhileIdleWakeupBasePrice));
         mActions.put(ACTION_ALARM_WAKEUP_INEXACT,
-                new Action(ACTION_ALARM_WAKEUP_INEXACT, arcToNarc(3), arcToNarc(3)));
+                new Action(ACTION_ALARM_WAKEUP_INEXACT,
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP,
+                                DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP)),
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE,
+                                DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE))));
+
+        final long exactAllowWhileIdleNonWakeupBasePrice =
+                arcToNarc(mParser.getInt(
+                        KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE,
+                        DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE));
+
         mActions.put(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE,
-                        arcToNarc(1), arcToNarc(3)));
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP,
+                                DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP)),
+                        exactAllowWhileIdleNonWakeupBasePrice));
         mActions.put(ACTION_ALARM_NONWAKEUP_EXACT,
-                new Action(ACTION_ALARM_NONWAKEUP_EXACT, arcToNarc(1), arcToNarc(2)));
+                new Action(ACTION_ALARM_NONWAKEUP_EXACT,
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP,
+                                DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP)),
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE,
+                                DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE))));
+
+        final long inexactAllowWhileIdleNonWakeupBasePrice =
+                arcToNarc(mParser.getInt(
+                        KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE,
+                        DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE));
+
         mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE,
-                        arcToNarc(1), arcToNarc(2)));
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP,
+                                DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP)),
+                        inexactAllowWhileIdleNonWakeupBasePrice));
         mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT,
-                new Action(ACTION_ALARM_NONWAKEUP_INEXACT, arcToNarc(1), arcToNarc(1)));
+                new Action(ACTION_ALARM_NONWAKEUP_INEXACT,
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP,
+                                DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP)),
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE,
+                                DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE))));
         mActions.put(ACTION_ALARM_CLOCK,
-                new Action(ACTION_ALARM_CLOCK, arcToNarc(5), arcToNarc(10)));
-    }
+                new Action(ACTION_ALARM_CLOCK,
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_ALARMCLOCK_CTP,
+                                DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP)),
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE,
+                                DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE))));
 
-    private void loadRewards() {
-        mRewards.put(REWARD_TOP_ACTIVITY,
-                new Reward(REWARD_TOP_ACTIVITY,
-                        arcToNarc(0), /* .01 arcs */ arcToNarc(1) / 100, arcToNarc(500)));
-        mRewards.put(REWARD_NOTIFICATION_SEEN,
-                new Reward(REWARD_NOTIFICATION_SEEN, arcToNarc(3), arcToNarc(0), arcToNarc(60)));
+        mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY,
+                arcToNarc(mParser.getInt(KEY_AM_REWARD_TOP_ACTIVITY_INSTANT,
+                        DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT)),
+                (long) (arcToNarc(1) * mParser.getFloat(KEY_AM_REWARD_TOP_ACTIVITY_ONGOING,
+                        DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING)),
+                arcToNarc(mParser.getInt(KEY_AM_REWARD_TOP_ACTIVITY_MAX,
+                                DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX))));
+        mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN,
+                arcToNarc(mParser.getInt(KEY_AM_REWARD_NOTIFICATION_SEEN_INSTANT,
+                        DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT)),
+                arcToNarc(mParser.getInt(KEY_AM_REWARD_NOTIFICATION_SEEN_ONGOING,
+                        DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING)),
+                arcToNarc(mParser.getInt(KEY_AM_REWARD_NOTIFICATION_SEEN_MAX,
+                        DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX))));
         mRewards.put(REWARD_NOTIFICATION_INTERACTION,
                 new Reward(REWARD_NOTIFICATION_INTERACTION,
-                        arcToNarc(5), arcToNarc(0), arcToNarc(500)));
-        mRewards.put(REWARD_WIDGET_INTERACTION,
-                new Reward(REWARD_WIDGET_INTERACTION, arcToNarc(10), arcToNarc(0), arcToNarc(500)));
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT,
+                                DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT)),
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING,
+                                DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING)),
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX,
+                                DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX))));
+        mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION,
+                arcToNarc(mParser.getInt(KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT,
+                        DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT)),
+                arcToNarc(mParser.getInt(KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING,
+                        DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING)),
+                arcToNarc(mParser.getInt(KEY_AM_REWARD_WIDGET_INTERACTION_MAX,
+                        DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX))));
         mRewards.put(REWARD_OTHER_USER_INTERACTION,
                 new Reward(REWARD_OTHER_USER_INTERACTION,
-                        arcToNarc(10), arcToNarc(0), arcToNarc(500)));
+                        arcToNarc(mParser.getInt(KEY_AM_REWARD_OTHER_USER_INTERACTION_INSTANT,
+                                DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT)),
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_REWARD_OTHER_USER_INTERACTION_ONGOING,
+                                DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING)),
+                        arcToNarc(mParser.getInt(
+                                KEY_AM_REWARD_OTHER_USER_INTERACTION_MAX,
+                                DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX))));
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            loadConstants(Settings.Global.getString(
+                    mInternalResourceService.getContext().getContentResolver(),
+                    TARE_ALARM_MANAGER_CONSTANTS));
+        }
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index 8c56c61..f05e5c9 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -67,6 +67,14 @@
     }
 
     @Override
+    void setup() {
+        super.setup();
+        for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
+            mEnabledEconomicPolicies.valueAt(i).setup();
+        }
+    }
+
+    @Override
     public long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
         long min = 0;
         for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index ff008a2..e339650 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -16,6 +16,88 @@
 
 package com.android.server.tare;
 
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_HIGH_START_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_START_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MAX_START_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_CIRCULATION;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_DEFAULT_START_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_HIGH_RUNNING_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_HIGH_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_HIGH_START_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_LOW_RUNNING_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_LOW_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_LOW_START_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MAX_RUNNING_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MAX_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MAX_START_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_RUNNING_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
+import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP;
+import static android.app.tare.EconomyManager.KEY_JS_MAX_CIRCULATION;
+import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE;
+import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_SEEN_INSTANT;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_SEEN_MAX;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_SEEN_ONGOING;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_OTHER_USER_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_OTHER_USER_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_OTHER_USER_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_TOP_ACTIVITY_INSTANT;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_TOP_ACTIVITY_MAX;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_TOP_ACTIVITY_ONGOING;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_WIDGET_INTERACTION_MAX;
+import static android.app.tare.EconomyManager.KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING;
+import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS;
+
 import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING;
 import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE;
 import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE;
@@ -24,6 +106,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Slog;
 import android.util.SparseArray;
 
 /**
@@ -31,6 +120,8 @@
  * JobScheduler.
  */
 public class JobSchedulerEconomicPolicy extends EconomicPolicy {
+    private static final String TAG = "TARE- " + JobSchedulerEconomicPolicy.class.getSimpleName();
+
     public static final int ACTION_JOB_MAX_START = TYPE_ACTION | POLICY_JS | 0;
     public static final int ACTION_JOB_MAX_RUNNING = TYPE_ACTION | POLICY_JS | 1;
     public static final int ACTION_JOB_HIGH_START = TYPE_ACTION | POLICY_JS | 2;
@@ -50,29 +141,50 @@
             COST_MODIFIER_PROCESS_STATE
     };
 
+    private long mMinSatiatedBalance;
+    private long mMaxSatiatedBalance;
+    private long mMaxSatiatedCirculation;
+
+    private final KeyValueListParser mParser = new KeyValueListParser(',');
+    private final SettingsObserver mSettingsObserver;
+    private final InternalResourceService mInternalResourceService;
+
     private final SparseArray<Action> mActions = new SparseArray<>();
     private final SparseArray<Reward> mRewards = new SparseArray<>();
 
     JobSchedulerEconomicPolicy(InternalResourceService irs) {
         super(irs);
-        loadActions();
-        loadRewards();
+        mInternalResourceService = irs;
+        mSettingsObserver = new SettingsObserver(TareHandlerThread.getHandler());
+        loadConstants("");
+    }
+
+    @Override
+    void setup() {
+        super.setup();
+        ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+        resolver.registerContentObserver(
+                Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS),
+                false, mSettingsObserver, UserHandle.USER_ALL);
+        loadConstants(Settings.Global.getString(
+                mInternalResourceService.getContext().getContentResolver(),
+                TARE_JOB_SCHEDULER_CONSTANTS));
     }
 
     @Override
     long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
         // TODO: incorporate time since usage
-        return arcToNarc(2000);
+        return mMinSatiatedBalance;
     }
 
     @Override
     long getMaxSatiatedBalance() {
-        return arcToNarc(60000);
+        return mMaxSatiatedBalance;
     }
 
     @Override
     long getMaxSatiatedCirculation() {
-        return arcToNarc(691200);
+        return mMaxSatiatedCirculation;
     }
 
     @NonNull
@@ -93,45 +205,134 @@
         return mRewards.get(rewardId);
     }
 
-    private void loadActions() {
-        mActions.put(ACTION_JOB_MAX_START,
-                new Action(ACTION_JOB_MAX_START, arcToNarc(3), arcToNarc(10)));
-        mActions.put(ACTION_JOB_MAX_RUNNING,
-                new Action(ACTION_JOB_MAX_RUNNING, arcToNarc(2), arcToNarc(5)));
-        mActions.put(ACTION_JOB_HIGH_START,
-                new Action(ACTION_JOB_HIGH_START, arcToNarc(3), arcToNarc(8)));
-        mActions.put(ACTION_JOB_HIGH_RUNNING,
-                new Action(ACTION_JOB_HIGH_RUNNING, arcToNarc(2), arcToNarc(4)));
-        mActions.put(ACTION_JOB_DEFAULT_START,
-                new Action(ACTION_JOB_DEFAULT_START, arcToNarc(3), arcToNarc(6)));
-        mActions.put(ACTION_JOB_DEFAULT_RUNNING,
-                new Action(ACTION_JOB_DEFAULT_RUNNING, arcToNarc(2), arcToNarc(3)));
-        mActions.put(ACTION_JOB_LOW_START,
-                new Action(ACTION_JOB_LOW_START, arcToNarc(3), arcToNarc(4)));
-        mActions.put(ACTION_JOB_LOW_RUNNING,
-                new Action(ACTION_JOB_LOW_RUNNING, arcToNarc(2), arcToNarc(2)));
-        mActions.put(ACTION_JOB_MIN_START,
-                new Action(ACTION_JOB_MIN_START, arcToNarc(3), arcToNarc(2)));
-        mActions.put(ACTION_JOB_MIN_RUNNING,
-                new Action(ACTION_JOB_MIN_RUNNING, arcToNarc(2), arcToNarc(1)));
-        mActions.put(ACTION_JOB_TIMEOUT,
-                new Action(ACTION_JOB_TIMEOUT, arcToNarc(30), arcToNarc(60)));
-    }
+    private void loadConstants(String policyValuesString) {
+        mActions.clear();
+        mRewards.clear();
 
-    private void loadRewards() {
-        mRewards.put(REWARD_TOP_ACTIVITY,
-                new Reward(REWARD_TOP_ACTIVITY,
-                        arcToNarc(0), /* .5 arcs */ arcToNarc(5) / 10, arcToNarc(15000)));
-        mRewards.put(REWARD_NOTIFICATION_SEEN,
-                new Reward(REWARD_NOTIFICATION_SEEN, arcToNarc(1), arcToNarc(0), arcToNarc(10)));
+        try {
+            mParser.setString(policyValuesString);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Global setting key incorrect: ", e);
+        }
+
+        mMinSatiatedBalance = arcToNarc(
+                mParser.getInt(KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP,
+                        DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP));
+        mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_JS_MAX_SATIATED_BALANCE,
+                DEFAULT_JS_MAX_SATIATED_BALANCE));
+        mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_JS_MAX_CIRCULATION,
+                DEFAULT_JS_MAX_CIRCULATION));
+
+        mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_CTP,
+                        DEFAULT_JS_ACTION_JOB_MAX_START_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE))));
+        mActions.put(ACTION_JOB_MAX_RUNNING, new Action(ACTION_JOB_MAX_RUNNING,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MAX_RUNNING_CTP,
+                        DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE))));
+        mActions.put(ACTION_JOB_HIGH_START, new Action(ACTION_JOB_HIGH_START,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_START_CTP,
+                        DEFAULT_JS_ACTION_JOB_HIGH_START_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_START_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE))));
+        mActions.put(ACTION_JOB_HIGH_RUNNING, new Action(ACTION_JOB_HIGH_RUNNING,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_RUNNING_CTP,
+                        DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE))));
+        mActions.put(ACTION_JOB_DEFAULT_START, new Action(ACTION_JOB_DEFAULT_START,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_START_CTP,
+                        DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE))));
+        mActions.put(ACTION_JOB_DEFAULT_RUNNING, new Action(ACTION_JOB_DEFAULT_RUNNING,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP,
+                        DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE))));
+        mActions.put(ACTION_JOB_LOW_START, new Action(ACTION_JOB_LOW_START,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_LOW_START_CTP,
+                        DEFAULT_JS_ACTION_JOB_LOW_START_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_LOW_START_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE))));
+        mActions.put(ACTION_JOB_LOW_RUNNING, new Action(ACTION_JOB_LOW_RUNNING,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_LOW_RUNNING_CTP,
+                        DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE))));
+        mActions.put(ACTION_JOB_MIN_START, new Action(ACTION_JOB_MIN_START,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MIN_START_CTP,
+                        DEFAULT_JS_ACTION_JOB_MIN_START_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE))));
+        mActions.put(ACTION_JOB_MIN_RUNNING, new Action(ACTION_JOB_MIN_RUNNING,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MIN_RUNNING_CTP,
+                        DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE))));
+        mActions.put(ACTION_JOB_TIMEOUT, new Action(ACTION_JOB_TIMEOUT,
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP,
+                        DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP)),
+                arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE,
+                        DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE))));
+
+        mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY,
+                arcToNarc(mParser.getInt(KEY_JS_REWARD_TOP_ACTIVITY_INSTANT,
+                        DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT)),
+                (long) (arcToNarc(1) * mParser.getFloat(KEY_JS_REWARD_TOP_ACTIVITY_ONGOING,
+                        DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING)),
+                arcToNarc(mParser.getInt(KEY_JS_REWARD_TOP_ACTIVITY_MAX,
+                        DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX))));
+        mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN,
+                arcToNarc(mParser.getInt(KEY_JS_REWARD_NOTIFICATION_SEEN_INSTANT,
+                        DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT)),
+                arcToNarc(mParser.getInt(KEY_JS_REWARD_NOTIFICATION_SEEN_ONGOING,
+                        DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING)),
+                arcToNarc(mParser.getInt(KEY_JS_REWARD_NOTIFICATION_SEEN_MAX,
+                        DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX))));
         mRewards.put(REWARD_NOTIFICATION_INTERACTION,
                 new Reward(REWARD_NOTIFICATION_INTERACTION,
-                        arcToNarc(5), arcToNarc(0), arcToNarc(5000)));
-        mRewards.put(REWARD_WIDGET_INTERACTION,
-                new Reward(REWARD_WIDGET_INTERACTION,
-                        arcToNarc(10), arcToNarc(0), arcToNarc(5000)));
+                        arcToNarc(mParser.getInt(
+                                KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT,
+                                DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT)),
+                        arcToNarc(mParser.getInt(
+                                KEY_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING,
+                                DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING)),
+                        arcToNarc(mParser.getInt(
+                                KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX,
+                                DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX))));
+        mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION,
+                arcToNarc(mParser.getInt(KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT,
+                        DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT)),
+                arcToNarc(mParser.getInt(KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING,
+                        DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING)),
+                arcToNarc(mParser.getInt(KEY_JS_REWARD_WIDGET_INTERACTION_MAX,
+                        DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX))));
         mRewards.put(REWARD_OTHER_USER_INTERACTION,
                 new Reward(REWARD_OTHER_USER_INTERACTION,
-                        arcToNarc(10), arcToNarc(0), arcToNarc(5000)));
+                        arcToNarc(mParser.getInt(KEY_JS_REWARD_OTHER_USER_INTERACTION_INSTANT,
+                                DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT)),
+                        arcToNarc(mParser.getInt(
+                                KEY_JS_REWARD_OTHER_USER_INTERACTION_ONGOING,
+                                DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING)),
+                        arcToNarc(mParser.getInt(
+                                KEY_JS_REWARD_OTHER_USER_INTERACTION_MAX,
+                                DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX))));
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            loadConstants(Settings.Global.getString(
+                    mInternalResourceService.getContext().getContentResolver(),
+                    TARE_JOB_SCHEDULER_CONSTANTS));
+        }
     }
 }
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 6e27aff..929e63e 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -107,9 +107,13 @@
 static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
 static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
 static constexpr size_t TEXT_POS_LEN_MAX = 16;
+static const int DYNAMIC_COLOR_COUNT = 4;
 static const char U_TEXTURE[] = "uTexture";
 static const char U_FADE[] = "uFade";
 static const char U_CROP_AREA[] = "uCropArea";
+static const char U_START_COLOR_PREFIX[] = "uStartColor";
+static const char U_END_COLOR_PREFIX[] = "uEndColor";
+static const char U_COLOR_PROGRESS[] = "uColorProgress";
 static const char A_UV[] = "aUv";
 static const char A_POSITION[] = "aPosition";
 static const char VERTEX_SHADER_SOURCE[] = R"(
@@ -121,6 +125,28 @@
         gl_Position = aPosition;
         vUv = aUv;
     })";
+static const char IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE[] = R"(
+    precision mediump float;
+    uniform sampler2D uTexture;
+    uniform float uFade;
+    uniform float uColorProgress;
+    uniform vec4 uStartColor0;
+    uniform vec4 uStartColor1;
+    uniform vec4 uStartColor2;
+    uniform vec4 uStartColor3;
+    uniform vec4 uEndColor0;
+    uniform vec4 uEndColor1;
+    uniform vec4 uEndColor2;
+    uniform vec4 uEndColor3;
+    varying highp vec2 vUv;
+    void main() {
+        vec4 mask = texture2D(uTexture, vUv);
+        vec4 color = mask.r * mix(uStartColor0, uEndColor0, uColorProgress)
+            + mask.g * mix(uStartColor1, uEndColor1, uColorProgress)
+            + mask.b * mix(uStartColor2, uEndColor2, uColorProgress)
+            + mask.a * mix(uStartColor3, uEndColor3, uColorProgress);
+        gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+    })";
 static const char IMAGE_FRAG_SHADER_SOURCE[] = R"(
     precision mediump float;
     uniform sampler2D uTexture;
@@ -128,7 +154,7 @@
     varying highp vec2 vUv;
     void main() {
         vec4 color = texture2D(uTexture, vUv);
-        gl_FragColor = vec4(color.x, color.y, color.z, 1.0 - uFade);
+        gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
     })";
 static const char TEXT_FRAG_SHADER_SOURCE[] = R"(
     precision mediump float;
@@ -226,6 +252,10 @@
     outInfo->stride = AImageDecoder_getMinimumStride(decoder);
     outInfo->flags = 0;
 
+    // Set decoding option to alpha unpremultiplied so that the R, G, B channels
+    // of transparent pixels are preserved.
+    AImageDecoder_setUnpremultipliedRequired(decoder, true);
+
     const size_t size = outInfo->stride * outInfo->height;
     void* pixels = malloc(size);
     int result = AImageDecoder_decodeImage(decoder, pixels, outInfo->stride, size);
@@ -675,9 +705,12 @@
 }
 
 void BootAnimation::initShaders() {
+    bool dynamicColoringEnabled = mAnimation != nullptr && mAnimation->dynamicColoringEnabled;
     GLuint vertexShader = compileShader(GL_VERTEX_SHADER, (const GLchar *)VERTEX_SHADER_SOURCE);
     GLuint imageFragmentShader =
-        compileShader(GL_FRAGMENT_SHADER, (const GLchar *)IMAGE_FRAG_SHADER_SOURCE);
+        compileShader(GL_FRAGMENT_SHADER, dynamicColoringEnabled
+            ? (const GLchar *)IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE
+            : (const GLchar *)IMAGE_FRAG_SHADER_SOURCE);
     GLuint textFragmentShader =
         compileShader(GL_FRAGMENT_SHADER, (const GLchar *)TEXT_FRAG_SHADER_SOURCE);
 
@@ -692,6 +725,22 @@
     glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);
     glEnableVertexAttribArray(uvLocation);
 
+    if (dynamicColoringEnabled) {
+        glUseProgram(mImageShader);
+        SLOGI("[BootAnimation] Dynamically coloring boot animation.");
+        for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+            float *startColor = mAnimation->startColors[i];
+            float *endColor = mAnimation->endColors[i];
+            glUniform4f(glGetUniformLocation(mImageShader,
+                (U_START_COLOR_PREFIX + std::to_string(i)).c_str()),
+                startColor[0], startColor[1], startColor[2], 1 /* alpha */);
+            glUniform4f(glGetUniformLocation(mImageShader,
+                (U_END_COLOR_PREFIX + std::to_string(i)).c_str()),
+                endColor[0], endColor[1], endColor[2], 1 /* alpha */);
+        }
+        mImageColorProgressLocation = glGetUniformLocation(mImageShader, U_COLOR_PROGRESS);
+    }
+
     // Initialize text shader.
     mTextShader = linkShader(vertexShader, textFragmentShader);
     positionLocation = glGetAttribLocation(mTextShader, A_POSITION);
@@ -869,6 +918,20 @@
     return true;
 }
 
+// Parse a color represented as a signed decimal int string.
+// E.g. "-2757722" (whose hex 2's complement is 0xFFD5EBA6).
+// If the input color string is empty, set color with values in defaultColor.
+static void parseColorDecimalString(const std::string& colorString,
+    float color[3], float defaultColor[3]) {
+    if (colorString == "") {
+        memcpy(color, defaultColor, sizeof(float) * 3);
+        return;
+    }
+    int colorInt = atoi(colorString.c_str());
+    color[0] = ((float)((colorInt >> 16) & 0xFF)) / 0xFF; // r
+    color[1] = ((float)((colorInt >> 8) & 0xFF)) / 0xFF; // g
+    color[2] = ((float)(colorInt & 0xFF)) / 0xFF; // b
+}
 
 static bool readFile(ZipFileRO* zip, const char* name, String8& outString) {
     ZipEntryRO entry = zip->findEntryByName(name);
@@ -1010,6 +1073,8 @@
         return false;
     }
     char const* s = desString.string();
+    std::string dynamicColoringPartName = "";
+    bool postDynamicColoring = false;
 
     // Parse the description file
     for (;;) {
@@ -1028,7 +1093,13 @@
         char color[7] = "000000"; // default to black if unspecified
         char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
         char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
+        char dynamicColoringPartNameBuffer[ANIM_ENTRY_NAME_MAX];
         char pathType;
+        // start colors default to black if unspecified
+        char start_color_0[7] = "000000";
+        char start_color_1[7] = "000000";
+        char start_color_2[7] = "000000";
+        char start_color_3[7] = "000000";
 
         int nextReadPos;
 
@@ -1043,6 +1114,15 @@
             } else {
               animation.progressEnabled = false;
             }
+        } else if (sscanf(l, "dynamic_colors %" STRTO(ANIM_PATH_MAX) "s #%6s #%6s #%6s #%6s",
+            dynamicColoringPartNameBuffer,
+            start_color_0, start_color_1, start_color_2, start_color_3)) {
+            animation.dynamicColoringEnabled = true;
+            parseColor(start_color_0, animation.startColors[0]);
+            parseColor(start_color_1, animation.startColors[1]);
+            parseColor(start_color_2, animation.startColors[2]);
+            parseColor(start_color_3, animation.startColors[3]);
+            dynamicColoringPartName = std::string(dynamicColoringPartNameBuffer);
         } else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n",
                           &pathType, &count, &pause, path, &nextReadPos) >= 4) {
             if (pathType == 'f') {
@@ -1055,6 +1135,16 @@
             //       "clockPos1=%s, clockPos2=%s",
             //       pathType, count, pause, path, framesToFadeCount, color, clockPos1, clockPos2);
             Animation::Part part;
+            if (path == dynamicColoringPartName) {
+                // Part is specified to use dynamic coloring.
+                part.useDynamicColoring = true;
+                part.postDynamicColoring = false;
+                postDynamicColoring = true;
+            } else {
+                // Part does not use dynamic coloring.
+                part.useDynamicColoring = false;
+                part.postDynamicColoring =  postDynamicColoring;
+            }
             part.playUntilComplete = pathType == 'c';
             part.framesToFadeCount = framesToFadeCount;
             part.count = count;
@@ -1086,6 +1176,12 @@
         s = ++endl;
     }
 
+    for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+        parseColorDecimalString(
+            android::base::GetProperty("persist.bootanim.color" + std::to_string(i + 1), ""),
+            animation.endColors[i], animation.startColors[i]);
+    }
+
     return true;
 }
 
@@ -1357,6 +1453,14 @@
             for (size_t j=0 ; j<fcount ; j++) {
                 if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;
 
+                // Color progress is
+                // - the normalized animation progress between [0, 1] for the dynamic coloring part,
+                // - 0 for parts that come before,
+                // - 1 for parts that come after.
+                float colorProgress = part.useDynamicColoring
+                    ? (float)j / fcount
+                    : (part.postDynamicColoring ? 1 : 0);
+
                 processDisplayEvents();
 
                 const int animationX = (mWidth - animation.width) / 2;
@@ -1376,19 +1480,7 @@
 
                 const int xc = animationX + frame.trimX;
                 const int yc = animationY + frame.trimY;
-                Region clearReg(Rect(mWidth, mHeight));
-                clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
-                if (!clearReg.isEmpty()) {
-                    Region::const_iterator head(clearReg.begin());
-                    Region::const_iterator tail(clearReg.end());
-                    glEnable(GL_SCISSOR_TEST);
-                    while (head != tail) {
-                        const Rect& r2(*head++);
-                        glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
-                        glClear(GL_COLOR_BUFFER_BIT);
-                    }
-                    glDisable(GL_SCISSOR_TEST);
-                }
+                glClear(GL_COLOR_BUFFER_BIT);
                 // specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
                 // which is equivalent to mHeight - (yc + frame.trimHeight)
                 const int frameDrawY = mHeight - (yc + frame.trimHeight);
@@ -1404,6 +1496,9 @@
                 glUseProgram(mImageShader);
                 glUniform1i(mImageTextureLocation, 0);
                 glUniform1f(mImageFadeLocation, fade);
+                if (animation.dynamicColoringEnabled) {
+                    glUniform1f(mImageColorProgressLocation, colorProgress);
+                }
                 glEnable(GL_BLEND);
                 drawTexturedQuad(xc, frameDrawY, frame.trimWidth, frame.trimHeight);
                 glDisable(GL_BLEND);
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 7b616d9..2c861eb 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -90,6 +90,10 @@
             uint8_t* audioData;
             int audioLength;
             Animation* animation;
+            // Controls if dynamic coloring is enabled for this part.
+            bool useDynamicColoring = false;
+            // Defines if this part is played after the dynamic coloring part.
+            bool postDynamicColoring = false;
 
             bool hasFadingPhase() const {
                 return !playUntilComplete && framesToFadeCount > 0;
@@ -105,6 +109,10 @@
         ZipFileRO* zip;
         Font clockFont;
         Font progressFont;
+         // Controls if dynamic coloring is enabled for the whole animation.
+        bool dynamicColoringEnabled = false;
+        float startColors[4][3]; // Start colors of dynamic color transition.
+        float endColors[4][3];   // End colors of dynamic color transition.
     };
 
     // All callbacks will be called from this class's internal thread.
@@ -226,6 +234,7 @@
     GLuint mImageTextureLocation;
     GLuint mTextCropAreaLocation;
     GLuint mTextTextureLocation;
+    GLuint mImageColorProgressLocation;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ae812ec..bff1e57 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1105,18 +1105,17 @@
                 IUiAutomationConnection instrumentationUiConnection, int debugMode,
                 boolean enableBinderTracking, boolean trackAllocation,
                 boolean isRestrictedBackupMode, boolean persistent, Configuration config,
-                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
+                CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings,
                 String buildSerial, AutofillOptions autofillOptions,
                 ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges,
                 SharedMemory serializedSystemFontMap) {
             if (services != null) {
                 if (false) {
                     // Test code to make sure the app could see the passed-in services.
-                    for (Object oname : services.keySet()) {
-                        if (services.get(oname) == null) {
+                    for (String name : services.keySet()) {
+                        if (services.get(name) == null) {
                             continue; // AM just passed in a null service.
                         }
-                        String name = (String) oname;
 
                         // See b/79378449 about the following exemption.
                         switch (name) {
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index d6ff6d3..2afd98e 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -77,7 +77,7 @@
             IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection,
             int debugMode, boolean enableBinderTracking, boolean trackAllocation,
             boolean restrictedBackupMode, boolean persistent, in Configuration config,
-            in CompatibilityInfo compatInfo, in Map services,
+            in CompatibilityInfo compatInfo, in Map<String, IBinder> services,
             in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions,
             in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges,
             in SharedMemory serializedSystemFontMap);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4b401a5..7102314 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3270,22 +3270,6 @@
     }
 
     /**
-     * Returns true if the Profile Challenge is available to use for the given profile user.
-     *
-     * @hide
-     */
-    public boolean isSeparateProfileChallengeAllowed(int userHandle) {
-        if (mService != null) {
-            try {
-                return mService.isSeparateProfileChallengeAllowed(userHandle);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return false;
-    }
-
-    /**
      * Constant for {@link #setPasswordQuality}: the policy has no requirements
      * for the password.  Note that quality constants are ordered so that higher
      * values are more restrictive.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index b6c48a1..8cf1f80 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -373,8 +373,6 @@
     CharSequence getShortSupportMessageForUser(in ComponentName admin, int userHandle);
     CharSequence getLongSupportMessageForUser(in ComponentName admin, int userHandle);
 
-    boolean isSeparateProfileChallengeAllowed(int userHandle);
-
     void setOrganizationColor(in ComponentName admin, in int color);
     void setOrganizationColorForUser(in int color, in int userId);
     int getOrganizationColor(in ComponentName admin);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 2ed26a9f..2b28c11 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -64,6 +64,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -78,6 +80,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -715,10 +718,21 @@
     private final IBluetoothManager mManagerService;
     private final AttributionSource mAttributionSource;
 
+    // Yeah, keeping both mService and sService isn't pretty, but it's too late
+    // in the current release for a major refactoring, so we leave them both
+    // intact until this can be cleaned up in a future release
+
     @UnsupportedAppUsage
+    @GuardedBy("mServiceLock")
     private IBluetooth mService;
     private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock();
 
+    @GuardedBy("sServiceLock")
+    private static boolean sServiceRegistered;
+    @GuardedBy("sServiceLock")
+    private static IBluetooth sService;
+    private static final Object sServiceLock = new Object();
+
     private final Object mLock = new Object();
     private final Map<LeScanCallback, ScanCallback> mLeScanClients;
     private final Map<BluetoothDevice, List<Pair<OnMetadataChangedListener, Executor>>>
@@ -792,19 +806,11 @@
      * Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance.
      */
     BluetoothAdapter(IBluetoothManager managerService, AttributionSource attributionSource) {
-        if (managerService == null) {
-            throw new IllegalArgumentException("bluetooth manager service is null");
-        }
-        try {
-            mServiceLock.writeLock().lock();
-            mService = managerService.registerAdapter(mManagerCallback);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-        } finally {
-            mServiceLock.writeLock().unlock();
-        }
         mManagerService = Objects.requireNonNull(managerService);
         mAttributionSource = Objects.requireNonNull(attributionSource);
+        synchronized (mServiceLock.writeLock()) {
+            mService = getBluetoothService(mManagerCallback);
+        }
         mLeScanClients = new HashMap<LeScanCallback, ScanCallback>();
         mToken = new Binder(DESCRIPTOR);
     }
@@ -3162,21 +3168,16 @@
         }
     }
 
-    @SuppressLint("AndroidFrameworkBluetoothPermission")
-    private final IBluetoothManagerCallback mManagerCallback =
+    private static final IBluetoothManagerCallback sManagerCallback =
             new IBluetoothManagerCallback.Stub() {
-                @SuppressLint("AndroidFrameworkRequiresPermission")
                 public void onBluetoothServiceUp(IBluetooth bluetoothService) {
                     if (DBG) {
                         Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService);
                     }
 
-                    mServiceLock.writeLock().lock();
-                    mService = bluetoothService;
-                    mServiceLock.writeLock().unlock();
-
-                    synchronized (mProxyServiceStateCallbacks) {
-                        for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks) {
+                    synchronized (sServiceLock) {
+                        sService = bluetoothService;
+                        for (IBluetoothManagerCallback cb : sProxyServiceStateCallbacks.keySet()) {
                             try {
                                 if (cb != null) {
                                     cb.onBluetoothServiceUp(bluetoothService);
@@ -3188,6 +3189,56 @@
                             }
                         }
                     }
+                }
+
+                public void onBluetoothServiceDown() {
+                    if (DBG) {
+                        Log.d(TAG, "onBluetoothServiceDown");
+                    }
+
+                    synchronized (sServiceLock) {
+                        sService = null;
+                        for (IBluetoothManagerCallback cb : sProxyServiceStateCallbacks.keySet()) {
+                            try {
+                                if (cb != null) {
+                                    cb.onBluetoothServiceDown();
+                                } else {
+                                    Log.d(TAG, "onBluetoothServiceDown: cb is null!");
+                                }
+                            } catch (Exception e) {
+                                Log.e(TAG, "", e);
+                            }
+                        }
+                    }
+                }
+
+                public void onBrEdrDown() {
+                    if (VDBG) {
+                        Log.i(TAG, "onBrEdrDown");
+                    }
+
+                    synchronized (sServiceLock) {
+                        for (IBluetoothManagerCallback cb : sProxyServiceStateCallbacks.keySet()) {
+                            try {
+                                if (cb != null) {
+                                    cb.onBrEdrDown();
+                                } else {
+                                    Log.d(TAG, "onBrEdrDown: cb is null!");
+                                }
+                            } catch (Exception e) {
+                                Log.e(TAG, "", e);
+                            }
+                        }
+                    }
+                }
+            };
+
+    private final IBluetoothManagerCallback mManagerCallback =
+            new IBluetoothManagerCallback.Stub() {
+                public void onBluetoothServiceUp(IBluetooth bluetoothService) {
+                    synchronized (mServiceLock.writeLock()) {
+                        mService = bluetoothService;
+                    }
                     synchronized (mMetadataListeners) {
                         mMetadataListeners.forEach((device, pair) -> {
                             try {
@@ -3212,12 +3263,7 @@
                 }
 
                 public void onBluetoothServiceDown() {
-                    if (DBG) {
-                        Log.d(TAG, "onBluetoothServiceDown: " + mService);
-                    }
-
-                    try {
-                        mServiceLock.writeLock().lock();
+                    synchronized (mServiceLock.writeLock()) {
                         mService = null;
                         if (mLeScanClients != null) {
                             mLeScanClients.clear();
@@ -3228,29 +3274,10 @@
                         if (mBluetoothLeScanner != null) {
                             mBluetoothLeScanner.cleanup();
                         }
-                    } finally {
-                        mServiceLock.writeLock().unlock();
-                    }
-
-                    synchronized (mProxyServiceStateCallbacks) {
-                        for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks) {
-                            try {
-                                if (cb != null) {
-                                    cb.onBluetoothServiceDown();
-                                } else {
-                                    Log.d(TAG, "onBluetoothServiceDown: cb is null!");
-                                }
-                            } catch (Exception e) {
-                                Log.e(TAG, "", e);
-                            }
-                        }
                     }
                 }
 
                 public void onBrEdrDown() {
-                    if (VDBG) {
-                        Log.i(TAG, "onBrEdrDown: " + mService);
-                    }
                 }
             };
 
@@ -3485,15 +3512,12 @@
 
     protected void finalize() throws Throwable {
         try {
-            mManagerService.unregisterAdapter(mManagerCallback);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
+            removeServiceStateCallback(mManagerCallback);
         } finally {
             super.finalize();
         }
     }
 
-
     /**
      * Validate a String Bluetooth address, such as "00:43:A8:23:10:F0"
      * <p>Alphabetic characters must be uppercase to be valid.
@@ -3557,24 +3581,64 @@
         return mAttributionSource;
     }
 
-    private final ArrayList<IBluetoothManagerCallback> mProxyServiceStateCallbacks =
-            new ArrayList<IBluetoothManagerCallback>();
+    @GuardedBy("sServiceLock")
+    private static final WeakHashMap<IBluetoothManagerCallback, Void> sProxyServiceStateCallbacks =
+            new WeakHashMap<>();
+
+    /*package*/ IBluetooth getBluetoothService() {
+        synchronized (sServiceLock) {
+            if (sProxyServiceStateCallbacks.isEmpty()) {
+                throw new IllegalStateException(
+                        "Anonymous service access requires at least one lifecycle in process");
+            }
+            return sService;
+        }
+    }
 
     @UnsupportedAppUsage
     /*package*/ IBluetooth getBluetoothService(IBluetoothManagerCallback cb) {
-        synchronized (mProxyServiceStateCallbacks) {
-            if (cb == null) {
-                Log.w(TAG, "getBluetoothService() called with no BluetoothManagerCallback");
-            } else if (!mProxyServiceStateCallbacks.contains(cb)) {
-                mProxyServiceStateCallbacks.add(cb);
-            }
+        Objects.requireNonNull(cb);
+        synchronized (sServiceLock) {
+            sProxyServiceStateCallbacks.put(cb, null);
+            registerOrUnregisterAdapterLocked();
+            return sService;
         }
-        return mService;
     }
 
     /*package*/ void removeServiceStateCallback(IBluetoothManagerCallback cb) {
-        synchronized (mProxyServiceStateCallbacks) {
-            mProxyServiceStateCallbacks.remove(cb);
+        Objects.requireNonNull(cb);
+        synchronized (sServiceLock) {
+            sProxyServiceStateCallbacks.remove(cb);
+            registerOrUnregisterAdapterLocked();
+        }
+    }
+
+    /**
+     * Handle registering (or unregistering) a single process-wide
+     * {@link IBluetoothManagerCallback} based on the presence of local
+     * {@link #sProxyServiceStateCallbacks} clients.
+     */
+    @GuardedBy("sServiceLock")
+    private void registerOrUnregisterAdapterLocked() {
+        final boolean isRegistered = sServiceRegistered;
+        final boolean wantRegistered = !sProxyServiceStateCallbacks.isEmpty();
+
+        if (isRegistered != wantRegistered) {
+            if (wantRegistered) {
+                try {
+                    sService = mManagerService.registerAdapter(sManagerCallback);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            } else {
+                try {
+                    mManagerService.unregisterAdapter(sManagerCallback);
+                    sService = null;
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            sServiceRegistered = wantRegistered;
         }
     }
 
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index bb409d5..1655b62 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -399,7 +399,7 @@
         try {
             if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
             IBluetooth bluetoothProxy =
-                    BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+                    BluetoothAdapter.getDefaultAdapter().getBluetoothService();
             if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
             mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType,
                     mUuid, mPort, getSecurityFlags());
@@ -438,7 +438,7 @@
     /*package*/ int bindListen() {
         int ret;
         if (mSocketState == SocketState.CLOSED) return EBADFD;
-        IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+        IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService();
         if (bluetoothProxy == null) {
             Log.e(TAG, "bindListen fail, reason: bluetooth is off");
             return -1;
@@ -706,7 +706,7 @@
                 throw new IOException("socket closed");
             }
             IBluetooth bluetoothProxy =
-                    BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+                    BluetoothAdapter.getDefaultAdapter().getBluetoothService();
             if (bluetoothProxy == null) {
                 throw new IOException("Bluetooth is off");
             }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a5cd331..19db0ba 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1077,8 +1077,7 @@
     public static final int ROLLBACK_DATA_POLICY_WIPE = 1;
 
     /**
-     * User data won't be backed up during install and won't be restored during rollback.
-     * TODO: Not implemented yet.
+     * User data won't be backed up during install and will remain unchanged during rollback.
      *
      * @hide
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4b6b876..57fd736 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -16910,6 +16910,14 @@
              * @hide
              */
             public static final String COMBINED_LOCATION_ENABLED = "combined_location_enable";
+
+            /**
+             * The wrist orientation mode of the device
+             * Valid values - LEFT_WRIST_ROTATION_0 = "0" (default), LEFT_WRIST_ROTATION_180 = "1",
+             *          RIGHT_WRIST_ROTATION_0 = "2", RIGHT_WRIST_ROTATION_180 = "3"
+             * @hide
+             */
+            public static final String WRIST_ORIENTATION_MODE = "wear_wrist_orientation_mode";
         }
     }
 
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index c16ee42..7d2cb50 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -789,6 +789,9 @@
             } else {
                 mDisplayResolveInfo.setDisplayIcon(d);
                 mHolder.bindIcon(mDisplayResolveInfo);
+                // Notify in case view is already bound to resolve the race conditions on
+                // low end devices
+                notifyDataSetChanged();
             }
         }
 
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 10750b6..0c56c67 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -64,8 +64,7 @@
     public static final String LIB_DIR_NAME = "lib";
     public static final String LIB64_DIR_NAME = "lib64";
 
-    // Special value for {@code PackageParser.Package#cpuAbiOverride} to indicate
-    // that the cpuAbiOverride must be clear.
+    // Special value for indicating that the cpuAbiOverride must be clear.
     public static final String CLEAR_ABI_OVERRIDE = "-";
 
     /**
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 498505c..dbf4528 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -846,14 +846,6 @@
         return isManagedProfile(userHandle) && !hasSeparateChallenge(userHandle);
     }
 
-    /**
-     * Retrieves whether the current DPM allows use of the Profile Challenge.
-     */
-    public boolean isSeparateProfileChallengeAllowed(int userHandle) {
-        return isManagedProfile(userHandle)
-                && getDevicePolicyManager().isSeparateProfileChallengeAllowed(userHandle);
-    }
-
     private boolean hasSeparateChallenge(int userHandle) {
         try {
             return getLockSettings().getSeparateProfileChallengeEnabled(userHandle);
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index d1c4b34..a90a38b 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -641,7 +641,7 @@
 
     if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0) {
         sp<MediaEvent> mediaEventSp =
-                new MediaEvent(mFilterClient, makeFromAidl(mediaEvent.avMemory),
+                new MediaEvent(mFilterClient, dupFromAidl(mediaEvent.avMemory),
                                mediaEvent.avDataId, dataLength + offset, obj);
         mediaEventSp->mAvHandleRefCnt++;
         env->SetLongField(obj, eventContext, (jlong)mediaEventSp.get());
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index eea27ea..d4d8837 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -160,7 +160,7 @@
 
 Result FilterClient::releaseAvHandle(native_handle_t* handle, uint64_t avDataId) {
     if (mTunerFilter != nullptr) {
-        Status s = mTunerFilter->releaseAvHandle(makeToAidl(handle), avDataId);
+        Status s = mTunerFilter->releaseAvHandle(dupToAidl(handle), avDataId);
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
@@ -308,7 +308,7 @@
         NativeHandle avMemory;
         Status s = mTunerFilter->getAvSharedHandle(&avMemory, &size);
         if (s.isOk()) {
-            mAvSharedHandle = native_handle_clone(makeFromAidl(avMemory));
+            mAvSharedHandle = dupFromAidl(avMemory);
             mAvSharedMemSize = size;
         }
     }
diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml
index 99efbde..b1cba1a 100644
--- a/packages/SettingsLib/res/values-te/arrays.xml
+++ b/packages/SettingsLib/res/values-te/arrays.xml
@@ -138,15 +138,15 @@
     <item msgid="1333279807604675720">"స్టీరియో"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
-    <item msgid="1241278021345116816">"ఆడియో నాణ్యత (990kbps/909kbps) కోసం అనుకూలీకరించబడింది"</item>
-    <item msgid="3523665555859696539">"సమతుల్య ఆడియో మరియు కనెక్షన్ నాణ్యత (660kbps/606kbps)"</item>
-    <item msgid="886408010459747589">"కనెక్షన్ నాణ్యత (330kbps/303kbps) కోసం అనుకూలీకరించబడింది"</item>
+    <item msgid="1241278021345116816">"ఆడియో క్వాలిటీ (990kbps/909kbps) కోసం అనుకూలీకరించబడింది"</item>
+    <item msgid="3523665555859696539">"సమతుల్య ఆడియో మరియు కనెక్షన్ క్వాలిటీ (660kbps/606kbps)"</item>
+    <item msgid="886408010459747589">"కనెక్షన్ క్వాలిటీ (330kbps/303kbps) కోసం అనుకూలీకరించబడింది"</item>
     <item msgid="3808414041654351577">"ఉత్తమ కృషి (అనుకూల బిట్ రేట్)"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
-    <item msgid="804499336721569838">"ఆడియో నాణ్యత కోసం అనుకూలీకరించబడింది"</item>
-    <item msgid="7451422070435297462">"సమతుల్య ఆడియో మరియు కనెక్షన్ నాణ్యత"</item>
-    <item msgid="6173114545795428901">"కనెక్షన్ నాణ్యత కోసం అనుకూలీకరించబడింది"</item>
+    <item msgid="804499336721569838">"ఆడియో క్వాలిటీ కోసం అనుకూలీకరించబడింది"</item>
+    <item msgid="7451422070435297462">"సమతుల్య ఆడియో మరియు కనెక్షన్ క్వాలిటీ"</item>
+    <item msgid="6173114545795428901">"కనెక్షన్ క్వాలిటీ కోసం అనుకూలీకరించబడింది"</item>
     <item msgid="4349908264188040530">"ఉత్తమ కృషి (అనుకూల బిట్ రేట్)"</item>
   </string-array>
   <string-array name="bluetooth_audio_active_device_summaries">
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 7ee8353..54e8fc8 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -90,7 +90,7 @@
     <string name="bluetooth_profile_pbap" msgid="7064307749579335765">"కాంటాక్ట్ షేరింగ్"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="2955819694801952056">"పరిచయ భాగస్వామ్యం కోసం ఉపయోగించు"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ఇంటర్నెట్ కనెక్షన్ భాగస్వామ్యం"</string>
-    <string name="bluetooth_profile_map" msgid="8907204701162107271">"వచన సందేశాలు"</string>
+    <string name="bluetooth_profile_map" msgid="8907204701162107271">"వచన మెసేజ్‌లు"</string>
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM యాక్సెస్"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ఆడియో: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ఆడియో"</string>
@@ -116,7 +116,7 @@
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"జత చేయి"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"జత చేయి"</string>
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"రద్దు చేయి"</string>
-    <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"జత చేయడం వలన కనెక్ట్ చేయబడినప్పుడు మీ పరిచయాలకు మరియు కాల్ చరిత్రకు ప్రాప్యతను మంజూరు చేస్తుంది."</string>
+    <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"జత చేయడం వలన కనెక్ట్ చేయబడినప్పుడు మీ పరిచయాలకు మరియు కాల్ చరిత్రకు యాక్సెస్‌ను మంజూరు చేస్తుంది."</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>తో జత చేయడం సాధ్యపడలేదు."</string>
     <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"పిన్ లేదా పాస్‌కీ చెల్లని కారణంగా <xliff:g id="DEVICE_NAME">%1$s</xliff:g>తో పెయిర్ చేయడం సాధ్యపడలేదు."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>తో కమ్యూనికేట్ చేయడం సాధ్యపడదు."</string>
@@ -199,7 +199,7 @@
     <string name="category_work" msgid="4014193632325996115">"ఆఫీస్"</string>
     <string name="development_settings_title" msgid="140296922921597393">"డెవలపర్ ఆప్షన్‌లు"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"డెవలపర్ ఎంపికలను ప్రారంభించండి"</string>
-    <string name="development_settings_summary" msgid="8718917813868735095">"అనువర్తన అభివృద్ధి కోసం ఎంపికలను సెట్ చేయండి"</string>
+    <string name="development_settings_summary" msgid="8718917813868735095">"యాప్‌ అభివృద్ధి కోసం ఎంపికలను సెట్ చేయండి"</string>
     <string name="development_settings_not_available" msgid="355070198089140951">"ఈ వినియోగదారు కోసం డెవలపర్ ఎంపికలు అందుబాటులో లేవు"</string>
     <string name="vpn_settings_not_available" msgid="2894137119965668920">"VPN సెట్టింగ్‌లు ఈ వినియోగదారుకి అందుబాటులో లేవు"</string>
     <string name="tethering_settings_not_available" msgid="266821736434699780">"టీథరింగ్ సెట్టింగ్‌లు ఈ వినియోగదారుకి అందుబాటులో లేవు"</string>
@@ -227,12 +227,12 @@
     <string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi‑Fi పెయిరింగ్ కోడ్"</string>
     <string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"పెయిరింగ్ విఫలమైంది"</string>
     <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"పరికరం అదే నెట్‌వర్క్‌కు కనెక్ట్ చేయబడి ఉందని నిర్ధారించుకోండి."</string>
-    <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"QR కోడ్‌ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చెయ్యండి"</string>
+    <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"QR కోడ్‌ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చేయండి"</string>
     <string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"పరికరం పెయిర్ చేయబడుతోంది…"</string>
     <string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"పరికరాన్ని పెయిర్ చేయడం విఫలమైంది. QR కోడ్ తప్పుగా ఉండడం గాని, లేదా పరికరం అదే నెట్‌వర్క్‌కు కనెక్ట్ అయి లేకపోవడం గాని జరిగింది."</string>
     <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP అడ్రస్ &amp; పోర్ట్"</string>
     <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"QR కోడ్‌ను స్కాన్ చేయండి"</string>
-    <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"QR కోడ్‌ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చెయ్యండి"</string>
+    <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"QR కోడ్‌ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చేయండి"</string>
     <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"దయచేసి Wi-Fi నెట్‌వర్క్‌కు కనెక్ట్ చేయండి"</string>
     <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, డీబగ్, dev"</string>
     <string name="bugreport_in_power" msgid="8664089072534638709">"బగ్ రిపోర్ట్ షార్ట్‌కట్"</string>
@@ -271,8 +271,8 @@
     <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"బ్లూటూత్ ఆడియో కోడెక్‌ని సక్రియం చేయండి\nఎంపిక: ఒక్కో నమూనాలో బిట్‌లు"</string>
     <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"బ్లూటూత్ ఆడియో ఛానెల్ మోడ్"</string>
     <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"బ్లూటూత్ ఆడియో కోడెక్‌ని సక్రియం చేయండి\nఎంపిక: ఛానెల్ మోడ్"</string>
-    <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"బ్లూటూత్ ఆడియో LDAC కోడెక్: ప్లేబ్యాక్ నాణ్యత"</string>
-    <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"బ్లూటూత్ ఆడియో LDAC యాక్టివ్ చేయండి\nకోడెక్ ఎంపిక: ప్లేబ్యాక్ నాణ్యత"</string>
+    <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"బ్లూటూత్ ఆడియో LDAC కోడెక్: ప్లేబ్యాక్ క్వాలిటీ"</string>
+    <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"బ్లూటూత్ ఆడియో LDAC యాక్టివ్ చేయండి\nకోడెక్ ఎంపిక: ప్లేబ్యాక్ క్వాలిటీ"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"ప్రసారం చేస్తోంది: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
     <string name="select_private_dns_configuration_title" msgid="7887550926056143018">"ప్రైవేట్ DNS"</string>
     <string name="select_private_dns_configuration_dialog_title" msgid="3731422918335951912">"ప్రైవేట్ DNS మోడ్‌ను ఎంచుకోండి"</string>
@@ -304,7 +304,7 @@
     <string name="adb_warning_message" msgid="8145270656419669221">"USB డీబగ్గింగ్ అనేది అభివృద్ధి ప్రయోజనాల కోసం మాత్రమే ఉద్దేశించబడింది. మీ కంప్యూటర్ మరియు మీ పరికరం మధ్య డేటాను కాపీ చేయడానికి, నోటిఫికేషన్ లేకుండా మీ పరికరంలో యాప్‌లను ఇన్‌స్టాల్ చేయడానికి మరియు లాగ్ డేటాను చదవడానికి దీన్ని ఉపయోగించండి."</string>
     <string name="adbwifi_warning_title" msgid="727104571653031865">"వైర్‌లెస్ డీబగ్గింగ్‌ను అనుమతించాలా?"</string>
     <string name="adbwifi_warning_message" msgid="8005936574322702388">"వైర్‌లెస్ డీబగ్గింగ్ అనేది అభివృద్ధి ప్రయోజనాల కోసం మాత్రమే ఉద్దేశించబడింది. మీ కంప్యూటర్, పరికరాల మధ్య డేటాను కాపీ చేయడానికి, నోటిఫికేషన్ లేకుండా మీ పరికరంలో యాప్‌లను ఇన్‌స్టాల్ చేయడానికి, లాగ్ డేటాను చదవడానికి దీన్ని ఉపయోగించండి."</string>
-    <string name="adb_keys_warning_message" msgid="2968555274488101220">"మీరు గతంలో ప్రామాణీకరించిన అన్ని కంప్యూటర్‌ల నుండి USB డీబగ్గింగ్‌కు ప్రాప్యతను ఉపసంహరించాలా?"</string>
+    <string name="adb_keys_warning_message" msgid="2968555274488101220">"మీరు గతంలో ప్రామాణీకరించిన అన్ని కంప్యూటర్‌ల నుండి USB డీబగ్గింగ్‌కు యాక్సెస్‌ను ఉపసంహరించాలా?"</string>
     <string name="dev_settings_warning_title" msgid="8251234890169074553">"అభివృద్ధి సెట్టింగ్‌లను అనుమతించాలా?"</string>
     <string name="dev_settings_warning_message" msgid="37741686486073668">"ఈ సెట్టింగ్‌లు అభివృద్ధి వినియోగం కోసం మాత్రమే ఉద్దేశించబడినవి. వీటి వలన మీ పరికరం మరియు దీనిలోని యాప్‌లు విచ్ఛిన్నం కావచ్చు లేదా తప్పుగా ప్రవర్తించవచ్చు."</string>
     <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"USB ద్వారా యాప్‌లను వెరిఫై చేయి"</string>
@@ -314,7 +314,7 @@
     <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"బ్లూటూత్ Gabeldorsche ఫీచర్ స్ట్యాక్‌ను ఎనేబుల్ చేస్తుంది."</string>
     <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"మెరుగైన కనెక్టివిటీ ఫీచర్‌ను ఎనేబుల్ చేస్తుంది."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"స్థానిక టెర్మినల్"</string>
-    <string name="enable_terminal_summary" msgid="2481074834856064500">"స్థానిక షెల్ ప్రాప్యతను అందించే టెర్మినల్ యాప్‌ను ప్రారంభించు"</string>
+    <string name="enable_terminal_summary" msgid="2481074834856064500">"స్థానిక షెల్ యాక్సెస్‌ను అందించే టెర్మినల్ యాప్‌ను ప్రారంభించు"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP చెకింగ్‌"</string>
     <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"HDCP తనిఖీ ప్రవర్తనను సెట్ చేయండి"</string>
     <string name="debug_debugging_category" msgid="535341063709248842">"డీబగ్గింగ్"</string>
@@ -383,7 +383,7 @@
     <string name="local_backup_password_title" msgid="4631017948933578709">"డెస్క్‌టాప్ బ్యాకప్ పాస్‌వర్డ్"</string>
     <string name="local_backup_password_summary_none" msgid="7646898032616361714">"డెస్క్‌టాప్ పూర్తి బ్యాకప్‌లు ప్రస్తుతం రక్షించబడలేదు"</string>
     <string name="local_backup_password_summary_change" msgid="1707357670383995567">"డెస్క్‌టాప్ పూర్తి బ్యాకప్‌ల కోసం పాస్‌వర్డ్‌ను మార్చడానికి లేదా తీసివేయడానికి నొక్కండి"</string>
-    <string name="local_backup_password_toast_success" msgid="4891666204428091604">"కొత్త బ్యాకప్ పాస్‌వర్డ్‌ను సెట్ చేసారు"</string>
+    <string name="local_backup_password_toast_success" msgid="4891666204428091604">"కొత్త బ్యాకప్ పాస్‌వర్డ్‌ను సెట్ చేశారు"</string>
     <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"కొత్త పాస్‌వర్డ్ మరియు నిర్ధారణ సరిపోలడం లేదు"</string>
     <string name="local_backup_password_toast_validation_failure" msgid="714669442363647122">"బ్యాకప్ పాస్‌వర్డ్‌ను సెట్ చేయడంలో వైఫల్యం"</string>
     <string name="loading_injected_setting_summary" msgid="8394446285689070348">"లోడ్ చేస్తోంది…"</string>
@@ -408,7 +408,7 @@
     <string name="transcode_notification" msgid="5560515979793436168">"ట్రాన్స్‌కోడింగ్ నోటిఫికేషన్‌లను చూపండి"</string>
     <string name="transcode_disable_cache" msgid="3160069309377467045">"ట్రాన్స్‌కోడింగ్ కాష్‌ను డిజేబుల్ చేయండి"</string>
     <string name="runningservices_settings_title" msgid="6460099290493086515">"అమలులో ఉన్న సర్వీస్‌లు"</string>
-    <string name="runningservices_settings_summary" msgid="1046080643262665743">"ప్రస్తుతం అమలులో ఉన్న సర్వీస్‌లను వీక్షించండి, కంట్రోల్‌ చేయండి"</string>
+    <string name="runningservices_settings_summary" msgid="1046080643262665743">"ప్రస్తుతం అమలులో ఉన్న సర్వీస్‌లను చూడండి, కంట్రోల్‌ చేయండి"</string>
     <string name="select_webview_provider_title" msgid="3917815648099445503">"వెబ్ వీక్షణ అమలు"</string>
     <string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"వెబ్ వీక్షణ అమలుని సెట్ చేయండి"</string>
     <string name="select_webview_provider_toast_text" msgid="8512254949169359848">"ఈ ఎంపిక ఇప్పుడు లేదు. మళ్లీ ప్రయత్నించండి."</string>
@@ -529,7 +529,7 @@
     <string name="help_label" msgid="3528360748637781274">"సహాయం &amp; ఫీడ్‌బ్యాక్"</string>
     <string name="storage_category" msgid="2287342585424631813">"స్టోరేజ్"</string>
     <string name="shared_data_title" msgid="1017034836800864953">"షేర్ చేసిన డేటా"</string>
-    <string name="shared_data_summary" msgid="5516326713822885652">"షేర్ చేసిన డేటాను చూసి, సవరించండి"</string>
+    <string name="shared_data_summary" msgid="5516326713822885652">"షేర్ చేసిన డేటాను చూసి, ఎడిట్ చేయండి"</string>
     <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"ఈ యూజర్ కోసం షేర్ చేసిన డేటా ఏదీ లేదు."</string>
     <string name="shared_data_query_failure_text" msgid="3489828881998773687">"షేర్ చేసిన డేటా పొందడంలో ఎర్రర్ ఏర్పడింది. మళ్లీ ట్రై చేయండి."</string>
     <string name="blob_id_text" msgid="8680078988996308061">"షేర్ చేసిన డేటా ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string>
@@ -542,7 +542,7 @@
     <string name="delete_blob_text" msgid="2819192607255625697">"షేర్ చేసిన డేటాను తొలగించు"</string>
     <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"మీరు ఖచ్చితంగా ఈ షేర్ చేసిన డేటాను తొలగించాలనుకుంటున్నారా?"</string>
     <string name="user_add_user_item_summary" msgid="5748424612724703400">"వినియోగదారులు వారి స్వంత యాప్‌లను మరియు కంటెంట్‌ను కలిగి ఉన్నారు"</string>
-    <string name="user_add_profile_item_summary" msgid="5418602404308968028">"మీరు మీ ఖాతా నుండి యాప్‌లకు మరియు కంటెంట్‌కు ప్రాప్యతను పరిమితం చేయవచ్చు"</string>
+    <string name="user_add_profile_item_summary" msgid="5418602404308968028">"మీరు మీ ఖాతా నుండి యాప్‌లకు మరియు కంటెంట్‌కు యాక్సెస్‌ను పరిమితం చేయవచ్చు"</string>
     <string name="user_add_user_item_title" msgid="2394272381086965029">"యూజర్"</string>
     <string name="user_add_profile_item_title" msgid="3111051717414643029">"పరిమితం చేయబడిన ప్రొఫైల్"</string>
     <string name="user_add_user_title" msgid="5457079143694924885">"కొత్త వినియోగదారుని జోడించాలా?"</string>
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index fe48575..3ff1e48 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -317,6 +317,8 @@
                 NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.BURN_IN_PROTECTION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.COMBINED_LOCATION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.WRIST_ORIENTATION_MODE,
+                       new DiscreteValueValidator(new String[] {"0", "1", "2", "3"}));
     }
 }
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 61df530..4f27de1 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -666,7 +666,8 @@
                     Settings.Global.Wearable.MASTER_GESTURES_ENABLED,
                     Settings.Global.Wearable.UNGAZE_ENABLED,
                     Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MS,
-                    Settings.Global.Wearable.BURN_IN_PROTECTION_ENABLED);
+                    Settings.Global.Wearable.BURN_IN_PROTECTION_ENABLED,
+                    Settings.Global.Wearable.WRIST_ORIENTATION_MODE);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index ea769c6..871b1c4 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -62,8 +62,8 @@
         <item name="android:src">@drawable/ic_backspace_24dp</item>
     </style>
     <style name="NumPadKey.Enter">
-      <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
-      <item name="android:src">@drawable/ic_keyboard_tab_36dp</item>
+        <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="android:src">@drawable/ic_keyboard_tab_36dp</item>
     </style>
     <style name="Widget.TextView.NumPadKey.Klondike"
            parent="@android:style/Widget.DeviceDefault.TextView">
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 5b58fe8..3aa2e5a 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -191,6 +191,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="match_parent">
                         <TextView
+                            android:id="@+id/wifi_toggle_title"
                             android:text="@string/turn_on_wifi"
                             android:textDirection="locale"
                             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index a4c1d94..38af659 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -69,6 +69,9 @@
     <!-- Shadows under the clock, date and other keyguard text fields -->
     <color name="keyguard_shadow_color">#B2000000</color>
 
+    <!-- Color for the images in keyguard number pad buttons   -->
+    <color name="keyguard_keypad_image_color">@android:color/background_light</color>
+
     <!-- Color for rounded background for activated user in keyguard user switcher -->
     <color name="kg_user_switcher_activated_background_color">#26000000</color>
     <!-- Icon color for user avatars in keyguard user switcher -->
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 99f9558..c659bf7 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -26,7 +26,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.settingslib.Utils;
+import com.android.systemui.R;
 
 /**
  * Similar to the {@link NumPadKey}, but displays an image.
@@ -92,8 +92,7 @@
     public void reloadColors() {
         if (mAnimator != null) mAnimator.reloadColors(getContext());
 
-        int textColor = Utils.getColorAttrDefaultColor(getContext(),
-                android.R.attr.colorBackground);
-        ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(textColor));
+        int imageColor = getContext().getColor(R.color.keyguard_keypad_image_color);
+        ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(imageColor));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index bfa4780..5b327bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -34,7 +34,7 @@
  * See [DumpHandler] for more information on how and when this information is dumped.
  */
 @SysUISingleton
-class DumpManager @Inject constructor() {
+open class DumpManager @Inject constructor() {
     private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
     private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 28f63b0..6561bd5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -25,8 +25,12 @@
  * Proxy to make {@link SystemProperties} easily testable.
  */
 @SysUISingleton
-class SystemPropertiesHelper @Inject constructor() {
+open class SystemPropertiesHelper @Inject constructor() {
     fun getBoolean(name: String, default: Boolean): Boolean {
         return SystemProperties.getBoolean(name, default)
     }
+
+    fun set(name: String, value: Int) {
+        SystemProperties.set(name, value.toString())
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index d0a4b62..cc9e748 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -122,8 +122,9 @@
 
     @Override
     protected void handleClick(@Nullable View view) {
-        boolean canConfigMobileData = mAccessPointController.canConfigMobileData();
-        mHandler.post(() -> mInternetDialogFactory.create(true, canConfigMobileData));
+        mHandler.post(() -> mInternetDialogFactory.create(true,
+                mAccessPointController.canConfigMobileData(),
+                mAccessPointController.canConfigWifi()));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index e1e0ba7..7d3734e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -51,7 +51,6 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
-import android.widget.Space;
 import android.widget.Switch;
 import android.widget.TextView;
 
@@ -93,6 +92,8 @@
     protected View mDialogView;
     @VisibleForTesting
     protected WifiEntry mConnectedWifiEntry;
+    @VisibleForTesting
+    protected boolean mCanConfigWifi;
 
     private InternetDialogFactory mInternetDialogFactory;
     private SubscriptionManager mSubscriptionManager;
@@ -111,6 +112,7 @@
     private LinearLayout mMobileNetworkLayout;
     private LinearLayout mMobileNetworkList;
     private LinearLayout mTurnWifiOnLayout;
+    private TextView mWifiToggleTitleText;
     private LinearLayout mSeeAllLayout;
     private RecyclerView mWifiRecyclerView;
     private ImageView mConnectedWifiIcon;
@@ -151,7 +153,8 @@
 
     public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
             InternetDialogController internetDialogController, boolean canConfigMobileData,
-            boolean aboveStatusBar, UiEventLogger uiEventLogger, @Main Handler handler) {
+            boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
+            @Main Handler handler) {
         super(context, R.style.Theme_SystemUI_Dialog_Internet);
         if (DEBUG) {
             Log.d(TAG, "Init InternetDialog");
@@ -165,6 +168,7 @@
         mTelephonyManager = mInternetDialogController.getTelephonyManager();
         mWifiManager = mInternetDialogController.getWifiManager();
         mCanConfigMobileData = canConfigMobileData;
+        mCanConfigWifi = canConfigWifi;
 
         mLayoutManager = new LinearLayoutManager(mContext) {
             @Override
@@ -215,6 +219,7 @@
         mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
         mMobileNetworkList = mDialogView.requireViewById(R.id.mobile_network_list);
         mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+        mWifiToggleTitleText = mDialogView.requireViewById(R.id.wifi_toggle_title);
         mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout);
         mConnectedWifList = mDialogView.requireViewById(R.id.wifi_connected_list);
         mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon);
@@ -247,7 +252,19 @@
         if (DEBUG) {
             Log.d(TAG, "onStart");
         }
-        mInternetDialogController.onStart(this);
+        mInternetDialogController.onStart(this, mCanConfigWifi);
+        if (!mCanConfigWifi) {
+            hideWifiViews();
+        }
+    }
+
+    @VisibleForTesting
+    void hideWifiViews() {
+        setProgressBarVisible(false);
+        mTurnWifiOnLayout.setVisibility(View.GONE);
+        mConnectedWifListLayout.setVisibility(View.GONE);
+        mWifiRecyclerView.setVisibility(View.GONE);
+        mSeeAllLayout.setVisibility(View.GONE);
     }
 
     @Override
@@ -286,9 +303,13 @@
         } else {
             mInternetDialogSubTitle.setText(getSubtitleText());
         }
-        showProgressBar();
         setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular());
 
+        if (!mCanConfigWifi) {
+            return;
+        }
+
+        showProgressBar();
         final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
         final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
         updateWifiToggle(isWifiEnabled, isDeviceLocked);
@@ -368,6 +389,12 @@
 
     private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
         mWiFiToggle.setChecked(isWifiEnabled);
+        if (isDeviceLocked && mInternetDialogController.isNightMode()) {
+            int titleColor = mConnectedWifiEntry != null ? mContext.getColor(
+                    R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor(
+                    mContext, android.R.attr.textColorPrimary);
+            mWifiToggleTitleText.setTextColor(titleColor);
+        }
         mTurnWifiOnLayout.setBackground(
                 (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
     }
@@ -379,8 +406,8 @@
             return;
         }
         mConnectedWifListLayout.setVisibility(View.VISIBLE);
-        mConnectedWifiTitleText.setText(mInternetDialogController.getInternetWifiTitle());
-        mConnectedWifiSummaryText.setText(mInternetDialogController.getInternetWifiSummary());
+        mConnectedWifiTitleText.setText(mConnectedWifiEntry.getTitle());
+        mConnectedWifiSummaryText.setText(mConnectedWifiEntry.getSummary(false));
         mConnectedWifiIcon.setImageDrawable(
                 mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry));
         if (mInternetDialogController.isNightMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index ed32730..8838e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -139,6 +139,8 @@
     protected InternetTelephonyCallback mInternetTelephonyCallback;
     @VisibleForTesting
     protected WifiUtils.InternetIconInjector mWifiIconInjector;
+    @VisibleForTesting
+    protected boolean mCanConfigWifi;
 
     @VisibleForTesting
     KeyguardStateController mKeyguardStateController;
@@ -193,7 +195,7 @@
         mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
     }
 
-    void onStart(@NonNull InternetDialogCallback callback) {
+    void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
         if (DEBUG) {
             Log.d(TAG, "onStart");
         }
@@ -217,6 +219,7 @@
         mConnectivityManager.registerNetworkCallback(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build(), new DataConnectivityListener(), mHandler);
+        mCanConfigWifi = canConfigWifi;
         scanWifiAccessPoints();
     }
 
@@ -270,7 +273,7 @@
             return null;
         }
 
-        if (!mWifiManager.isWifiEnabled()) {
+        if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) {
             // When the airplane mode is off and Wi-Fi is disabled.
             //   Sub-Title: Wi-Fi is off
             if (DEBUG) {
@@ -290,10 +293,10 @@
 
         final List<ScanResult> wifiList = mWifiManager.getScanResults();
         if (wifiList != null && wifiList.size() != 0) {
-            return mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT);
+            return mCanConfigWifi ? mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT) : null;
         }
 
-        if (isProgressBarVisible) {
+        if (mCanConfigWifi && isProgressBarVisible) {
             // When the Wi-Fi scan result callback is received
             //   Sub-Title: Searching for networks...
             return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS);
@@ -317,7 +320,7 @@
             return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
         }
 
-        if (!isMobileDataEnabled()) {
+        if (mCanConfigWifi && !isMobileDataEnabled()) {
             if (DEBUG) {
                 Log.d(TAG, "Mobile data off");
             }
@@ -331,7 +334,10 @@
             return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
         }
 
-        return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+        if (mCanConfigWifi) {
+            return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+        }
+        return null;
     }
 
     Drawable getInternetWifiDrawable(@NonNull WifiEntry wifiEntry) {
@@ -549,26 +555,6 @@
         return summary;
     }
 
-    String getInternetWifiTitle() {
-        if (getInternetWifiEntry() == null) {
-            if (DEBUG) {
-                Log.d(TAG, "connected entry is null");
-            }
-            return "";
-        }
-        return getInternetWifiEntry().getTitle();
-    }
-
-    String getInternetWifiSummary() {
-        if (getInternetWifiEntry() == null) {
-            if (DEBUG) {
-                Log.d(TAG, "connected entry is null");
-            }
-            return "";
-        }
-        return getInternetWifiEntry().getSummary(false);
-    }
-
     void launchNetworkSetting() {
         mCallback.dismissDialog();
         mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0);
@@ -780,12 +766,14 @@
     }
 
     void scanWifiAccessPoints() {
-        mAccessPointController.scanForAccessPoints();
+        if (mCanConfigWifi) {
+            mAccessPointController.scanForAccessPoints();
+        }
     }
 
     @Override
     public void onAccessPointsChanged(List<WifiEntry> accessPoints) {
-        if (accessPoints == null) {
+        if (accessPoints == null || !mCanConfigWifi) {
             return;
         }
 
@@ -869,8 +857,8 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
-            if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)
-                    || action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+            if (mCanConfigWifi && (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)
+                    || action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))) {
                 mCallback.onWifiStateReceived(context, intent);
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
index e82e89ef1..11c6980 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -31,17 +31,17 @@
  */
 @SysUISingleton
 class InternetDialogFactory @Inject constructor(
-        @Main private val handler: Handler,
-        private val internetDialogController: InternetDialogController,
-        private val context: Context,
-        private val uiEventLogger: UiEventLogger
+    @Main private val handler: Handler,
+    private val internetDialogController: InternetDialogController,
+    private val context: Context,
+    private val uiEventLogger: UiEventLogger
 ) {
     companion object {
         var internetDialog: InternetDialog? = null
     }
 
     /** Creates a [InternetDialog]. */
-    fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean) {
+    fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean) {
         if (internetDialog != null) {
             if (DEBUG) {
                 Log.d(TAG, "InternetDialog is showing, do not create it twice.")
@@ -49,7 +49,7 @@
             return
         } else {
             internetDialog = InternetDialog(context, this, internetDialogController,
-                    canConfigMobileData, aboveStatusBar, uiEventLogger, handler)
+                    canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler)
             internetDialog?.show()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 9bba7ef..1cc1dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -280,7 +280,8 @@
 
     @VisibleForTesting
     fun launchSettings(sender: View) {
-        onSettingsClickListener?.onClick(sender, null, appUid!!)
+        val channel = if (providedChannels.size == 1) providedChannels[0] else null
+        onSettingsClickListener?.onClick(sender, channel, appUid!!)
     }
 
     private fun initDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index d6ac6d1..65e691f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -793,8 +793,8 @@
                 mReceiverHandler.post(this::handleConfigurationChanged);
                 break;
             case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
-                boolean canConfigMobileData = mAccessPoints.canConfigMobileData();
-                mMainHandler.post(() -> mInternetDialogFactory.create(true, canConfigMobileData));
+                mMainHandler.post(() -> mInternetDialogFactory.create(true,
+                        mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi()));
                 break;
             default:
                 int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index cfd82f5..377a7e6 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -535,6 +535,10 @@
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
                     managedProfiles);
         }
+        onOverlaysApplied();
+    }
+
+    protected void onOverlaysApplied() {
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 830fe5a..a57d439 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -10,6 +10,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -100,6 +101,7 @@
         when(mKeyguardStateController.isUnlocked()).thenReturn(true);
         when(mConnectedEntry.isDefaultNetwork()).thenReturn(true);
         when(mConnectedEntry.hasInternetAccess()).thenReturn(true);
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
 
         mInternetDialogController = new MockInternetDialogController(mContext,
                 mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
@@ -109,7 +111,7 @@
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mInternetDialogController.mOnSubscriptionsChangedListener);
         mInternetDialogController.onStart(
-                mock(InternetDialogController.InternetDialogCallback.class));
+                mock(InternetDialogController.InternetDialogCallback.class), true);
         mInternetDialogController.mActivityStarter = mActivityStarter;
         mInternetDialogController.mConnectedEntry = mConnectedEntry;
         mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
@@ -143,8 +145,14 @@
         mInternetDialogController.setAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(false);
 
-        assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
-                getResourcesString("wifi_is_off")));
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isEqualTo(getResourcesString("wifi_is_off"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isNotEqualTo(getResourcesString("wifi_is_off"));
     }
 
     @Test
@@ -155,8 +163,14 @@
         doReturn(0).when(wifiScanResults).size();
         when(mWifiManager.getScanResults()).thenReturn(wifiScanResults);
 
-        assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(true),
-                getResourcesString("wifi_empty_list_wifi_on")));
+        assertThat(mInternetDialogController.getSubtitleText(true))
+                .isEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(true))
+                .isNotEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
     }
 
     @Test
@@ -167,8 +181,14 @@
         doReturn(1).when(wifiScanResults).size();
         when(mWifiManager.getScanResults()).thenReturn(wifiScanResults);
 
-        assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
-                getResourcesString("tap_a_network_to_connect")));
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isEqualTo(getResourcesString("tap_a_network_to_connect"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isNotEqualTo(getResourcesString("tap_a_network_to_connect"));
     }
 
     @Test
@@ -188,8 +208,6 @@
         List<ScanResult> wifiScanResults = new ArrayList<>();
         doReturn(wifiScanResults).when(mWifiManager).getScanResults();
         when(mWifiManager.getScanResults()).thenReturn(wifiScanResults);
-        when(mSubscriptionManager.getActiveSubscriptionIdList())
-                .thenReturn(new int[] {SUB_ID});
 
         doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
         doReturn(mServiceState).when(mTelephonyManager).getServiceState();
@@ -206,16 +224,20 @@
         List<ScanResult> wifiScanResults = new ArrayList<>();
         doReturn(wifiScanResults).when(mWifiManager).getScanResults();
         when(mWifiManager.getScanResults()).thenReturn(wifiScanResults);
-        when(mSubscriptionManager.getActiveSubscriptionIdList())
-                .thenReturn(new int[] {SUB_ID});
 
         doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
         doReturn(mServiceState).when(mTelephonyManager).getServiceState();
 
         when(mTelephonyManager.isDataEnabled()).thenReturn(false);
 
-        assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
-                getResourcesString("non_carrier_network_unavailable")));
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isEqualTo(getResourcesString("non_carrier_network_unavailable"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isNotEqualTo(getResourcesString("non_carrier_network_unavailable"));
     }
 
     @Test
@@ -248,36 +270,6 @@
     }
 
     @Test
-    public void getInternetWifiTitle_withNoConnectedWifiEntry_returnEmpty() {
-        mInternetDialogController.mConnectedEntry = null;
-
-        assertThat(mInternetDialogController.getInternetWifiTitle()).isEmpty();
-    }
-
-    @Test
-    public void getInternetWifiTitle_withInternetWifi_returnTitle() {
-        // The preconditions have been set in setUp().
-        //   - The connected Wi-Fi entry have both default network and internet access conditions.
-        when(mConnectedEntry.getTitle()).thenReturn(CONNECTED_TITLE);
-
-        assertThat(mInternetDialogController.getInternetWifiTitle()).isEqualTo(CONNECTED_TITLE);
-    }
-
-    @Test
-    public void getInternetWifiSummary_withNoConnectedWifiEntry_returnEmpty() {
-        mInternetDialogController.mConnectedEntry = null;
-
-        assertThat(mInternetDialogController.getInternetWifiSummary()).isEmpty();
-    }
-
-    @Test
-    public void getInternetWifiSummary_withInternetWifi_returnSummary() {
-        when(mConnectedEntry.getSummary(false)).thenReturn(CONNECTED_SUMMARY);
-
-        assertThat(mInternetDialogController.getInternetWifiSummary()).isEqualTo(CONNECTED_SUMMARY);
-    }
-
-    @Test
     public void getWifiDetailsSettingsIntent_withNoConnectedEntry_returnNull() {
         mInternetDialogController.mConnectedEntry = null;
 
@@ -342,6 +334,16 @@
         assertThat(mInternetDialogController.isDeviceLocked()).isTrue();
     }
 
+    @Test
+    public void scanWifiAccessPoints_cannotConfigWifi_doNothing() {
+        reset(mAccessPointController);
+        mInternetDialogController.mCanConfigWifi = false;
+
+        mInternetDialogController.scanWifiAccessPoints();
+
+        verify(mAccessPointController, never()).scanForAccessPoints();
+    }
+
     private String getResourcesString(String name) {
         return mContext.getResources().getString(getResourcesId(name));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 5a018f4..87e81e40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -20,7 +20,6 @@
 import android.testing.TestableLooper;
 import android.view.View;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.filters.SmallTest;
@@ -69,6 +68,9 @@
     private InternetDialogController mInternetDialogController;
 
     private InternetDialog mInternetDialog;
+    private View mDialogView;
+    private View mSubTitle;
+    private LinearLayout mMobileDataToggle;
     private LinearLayout mWifiToggle;
     private LinearLayout mConnectedWifi;
     private RecyclerView mWifiList;
@@ -94,15 +96,18 @@
         when(mInternetDialogController.getWifiEntryList()).thenReturn(Arrays.asList(mWifiEntry));
 
         mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class),
-                mInternetDialogController, true, true, mock(UiEventLogger.class), mHandler);
+                mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler);
         mInternetDialog.mAdapter = mInternetAdapter;
         mInternetDialog.mConnectedWifiEntry = mInternetWifiEntry;
         mInternetDialog.show();
 
-        mWifiToggle = mInternetDialog.mDialogView.requireViewById(R.id.turn_on_wifi_layout);
-        mConnectedWifi = mInternetDialog.mDialogView.requireViewById(R.id.wifi_connected_layout);
-        mWifiList = mInternetDialog.mDialogView.requireViewById(R.id.wifi_list_layout);
-        mSeeAll = mInternetDialog.mDialogView.requireViewById(R.id.see_all_layout);
+        mDialogView = mInternetDialog.mDialogView;
+        mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+        mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
+        mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+        mConnectedWifi = mDialogView.requireViewById(R.id.wifi_connected_layout);
+        mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout);
+        mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
     }
 
     @After
@@ -111,33 +116,41 @@
     }
 
     @Test
+    public void hideWifiViews_WifiViewsGone() {
+        mInternetDialog.hideWifiViews();
+
+        assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+        assertThat(mWifiToggle.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
     public void updateDialog_withApmOn_internetDialogSubTitleGone() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-        mInternetDialog.updateDialog();
-        final TextView view = mInternetDialog.mDialogView.requireViewById(
-                R.id.internet_dialog_subtitle);
 
-        assertThat(view.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialog.updateDialog();
+
+        assertThat(mSubTitle.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
     public void updateDialog_withApmOff_internetDialogSubTitleVisible() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
-        mInternetDialog.updateDialog();
-        final TextView view = mInternetDialog.mDialogView.requireViewById(
-                R.id.internet_dialog_subtitle);
 
-        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialog.updateDialog();
+
+        assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
     public void updateDialog_withApmOn_mobileDataLayoutGone() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-        mInternetDialog.updateDialog();
-        final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById(
-                R.id.mobile_network_layout);
 
-        assertThat(linearLayout.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialog.updateDialog();
+
+        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
diff --git a/services/core/java/com/android/server/PermissionThread.java b/services/core/java/com/android/server/PermissionThread.java
deleted file mode 100644
index cf444fa..0000000
--- a/services/core/java/com/android/server/PermissionThread.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.Looper;
-import android.os.Trace;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.ServiceThread;
-
-import java.util.concurrent.Executor;
-
-/**
- * Shared singleton thread for the system. This is a thread for handling
- * calls to and from the PermissionController and handling synchronization
- * between permissions and appops states.
- */
-public final class PermissionThread extends ServiceThread {
-    private static final long SLOW_DISPATCH_THRESHOLD_MS = 100;
-    private static final long SLOW_DELIVERY_THRESHOLD_MS = 200;
-
-    private static final Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    private static PermissionThread sInstance;
-    private static Handler sHandler;
-    private static HandlerExecutor sHandlerExecutor;
-
-    private PermissionThread() {
-        super("android.perm", android.os.Process.THREAD_PRIORITY_DEFAULT, /* allowIo= */ true);
-    }
-
-    private static void ensureThreadLocked() {
-        if (sInstance != null) {
-            return;
-        }
-
-        sInstance = new PermissionThread();
-        sInstance.start();
-        final Looper looper = sInstance.getLooper();
-        looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
-        looper.setSlowLogThresholdMs(
-                SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
-        sHandler = new Handler(sInstance.getLooper());
-        sHandlerExecutor = new HandlerExecutor(sHandler);
-    }
-
-    public static PermissionThread get() {
-        synchronized (sLock) {
-            ensureThreadLocked();
-            return sInstance;
-        }
-    }
-
-    public static Handler getHandler() {
-        synchronized (sLock) {
-            ensureThreadLocked();
-            return sHandler;
-        }
-    }
-
-    public static Executor getExecutor() {
-        synchronized (sLock) {
-            ensureThreadLocked();
-            return sHandlerExecutor;
-        }
-    }
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 317775f2..e4c0765 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -329,16 +329,25 @@
     };
 
     /**
-     * Watch for apps being put into forced app standby, so we can step their fg
+     * Reference to the AppStateTracker service. No lock is needed as we'll assign with the same
+     * instance to it always.
+     */
+    AppStateTracker mAppStateTracker;
+
+    /**
+     * Watch for apps being put into background restricted, so we can step their fg
      * services down.
      */
-    class ForcedStandbyListener implements AppStateTracker.ServiceStateListener {
+    class BackgroundRestrictedListener implements AppStateTracker.BackgroundRestrictedAppListener {
         @Override
-        public void stopForegroundServicesForUidPackage(final int uid, final String packageName) {
+        public void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
+                boolean restricted) {
             synchronized (mAm) {
                 if (!isForegroundServiceAllowedInBackgroundRestricted(uid, packageName)) {
                     stopAllForegroundServicesLocked(uid, packageName);
                 }
+                mAm.mProcessList.updateBackgroundRestrictedForUidPackageLocked(
+                        uid, packageName, restricted);
             }
         }
     }
@@ -523,12 +532,18 @@
     }
 
     void systemServicesReady() {
-        AppStateTracker ast = LocalServices.getService(AppStateTracker.class);
-        ast.addServiceStateListener(new ForcedStandbyListener());
+        getAppStateTracker().addBackgroundRestrictedAppListener(new BackgroundRestrictedListener());
         mAppWidgetManagerInternal = LocalServices.getService(AppWidgetManagerInternal.class);
         setAllowListWhileInUsePermissionInFgs();
     }
 
+    private AppStateTracker getAppStateTracker() {
+        if (mAppStateTracker == null) {
+            mAppStateTracker = LocalServices.getService(AppStateTracker.class);
+        }
+        return mAppStateTracker;
+    }
+
     private void setAllowListWhileInUsePermissionInFgs() {
         final String attentionServicePackageName =
                 mAm.mContext.getPackageManager().getAttentionServicePackageName();
@@ -607,9 +622,11 @@
     }
 
     private boolean appRestrictedAnyInBackground(final int uid, final String packageName) {
-        final int mode = mAm.getAppOpsManager().checkOpNoThrow(
-                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName);
-        return (mode != AppOpsManager.MODE_ALLOWED);
+        final AppStateTracker appStateTracker = getAppStateTracker();
+        if (appStateTracker != null) {
+            return appStateTracker.isAppBackgroundRestricted(uid, packageName);
+        }
+        return false;
     }
 
     void updateAppRestrictedAnyInBackgroundLocked(final int uid, final String packageName) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 9a755da..048a787 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -56,7 +56,8 @@
     private static final String TAG = "ActivityManagerConstants";
 
     // Key names stored in the settings value.
-    private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time";
+    static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time";
+
     private static final String KEY_FGSERVICE_MIN_SHOWN_TIME
             = "fgservice_min_shown_time";
     private static final String KEY_FGSERVICE_MIN_REPORT_TIME
@@ -119,9 +120,11 @@
             "extra_delay_svc_restart_mem_pressure";
     static final String KEY_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE =
             "enable_extra_delay_svc_restart_mem_pressure";
+    static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE = "kill_bg_restricted_cached_idle";
+    static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME =
+            "kill_bg_restricted_cached_idle_settle_time";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
-    private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
     private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
     private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
     private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
@@ -164,6 +167,11 @@
     private static final float DEFAULT_FGS_ATOM_SAMPLE_RATE = 1; // 100 %
     private static final float DEFAULT_FGS_START_ALLOWED_LOG_SAMPLE_RATE = 0.25f; // 25%
     private static final float DEFAULT_FGS_START_DENIED_LOG_SAMPLE_RATE = 1; // 100%
+
+    static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60 * 1000;
+    static final long DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME_MS = 60 * 1000;
+    static final boolean DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE = true;
+
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
      */
@@ -541,6 +549,19 @@
     volatile float mFgsStartDeniedLogSampleRate = DEFAULT_FGS_START_DENIED_LOG_SAMPLE_RATE;
 
     /**
+     * Whether or not to kill apps in background restricted mode and it's cached, its UID state is
+     * idle.
+     */
+    volatile boolean mKillBgRestrictedAndCachedIdle = DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE;
+
+    /**
+     * The amount of time we allow an app in background restricted mode to settle after it goes
+     * into the cached &amp; UID idle, before we decide to kill it.
+     */
+    volatile long mKillBgRestrictedAndCachedIdleSettleTimeMs =
+            DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME_MS;
+
+    /**
      * Whether to allow "opt-out" from the foreground service restrictions.
      * (https://developer.android.com/about/versions/12/foreground-services)
      */
@@ -776,6 +797,12 @@
                             case KEY_FGS_START_DENIED_LOG_SAMPLE_RATE:
                                 updateFgsStartDeniedLogSamplePercent();
                                 break;
+                            case KEY_KILL_BG_RESTRICTED_CACHED_IDLE:
+                                updateKillBgRestrictedCachedIdle();
+                                break;
+                            case KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME:
+                                updateKillBgRestrictedCachedIdleSettleTime();
+                                break;
                             case KEY_FGS_ALLOW_OPT_OUT:
                                 updateFgsAllowOptOut();
                                 break;
@@ -1142,6 +1169,28 @@
                 DEFAULT_FGS_START_DENIED_LOG_SAMPLE_RATE);
     }
 
+    private void updateKillBgRestrictedCachedIdle() {
+        mKillBgRestrictedAndCachedIdle = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_KILL_BG_RESTRICTED_CACHED_IDLE,
+                DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE);
+    }
+
+    private void updateKillBgRestrictedCachedIdleSettleTime() {
+        final long currentSettleTime = mKillBgRestrictedAndCachedIdleSettleTimeMs;
+        mKillBgRestrictedAndCachedIdleSettleTimeMs = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME,
+                DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME_MS);
+        if (mKillBgRestrictedAndCachedIdleSettleTimeMs != currentSettleTime) {
+            mService.mHandler.removeMessages(
+                    ActivityManagerService.IDLE_UIDS_MSG);
+            mService.mHandler.sendEmptyMessageDelayed(
+                    ActivityManagerService.IDLE_UIDS_MSG,
+                    mKillBgRestrictedAndCachedIdleSettleTimeMs);
+        }
+    }
+
     private void updateFgsAllowOptOut() {
         mFgsAllowOptOut = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 70ceb92..7fc35d4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1832,18 +1832,6 @@
                     }
                 });
 
-        mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
-                new IAppOpsCallback.Stub() {
-                    @Override public void opChanged(int op, int uid, String packageName) {
-                        if (op == AppOpsManager.OP_RUN_ANY_IN_BACKGROUND && packageName != null) {
-                            synchronized (ActivityManagerService.this) {
-                                mServices.updateAppRestrictedAnyInBackgroundLocked(
-                                        uid, packageName);
-                            }
-                        }
-                    }
-                });
-
         final int[] cameraOp = {AppOpsManager.OP_CAMERA};
         mAppOpsService.startWatchingActive(cameraOp, new IAppOpsActiveCallback.Stub() {
             @Override
@@ -9495,6 +9483,15 @@
                 TimeUtils.dumpTimeWithDelta(pw, expirationInCurrentTime, currentTimeNow);
                 pw.println();
             });
+
+            if (!mProcessList.mAppsInBackgroundRestricted.isEmpty()) {
+                pw.println("  Processes that are in background restricted:");
+                for (int i = 0, size = mProcessList.mAppsInBackgroundRestricted.size();
+                        i < size; i++) {
+                    pw.println(String.format("%s #%2d: %s", "    ", i,
+                            mProcessList.mAppsInBackgroundRestricted.valueAt(i).toString()));
+                }
+            }
         }
         if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
                 || mOrigWaitForDebugger) {
@@ -14441,6 +14438,10 @@
         final int capability = uidRec != null ? uidRec.getSetCapability() : 0;
         final boolean ephemeral = uidRec != null ? uidRec.isEphemeral() : isEphemeralLocked(uid);
 
+        if (uidRec != null && uidRec.isIdle() && (change & UidRecord.CHANGE_IDLE) != 0) {
+            mProcessList.killAppIfBgRestrictedAndCachedIdleLocked(uidRec);
+        }
+
         if (uidRec != null && !uidRec.isIdle() && (change & UidRecord.CHANGE_GONE) != 0) {
             // If this uid is going away, and we haven't yet reported it is gone,
             // then do so now.
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 27d8119..e94276c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1564,6 +1564,7 @@
         state.resetAllowStartFgsState();
         if (!cycleReEval) {
             // Don't reset this flag when doing cycles re-evaluation.
+            state.setNoKillOnBgRestrictedAndIdle(false);
             app.mOptRecord.setShouldNotFreeze(false);
         }
 
@@ -2900,6 +2901,23 @@
                             + " target=" + state.getAdjTarget() + " capability=" + item.capability);
         }
 
+        if (state.isCached() && !state.shouldNotKillOnBgRestrictedAndIdle()) {
+            // It's eligible to get killed when in UID idle and bg restricted mode,
+            // check if these states are just flipped.
+            if (!state.isSetCached() || state.isSetNoKillOnBgRestrictedAndIdle()) {
+                // Take the timestamp, we'd hold the killing for the background settle time
+                // (for states debouncing to avoid from thrashing).
+                state.setLastCanKillOnBgRestrictedAndIdleTime(nowElapsed);
+                // Kick off the delayed checkup message if needed.
+                if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
+                    mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+                            mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs);
+                }
+            }
+        }
+        state.setSetCached(state.isCached());
+        state.setSetNoKillOnBgRestrictedAndIdle(state.shouldNotKillOnBgRestrictedAndIdle());
+
         return success;
     }
 
@@ -3048,6 +3066,20 @@
         if (mLocalPowerManager != null) {
             mLocalPowerManager.finishUidChanges();
         }
+        // Also check if there are any apps in cached and background restricted mode,
+        // if so, kill it if it's been there long enough, or kick off a msg to check
+        // it later.
+        if (mService.mConstants.mKillBgRestrictedAndCachedIdle) {
+            final ArraySet<ProcessRecord> apps = mProcessList.mAppsInBackgroundRestricted;
+            for (int i = 0, size = apps.size(); i < size; i++) {
+                // Check to see if needs to be killed.
+                final long bgTime = mProcessList.killAppIfBgRestrictedAndCachedIdleLocked(
+                        apps.valueAt(i), nowElapsed) - mConstants.BACKGROUND_SETTLE_TIME;
+                if (bgTime > 0 && (nextTime == 0 || nextTime > bgTime)) {
+                    nextTime = bgTime;
+                }
+            }
+        }
         if (nextTime > 0) {
             mService.mHandler.removeMessages(IDLE_UIDS_MSG);
             mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a97738f..07d9cb2 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -42,6 +42,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESSES_CHANGED_UI_MSG;
 import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESS_DIED_UI_MSG;
+import static com.android.server.am.ActivityManagerService.IDLE_UIDS_MSG;
 import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_DELAY_MS;
 import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_MSG;
 import static com.android.server.am.ActivityManagerService.PERSISTENT_MASK;
@@ -126,6 +127,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
+import com.android.server.AppStateTracker;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
@@ -544,6 +546,12 @@
     final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses =
             new ArrayMap<AppZygote, ArrayList<ProcessRecord>>();
 
+    /**
+     * The list of apps in background restricted mode.
+     */
+    @GuardedBy("mService")
+    final ArraySet<ProcessRecord> mAppsInBackgroundRestricted = new ArraySet<>();
+
     private PlatformCompat mPlatformCompat = null;
 
     /**
@@ -2370,6 +2378,16 @@
                 allowlistedAppDataInfoMap = null;
             }
 
+            AppStateTracker ast = mService.mServices.mAppStateTracker;
+            if (ast != null) {
+                final boolean inBgRestricted = ast.isAppBackgroundRestricted(
+                        app.info.uid, app.info.packageName);
+                if (inBgRestricted) {
+                    mAppsInBackgroundRestricted.add(app);
+                }
+                app.mState.setBackgroundRestricted(inBgRestricted);
+            }
+
             final Process.ProcessStartResult startResult;
             boolean regularZygote = false;
             if (hostingRecord.usesWebviewZygote()) {
@@ -3104,6 +3122,7 @@
         if (record != null && record.appZygote) {
             removeProcessFromAppZygoteLocked(record);
         }
+        mAppsInBackgroundRestricted.remove(record);
 
         return old;
     }
@@ -5019,6 +5038,75 @@
         return true;
     }
 
+    @GuardedBy("mService")
+    void updateBackgroundRestrictedForUidPackageLocked(int uid, String packageName,
+            boolean restricted) {
+        final UidRecord uidRec = getUidRecordLOSP(uid);
+        if (uidRec != null) {
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            uidRec.forEachProcess(app -> {
+                if (TextUtils.equals(app.info.packageName, packageName)) {
+                    app.mState.setBackgroundRestricted(restricted);
+                    if (restricted) {
+                        mAppsInBackgroundRestricted.add(app);
+                        final long future = killAppIfBgRestrictedAndCachedIdleLocked(
+                                app, nowElapsed);
+                        if (future > 0 && !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
+                            mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+                                    future - nowElapsed);
+                        }
+                    } else {
+                        mAppsInBackgroundRestricted.remove(app);
+                    }
+                    if (!app.isKilledByAm()) {
+                        mService.enqueueOomAdjTargetLocked(app);
+                    }
+                }
+            });
+            /* Will be a no-op if nothing pending */
+            mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        }
+    }
+
+    /**
+     * Kill the given app if it's in cached idle and background restricted mode.
+     *
+     * @return A future timestamp when the app should be killed at, or a 0 if it shouldn't
+     * be killed or it has been killed.
+     */
+    @GuardedBy("mService")
+    long killAppIfBgRestrictedAndCachedIdleLocked(ProcessRecord app, long nowElapsed) {
+        final UidRecord uidRec = app.getUidRecord();
+        final long lastCanKillTime = app.mState.getLastCanKillOnBgRestrictedAndIdleTime();
+        if (!mService.mConstants.mKillBgRestrictedAndCachedIdle
+                || app.isKilled() || app.getThread() == null || uidRec == null || !uidRec.isIdle()
+                || !app.isCached() || app.mState.shouldNotKillOnBgRestrictedAndIdle()
+                || !app.mState.isBackgroundRestricted() || lastCanKillTime == 0) {
+            return 0;
+        }
+        final long future = lastCanKillTime
+                + mService.mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs;
+        if (future <= nowElapsed) {
+            app.killLocked("cached idle & background restricted",
+                    ApplicationExitInfo.REASON_OTHER,
+                    ApplicationExitInfo.SUBREASON_CACHED_IDLE_FORCED_APP_STANDBY,
+                    true);
+            return 0;
+        }
+        return future;
+    }
+
+    /**
+     * Called by {@link ActivityManagerService#enqueueUidChangeLocked} only, it doesn't schedule
+     * the standy killing checks because it should have been scheduled before enqueueing UID idle
+     * changed.
+     */
+    @GuardedBy("mService")
+    void killAppIfBgRestrictedAndCachedIdleLocked(UidRecord uidRec) {
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        uidRec.forEachProcess(app -> killAppIfBgRestrictedAndCachedIdleLocked(app, nowElapsed));
+    }
+
     /**
      * Called by ActivityManagerService when a process died.
      */
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 85d06c3..46144f5 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -374,6 +374,34 @@
     @ElapsedRealtimeLong
     private long mLastInvisibleTime;
 
+    /**
+     * Whether or not this process could be killed when it's in background restricted mode
+     * and cached &amp; idle state.
+     */
+    @GuardedBy("mService")
+    private boolean mNoKillOnBgRestrictedAndIdle;
+
+    /**
+     * Last set value of {@link #mCached}.
+     */
+    @GuardedBy("mService")
+    private boolean mSetCached;
+
+    /**
+     * Last set value of {@link #mNoKillOnBgRestrictedAndIdle}.
+     */
+    @GuardedBy("mService")
+    private boolean mSetNoKillOnBgRestrictedAndIdle;
+
+    /**
+     * The last time when the {@link #mNoKillOnBgRestrictedAndIdle} is false and the
+     * {@link #mCached} is true, and either the former state is flipping from true to false
+     * when latter state is true, or the latter state is flipping from false to true when the
+     * former state is false.
+     */
+    @GuardedBy("mService")
+    private @ElapsedRealtimeLong long mLastCanKillOnBgRestrictedAndIdleTime;
+
     // Below are the cached task info for OomAdjuster only
     private static final int VALUE_INVALID = -1;
     private static final int VALUE_FALSE = 0;
@@ -1174,6 +1202,47 @@
         return mLastInvisibleTime;
     }
 
+    @GuardedBy("mService")
+    void setNoKillOnBgRestrictedAndIdle(boolean shouldNotKill) {
+        mNoKillOnBgRestrictedAndIdle = shouldNotKill;
+    }
+
+    @GuardedBy("mService")
+    boolean shouldNotKillOnBgRestrictedAndIdle() {
+        return mNoKillOnBgRestrictedAndIdle;
+    }
+
+    @GuardedBy("mService")
+    void setSetCached(boolean cached) {
+        mSetCached = cached;
+    }
+
+    @GuardedBy("mService")
+    boolean isSetCached() {
+        return mSetCached;
+    }
+
+    @GuardedBy("mService")
+    void setSetNoKillOnBgRestrictedAndIdle(boolean shouldNotKill) {
+        mSetNoKillOnBgRestrictedAndIdle = shouldNotKill;
+    }
+
+    @GuardedBy("mService")
+    boolean isSetNoKillOnBgRestrictedAndIdle() {
+        return mSetNoKillOnBgRestrictedAndIdle;
+    }
+
+    @GuardedBy("mService")
+    void setLastCanKillOnBgRestrictedAndIdleTime(@ElapsedRealtimeLong long now) {
+        mLastCanKillOnBgRestrictedAndIdleTime = now;
+    }
+
+    @ElapsedRealtimeLong
+    @GuardedBy("mService")
+    long getLastCanKillOnBgRestrictedAndIdleTime() {
+        return mLastCanKillOnBgRestrictedAndIdleTime;
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     void dump(PrintWriter pw, String prefix, long nowUptime) {
         if (mReportedInteraction || mFgInteractionTime != 0) {
diff --git a/services/core/java/com/android/server/pm/PackageInstalledInfo.java b/services/core/java/com/android/server/pm/PackageInstalledInfo.java
index afe6bb2..d0ca9d84 100644
--- a/services/core/java/com/android/server/pm/PackageInstalledInfo.java
+++ b/services/core/java/com/android/server/pm/PackageInstalledInfo.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.pm.PackageManagerService.TAG;
 
-import android.content.pm.PackageParser;
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
@@ -59,12 +58,6 @@
         Slog.w(TAG, msg);
     }
 
-    public void setError(String msg, PackageParser.PackageParserException e) {
-        setReturnCode(e.error);
-        setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
-        Slog.w(TAG, msg, e);
-    }
-
     public void setError(String msg, PackageManagerException e) {
         mReturnCode = e.error;
         setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
index 8a8a302..4334bf6 100644
--- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
+++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
@@ -21,7 +21,7 @@
 import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_TEST_RUNNER;
 import static com.android.server.pm.parsing.library.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY;
 
-import android.content.pm.PackageParser;
+import android.content.pm.parsing.ParsingPackage;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -82,7 +82,7 @@
         boolean hasClass = false;
         String className = "android.content.pm.AndroidTestBaseUpdater";
         try {
-            Class clazz = (PackageParser.class.getClassLoader().loadClass(className));
+            Class clazz = ParsingPackage.class.getClassLoader().loadClass(className);
             hasClass = clazz != null;
             Log.i(TAG, "Loaded " + className);
         } catch (ClassNotFoundException e) {
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index dd50b0c..4bbe373 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -33,7 +33,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.PermissionThread;
 
 /**
  * Class that handles one-time permissions for a user
@@ -80,8 +79,7 @@
         mContext = context;
         mActivityManager = context.getSystemService(ActivityManager.class);
         mAlarmManager = context.getSystemService(AlarmManager.class);
-        mPermissionControllerManager = new PermissionControllerManager(
-                mContext, PermissionThread.getHandler());
+        mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class);
         mHandler = context.getMainThreadHandler();
     }
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 919ed5f..cd03351 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -145,7 +145,6 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.PermissionThread;
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
 import com.android.server.Watchdog;
@@ -2057,7 +2056,7 @@
     private byte[] backupRuntimePermissions(@UserIdInt int userId) {
         CompletableFuture<byte[]> backup = new CompletableFuture<>();
         mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
-                PermissionThread.getExecutor(), backup::complete);
+                mContext.getMainExecutor(), backup::complete);
 
         try {
             return backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
@@ -2102,7 +2101,7 @@
             }
         }
         mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName,
-                UserHandle.of(userId), PermissionThread.getExecutor(), (hasMoreBackup) -> {
+                UserHandle.of(userId), mContext.getMainExecutor(), (hasMoreBackup) -> {
                     if (hasMoreBackup) {
                         return;
                     }
@@ -4551,8 +4550,7 @@
             }
         }
 
-        mPermissionControllerManager = new PermissionControllerManager(
-                mContext, PermissionThread.getHandler());
+        mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
         mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
     }
 
diff --git a/services/core/java/com/android/server/policy/DisplayFoldDurationLogger.java b/services/core/java/com/android/server/policy/DisplayFoldDurationLogger.java
index bdcd2cd..3524d7f 100644
--- a/services/core/java/com/android/server/policy/DisplayFoldDurationLogger.java
+++ b/services/core/java/com/android/server/policy/DisplayFoldDurationLogger.java
@@ -44,8 +44,8 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ScreenState {}
 
-    private @ScreenState int mScreenState = SCREEN_STATE_UNKNOWN;
-    private Long mLastChanged = null;
+    private volatile @ScreenState int mScreenState = SCREEN_STATE_UNKNOWN;
+    private volatile Long mLastChanged = null;
 
     private static final int LOG_SUBTYPE_UNFOLDED = 0;
     private static final int LOG_SUBTYPE_FOLDED = 1;
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 2709d4f..ad43514 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -64,8 +64,8 @@
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.IntPair;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.PermissionThread;
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -279,7 +279,7 @@
                 PermissionControllerManager manager = mPermControllerManagers.get(user);
                 if (manager == null) {
                     manager = new PermissionControllerManager(
-                            getUserContext(getContext(), user), PermissionThread.getHandler());
+                            getUserContext(getContext(), user), FgThread.getHandler());
                     mPermControllerManagers.put(user, manager);
                 }
                 manager.updateUserSensitiveForApp(uid);
@@ -287,9 +287,8 @@
         }, UserHandle.ALL, intentFilter, null, null);
 
         PermissionControllerManager manager = new PermissionControllerManager(
-                getUserContext(getContext(), Process.myUserHandle()),
-                PermissionThread.getHandler());
-        PermissionThread.getHandler().postDelayed(manager::updateUserSensitive,
+                getUserContext(getContext(), Process.myUserHandle()), FgThread.getHandler());
+        FgThread.getHandler().postDelayed(manager::updateUserSensitive,
                 USER_SENSITIVE_UPDATE_DELAY_MS);
     }
 
@@ -316,7 +315,7 @@
         if (isStarted(changedUserId)) {
             synchronized (mLock) {
                 if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
-                    PermissionThread.getHandler().sendMessage(PooledLambda.obtainMessage(
+                    FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                             PermissionPolicyService
                                     ::synchronizePackagePermissionsAndAppOpsForUser,
                             this, packageName, changedUserId));
@@ -422,9 +421,9 @@
             final PermissionControllerManager permissionControllerManager =
                     new PermissionControllerManager(
                             getUserContext(getContext(), UserHandle.of(userId)),
-                            PermissionThread.getHandler());
+                            FgThread.getHandler());
             permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
-                    PermissionThread.getExecutor(), successful -> {
+                    FgThread.getExecutor(), successful -> {
                         if (successful) {
                             future.complete(null);
                         } else {
@@ -528,7 +527,7 @@
             synchronized (mLock) {
                 if (!mIsUidSyncScheduled.get(uid)) {
                     mIsUidSyncScheduled.put(uid, true);
-                    PermissionThread.getHandler().sendMessage(PooledLambda.obtainMessage(
+                    FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                             PermissionPolicyService::resetAppOpPermissionsIfNotRequestedForUid,
                             this, uid));
                 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 2a95416..06253a0 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -124,12 +124,8 @@
         @Override
         public void read(InputStream in) throws IOException {
             while (in.available() > 0) {
-                try {
-                    DataElement dataElement = new DataElement(in);
-                    mCallback.onReadDataElement(dataElement.getData());
-                } catch (IOException e) {
-                    Slog.e(TAG, "Failed to read from storage. " + e.getMessage());
-                }
+                DataElement dataElement = new DataElement(in);
+                mCallback.onReadDataElement(dataElement.getData());
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3a5d771..ed2c3b6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8644,8 +8644,9 @@
             if (imeTargetWindowTask == null) {
                 return false;
             }
-            final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId,
-                    false /* isLowResolution */);
+            final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId,
+                    imeTargetWindowTask.mUserId, false /* isLowResolution */,
+                    false /* restoreFromDisk */);
             return snapshot != null && snapshot.hasImeSurface();
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6a25965..e38d8dc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3804,17 +3804,6 @@
         }
     }
 
-    @Override
-    public boolean isSeparateProfileChallengeAllowed(int userHandle) {
-        Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
-                String.format(NOT_SYSTEM_CALLER_MSG, "query separate challenge support"));
-
-        ComponentName profileOwner = getProfileOwnerAsUser(userHandle);
-        // Profile challenge is supported on N or newer release.
-        return profileOwner != null &&
-                getTargetSdk(profileOwner.getPackageName(), userHandle) > Build.VERSION_CODES.M;
-    }
-
     private boolean canSetPasswordQualityOnParent(String packageName, final CallerIdentity caller) {
         return !mInjector.isChangeEnabled(
                 PREVENT_SETTING_PASSWORD_QUALITY_ON_PARENT, packageName, caller.getUserId())
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 607fb47..40b3664 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -1003,6 +1003,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2), anyBoolean());
+        verify(l, times(1)).updateBackgroundRestrictedForUidPackage(eq(UID_10_2), eq(PACKAGE_2),
+                eq(true));
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1017,6 +1019,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2), anyBoolean());
+        verify(l, times(1)).updateBackgroundRestrictedForUidPackage(eq(UID_10_2), eq(PACKAGE_2),
+                eq(false));
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1030,6 +1034,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1047,6 +1053,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2), anyBoolean());
+        verify(l, times(1)).updateBackgroundRestrictedForUidPackage(eq(UID_10_2), eq(PACKAGE_2),
+                eq(true));
 
         verify(l, times(1)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1063,6 +1071,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(1)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1081,6 +1091,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(1)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1095,6 +1107,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(1)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1111,6 +1125,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1126,6 +1142,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1142,6 +1160,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(1)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1158,6 +1178,8 @@
         verify(l, times(2)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(1)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1172,6 +1194,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(1)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1188,6 +1212,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1203,6 +1229,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(anyInt(), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(anyInt());
@@ -1225,6 +1253,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
@@ -1240,6 +1270,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
@@ -1255,6 +1287,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
@@ -1270,6 +1304,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
@@ -1286,6 +1322,8 @@
         verify(l, times(1)).updateAllJobs();
         verify(l, times(0)).updateJobsForUid(eq(UID_10_1), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(1)).updateAllAlarms();
         verify(l, times(0)).updateAlarmsForUid(eq(UID_10_1));
@@ -1301,6 +1339,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
@@ -1316,6 +1356,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
@@ -1331,6 +1373,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
@@ -1346,6 +1390,8 @@
         verify(l, times(0)).updateAllJobs();
         verify(l, times(1)).updateJobsForUid(eq(UID_10_1), anyBoolean());
         verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString(), anyBoolean());
+        verify(l, times(0)).updateBackgroundRestrictedForUidPackage(anyInt(), anyString(),
+                anyBoolean());
 
         verify(l, times(0)).updateAllAlarms();
         verify(l, times(1)).updateAlarmsForUid(eq(UID_10_1));
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
index b580eae..850f881 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -55,6 +55,7 @@
 import android.support.test.uiautomator.UiDevice;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.text.TextUtils;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Pair;
 
@@ -99,6 +100,8 @@
     private static final String ACTION_FGS_STATS_TEST =
             "com.android.servicestests.apps.simpleservicetestapp.ACTION_FGS_STATS_TEST";
     private static final String EXTRA_MESSENGER = "extra_messenger";
+    private static final String ACTION_RECEIVER_TEST =
+            "com.android.servicestests.apps.simpleservicetestapp.TEST";
 
     private static final String EXTRA_CALLBACK = "callback";
     private static final String EXTRA_COMMAND = "command";
@@ -501,6 +504,205 @@
         return false;
     }
 
+    @LargeTest
+    @Test
+    public void testKillAppIfBgRestrictedCachedIdle() throws Exception {
+        final long shortTimeoutMs = 5_000;
+        final long backgroundSettleMs = 10_000;
+        final PackageManager pm = mContext.getPackageManager();
+        final int uid = pm.getPackageUid(TEST_APP1, 0);
+        final MyUidImportanceListener uidListener1 = new MyUidImportanceListener(uid);
+        final MyUidImportanceListener uidListener2 = new MyUidImportanceListener(uid);
+        final MyUidImportanceListener uidListener3 = new MyUidImportanceListener(uid);
+        SettingsSession<String> amConstantsSettings = null;
+        DeviceConfigSession<Boolean> killBgRestrictedAndCachedIdle = null;
+        DeviceConfigSession<Long> killBgRestrictedAndCachedIdleSettleTime = null;
+        final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+        final CountDownLatch[] latchHolder = new CountDownLatch[1];
+        final H handler = new H(Looper.getMainLooper(), latchHolder);
+        final Messenger messenger = new Messenger(handler);
+        try {
+            am.addOnUidImportanceListener(uidListener1,
+                    RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+            am.addOnUidImportanceListener(uidListener2, RunningAppProcessInfo.IMPORTANCE_GONE);
+            am.addOnUidImportanceListener(uidListener3, RunningAppProcessInfo.IMPORTANCE_CACHED);
+            toggleScreenOn(true);
+
+            killBgRestrictedAndCachedIdle = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    ActivityManagerConstants.KEY_KILL_BG_RESTRICTED_CACHED_IDLE,
+                    DeviceConfig::getBoolean,
+                    ActivityManagerConstants.DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE);
+            killBgRestrictedAndCachedIdle.set(true);
+            killBgRestrictedAndCachedIdleSettleTime = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    ActivityManagerConstants.KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME,
+                    DeviceConfig::getLong,
+                    ActivityManagerConstants.DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME_MS);
+            killBgRestrictedAndCachedIdleSettleTime.set(backgroundSettleMs);
+            amConstantsSettings = new SettingsSession<>(
+                Settings.Global.getUriFor(Settings.Global.ACTIVITY_MANAGER_CONSTANTS),
+                Settings.Global::getString, Settings.Global::putString);
+            final KeyValueListParser parser = new KeyValueListParser(',');
+            long currentBackgroundSettleMs =
+                    ActivityManagerConstants.DEFAULT_BACKGROUND_SETTLE_TIME;
+            try {
+                parser.setString(amConstantsSettings.get());
+                currentBackgroundSettleMs = parser.getLong(
+                        ActivityManagerConstants.KEY_BACKGROUND_SETTLE_TIME,
+                        ActivityManagerConstants.DEFAULT_BACKGROUND_SETTLE_TIME);
+            } catch (IllegalArgumentException e) {
+            }
+            // Drain queue to make sure the existing UID_IDLE_MSG has been processed.
+            Thread.sleep(currentBackgroundSettleMs);
+            amConstantsSettings.set(
+                    ActivityManagerConstants.KEY_BACKGROUND_SETTLE_TIME + "=" + backgroundSettleMs);
+
+            runShellCommand("cmd appops set " + TEST_APP1 + " RUN_ANY_IN_BACKGROUND allow");
+
+            final Intent intent = new Intent(ACTION_FGS_STATS_TEST);
+            final ComponentName cn = ComponentName.unflattenFromString(
+                    TEST_APP1 + "/" + TEST_FGS_CLASS);
+            final Bundle bundle = new Bundle();
+            intent.setComponent(cn);
+            bundle.putBinder(EXTRA_MESSENGER, messenger.getBinder());
+            intent.putExtras(bundle);
+
+            // Start the FGS.
+            latchHolder[0] = new CountDownLatch(1);
+            mContext.startForegroundService(intent);
+            assertTrue("Timed out to start fg service", uidListener1.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE, shortTimeoutMs));
+            assertTrue("Timed out to get the remote messenger", latchHolder[0].await(
+                    shortTimeoutMs, TimeUnit.MILLISECONDS));
+            assertFalse("FGS shouldn't be killed", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE, backgroundSettleMs + shortTimeoutMs));
+
+            // Stop the FGS, it shouldn't be killed because it's not in FAS state.
+            latchHolder[0] = new CountDownLatch(1);
+            handler.sendRemoteMessage(H.MSG_STOP_SERVICE, 0, 0, null);
+            assertTrue("Timed out to wait for stop fg", latchHolder[0].await(
+                    shortTimeoutMs, TimeUnit.MILLISECONDS));
+            assertFalse("FGS shouldn't be killed", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE, backgroundSettleMs + shortTimeoutMs));
+
+            // Set the FAS state.
+            runShellCommand("cmd appops set " + TEST_APP1 + " RUN_ANY_IN_BACKGROUND deny");
+            // Now it should've been killed.
+            assertTrue("Should have been killed", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE, backgroundSettleMs + shortTimeoutMs));
+
+            // Start the FGS.
+            // Temporarily allow RUN_ANY_IN_BACKGROUND to start FGS.
+            runShellCommand("cmd appops set " + TEST_APP1 + " RUN_ANY_IN_BACKGROUND allow");
+            latchHolder[0] = new CountDownLatch(1);
+            mContext.startForegroundService(intent);
+            assertTrue("Timed out to start fg service", uidListener1.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE, shortTimeoutMs));
+            assertTrue("Timed out to get the remote messenger", latchHolder[0].await(
+                    shortTimeoutMs, TimeUnit.MILLISECONDS));
+            runShellCommand("cmd appops set " + TEST_APP1 + " RUN_ANY_IN_BACKGROUND deny");
+            // It shouldn't be killed since it's not cached.
+            assertFalse("FGS shouldn't be killed", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE, backgroundSettleMs + shortTimeoutMs));
+
+            // Stop the FGS, it should get killed because it's cached & uid idle & in FAS state.
+            latchHolder[0] = new CountDownLatch(1);
+            handler.sendRemoteMessage(H.MSG_STOP_SERVICE, 0, 0, null);
+            assertTrue("Timed out to wait for stop fg", latchHolder[0].await(
+                    shortTimeoutMs, TimeUnit.MILLISECONDS));
+            assertTrue("Should have been killed", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE, backgroundSettleMs + shortTimeoutMs));
+
+            // Start the FGS.
+            // Temporarily allow RUN_ANY_IN_BACKGROUND to start FGS.
+            runShellCommand("cmd appops set " + TEST_APP1 + " RUN_ANY_IN_BACKGROUND allow");
+            latchHolder[0] = new CountDownLatch(1);
+            mContext.startForegroundService(intent);
+            assertTrue("Timed out to start fg service", uidListener1.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE, shortTimeoutMs));
+            assertTrue("Timed out to get the remote messenger", latchHolder[0].await(
+                    shortTimeoutMs, TimeUnit.MILLISECONDS));
+            runShellCommand("cmd appops set " + TEST_APP1 + " RUN_ANY_IN_BACKGROUND deny");
+            // It shouldn't be killed since it's not cached.
+            assertFalse("FGS shouldn't be killed", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE, backgroundSettleMs + shortTimeoutMs));
+
+            // Stop the FGS.
+            latchHolder[0] = new CountDownLatch(1);
+            handler.sendRemoteMessage(H.MSG_STOP_SERVICE, 0, 0, null);
+            assertTrue("Timed out to wait for stop fg", latchHolder[0].await(
+                    shortTimeoutMs, TimeUnit.MILLISECONDS));
+            assertTrue("Should have been in cached state", uidListener3.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_CACHED, shortTimeoutMs));
+            final long now = SystemClock.uptimeMillis();
+            // Sleep a while to let the UID idle ticking.
+            Thread.sleep(shortTimeoutMs);
+            // Now send a broadcast, it should bring the app out of cached for a while.
+            final Intent intent2 = new Intent(ACTION_RECEIVER_TEST);
+            final Bundle extras = new Bundle();
+            final IRemoteCallback callback = new IRemoteCallback.Stub() {
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    latchHolder[0].countDown();
+                }
+            };
+            extras.putBinder(EXTRA_CALLBACK, callback.asBinder());
+            intent2.putExtras(extras);
+            intent2.setPackage(TEST_APP1);
+            latchHolder[0] = new CountDownLatch(1);
+            mContext.sendBroadcast(intent2);
+            assertTrue("Timed out to wait for receiving broadcast", latchHolder[0].await(
+                    shortTimeoutMs, TimeUnit.MILLISECONDS));
+            // Try to wait for the killing, it should be killed after backgroundSettleMs
+            // since receiving the broadcast
+            assertFalse("Shouldn't be killed now", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE,
+                    backgroundSettleMs + now - SystemClock.uptimeMillis() + 2_000));
+            // Now wait a bit longer, it should be killed.
+            assertTrue("Should have been killed", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE, backgroundSettleMs + shortTimeoutMs));
+
+            // Disable this FAS cached idle kill feature.
+            killBgRestrictedAndCachedIdle.set(false);
+
+            // Start the FGS.
+            // Temporarily allow RUN_ANY_IN_BACKGROUND to start FGS.
+            runShellCommand("cmd appops set " + TEST_APP1 + " RUN_ANY_IN_BACKGROUND allow");
+            latchHolder[0] = new CountDownLatch(1);
+            mContext.startForegroundService(intent);
+            assertTrue("Timed out to start fg service", uidListener1.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE, shortTimeoutMs));
+            assertTrue("Timed out to get the remote messenger", latchHolder[0].await(
+                    shortTimeoutMs, TimeUnit.MILLISECONDS));
+            runShellCommand("cmd appops set " + TEST_APP1 + " RUN_ANY_IN_BACKGROUND deny");
+            assertFalse("FGS shouldn't be killed", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE, backgroundSettleMs + shortTimeoutMs));
+
+            // Stop the FGS, it shouldn't be killed because the feature has been turned off.
+            latchHolder[0] = new CountDownLatch(1);
+            handler.sendRemoteMessage(H.MSG_STOP_SERVICE, 0, 0, null);
+            assertTrue("Timed out to wait for stop fg", latchHolder[0].await(
+                    shortTimeoutMs, TimeUnit.MILLISECONDS));
+            assertFalse("FGS shouldn't be killed", uidListener2.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_GONE, backgroundSettleMs + shortTimeoutMs));
+        } finally {
+            runShellCommand("cmd appops set " + TEST_APP1 + " RUN_ANY_IN_BACKGROUND default");
+            if (amConstantsSettings != null) {
+                amConstantsSettings.close();
+            }
+            if (killBgRestrictedAndCachedIdle != null) {
+                killBgRestrictedAndCachedIdle.close();
+            }
+            if (killBgRestrictedAndCachedIdleSettleTime != null) {
+                killBgRestrictedAndCachedIdleSettleTime.close();
+            }
+            am.removeOnUidImportanceListener(uidListener1);
+            am.removeOnUidImportanceListener(uidListener2);
+            am.removeOnUidImportanceListener(uidListener3);
+        }
+    }
+
     @Ignore("Need to disable calling uid check in ActivityManagerService.killPids before this test")
     @Test
     public void testKillPids() throws Exception {
@@ -717,6 +919,7 @@
         static final int MSG_DONE = 1;
         static final int MSG_START_FOREGROUND = 2;
         static final int MSG_STOP_FOREGROUND = 3;
+        static final int MSG_STOP_SERVICE = 4;
 
         private Messenger mRemoteMessenger;
         private CountDownLatch[] mLatchHolder;
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
index 78afb7b..fdaf7cc 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
@@ -27,6 +27,13 @@
         <service android:name=".SimpleIsolatedService"
                  android:isolatedProcess="true"
                  android:exported="true" />
+        <receiver android:name=".SimpleReceiver"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.servicestests.apps.simpleservicetestapp.TEST" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
     </application>
 
 </manifest>
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java
index ccfc0b7..56e1ab7 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java
@@ -38,6 +38,7 @@
     private static final int MSG_DONE = 1;
     private static final int MSG_START_FOREGROUND = 2;
     private static final int MSG_STOP_FOREGROUND = 3;
+    private static final int MSG_STOP_SERVICE = 4;
 
     private static final String ACTION_FGS_STATS_TEST =
             "com.android.servicestests.apps.simpleservicetestapp.ACTION_FGS_STATS_TEST";
@@ -57,6 +58,11 @@
                     stopForeground(true);
                     sendRemoteMessage(MSG_DONE, 0, 0, null);
                 } break;
+                case MSG_STOP_SERVICE: {
+                    Log.i(TAG, "stopSelf");
+                    stopSelf();
+                    sendRemoteMessage(MSG_DONE, 0, 0, null);
+                } break;
             }
         }
     };
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleReceiver.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleReceiver.java
new file mode 100644
index 0000000..1eced84
--- /dev/null
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleReceiver.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.servicestests.apps.simpleservicetestapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class SimpleReceiver extends BroadcastReceiver {
+    private static final String TAG = SimpleReceiver.class.getSimpleName();
+    private static final String EXTRA_CALLBACK = "callback";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "onReceive " + intent);
+        final Bundle extra = intent.getExtras();
+        if (extra != null) {
+            final IBinder binder = extra.getBinder(EXTRA_CALLBACK);
+            if (binder != null) {
+                IRemoteCallback callback = IRemoteCallback.Stub.asInterface(binder);
+                try {
+                    callback.sendResult(null);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+}