Merge "Support to query TYPE_WIFI usage with subscriberId"
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index d3c8957..fd446cd 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -274,11 +274,14 @@
     }
 
     /**
-     * Template to match all carrier networks with the given IMSI.
+     * Template to match all metered carrier networks with the given IMSI.
      */
-    public static NetworkTemplate buildTemplateCarrier(@NonNull String subscriberId) {
+    public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
         Objects.requireNonNull(subscriberId);
-        return new NetworkTemplate(MATCH_CARRIER, subscriberId, null);
+        return new NetworkTemplate(MATCH_CARRIER, subscriberId,
+                new String[] { subscriberId }, null /* networkId */, METERED_YES, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
+                SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
     private final int mMatchRule;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index c34b9f0..98082cb 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6126,6 +6126,17 @@
         }
     }
 
+    /**
+     * Records timing data related to an incoming Binder call in order to attribute
+     * the power consumption to the calling app.
+     */
+    public void noteBinderCallStats(int workSourceUid,
+            Collection<BinderCallsStats.CallStat> callStats) {
+        synchronized (this) {
+            getUidStatsLocked(workSourceUid).noteBinderCallStatsLocked(callStats);
+        }
+    }
+
     public String[] getWifiIfaces() {
         synchronized (mWifiNetworkLock) {
             return mWifiIfaces;
@@ -8693,6 +8704,17 @@
         }
 
         /**
+         * Notes incoming binder call stats associated with this work source UID.
+         */
+        public void noteBinderCallStatsLocked(Collection<BinderCallsStats.CallStat> callStats) {
+            if (DEBUG) {
+                Slog.d(TAG, "noteBinderCalls() workSourceUid = [" + mUid + "], callStats = ["
+                        + new ArrayList<>(callStats) + "]");
+            }
+            // TODO(dplotnikov): finish the implementation by actually remembering the stats
+        }
+
+        /**
          * The statistics associated with a particular wake lock.
          */
         public static class Wakelock extends BatteryStats.Uid.Wakelock {
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index dbba469..4cb5b02 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -19,10 +19,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -95,7 +99,33 @@
     private CachedDeviceState.Readonly mDeviceState;
     private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
 
+    private static final int CALL_STATS_OBSERVER_DEBOUNCE_MILLIS = 5000;
     private BinderLatencyObserver mLatencyObserver;
+    private BinderInternal.CallStatsObserver mCallStatsObserver;
+    private ArraySet<Integer> mSendUidsToObserver = new ArraySet<>(32);
+    private final Handler mCallStatsObserverHandler;
+    private Runnable mCallStatsObserverRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mCallStatsObserver == null) {
+                return;
+            }
+
+            noteCallsStatsDelayed();
+
+            synchronized (mLock) {
+                int size = mSendUidsToObserver.size();
+                for (int i = 0; i < size; i++) {
+                    UidEntry uidEntry = mUidEntries.get(mSendUidsToObserver.valueAt(i));
+                    if (uidEntry != null) {
+                        mCallStatsObserver.noteCallStats(uidEntry.workSourceUid,
+                                uidEntry.getCallStatsList());
+                    }
+                }
+                mSendUidsToObserver.clear();
+            }
+        }
+    };
 
     /** Injector for {@link BinderCallsStats}. */
     public static class Injector {
@@ -103,6 +133,10 @@
             return new Random();
         }
 
+        public Handler getHandler() {
+            return new Handler(Looper.getMainLooper());
+        }
+
         public BinderLatencyObserver getLatencyObserver() {
             return new BinderLatencyObserver(new BinderLatencyObserver.Injector());
         }
@@ -110,6 +144,7 @@
 
     public BinderCallsStats(Injector injector) {
         this.mRandom = injector.getRandomGenerator();
+        this.mCallStatsObserverHandler = injector.getHandler();
         this.mLatencyObserver = injector.getLatencyObserver();
     }
 
@@ -121,6 +156,24 @@
         mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch();
     }
 
+    /**
+     * Registers an observer for call stats, which is invoked periodically with accumulated
+     * binder call stats.
+     */
+    public void setCallStatsObserver(
+            BinderInternal.CallStatsObserver callStatsObserver) {
+        mCallStatsObserver = callStatsObserver;
+        noteCallsStatsDelayed();
+    }
+
+    private void noteCallsStatsDelayed() {
+        mCallStatsObserverHandler.removeCallbacks(mCallStatsObserverRunnable);
+        if (mCallStatsObserver != null) {
+            mCallStatsObserverHandler.postDelayed(mCallStatsObserverRunnable,
+                    CALL_STATS_OBSERVER_DEBOUNCE_MILLIS);
+        }
+    }
+
     @Override
     @Nullable
     public CallSession callStarted(Binder binder, int code, int workSourceUid) {
@@ -233,6 +286,9 @@
                     callStat.callCount++;
                 }
             }
+            if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) {
+                mSendUidsToObserver.add(workSourceUid);
+            }
         }
     }
 
@@ -267,7 +323,7 @@
     }
 
     @Nullable
-    private Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) {
+    private static Method getDefaultTransactionNameMethod(Class<? extends Binder> binder) {
         try {
             return binder.getMethod("getDefaultTransactionName", int.class);
         } catch (NoSuchMethodException e) {
@@ -277,16 +333,17 @@
     }
 
     @Nullable
-    private String resolveTransactionCode(Method getDefaultTransactionName, int transactionCode) {
-        if (getDefaultTransactionName == null) {
-            return null;
+    private static String resolveTransactionCode(Method getDefaultTransactionName,
+            int transactionCode) {
+        String resolvedCode = null;
+        if (getDefaultTransactionName != null) {
+            try {
+                resolvedCode = (String) getDefaultTransactionName.invoke(null, transactionCode);
+            } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) {
+                throw new RuntimeException(e);
+            }
         }
-
-        try {
-            return (String) getDefaultTransactionName.invoke(null, transactionCode);
-        } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) {
-            throw new RuntimeException(e);
-        }
+        return resolvedCode == null ? String.valueOf(transactionCode) : resolvedCode;
     }
 
     /**
@@ -342,11 +399,8 @@
                     || previous.transactionCode != exported.transactionCode;
             final String methodName;
             if (isClassDifferent || isCodeDifferent) {
-                String resolvedCode = resolveTransactionCode(
+                methodName = resolveTransactionCode(
                         getDefaultTransactionName, exported.transactionCode);
-                methodName = resolvedCode == null
-                        ? String.valueOf(exported.transactionCode)
-                        : resolvedCode;
             } else {
                 methodName = previousMethodName;
             }
@@ -657,6 +711,20 @@
             this.transactionCode = transactionCode;
             this.screenInteractive = screenInteractive;
         }
+
+        @Override
+        public String toString() {
+            return "CallStat{"
+                    + "callingUid=" + callingUid
+                    + ", transaction=" + binderClass.getSimpleName()
+                    + '.' + resolveTransactionCode(
+                    getDefaultTransactionNameMethod(binderClass), transactionCode)
+                    + ", callCount=" + callCount
+                    + ", recordedCallCount=" + recordedCallCount
+                    + ", cpuTimeMicros=" + cpuTimeMicros
+                    + ", latencyMicros=" + latencyMicros
+                    + '}';
+        }
     }
 
     /** Key used to store CallStat object in a Map. */
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index 95c36ca..7253307 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -31,6 +31,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collection;
 
 /**
  * Private and debugging Binder APIs.
@@ -134,6 +135,16 @@
     }
 
     /**
+     * Allows to track observe incoming binder call stats.
+     */
+    public interface CallStatsObserver {
+        /**
+         * Notes incoming binder call stats associated with this work source UID.
+         */
+        void noteCallStats(int workSourceUid, Collection<BinderCallsStats.CallStat> callStats);
+    }
+
+    /**
      * Add the calling thread to the IPC thread pool.  This function does
      * not return until the current process is exiting.
      */
diff --git a/core/java/com/android/internal/os/BinderLatencyBuckets.java b/core/java/com/android/internal/os/BinderLatencyBuckets.java
index bdee4ca..d7d2d6a 100644
--- a/core/java/com/android/internal/os/BinderLatencyBuckets.java
+++ b/core/java/com/android/internal/os/BinderLatencyBuckets.java
@@ -20,8 +20,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Arrays;
 
 /**
  * Generates the bucket thresholds (with a custom logarithmic scale) for a histogram to store
@@ -29,7 +28,7 @@
  */
 public class BinderLatencyBuckets {
     private static final String TAG = "BinderLatencyBuckets";
-    private ArrayList<Integer> mBuckets;
+    private final int[] mBuckets;
 
     /**
      * @param bucketCount      the number of buckets the histogram should have
@@ -37,12 +36,11 @@
      * @param scaleFactor      the rate in which each consecutive bucket increases (before rounding)
      */
     public BinderLatencyBuckets(int bucketCount, int firstBucketSize, float scaleFactor) {
-        mBuckets = new ArrayList<>(bucketCount - 1);
-        mBuckets.add(firstBucketSize);
+        int[] buffer = new int[bucketCount - 1];
+        buffer[0] = firstBucketSize;
 
         // Last value and the target are disjoint as we never want to create buckets smaller than 1.
         double lastTarget = firstBucketSize;
-        int lastValue = firstBucketSize;
 
         // First bucket is already created and the last bucket is anything greater than the final
         // bucket in the list, so create 'bucketCount' - 2 buckets.
@@ -50,29 +48,29 @@
             // Increase the target bucket limit value by the scale factor.
             double nextTarget = lastTarget * scaleFactor;
 
-            if (nextTarget > Integer.MAX_VALUE || lastValue == Integer.MAX_VALUE) {
+            if (nextTarget > Integer.MAX_VALUE) {
                 // Do not throw an exception here as this should not affect binder calls.
                 Slog.w(TAG, "Attempted to create a bucket larger than maxint");
+                mBuckets = Arrays.copyOfRange(buffer, 0, i);
                 return;
             }
 
-            if ((int) nextTarget > lastValue) {
+            if ((int) nextTarget > buffer[i - 1]) {
                 // Convert the target bucket limit value to an integer.
-                mBuckets.add((int) nextTarget);
-                lastValue = (int) nextTarget;
+                buffer[i] = (int) nextTarget;
             } else {
                 // Avoid creating redundant buckets, so bucket size should be 1 at a minimum.
-                mBuckets.add(lastValue + 1);
-                lastValue = lastValue + 1;
+                buffer[i] = buffer[i - 1] + 1;
             }
             lastTarget = nextTarget;
         }
+        mBuckets = buffer;
     }
 
     /** Gets the bucket index to insert the provided sample in. */
     public int sampleToBucket(int sample) {
-        if (sample > mBuckets.get(mBuckets.size() - 1)) {
-            return mBuckets.size();
+        if (sample >= mBuckets[mBuckets.length - 1]) {
+            return mBuckets.length;
         }
 
         // Binary search returns the element index if it is contained in the list - in this case the
@@ -80,12 +78,12 @@
         // Otherwise, it returns (-(insertion point) - 1), where insertion point is the point where
         // to insert the element so that the array remains sorted - in this case the bucket index
         // is the insertion point.
-        int searchResult = Collections.binarySearch(mBuckets, sample);
+        int searchResult = Arrays.binarySearch(mBuckets, sample);
         return searchResult < 0 ? -(1 + searchResult) : searchResult + 1;
     }
 
     @VisibleForTesting
-    public ArrayList<Integer> getBuckets() {
+    public int[] getBuckets() {
         return mBuckets;
     }
 }
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 6860759e..508782b 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -21,8 +21,8 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.system.StructCapUserData;
-import android.system.StructCapUserHeader;
+import android.system.StructUserCapData;
+import android.system.StructUserCapHeader;
 import android.util.Slog;
 import android.util.TimingsTraceLog;
 
@@ -187,9 +187,9 @@
      *       capabilities, which may make it crash, but not exceed its allowances.
      */
     private static void preserveCapabilities() {
-        StructCapUserHeader header = new StructCapUserHeader(
+        StructUserCapHeader header = new StructUserCapHeader(
                 OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
-        StructCapUserData[] data;
+        StructUserCapData[] data;
         try {
             data = Os.capget(header);
         } catch (ErrnoException e) {
@@ -199,9 +199,9 @@
 
         if (data[0].permitted != data[0].inheritable ||
                 data[1].permitted != data[1].inheritable) {
-            data[0] = new StructCapUserData(data[0].effective, data[0].permitted,
+            data[0] = new StructUserCapData(data[0].effective, data[0].permitted,
                     data[0].permitted);
-            data[1] = new StructCapUserData(data[1].effective, data[1].permitted,
+            data[1] = new StructUserCapData(data[1].effective, data[1].permitted,
                     data[1].permitted);
             try {
                 Os.capset(header, data);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a541089..0121b78 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -45,8 +45,8 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.system.StructCapUserData;
-import android.system.StructCapUserHeader;
+import android.system.StructUserCapData;
+import android.system.StructUserCapHeader;
 import android.text.Hyphenator;
 import android.util.EventLog;
 import android.util.Log;
@@ -742,9 +742,9 @@
                 OsConstants.CAP_BLOCK_SUSPEND
         );
         /* Containers run without some capabilities, so drop any caps that are not available. */
-        StructCapUserHeader header = new StructCapUserHeader(
+        StructUserCapHeader header = new StructUserCapHeader(
                 OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
-        StructCapUserData[] data;
+        StructUserCapData[] data;
         try {
             data = Os.capget(header);
         } catch (ErrnoException ex) {
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index cec6216..ee6e6c0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -16,10 +16,16 @@
 
 package com.android.internal.os;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
@@ -46,11 +52,12 @@
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public class BinderCallsStatsTest {
-    private static final int WORKSOURCE_UID = 1;
+    private static final int WORKSOURCE_UID = Process.FIRST_APPLICATION_UID;
     private static final int CALLING_UID = 2;
     private static final int REQUEST_SIZE = 2;
     private static final int REPLY_SIZE = 3;
     private final CachedDeviceState mDeviceState = new CachedDeviceState(false, true);
+    private final TestHandler mHandler = new TestHandler();
 
     @Test
     public void testDetailedOff() {
@@ -754,6 +761,51 @@
     }
 
     @Test
+    public void testCallStatsObserver() {
+        TestBinderCallsStats bcs = new TestBinderCallsStats();
+        bcs.setSamplingInterval(1);
+        bcs.setTrackScreenInteractive(false);
+
+        final ArrayList<BinderCallsStats.CallStat> callStatsList = new ArrayList<>();
+        bcs.setCallStatsObserver((workSourceUid, callStats) -> callStatsList.addAll(callStats));
+
+        Binder binder = new Binder();
+
+        CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.time += 10;
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+
+        callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+        bcs.time += 20;
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+
+        callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID);
+        bcs.time += 30;
+        bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+
+        for (Runnable runnable: mHandler.mRunnables) {
+            // Execute all pending runnables. Ignore the delay.
+            runnable.run();
+        }
+
+        assertThat(callStatsList).hasSize(2);
+        for (int i = 0; i < 2; i++) {
+            BinderCallsStats.CallStat callStats = callStatsList.get(i);
+            if (callStats.transactionCode == 1) {
+                assertEquals(2, callStats.callCount);
+                assertEquals(2, callStats.recordedCallCount);
+                assertEquals(30, callStats.cpuTimeMicros);
+                assertEquals(20, callStats.maxCpuTimeMicros);
+            } else {
+                assertEquals(1, callStats.callCount);
+                assertEquals(1, callStats.recordedCallCount);
+                assertEquals(30, callStats.cpuTimeMicros);
+                assertEquals(30, callStats.maxCpuTimeMicros);
+            }
+        }
+    }
+
+    @Test
     public void testLatencyCollectionEnabled() {
         TestBinderCallsStats bcs = new TestBinderCallsStats();
         bcs.setCollectLatencyData(true);
@@ -781,6 +833,20 @@
         assertEquals(0, bcs.getLatencyObserver().getLatencyHistograms().size());
     }
 
+    private static class TestHandler extends Handler {
+        ArrayList<Runnable> mRunnables = new ArrayList<>();
+
+        TestHandler() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            mRunnables.add(msg.getCallback());
+            return true;
+        }
+    }
+
     class TestBinderCallsStats extends BinderCallsStats {
         public int callingUid = CALLING_UID;
         public long time = 1234;
@@ -803,6 +869,10 @@
                     };
                 }
 
+                public Handler getHandler() {
+                    return mHandler;
+                }
+
                 public BinderLatencyObserver getLatencyObserver() {
                     return new BinderLatencyObserverTest.TestBinderLatencyObserver();
                 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
index 00443a9..b2054f1 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
@@ -36,6 +36,7 @@
     public void testBucketThresholds() {
         BinderLatencyBuckets latencyBuckets = new BinderLatencyBuckets(10, 2, 1.45f);
         assertThat(latencyBuckets.getBuckets())
+            .asList()
             .containsExactly(2, 3, 4, 6, 8, 12, 18, 26, 39)
             .inOrder();
     }
@@ -58,6 +59,7 @@
     public void testMaxIntBuckets() {
         BinderLatencyBuckets latencyBuckets = new BinderLatencyBuckets(5, Integer.MAX_VALUE / 2, 2);
         assertThat(latencyBuckets.getBuckets())
+            .asList()
             .containsExactly(Integer.MAX_VALUE / 2, Integer.MAX_VALUE - 1)
             .inOrder();
 
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 730555b..27bf114 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -294,7 +294,6 @@
     method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
     method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
     method @NonNull public android.net.NetworkCapabilities build();
-    method @NonNull public android.net.NetworkCapabilities.Builder clearAll();
     method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
     method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
@@ -308,6 +307,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
     method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
     method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
+    method @NonNull public static android.net.NetworkCapabilities.Builder withoutDefaultCapabilities();
   }
 
   public class NetworkProvider {
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index c19a906..90d821b 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -2399,6 +2399,11 @@
         return mTransportInfo.getApplicableRedactions();
     }
 
+    private NetworkCapabilities removeDefaultCapabilites() {
+        mNetworkCapabilities &= ~DEFAULT_CAPABILITIES;
+        return this;
+    }
+
     /**
      * Builder class for NetworkCapabilities.
      *
@@ -2435,6 +2440,16 @@
         }
 
         /**
+         * Creates a new Builder without the default capabilities.
+         */
+        @NonNull
+        public static Builder withoutDefaultCapabilities() {
+            final NetworkCapabilities nc = new NetworkCapabilities();
+            nc.removeDefaultCapabilites();
+            return new Builder(nc);
+        }
+
+        /**
          * Adds the given transport type.
          *
          * Multiple transports may be added. Note that when searching for a network to satisfy a
@@ -2495,17 +2510,6 @@
         }
 
         /**
-         * Completely clears the contents of this object, removing even the capabilities that are
-         * set by default when the object is constructed.
-         * @return this builder
-         */
-        @NonNull
-        public Builder clearAll() {
-            mCaps.clearAll();
-            return this;
-        }
-
-        /**
          * Sets the owner UID.
          *
          * The default value is {@link Process#INVALID_UID}. Pass this value to reset.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index dd88c5a..e6a96ef 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -200,8 +200,9 @@
 
         private final NetworkCapabilities mNetworkCapabilities;
 
-        // A boolean that represents the user modified NOT_VCN_MANAGED capability.
-        private boolean mModifiedNotVcnManaged = false;
+        // A boolean that represents whether the NOT_VCN_MANAGED capability should be deduced when
+        // the NetworkRequest object is built.
+        private boolean mShouldDeduceNotVcnManaged = true;
 
         /**
          * Default constructor for Builder.
@@ -223,7 +224,7 @@
             // If the caller constructed the builder from a request, it means the user
             // might explicitly want the capabilities from the request. Thus, the NOT_VCN_MANAGED
             // capabilities should not be touched later.
-            mModifiedNotVcnManaged = true;
+            mShouldDeduceNotVcnManaged = false;
         }
 
         /**
@@ -254,7 +255,7 @@
         public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.addCapability(capability);
             if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
-                mModifiedNotVcnManaged = true;
+                mShouldDeduceNotVcnManaged = false;
             }
             return this;
         }
@@ -268,7 +269,7 @@
         public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.removeCapability(capability);
             if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
-                mModifiedNotVcnManaged = true;
+                mShouldDeduceNotVcnManaged = false;
             }
             return this;
         }
@@ -352,7 +353,7 @@
             mNetworkCapabilities.clearAll();
             // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities
             // should not be add back later.
-            mModifiedNotVcnManaged = true;
+            mShouldDeduceNotVcnManaged = false;
             return this;
         }
 
@@ -453,6 +454,9 @@
                 throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted");
             }
             mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
+            // Do not touch NOT_VCN_MANAGED if the caller needs to access to a very specific
+            // Network.
+            mShouldDeduceNotVcnManaged = false;
             return this;
         }
 
@@ -486,12 +490,13 @@
          *      {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to
          *      allow the callers automatically utilize VCN networks if available.
          *   2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED,
+         *      or has clear intention of tracking specific network,
          *      do not alter them to allow user fire request that suits their need.
          *
          * @hide
          */
         private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) {
-            if (mModifiedNotVcnManaged) return;
+            if (!mShouldDeduceNotVcnManaged) return;
             for (final int cap : nc.getCapabilities()) {
                 if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return;
             }
diff --git a/packages/Connectivity/service/src/com/android/server/ConnectivityService.java b/packages/Connectivity/service/src/com/android/server/ConnectivityService.java
index 085943a..e192c8f 100644
--- a/packages/Connectivity/service/src/com/android/server/ConnectivityService.java
+++ b/packages/Connectivity/service/src/com/android/server/ConnectivityService.java
@@ -1045,14 +1045,10 @@
             } else {
                 // ConnectivityService publishes binder service using publishBinderService() with
                 // no priority assigned will be treated as NORMAL priority. Dumpsys does not send
-                // "--dump-priority" arguments to the service. Thus, dump both NORMAL and HIGH to
-                // align the legacy design.
+                // "--dump-priority" arguments to the service. Thus, dump NORMAL only to align the
+                // legacy output for dumpsys connectivity.
                 // TODO: Integrate into signal dump.
                 dumpNormal(fd, pw, args);
-                pw.println();
-                pw.println("DUMP OF SERVICE HIGH connectivity");
-                pw.println();
-                dumpHigh(fd, pw);
             }
         }
     }
diff --git a/packages/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java b/packages/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java
index 506cadb..673c804 100644
--- a/packages/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/packages/Connectivity/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -24,6 +24,7 @@
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
@@ -39,6 +40,8 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.net.ConnectivitySettingsManager;
 import android.net.INetd;
 import android.net.UidRange;
 import android.net.Uri;
@@ -48,7 +51,9 @@
 import android.os.SystemConfigManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.system.OsConstants;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -66,7 +71,6 @@
 import java.util.Map.Entry;
 import java.util.Set;
 
-
 /**
  * A utility class to inform Netd of UID permisisons.
  * Does a mass update at boot and then monitors for app install/remove.
@@ -105,6 +109,14 @@
     @GuardedBy("this")
     private final Set<Integer> mAllApps = new HashSet<>();
 
+    // A set of apps which are allowed to use restricted networks. These apps can't hold the
+    // CONNECTIVITY_USE_RESTRICTED_NETWORKS permission because they can't be signature|privileged
+    // apps. However, these apps should still be able to use restricted networks under certain
+    // conditions (e.g. government app using emergency services). So grant netd system permission
+    // to uids whose package name is listed in APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting.
+    @GuardedBy("this")
+    private final Set<String> mAppsAllowedOnRestrictedNetworks = new ArraySet<>();
+
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -135,6 +147,22 @@
         public int getDeviceFirstSdkInt() {
             return Build.VERSION.FIRST_SDK_INT;
         }
+
+        /**
+         * Get apps allowed to use restricted networks via ConnectivitySettingsManager.
+         */
+        public Set<String> getAppsAllowedOnRestrictedNetworks(@NonNull Context context) {
+            return ConnectivitySettingsManager.getAppsAllowedOnRestrictedNetworks(context);
+        }
+
+        /**
+         * Register ContentObserver for given Uri.
+         */
+        public void registerContentObserver(@NonNull Context context, @NonNull Uri uri,
+                boolean notifyForDescendants, @NonNull ContentObserver observer) {
+            context.getContentResolver().registerContentObserver(
+                    uri, notifyForDescendants, observer);
+        }
     }
 
     public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) {
@@ -157,14 +185,31 @@
     public synchronized void startMonitoring() {
         log("Monitoring");
 
+        final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         intentFilter.addDataScheme("package");
-        mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */).registerReceiver(
+        userAllContext.registerReceiver(
                 mIntentReceiver, intentFilter, null /* broadcastPermission */,
                 null /* scheduler */);
 
+        // Register APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer
+        mDeps.registerContentObserver(
+                userAllContext,
+                Settings.Secure.getUriFor(APPS_ALLOWED_ON_RESTRICTED_NETWORKS),
+                false /* notifyForDescendants */,
+                new ContentObserver(null) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        onSettingChanged();
+                    }
+                });
+
+        // Read APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting and update
+        // mAppsAllowedOnRestrictedNetworks.
+        updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext));
+
         List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS
                 | MATCH_ANY_USER);
         if (apps == null) {
@@ -220,11 +265,33 @@
     }
 
     @VisibleForTesting
+    void updateAppsAllowedOnRestrictedNetworks(final Set<String> apps) {
+        mAppsAllowedOnRestrictedNetworks.clear();
+        mAppsAllowedOnRestrictedNetworks.addAll(apps);
+    }
+
+    @VisibleForTesting
     static boolean isVendorApp(@NonNull ApplicationInfo appInfo) {
         return appInfo.isVendor() || appInfo.isOem() || appInfo.isProduct();
     }
 
     @VisibleForTesting
+    boolean isCarryoverPackage(final ApplicationInfo appInfo) {
+        if (appInfo == null) return false;
+        return (appInfo.targetSdkVersion < VERSION_Q && isVendorApp(appInfo))
+                // Backward compatibility for b/114245686, on devices that launched before Q daemons
+                // and apps running as the system UID are exempted from this check.
+                || (appInfo.uid == SYSTEM_UID && mDeps.getDeviceFirstSdkInt() < VERSION_Q);
+    }
+
+    @VisibleForTesting
+    boolean isAppAllowedOnRestrictedNetworks(@NonNull final PackageInfo app) {
+        // Check whether package name is in allowed on restricted networks app list. If so, this app
+        // can have netd system permission.
+        return mAppsAllowedOnRestrictedNetworks.contains(app.packageName);
+    }
+
+    @VisibleForTesting
     boolean hasPermission(@NonNull final PackageInfo app, @NonNull final String permission) {
         if (app.requestedPermissions == null || app.requestedPermissionsFlags == null) {
             return false;
@@ -241,22 +308,10 @@
 
     @VisibleForTesting
     boolean hasRestrictedNetworkPermission(@NonNull final PackageInfo app) {
-        // TODO : remove this check in the future(b/31479477). All apps should just
-        // request the appropriate permission for their use case since android Q.
-        if (app.applicationInfo != null) {
-            // Backward compatibility for b/114245686, on devices that launched before Q daemons
-            // and apps running as the system UID are exempted from this check.
-            if (app.applicationInfo.uid == SYSTEM_UID && mDeps.getDeviceFirstSdkInt() < VERSION_Q) {
-                return true;
-            }
-
-            if (app.applicationInfo.targetSdkVersion < VERSION_Q
-                    && isVendorApp(app.applicationInfo)) {
-                return true;
-            }
-        }
-
-        return hasPermission(app, PERMISSION_MAINLINE_NETWORK_STACK)
+        // TODO : remove carryover package check in the future(b/31479477). All apps should just
+        //  request the appropriate permission for their use case since android Q.
+        return isCarryoverPackage(app.applicationInfo) || isAppAllowedOnRestrictedNetworks(app)
+                || hasPermission(app, PERMISSION_MAINLINE_NETWORK_STACK)
                 || hasPermission(app, NETWORK_STACK)
                 || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
     }
@@ -410,6 +465,20 @@
         mAllApps.add(UserHandle.getAppId(uid));
     }
 
+    private Boolean highestUidNetworkPermission(int uid) {
+        Boolean permission = null;
+        final String[] packages = mPackageManager.getPackagesForUid(uid);
+        if (!CollectionUtils.isEmpty(packages)) {
+            for (String name : packages) {
+                permission = highestPermissionForUid(permission, name);
+                if (permission == SYSTEM) {
+                    break;
+                }
+            }
+        }
+        return permission;
+    }
+
     /**
      * Called when a package is removed.
      *
@@ -440,19 +509,14 @@
         }
 
         Map<Integer, Boolean> apps = new HashMap<>();
-        Boolean permission = null;
-        String[] packages = mPackageManager.getPackagesForUid(uid);
-        if (packages != null && packages.length > 0) {
-            for (String name : packages) {
-                permission = highestPermissionForUid(permission, name);
-                if (permission == SYSTEM) {
-                    // An app with this UID still has the SYSTEM permission.
-                    // Therefore, this UID must already have the SYSTEM permission.
-                    // Nothing to do.
-                    return;
-                }
-            }
+        final Boolean permission = highestUidNetworkPermission(uid);
+        if (permission == SYSTEM) {
+            // An app with this UID still has the SYSTEM permission.
+            // Therefore, this UID must already have the SYSTEM permission.
+            // Nothing to do.
+            return;
         }
+
         if (permission == mApps.get(uid)) {
             // The permissions of this UID have not changed. Nothing to do.
             return;
@@ -705,6 +769,38 @@
         return mVpnUidRanges.get(iface);
     }
 
+    private synchronized void onSettingChanged() {
+        // Step1. Update apps allowed to use restricted networks and compute the set of packages to
+        // update.
+        final Set<String> packagesToUpdate = new ArraySet<>(mAppsAllowedOnRestrictedNetworks);
+        updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext));
+        packagesToUpdate.addAll(mAppsAllowedOnRestrictedNetworks);
+
+        final Map<Integer, Boolean> updatedApps = new HashMap<>();
+        final Map<Integer, Boolean> removedApps = new HashMap<>();
+
+        // Step2. For each package to update, find out its new permission.
+        for (String app : packagesToUpdate) {
+            final PackageInfo info = getPackageInfo(app);
+            if (info == null || info.applicationInfo == null) continue;
+
+            final int uid = info.applicationInfo.uid;
+            final Boolean permission = highestUidNetworkPermission(uid);
+
+            if (null == permission) {
+                removedApps.put(uid, NETWORK); // Doesn't matter which permission is set here.
+                mApps.remove(uid);
+            } else {
+                updatedApps.put(uid, permission);
+                mApps.put(uid, permission);
+            }
+        }
+
+        // Step3. Update or revoke permission for uids with netd.
+        update(mUsers, updatedApps, true /* add */);
+        update(mUsers, removedApps, false /* add */);
+    }
+
     /** Dump info to dumpsys */
     public void dump(IndentingPrintWriter pw) {
         pw.println("Interface filtering rules:");
diff --git a/packages/Connectivity/tests/unit/java/android/net/NetworkTemplateTest.kt b/packages/Connectivity/tests/unit/java/android/net/NetworkTemplateTest.kt
index ab6b2f4..cb39a0c 100644
--- a/packages/Connectivity/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/packages/Connectivity/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -40,7 +40,7 @@
 import android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT
 import android.net.NetworkTemplate.buildTemplateWifi
 import android.net.NetworkTemplate.buildTemplateWifiWildcard
-import android.net.NetworkTemplate.buildTemplateCarrier
+import android.net.NetworkTemplate.buildTemplateCarrierMetered
 import android.net.NetworkTemplate.buildTemplateMobileWithRatType
 import android.telephony.TelephonyManager
 import com.android.testutils.assertParcelSane
@@ -73,11 +73,12 @@
         type: Int,
         subscriberId: String? = null,
         ssid: String? = null,
-        oemManaged: Int = OEM_NONE
+        oemManaged: Int = OEM_NONE,
+        metered: Boolean = true
     ): NetworkStateSnapshot {
         val lp = LinkProperties()
         val caps = NetworkCapabilities().apply {
-            setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false)
+            setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !metered)
             setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true)
             setSSID(ssid)
             setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID,
@@ -167,25 +168,38 @@
     }
 
     @Test
-    fun testCarrierMatches() {
-        val templateCarrierImsi1 = buildTemplateCarrier(TEST_IMSI1)
+    fun testCarrierMeteredMatches() {
+        val templateCarrierImsi1Metered = buildTemplateCarrierMetered(TEST_IMSI1)
 
-        val identMobile1 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI1),
-                false, TelephonyManager.NETWORK_TYPE_UMTS)
-        val identMobile2 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI2),
-                false, TelephonyManager.NETWORK_TYPE_UMTS)
-        val identWifiSsid1 = buildNetworkIdentity(
-                mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0)
-        val identCarrierWifiImsi1 = buildNetworkIdentity(
-                mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
-        val identCarrierWifiImsi2 = buildNetworkIdentity(
-                mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_SSID1), true, 0)
+        val mobileImsi1 = buildMobileNetworkState(TEST_IMSI1)
+        val mobileImsi1Unmetered = buildNetworkState(TYPE_MOBILE, TEST_IMSI1, null /* ssid */,
+                OEM_NONE, false /* metered */)
+        val mobileImsi2 = buildMobileNetworkState(TEST_IMSI2)
+        val wifiSsid1 = buildWifiNetworkState(null /* subscriberId */, TEST_SSID1)
+        val wifiImsi1Ssid1 = buildWifiNetworkState(TEST_IMSI1, TEST_SSID1)
+        val wifiImsi1Ssid1Unmetered = buildNetworkState(TYPE_WIFI, TEST_IMSI1, TEST_SSID1,
+                OEM_NONE, false /* metered */)
 
-        templateCarrierImsi1.assertMatches(identCarrierWifiImsi1)
-        templateCarrierImsi1.assertDoesNotMatch(identCarrierWifiImsi2)
-        templateCarrierImsi1.assertDoesNotMatch(identWifiSsid1)
-        templateCarrierImsi1.assertMatches(identMobile1)
-        templateCarrierImsi1.assertDoesNotMatch(identMobile2)
+        val identMobileImsi1Metered = buildNetworkIdentity(mockContext,
+                mobileImsi1, false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+        val identMobileImsi1Unmetered = buildNetworkIdentity(mockContext,
+                mobileImsi1Unmetered, false /* defaultNetwork */,
+                TelephonyManager.NETWORK_TYPE_UMTS)
+        val identMobileImsi2Metered = buildNetworkIdentity(mockContext,
+                mobileImsi2, false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+        val identWifiSsid1Metered = buildNetworkIdentity(
+                mockContext, wifiSsid1, true /* defaultNetwork */, 0 /* subType */)
+        val identCarrierWifiImsi1Metered = buildNetworkIdentity(
+                mockContext, wifiImsi1Ssid1, true /* defaultNetwork */, 0 /* subType */)
+        val identCarrierWifiImsi1NonMetered = buildNetworkIdentity(mockContext,
+                wifiImsi1Ssid1Unmetered, true /* defaultNetwork */, 0 /* subType */)
+
+        templateCarrierImsi1Metered.assertMatches(identMobileImsi1Metered)
+        templateCarrierImsi1Metered.assertDoesNotMatch(identMobileImsi1Unmetered)
+        templateCarrierImsi1Metered.assertDoesNotMatch(identMobileImsi2Metered)
+        templateCarrierImsi1Metered.assertDoesNotMatch(identWifiSsid1Metered)
+        templateCarrierImsi1Metered.assertMatches(identCarrierWifiImsi1Metered)
+        templateCarrierImsi1Metered.assertDoesNotMatch(identCarrierWifiImsi1NonMetered)
     }
 
     @Test
diff --git a/packages/Connectivity/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/packages/Connectivity/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 02a5808..c75618f 100644
--- a/packages/Connectivity/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/packages/Connectivity/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -30,6 +30,8 @@
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.Process.SYSTEM_UID;
 
 import static com.android.server.connectivity.PermissionMonitor.NETWORK;
@@ -43,8 +45,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.AdditionalMatchers.aryEq;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
@@ -61,6 +65,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.database.ContentObserver;
 import android.net.INetd;
 import android.net.UidRange;
 import android.net.Uri;
@@ -68,6 +73,7 @@
 import android.os.SystemConfigManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArraySet;
 import android.util.SparseIntArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -136,6 +142,7 @@
         final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mContext));
         doReturn(UserHandle.ALL).when(asUserCtx).getUser();
         when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
+        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
 
         mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps));
 
@@ -145,8 +152,15 @@
 
     private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion, int uid,
             String... permissions) {
+        return hasRestrictedNetworkPermission(
+                partition, targetSdkVersion, "" /* packageName */, uid, permissions);
+    }
+
+    private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion,
+            String packageName, int uid, String... permissions) {
         final PackageInfo packageInfo =
                 packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, permissions, partition);
+        packageInfo.packageName = packageName;
         packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion;
         packageInfo.applicationInfo.uid = uid;
         return mPermissionMonitor.hasRestrictedNetworkPermission(packageInfo);
@@ -280,6 +294,8 @@
                 PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
         assertFalse(hasRestrictedNetworkPermission(
                 PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_SYSTEM, VERSION_P, MOCK_UID1, PERMISSION_MAINLINE_NETWORK_STACK));
 
         assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
         assertFalse(hasRestrictedNetworkPermission(
@@ -324,6 +340,90 @@
                 PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_NETWORK_STATE));
     }
 
+    @Test
+    public void testHasRestrictedNetworkPermissionAppAllowedOnRestrictedNetworks() {
+        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
+                new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1, CHANGE_NETWORK_STATE));
+        assertTrue(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1, CONNECTIVITY_INTERNAL));
+
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1));
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1, CHANGE_NETWORK_STATE));
+        assertFalse(hasRestrictedNetworkPermission(
+                PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1, CONNECTIVITY_INTERNAL));
+
+    }
+
+    private boolean wouldBeCarryoverPackage(String partition, int targetSdkVersion, int uid) {
+        final PackageInfo packageInfo = packageInfoWithPermissions(
+                REQUESTED_PERMISSION_GRANTED, new String[] {}, partition);
+        packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion;
+        packageInfo.applicationInfo.uid = uid;
+        return mPermissionMonitor.isCarryoverPackage(packageInfo.applicationInfo);
+    }
+
+    @Test
+    public void testIsCarryoverPackage() {
+        doReturn(VERSION_P).when(mDeps).getDeviceFirstSdkInt();
+        assertTrue(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID));
+        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, SYSTEM_UID));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
+        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, MOCK_UID1));
+        assertTrue(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID));
+        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, SYSTEM_UID));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, MOCK_UID1));
+
+        doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt();
+        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID));
+        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, SYSTEM_UID));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
+        assertTrue(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_P, MOCK_UID1));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, SYSTEM_UID));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_VENDOR, VERSION_Q, MOCK_UID1));
+
+        assertFalse(wouldBeCarryoverPackage(PARTITION_OEM, VERSION_Q, SYSTEM_UID));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_PRODUCT, VERSION_Q, SYSTEM_UID));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_OEM, VERSION_Q, MOCK_UID1));
+        assertFalse(wouldBeCarryoverPackage(PARTITION_PRODUCT, VERSION_Q, MOCK_UID1));
+    }
+
+    private boolean wouldBeAppAllowedOnRestrictedNetworks(String packageName) {
+        final PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = packageName;
+        return mPermissionMonitor.isAppAllowedOnRestrictedNetworks(packageInfo);
+    }
+
+    @Test
+    public void testIsAppAllowedOnRestrictedNetworks() {
+        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(new ArraySet<>());
+        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
+        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+
+        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
+                new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+        assertTrue(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
+        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+
+        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
+                new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
+        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
+        assertTrue(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+
+        mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
+                new ArraySet<>(new String[] { "com.android.test" }));
+        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
+        assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+    }
+
     private void assertBackgroundPermission(boolean hasPermission, String name, int uid,
             String... permissions) throws Exception {
         when(mPackageManager.getPackageInfo(eq(name), anyInt()))
@@ -800,4 +900,102 @@
         mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[] { MOCK_UID1 });
     }
 
-}
+    @Test
+    public void testAppsAllowedOnRestrictedNetworksChanged() throws Exception {
+        final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
+        final ArgumentCaptor<ContentObserver> captor =
+                ArgumentCaptor.forClass(ContentObserver.class);
+        verify(mDeps, times(1)).registerContentObserver(any(),
+                argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)),
+                anyBoolean(), captor.capture());
+        final ContentObserver contentObserver = captor.getValue();
+
+        mPermissionMonitor.onUserAdded(MOCK_USER1);
+        // Prepare PackageInfo for MOCK_PACKAGE1
+        final PackageInfo packageInfo = buildPackageInfo(
+                false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1);
+        packageInfo.packageName = MOCK_PACKAGE1;
+        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), anyInt())).thenReturn(packageInfo);
+        when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{MOCK_PACKAGE1});
+        // Prepare PackageInfo for MOCK_PACKAGE2
+        final PackageInfo packageInfo2 = buildPackageInfo(
+                false /* hasSystemPermission */, MOCK_UID2, MOCK_USER1);
+        packageInfo2.packageName = MOCK_PACKAGE2;
+        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
+        when(mPackageManager.getPackagesForUid(MOCK_UID2)).thenReturn(new String[]{MOCK_PACKAGE2});
+
+        // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1
+        // should have SYSTEM permission.
+        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
+                new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+        contentObserver.onChange(true /* selfChange */);
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+        mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
+
+        // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID2
+        // should have SYSTEM permission but MOCK_UID1 should revoke permission.
+        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
+                new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
+        contentObserver.onChange(true /* selfChange */);
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
+        mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+        // No app lists in setting, should revoke permission from all uids.
+        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+        contentObserver.onChange(true /* selfChange */);
+        mNetdMonitor.expectNoPermission(
+                new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1, MOCK_UID2});
+    }
+
+    @Test
+    public void testAppsAllowedOnRestrictedNetworksChangedWithSharedUid() throws Exception {
+        final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
+        final ArgumentCaptor<ContentObserver> captor =
+                ArgumentCaptor.forClass(ContentObserver.class);
+        verify(mDeps, times(1)).registerContentObserver(any(),
+                argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)),
+                anyBoolean(), captor.capture());
+        final ContentObserver contentObserver = captor.getValue();
+
+        mPermissionMonitor.onUserAdded(MOCK_USER1);
+        // Prepare PackageInfo for MOCK_PACKAGE1 and MOCK_PACKAGE2 with shared uid MOCK_UID1.
+        final PackageInfo packageInfo = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE);
+        packageInfo.applicationInfo.uid = MOCK_USER1.getUid(MOCK_UID1);
+        packageInfo.packageName = MOCK_PACKAGE1;
+        final PackageInfo packageInfo2 = buildPackageInfo(
+                false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1);
+        packageInfo2.packageName = MOCK_PACKAGE2;
+        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), anyInt())).thenReturn(packageInfo);
+        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
+        when(mPackageManager.getPackagesForUid(MOCK_UID1))
+                .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2});
+
+        // MOCK_PACKAGE1 have CHANGE_NETWORK_STATE, MOCK_UID1 should have NETWORK permission.
+        addPackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1);
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+        // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID1
+        // should upgrade to SYSTEM permission.
+        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
+                new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
+        contentObserver.onChange(true /* selfChange */);
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+        // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1
+        // should still have SYSTEM permission.
+        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
+                new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+        contentObserver.onChange(true /* selfChange */);
+        mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+        // No app lists in setting, MOCK_UID1 should downgrade to NETWORK permission.
+        when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+        contentObserver.onChange(true /* selfChange */);
+        mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+
+        // MOCK_PACKAGE1 removed, should revoke permission from MOCK_UID1.
+        when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{MOCK_PACKAGE2});
+        removePackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1);
+        mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 679f18e..5c5edb2 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -16,6 +16,10 @@
 
 package android.os;
 
+import com.android.internal.os.BinderCallsStats;
+
+import java.util.Collection;
+
 /**
  * Battery stats local system service interface. This is used to pass internal data out of
  * BatteryStatsImpl, as well as make unchecked calls into BatteryStatsImpl.
@@ -41,4 +45,10 @@
      * @param sinceLast how long in millis has it been since a job was run
      */
     public abstract void noteJobsDeferred(int uid, int numDeferred, long sinceLast);
+
+    /**
+     * Informs battery stats of binder stats for the given work source UID.
+     */
+    public abstract void noteBinderCallStats(int workSourceUid,
+            Collection<BinderCallsStats.CallStat> callStats);
 }
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index f4a8f37..339ca84 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Process;
 import android.os.SystemProperties;
@@ -173,10 +174,10 @@
             }
 
             try {
-                    mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
-                            Settings.Global.BINDER_CALLS_STATS));
+                mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
+                        Settings.Global.BINDER_CALLS_STATS));
             } catch (IllegalArgumentException e) {
-                    Slog.e(TAG, "Bad binder call stats settings", e);
+                Slog.e(TAG, "Bad binder call stats settings", e);
             }
             mBinderCallsStats.setDetailedTracking(mParser.getBoolean(
                     SETTINGS_DETAILED_TRACKING_KEY, BinderCallsStats.DETAILED_TRACKING_DEFAULT));
@@ -298,6 +299,11 @@
                 CachedDeviceState.Readonly deviceState = getLocalService(
                         CachedDeviceState.Readonly.class);
                 mBinderCallsStats.setDeviceState(deviceState);
+
+                BatteryStatsInternal batteryStatsInternal = getLocalService(
+                        BatteryStatsInternal.class);
+                mBinderCallsStats.setCallStatsObserver(batteryStatsInternal::noteBinderCallStats);
+
                 // It needs to be called before mService.systemReady to make sure the observer is
                 // initialized before installing it.
                 mWorkSourceProvider.systemReady(getContext());
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2744f11..5122be2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10272,11 +10272,13 @@
                 if (lines > 0) {
                     sb.append("\n");
 
-                    // Merge several logcat streams, and take the last N lines
                     InputStreamReader input = null;
                     try {
                         java.lang.Process logcat = new ProcessBuilder(
-                                "/system/bin/timeout", "-k", "15s", "10s",
+                                // Time out after 10s, but kill logcat with SEGV
+                                // so we can investigate why it didn't finish.
+                                "/system/bin/timeout", "-s", "SEGV", "10s",
+                                // Merge several logcat streams, and take the last N lines.
                                 "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
                                 "-b", "main", "-b", "crash", "-t", String.valueOf(lines))
                                         .redirectErrorStream(true).start();
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 226802c..0260beb 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -66,6 +66,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.RailStats;
 import com.android.internal.os.RpmStats;
@@ -87,6 +88,7 @@
 import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
@@ -304,6 +306,12 @@
             if (DBG) Slog.d(TAG, "Jobs deferred " + uid + ": " + numDeferred + " " + sinceLast);
             BatteryStatsService.this.noteJobsDeferred(uid, numDeferred, sinceLast);
         }
+
+        @Override
+        public void noteBinderCallStats(int workSourceUid,
+                Collection<BinderCallsStats.CallStat> callStats) {
+            mStats.noteBinderCallStats(workSourceUid, callStats);
+        }
     }
 
     private static void awaitUninterruptibly(Future<?> future) {
@@ -1771,5 +1779,4 @@
             Binder.restoreCallingIdentity(ident);
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 77c1c1d..49a0a88 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -31,6 +31,9 @@
 import android.content.pm.PackageInfo;
 import android.os.BatteryManagerInternal;
 import android.os.Environment;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -82,10 +85,15 @@
     private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2;
     // Optimizations should be aborted. No space left on device.
     private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
+    // Optimizations should be aborted. Thermal throttling level too high.
+    private static final int OPTIMIZE_ABORT_THERMAL = 4;
 
     // Used for calculating space threshold for downgrading unused apps.
     private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
 
+    // Thermal cutoff value used if one isn't defined by a system property.
+    private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE;
+
     /**
      * Set of failed packages remembered across job runs.
      */
@@ -107,8 +115,14 @@
     private static final long mDowngradeUnusedAppsThresholdInMillis =
             getDowngradeUnusedAppsThresholdInMillis();
 
+    private final IThermalService mThermalService =
+            IThermalService.Stub.asInterface(
+                ServiceManager.getService(Context.THERMAL_SERVICE));
+
     private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>();
 
+    private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
+
     public static void schedule(Context context) {
         if (isBackgroundDexoptDisabled()) {
             return;
@@ -251,12 +265,18 @@
                     Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
                 } else if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                     Slog.w(TAG, "Idle optimizations aborted by job scheduler.");
+                } else if (result == OPTIMIZE_ABORT_THERMAL) {
+                    Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
                 } else {
                     Slog.w(TAG, "Idle optimizations ended with unexpected code: " + result);
                 }
-                if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+
+                if (result == OPTIMIZE_ABORT_THERMAL) {
+                    // Abandon our timeslice and reschedule
+                    jobFinished(jobParams, /* wantsReschedule */ true);
+                } else if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                     // Abandon our timeslice and do not reschedule.
-                    jobFinished(jobParams, /* reschedule */ false);
+                    jobFinished(jobParams, /* wantsReschedule */ false);
                 }
             }
         }.start();
@@ -542,6 +562,24 @@
             // JobScheduler requested an early abort.
             return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
         }
+
+        // Abort background dexopt if the device is in a moderate or stronger thermal throttling
+        // state.
+        try {
+            final int thermalStatus = mThermalService.getCurrentThermalStatus();
+
+            if (DEBUG) {
+                Log.i(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
+            }
+
+            if (thermalStatus >= mThermalStatusCutoff) {
+                return OPTIMIZE_ABORT_THERMAL;
+            }
+        } catch (RemoteException ex) {
+            // Because this is a intra-process Binder call it is impossible for a RemoteException
+            // to be raised.
+        }
+
         long usableSpace = mDataDir.getUsableSpace();
         if (usableSpace < lowStorageThreshold) {
             // Rather bail than completely fill up the disk.
@@ -603,6 +641,9 @@
             return false;
         }
 
+        mThermalStatusCutoff =
+            SystemProperties.getInt("dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT);
+
         boolean result;
         if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
             result = runPostBootUpdate(params, pm, pkgs);
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/OWNERS b/tests/BackgroundDexOptServiceIntegrationTests/OWNERS
new file mode 100644
index 0000000..3414a74
--- /dev/null
+++ b/tests/BackgroundDexOptServiceIntegrationTests/OWNERS
@@ -0,0 +1 @@
+include platform/art:/OWNERS
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
index e05816e..90ddb6f 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
+++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.os.storage.StorageManager;
 import android.util.Log;
@@ -201,11 +202,16 @@
         fillUpStorage((long) (getStorageLowBytes() * LOW_STORAGE_MULTIPLIER));
     }
 
-    // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run
     private static void runBackgroundDexOpt() throws IOException {
+        runBackgroundDexOpt("Success");
+    }
+
+    // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run
+    private static void runBackgroundDexOpt(String expectedStatus) throws IOException {
         String result = runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME);
-        if (!result.trim().equals("Success")) {
-            throw new IllegalStateException("Expected command success, received >" + result + "<");
+        if (!result.trim().equals(expectedStatus)) {
+            throw new IllegalStateException("Expected status: " + expectedStatus
+                + "; Received: " + result.trim());
         }
     }
 
@@ -242,6 +248,16 @@
         runShellCommand(String.format("cmd package compile -f -m %s %s", filter, pkg));
     }
 
+    // Override the thermal status of the device
+    public static void overrideThermalStatus(int status) throws IOException {
+        runShellCommand("cmd thermalservice override-status " + status);
+    }
+
+    // Reset the thermal status of the device
+    public static void resetThermalStatus() throws IOException {
+        runShellCommand("cmd thermalservice reset");
+    }
+
     // Test that background dexopt under normal conditions succeeds.
     @Test
     public void testBackgroundDexOpt() throws IOException {
@@ -307,4 +323,17 @@
         }
     }
 
+    // Test that background dexopt job doesn't trigger if the device is under thermal throttling.
+    @Test
+    public void testBackgroundDexOptThermalThrottling() throws IOException {
+        try {
+            compilePackageWithFilter(PACKAGE_NAME, "verify");
+            overrideThermalStatus(PowerManager.THERMAL_STATUS_MODERATE);
+            // The bgdexopt task should fail when onStartJob is run
+            runBackgroundDexOpt("Failure");
+            Assert.assertEquals("verify", getCompilerFilter(PACKAGE_NAME));
+        } finally {
+            resetThermalStatus();
+        }
+    }
 }