[automerger skipped] Replace the usage of UidRange am: 2257ecc830 -s ours am: 5e8d1596b6 -s ours

am skip reason: skip tag Change-Id I4bc0daf5ad9e4b4043f4a897ddab16aec8f8a536 with SHA-1 41918d6cd1 is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/13958466

Change-Id: I056312ed3ec9c22854a09fa2f75cacca19c07e03
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ef96d88..389453a 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -15,7 +15,8 @@
   ],
   "mainline-presubmit": [
     {
-      "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+      // TODO: add back the tethering modules when updatable in this branch
+      "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex]",
       "options": [
         {
           "exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -26,7 +27,8 @@
   // Tests on physical devices with SIM cards: postsubmit only for capacity constraints
   "mainline-postsubmit": [
     {
-      "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+      // TODO: add back the tethering module when updatable in this branch
+      "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex]",
       "keywords": ["sim"]
     }
   ]
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 742fd02..4eafc2a 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -25,7 +25,7 @@
     srcs: [
         "apishim/**/*.java",
         "src/**/*.java",
-        ":framework-tethering-shared-srcs",
+        ":framework-connectivity-shared-srcs",
         ":tethering-module-utils-srcs",
         ":services-tethering-shared-srcs",
     ],
@@ -41,6 +41,7 @@
         "netd-client",
     ],
     libs: [
+        "framework-connectivity",
         "framework-statsd.stubs.module_lib",
         "framework-tethering.impl",
         "framework-wifi",
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 6703e46..164bda4 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -20,14 +20,25 @@
 
 apex {
     name: "com.android.tethering",
-    updatable: true,
-    min_sdk_version: "30",
-    java_libs: ["framework-tethering"],
+    // TODO: make updatable again once this contains only updatable artifacts (in particular, this
+    // cannot build as updatable unless service-connectivity builds against stable API).
+    updatable: false,
+    // min_sdk_version: "30",
+    java_libs: [
+        "framework-tethering",
+        "service-connectivity",
+    ],
+    jni_libs: [
+        "libservice-connectivity",
+    ],
     bpfs: [
         "offload.o",
         "test.o",
     ],
-    apps: ["Tethering"],
+    apps: [
+        "ServiceConnectivityResources",
+        "Tethering",
+    ],
     manifest: "manifest.json",
     key: "com.android.tethering.key",
 
@@ -50,6 +61,7 @@
     base: "com.android.tethering",
     package_name: "com.android.tethering.inprocess",
     apps: [
+        "ServiceConnectivityResources",
         "InProcessTethering",
     ],
 }
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 2631d08..b141eae 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -26,6 +26,13 @@
     ],
 
     srcs: [":framework-tethering-srcs"],
+    libs: ["framework-connectivity"],
+    stub_only_libs: ["framework-connectivity"],
+    aidl: {
+        include_dirs: [
+            "frameworks/base/packages/Connectivity/framework/aidl-export",
+        ],
+    },
 
     jarjar_rules: "jarjar-rules.txt",
     installable: true,
diff --git a/Tethering/jarjar-rules.txt b/Tethering/jarjar-rules.txt
index d1ad569..5de4b97 100644
--- a/Tethering/jarjar-rules.txt
+++ b/Tethering/jarjar-rules.txt
@@ -1,5 +1,5 @@
-# These must be kept in sync with the framework-tethering-shared-srcs filegroup.
-# Classes from the framework-tethering-shared-srcs filegroup.
+# These must be kept in sync with the framework-connectivity-shared-srcs filegroup.
+# Classes from the framework-connectivity-shared-srcs filegroup.
 # If there are files in that filegroup that are not covered below, the classes in the
 # module will be overwritten by the ones in the framework.
 rule com.android.internal.util.** com.android.networkstack.tethering.util.@1
diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 7c0b7cc..afccd0a 100644
--- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -37,7 +37,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.TrafficStatsConstants;
+import com.android.net.module.util.NetworkStackConstants;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -589,7 +589,7 @@
         final int send_timout_ms = 300;
 
         final int oldTag = TrafficStats.getAndSetThreadStatsTag(
-                TrafficStatsConstants.TAG_SYSTEM_NEIGHBOR);
+                NetworkStackConstants.TAG_SYSTEM_NEIGHBOR);
         try {
             mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
             // Setting SNDTIMEO is purely for defensive purposes.
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 1622175..f3cd549 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -302,10 +302,8 @@
     boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
             @NonNull String callingPackage, @Nullable String callingAttributionTag,
             boolean throwException) {
-        // TODO: on S and above, pass the attribution tag to Settings instead of throwing it away.
-        // This will likely require a SettingsShim class.
         return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage,
-                throwException);
+                callingAttributionTag, throwException);
     }
 
     /**
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index d469b37..41135f2 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -33,6 +33,7 @@
     sdk_version: "core_platform",
     libs: [
         "framework-minus-apex",
+        "framework-connectivity.stubs.module_lib",
         "framework-tethering.impl",
     ],
     visibility: [
@@ -66,6 +67,7 @@
         "ext",
         "framework-minus-apex",
         "framework-res",
+        "framework-connectivity.impl",
         "framework-tethering.impl",
         "framework-wifi.stubs.module_lib",
     ],
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index b7fefaf..7a73313 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -23,7 +23,10 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.cts.net.NetworkPolicyTestsPreparer" />
 
+    <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.net.hostside.app2" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.net.hostside.app2" />
         <option name="teardown-command" value="cmd power set-mode 0" />
         <option name="teardown-command" value="cmd battery reset" />
         <option name="teardown-command" value="cmd netpolicy stop-watching" />
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
index 5aafdf0..fbbb68b 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
@@ -16,6 +16,8 @@
 
 package com.android.cts.net.hostside;
 
+import android.app.job.JobInfo;
+
 import com.android.cts.net.hostside.INetworkCallback;
 
 interface IMyService {
@@ -26,4 +28,5 @@
     void sendNotification(int notificationId, String notificationType);
     void registerNetworkCallback(in INetworkCallback cb);
     void unregisterNetworkCallback();
+    void scheduleJob(in JobInfo jobInfo);
 }
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
index 165f530..19198c5 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
@@ -17,6 +17,10 @@
 package com.android.cts.net.hostside;
 
 interface INetworkStateObserver {
-    boolean isForeground();
-    void onNetworkStateChecked(String resultData);
+    void onNetworkStateChecked(int resultCode, String resultData);
+
+    const int RESULT_SUCCESS_NETWORK_STATE_CHECKED = 0;
+    const int RESULT_ERROR_UNEXPECTED_PROC_STATE = 1;
+    const int RESULT_ERROR_UNEXPECTED_CAPABILITIES = 2;
+    const int RESULT_ERROR_OTHER = 3;
 }
\ No newline at end of file
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index 50fda6d..f351b47 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -37,10 +37,6 @@
         "android.test.base",
     ],
     srcs: ["src/**/*.java"],
-    // STOPSHIP: remove this before releasing any networking modules.
-    exclude_srcs: [
-        "src/com/android/cts/net/hostside/VpnTest.java",
-    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
index f9e30b6..d9ff539 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
@@ -50,7 +50,7 @@
     public final void tearDown() throws Exception {
         super.tearDown();
 
-        executeSilentShellCommand("cmd battery reset");
+        resetBatteryState();
         setAppIdle(false);
     }
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index 6f32c56..e0ce4ea 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
@@ -101,7 +101,7 @@
     @Test
     public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
             throws Exception {
-        setPendingIntentWhitelistDuration(NETWORK_TIMEOUT_MS);
+        setPendingIntentAllowlistDuration(NETWORK_TIMEOUT_MS);
         try {
             registerNotificationListenerService();
             setDozeMode(true);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java
new file mode 100644
index 0000000..a850e3b
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.cts.net.hostside;
+
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DOZE_MODE;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AbstractExpeditedJobTest extends AbstractRestrictBackgroundNetworkTestCase {
+    @Before
+    public final void setUp() throws Exception {
+        super.setUp();
+        resetDeviceState();
+    }
+
+    @After
+    public final void tearDown() throws Exception {
+        super.tearDown();
+        resetDeviceState();
+    }
+
+    private void resetDeviceState() throws Exception {
+        resetBatteryState();
+        setBatterySaverMode(false);
+        setRestrictBackground(false);
+        setAppIdle(false);
+        setDozeMode(false);
+    }
+
+    @Test
+    @RequiredProperties({BATTERY_SAVER_MODE})
+    public void testNetworkAccess_batterySaverMode() throws Exception {
+        assertBackgroundNetworkAccess(true);
+        assertExpeditedJobHasNetworkAccess();
+
+        setBatterySaverMode(true);
+        assertBackgroundNetworkAccess(false);
+        assertExpeditedJobHasNetworkAccess();
+    }
+
+    @Test
+    @RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
+    public void testNetworkAccess_dataSaverMode() throws Exception {
+        assertBackgroundNetworkAccess(true);
+        assertExpeditedJobHasNetworkAccess();
+
+        setRestrictBackground(true);
+        assertBackgroundNetworkAccess(false);
+        assertExpeditedJobHasNoNetworkAccess();
+    }
+
+    @Test
+    @RequiredProperties({APP_STANDBY_MODE})
+    public void testNetworkAccess_appIdleState() throws Exception {
+        turnBatteryOn();
+        assertBackgroundNetworkAccess(true);
+        assertExpeditedJobHasNetworkAccess();
+
+        setAppIdle(true);
+        assertBackgroundNetworkAccess(false);
+        assertExpeditedJobHasNetworkAccess();
+    }
+
+    @Test
+    @RequiredProperties({DOZE_MODE})
+    public void testNetworkAccess_dozeMode() throws Exception {
+        assertBackgroundNetworkAccess(true);
+        assertExpeditedJobHasNetworkAccess();
+
+        setDozeMode(true);
+        assertBackgroundNetworkAccess(false);
+        assertExpeditedJobHasNetworkAccess();
+    }
+
+    @Test
+    @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK})
+    public void testNetworkAccess_dataAndBatterySaverMode() throws Exception {
+        assertBackgroundNetworkAccess(true);
+        assertExpeditedJobHasNetworkAccess();
+
+        setRestrictBackground(true);
+        setBatterySaverMode(true);
+        assertBackgroundNetworkAccess(false);
+        assertExpeditedJobHasNoNetworkAccess();
+    }
+
+    @Test
+    @RequiredProperties({DOZE_MODE, DATA_SAVER_MODE, METERED_NETWORK})
+    public void testNetworkAccess_dozeAndDataSaverMode() throws Exception {
+        assertBackgroundNetworkAccess(true);
+        assertExpeditedJobHasNetworkAccess();
+
+        setRestrictBackground(true);
+        setDozeMode(true);
+        assertBackgroundNetworkAccess(false);
+        assertExpeditedJobHasNoNetworkAccess();
+    }
+
+    @Test
+    @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK, DOZE_MODE,
+            APP_STANDBY_MODE})
+    public void testNetworkAccess_allRestrictionsEnabled() throws Exception {
+        assertBackgroundNetworkAccess(true);
+        assertExpeditedJobHasNetworkAccess();
+
+        setRestrictBackground(true);
+        setBatterySaverMode(true);
+        setAppIdle(true);
+        setDozeMode(true);
+        assertBackgroundNetworkAccess(false);
+        assertExpeditedJobHasNoNetworkAccess();
+    }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index f423503..ac649b2 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -22,10 +22,12 @@
 import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
 
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.forceRunJob;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getWifiManager;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
 
@@ -38,6 +40,7 @@
 import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.NotificationManager;
+import android.app.job.JobInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -46,19 +49,23 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
-import android.net.wifi.WifiManager;
 import android.os.BatteryManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.service.notification.NotificationListenerService;
 import android.util.Log;
+import android.util.Pair;
+
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 
 import org.junit.Rule;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -75,12 +82,22 @@
 
     private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
     private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
+    private static final String TEST_APP2_JOB_SERVICE_CLASS = TEST_APP2_PKG + ".MyJobService";
+
+    private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(
+            TEST_APP2_PKG, TEST_APP2_JOB_SERVICE_CLASS);
+
+    private static final int TEST_JOB_ID = 7357437;
 
     private static final int SLEEP_TIME_SEC = 1;
 
     // Constants below must match values defined on app2's Common.java
     private static final String MANIFEST_RECEIVER = "ManifestReceiver";
     private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
+    private static final String ACTION_FINISH_ACTIVITY =
+            "com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
+    private static final String ACTION_FINISH_JOB =
+            "com.android.cts.net.hostside.app2.action.FINISH_JOB";
 
     private static final String ACTION_RECEIVER_READY =
             "com.android.cts.net.hostside.app2.action.RECEIVER_READY";
@@ -102,17 +119,22 @@
     private static final String NETWORK_STATUS_SEPARATOR = "\\|";
     private static final int SECOND_IN_MS = 1000;
     static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
+
     private static int PROCESS_STATE_FOREGROUND_SERVICE;
+    private static int PROCESS_STATE_IMPORTANT_FOREGROUND;
 
     private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
+    private static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
 
     protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
     protected static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
+    protected static final int TYPE_EXPEDITED_JOB = 2;
 
     private static final int BATTERY_STATE_TIMEOUT_MS = 5000;
     private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 500;
 
-    private static final int FOREGROUND_PROC_NETWORK_TIMEOUT_MS = 6000;
+    private static final int ACTIVITY_NETWORK_STATE_TIMEOUT_MS = 6_000;
+    private static final int JOB_NETWORK_STATE_TIMEOUT_MS = 10_000;
 
     // Must be higher than NETWORK_TIMEOUT_MS
     private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
@@ -130,24 +152,27 @@
     protected int mUid;
     private int mMyUid;
     private MyServiceClient mServiceClient;
-    private String mDeviceIdleConstantsSetting;
+    private DeviceConfigStateHelper mDeviceIdleDeviceConfigStateHelper;
 
     @Rule
     public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
             .around(new MeterednessConfigurationRule());
 
     protected void setUp() throws Exception {
-
+        // TODO: Annotate these constants with @TestApi instead of obtaining them using reflection
         PROCESS_STATE_FOREGROUND_SERVICE = (Integer) ActivityManager.class
                 .getDeclaredField("PROCESS_STATE_FOREGROUND_SERVICE").get(null);
+        PROCESS_STATE_IMPORTANT_FOREGROUND = (Integer) ActivityManager.class
+                .getDeclaredField("PROCESS_STATE_IMPORTANT_FOREGROUND").get(null);
         mInstrumentation = getInstrumentation();
         mContext = getContext();
         mCm = getConnectivityManager();
+        mDeviceIdleDeviceConfigStateHelper =
+                new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_DEVICE_IDLE);
         mUid = getUid(TEST_APP2_PKG);
         mMyUid = getUid(mContext.getPackageName());
         mServiceClient = new MyServiceClient(mContext);
         mServiceClient.bind();
-        mDeviceIdleConstantsSetting = "device_idle_constants";
         executeShellCommand("cmd netpolicy start-watching " + mUid);
         setAppIdle(false);
 
@@ -254,8 +279,8 @@
     /**
      * Asserts that an app always have access while on foreground or running a foreground service.
      *
-     * <p>This method will launch an activity and a foreground service to make the assertion, but
-     * will finish the activity / stop the service afterwards.
+     * <p>This method will launch an activity, a foreground service to make
+     * the assertion, but will finish the activity / stop the service afterwards.
      */
     protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{
         // Checks foreground first.
@@ -267,6 +292,16 @@
         stopForegroundService();
     }
 
+    protected void assertExpeditedJobHasNetworkAccess() throws Exception {
+        launchComponentAndAssertNetworkAccess(TYPE_EXPEDITED_JOB);
+        finishExpeditedJob();
+    }
+
+    protected void assertExpeditedJobHasNoNetworkAccess() throws Exception {
+        launchComponentAndAssertNetworkAccess(TYPE_EXPEDITED_JOB, false);
+        finishExpeditedJob();
+    }
+
     protected final void assertBackgroundState() throws Exception {
         final int maxTries = 30;
         ProcessState state = null;
@@ -323,7 +358,7 @@
      * Returns whether an app state should be considered "background" for restriction purposes.
      */
     protected boolean isBackground(int state) {
-        return state > PROCESS_STATE_FOREGROUND_SERVICE;
+        return state > PROCESS_STATE_IMPORTANT_FOREGROUND;
     }
 
     /**
@@ -338,7 +373,7 @@
         for (int i = 1; i <= maxTries; i++) {
             error = checkNetworkAccess(expectAvailable);
 
-            if (error.isEmpty()) return;
+            if (error == null) return;
 
             // TODO: ideally, it should retry only when it cannot connect to an external site,
             // or no retry at all! But, currently, the initial change fails almost always on
@@ -410,7 +445,7 @@
             errors.append("\tnetworkInfo: " + networkInfo + "\n");
             errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
         }
-        return errors.toString();
+        return errors.length() == 0 ? null : errors.toString();
     }
 
     /**
@@ -602,6 +637,10 @@
         assertBatteryState(true);
     }
 
+    protected void resetBatteryState() {
+        BatteryUtils.runDumpsysBatteryReset();
+    }
+
     private void assertBatteryState(boolean pluggedIn) throws Exception {
         final long endTime = SystemClock.elapsedRealtime() + BATTERY_STATE_TIMEOUT_MS;
         while (isDevicePluggedIn() != pluggedIn && SystemClock.elapsedRealtime() <= endTime) {
@@ -628,6 +667,9 @@
     }
 
     protected void setBatterySaverMode(boolean enabled) throws Exception {
+        if (!isBatterySaverSupported()) {
+            return;
+        }
         Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
         if (enabled) {
             turnBatteryOn();
@@ -639,8 +681,9 @@
     }
 
     protected void setDozeMode(boolean enabled) throws Exception {
-        // Check doze mode is supported.
-        assertTrue("Device does not support Doze Mode", isDozeModeSupported());
+        if (!isDozeModeSupported()) {
+            return;
+        }
 
         Log.i(TAG, "Setting Doze Mode to " + enabled);
         if (enabled) {
@@ -660,12 +703,18 @@
     }
 
     protected void setAppIdle(boolean enabled) throws Exception {
+        if (!isAppStandbySupported()) {
+            return;
+        }
         Log.i(TAG, "Setting app idle to " + enabled);
         executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
         assertAppIdle(enabled);
     }
 
     protected void setAppIdleNoAssert(boolean enabled) throws Exception {
+        if (!isAppStandbySupported()) {
+            return;
+        }
         Log.i(TAG, "Setting app idle to " + enabled);
         executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
     }
@@ -725,18 +774,21 @@
                 nm.isNotificationListenerAccessGranted(listenerComponent));
     }
 
-    protected void setPendingIntentWhitelistDuration(int durationMs) throws Exception {
-        executeSilentShellCommand(String.format(
-                "settings put global %s %s=%d", mDeviceIdleConstantsSetting,
-                "notification_whitelist_duration", durationMs));
+    protected void setPendingIntentAllowlistDuration(long durationMs) {
+        mDeviceIdleDeviceConfigStateHelper.set("notification_allowlist_duration_ms",
+                String.valueOf(durationMs));
     }
 
-    protected void resetDeviceIdleSettings() throws Exception {
-        executeShellCommand(String.format("settings delete global %s",
-                mDeviceIdleConstantsSetting));
+    protected void resetDeviceIdleSettings() {
+        mDeviceIdleDeviceConfigStateHelper.restoreOriginalValues();
     }
 
     protected void launchComponentAndAssertNetworkAccess(int type) throws Exception {
+        launchComponentAndAssertNetworkAccess(type, true);
+    }
+
+    protected void launchComponentAndAssertNetworkAccess(int type, boolean expectAvailable)
+            throws Exception {
         if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
             startForegroundService();
             assertForegroundServiceNetworkAccess();
@@ -748,21 +800,61 @@
             final CountDownLatch latch = new CountDownLatch(1);
             final Intent launchIntent = getIntentForComponent(type);
             final Bundle extras = new Bundle();
-            final String[] errors = new String[]{null};
-            extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, errors));
+            final ArrayList<Pair<Integer, String>> result = new ArrayList<>(1);
+            extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
+            extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
             launchIntent.putExtras(extras);
             mContext.startActivity(launchIntent);
-            if (latch.await(FOREGROUND_PROC_NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-                if (!errors[0].isEmpty()) {
-                    if (errors[0] == APP_NOT_FOREGROUND_ERROR) {
-                        // App didn't come to foreground when the activity is started, so try again.
-                        assertForegroundNetworkAccess();
-                    } else {
-                        fail("Network is not available for app2 (" + mUid + "): " + errors[0]);
+            if (latch.await(ACTIVITY_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                final int resultCode = result.get(0).first;
+                final String resultData = result.get(0).second;
+                if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
+                    final String error = checkForAvailabilityInResultData(
+                            resultData, expectAvailable);
+                    if (error != null) {
+                        fail("Network is not available for activity in app2 (" + mUid + "): "
+                                + error);
                     }
+                } else if (resultCode == INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE) {
+                    Log.d(TAG, resultData);
+                    // App didn't come to foreground when the activity is started, so try again.
+                    assertForegroundNetworkAccess();
+                } else {
+                    fail("Unexpected resultCode=" + resultCode + "; received=[" + resultData + "]");
                 }
             } else {
-                fail("Timed out waiting for network availability status from app2 (" + mUid + ")");
+                fail("Timed out waiting for network availability status from app2's activity ("
+                        + mUid + ")");
+            }
+        } else if (type == TYPE_EXPEDITED_JOB) {
+            final Bundle extras = new Bundle();
+            final ArrayList<Pair<Integer, String>> result = new ArrayList<>(1);
+            final CountDownLatch latch = new CountDownLatch(1);
+            extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
+            extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
+            final JobInfo jobInfo = new JobInfo.Builder(TEST_JOB_ID, TEST_JOB_COMPONENT)
+                    .setExpedited(true)
+                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                    .setTransientExtras(extras)
+                    .build();
+            mServiceClient.scheduleJob(jobInfo);
+            forceRunJob(TEST_APP2_PKG, TEST_JOB_ID);
+            if (latch.await(JOB_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                final int resultCode = result.get(0).first;
+                final String resultData = result.get(0).second;
+                if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
+                    final String error = checkForAvailabilityInResultData(
+                            resultData, expectAvailable);
+                    if (error != null) {
+                        fail("Network is not available for expedited job in app2 (" + mUid + "): "
+                                + error);
+                    }
+                } else {
+                    fail("Unexpected resultCode=" + resultCode + "; received=[" + resultData + "]");
+                }
+            } else {
+                fail("Timed out waiting for network availability status from app2's expedited job ("
+                        + mUid + ")");
             }
         } else {
             throw new IllegalArgumentException("Unknown type: " + type);
@@ -796,36 +888,34 @@
     }
 
     private Binder getNewNetworkStateObserver(final CountDownLatch latch,
-            final String[] errors) {
+            final ArrayList<Pair<Integer, String>> result) {
         return new INetworkStateObserver.Stub() {
             @Override
-            public boolean isForeground() {
-                try {
-                    final ProcessState state = getProcessStateByUid(mUid);
-                    return !isBackground(state.state);
-                } catch (Exception e) {
-                    Log.d(TAG, "Error while reading the proc state for " + mUid + ": " + e);
-                    return false;
-                }
-            }
-
-            @Override
-            public void onNetworkStateChecked(String resultData) {
-                errors[0] = resultData == null
-                        ? APP_NOT_FOREGROUND_ERROR
-                        : checkForAvailabilityInResultData(resultData, true);
+            public void onNetworkStateChecked(int resultCode, String resultData) {
+                result.add(Pair.create(resultCode, resultData));
                 latch.countDown();
             }
         };
     }
 
     /**
-     * Finishes an activity on app2 so its process is demoted fromforeground status.
+     * Finishes an activity on app2 so its process is demoted from foreground status.
      */
     protected void finishActivity() throws Exception {
-        executeShellCommand("am broadcast -a "
-                + " com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY "
-                + "--receiver-foreground --receiver-registered-only");
+        final Intent intent = new Intent(ACTION_FINISH_ACTIVITY)
+                .setPackage(TEST_APP2_PKG)
+                .setFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        sendOrderedBroadcast(intent);
+    }
+
+    /**
+     * Finishes the expedited job on app2 so its process is demoted from foreground status.
+     */
+    private void finishExpeditedJob() throws Exception {
+        final Intent intent = new Intent(ACTION_FINISH_JOB)
+                .setPackage(TEST_APP2_PKG)
+                .setFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        sendOrderedBroadcast(intent);
     }
 
     protected void sendNotification(int notificationId, String notificationType) throws Exception {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobMeteredTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobMeteredTest.java
new file mode 100644
index 0000000..3809534
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobMeteredTest.java
@@ -0,0 +1,23 @@
+/*
+ * 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.cts.net.hostside;
+
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+@RequiredProperties({METERED_NETWORK})
+public class ExpeditedJobMeteredTest extends AbstractExpeditedJobTest {
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java
new file mode 100644
index 0000000..6596269
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java
@@ -0,0 +1,23 @@
+/*
+ * 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.cts.net.hostside;
+
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+@RequiredProperties({NON_METERED_NETWORK})
+public class ExpeditedJobNonMeteredTest extends AbstractExpeditedJobTest {
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index 6546e26..1339be6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.net.hostside;
 
+import android.app.job.JobInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -104,4 +105,8 @@
     public void unregisterNetworkCallback() throws RemoteException {
         mService.unregisterNetworkCallback();
     }
+
+    public void scheduleJob(JobInfo jobInfo) throws RemoteException {
+        mService.scheduleJob(jobInfo);
+    }
 }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 05f86f7..9d1d418 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -48,6 +48,7 @@
 import android.net.wifi.WifiManager.ActionListener;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.UserHandle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.data.ApnSetting;
@@ -106,11 +107,11 @@
         if (mDataSaverSupported == null) {
             assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
             try {
-                setRestrictBackground(true);
+                setRestrictBackgroundInternal(true);
                 mDataSaverSupported = !isMyRestrictBackgroundStatus(
                         RESTRICT_BACKGROUND_STATUS_DISABLED);
             } finally {
-                setRestrictBackground(false);
+                setRestrictBackgroundInternal(false);
             }
         }
         return mDataSaverSupported;
@@ -137,6 +138,12 @@
         return am.isLowRamDevice();
     }
 
+    /** Forces JobScheduler to run the job if constraints are met. */
+    public static void forceRunJob(String pkg, int jobId) {
+        executeShellCommand("cmd jobscheduler run -f -u " + UserHandle.myUserId()
+                + " " + pkg + " " + jobId);
+    }
+
     public static boolean isLocationEnabled() {
         final LocationManager lm = (LocationManager) getContext().getSystemService(
                 Context.LOCATION_SERVICE);
@@ -318,6 +325,13 @@
     }
 
     public static void setRestrictBackground(boolean enabled) {
+        if (!isDataSaverSupported()) {
+            return;
+        }
+        setRestrictBackgroundInternal(enabled);
+    }
+
+    private static void setRestrictBackgroundInternal(boolean enabled) {
         executeShellCommand("cmd netpolicy set restrict-background " + enabled);
         final String output = executeShellCommand("cmd netpolicy get restrict-background");
         final String expectedSuffix = enabled ? "enabled" : "disabled";
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index b448459..dd33eed 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -21,7 +21,7 @@
 android_test_helper_app {
     name: "CtsHostsideNetworkTestsApp2",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    sdk_version: "test_current",
     static_libs: ["CtsHostsideNetworkTestsAidl"],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
diff --git a/tests/cts/hostside/app2/AndroidManifest.xml b/tests/cts/hostside/app2/AndroidManifest.xml
index eb777f2..4ac4bcb 100644
--- a/tests/cts/hostside/app2/AndroidManifest.xml
+++ b/tests/cts/hostside/app2/AndroidManifest.xml
@@ -21,20 +21,24 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
     <!--
-                 This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
-                 them in a shared preferences which is then read by the test app. These broadcasts are
-                 handled by 2 listeners, one defined the manifest and another dynamically registered by
-                 a service.
+     This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
+     them in a shared preferences which is then read by the test app. These broadcasts are
+     handled by 2 listeners, one defined the manifest and another dynamically registered by
+     a service.
 
-                 The manifest-defined listener also handles ordered broadcasts used to share data with the
-                 test app.
+     The manifest-defined listener also handles ordered broadcasts used to share data with the
+     test app.
 
-                 This application also provides a service, RemoteSocketFactoryService, that the test app can
-                 use to open sockets to remote hosts as a different user ID.
-            -->
-    <application android:usesCleartextTraffic="true">
+     This application also provides a service, RemoteSocketFactoryService, that the test app can
+     use to open sockets to remote hosts as a different user ID.
+    -->
+    <application android:usesCleartextTraffic="true"
+            android:testOnly="true"
+            android:debuggable="true">
+
         <activity android:name=".MyActivity"
              android:exported="true"/>
         <service android:name=".MyService"
@@ -55,6 +59,8 @@
                 <action android:name="com.android.cts.net.hostside.app2.action.SHOW_TOAST"/>
                 </intent-filter>
         </receiver>
+        <service android:name=".MyJobService"
+            android:permission="android.permission.BIND_JOB_SERVICE" />
     </application>
 
 </manifest>
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
index 351733e..62b508c 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
@@ -15,11 +15,13 @@
  */
 package com.android.cts.net.hostside.app2;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -38,6 +40,8 @@
             "com.android.cts.net.hostside.app2.action.RECEIVER_READY";
     static final String ACTION_FINISH_ACTIVITY =
             "com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
+    static final String ACTION_FINISH_JOB =
+            "com.android.cts.net.hostside.app2.action.FINISH_JOB";
     static final String ACTION_SHOW_TOAST =
             "com.android.cts.net.hostside.app2.action.SHOW_TOAST";
 
@@ -51,6 +55,11 @@
 
     static final String TEST_PKG = "com.android.cts.net.hostside";
     static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
+    static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
+
+    static final int TYPE_COMPONENT_ACTIVTY = 0;
+    static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
+    static final int TYPE_COMPONENT_EXPEDITED_JOB = 2;
 
     static int getUid(Context context) {
         final String packageName = context.getPackageName();
@@ -61,11 +70,57 @@
         }
     }
 
-    static void notifyNetworkStateObserver(Context context, Intent intent) {
+    private static boolean validateComponentState(Context context, int componentType,
+            INetworkStateObserver observer) throws RemoteException {
+        final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+        switch (componentType) {
+            case TYPE_COMPONENT_ACTIVTY: {
+                final int procState = activityManager.getUidProcessState(Process.myUid());
+                if (procState != ActivityManager.PROCESS_STATE_TOP) {
+                    observer.onNetworkStateChecked(
+                            INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE,
+                            "Unexpected procstate: " + procState);
+                    return false;
+                }
+                return true;
+            }
+            case TYPE_COMPONENT_FOREGROUND_SERVICE: {
+                final int procState = activityManager.getUidProcessState(Process.myUid());
+                if (procState != ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+                    observer.onNetworkStateChecked(
+                            INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE,
+                            "Unexpected procstate: " + procState);
+                    return false;
+                }
+                return true;
+            }
+            case TYPE_COMPONENT_EXPEDITED_JOB: {
+                final int capabilities = activityManager.getUidProcessCapabilities(Process.myUid());
+                if ((capabilities & ActivityManager.PROCESS_CAPABILITY_NETWORK) == 0) {
+                    observer.onNetworkStateChecked(
+                            INetworkStateObserver.RESULT_ERROR_UNEXPECTED_CAPABILITIES,
+                            "Unexpected capabilities: " + capabilities);
+                    return false;
+                }
+                return true;
+            }
+            default: {
+                observer.onNetworkStateChecked(INetworkStateObserver.RESULT_ERROR_OTHER,
+                        "Unknown component type: " + componentType);
+                return false;
+            }
+        }
+    }
+
+    static void notifyNetworkStateObserver(Context context, Intent intent, int componentType) {
         if (intent == null) {
             return;
         }
         final Bundle extras = intent.getExtras();
+        notifyNetworkStateObserver(context, extras, componentType);
+    }
+
+    static void notifyNetworkStateObserver(Context context, Bundle extras, int componentType) {
         if (extras == null) {
             return;
         }
@@ -73,17 +128,17 @@
                 extras.getBinder(KEY_NETWORK_STATE_OBSERVER));
         if (observer != null) {
             try {
-                if (!observer.isForeground()) {
-                    Log.e(TAG, "App didn't come to foreground");
-                    observer.onNetworkStateChecked(null);
+                final boolean skipValidation = extras.getBoolean(KEY_SKIP_VALIDATION_CHECKS);
+                if (!skipValidation && !validateComponentState(context, componentType, observer)) {
                     return;
                 }
             } catch (RemoteException e) {
-                Log.e(TAG, "Error occurred while reading the proc state: " + e);
+                Log.e(TAG, "Error occurred while informing the validation result: " + e);
             }
             AsyncTask.execute(() -> {
                 try {
                     observer.onNetworkStateChecked(
+                            INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED,
                             MyBroadcastReceiver.checkNetworkStatus(context));
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error occurred while notifying the observer: " + e);
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
index 286cc2f..9fdb9c9 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -18,6 +18,7 @@
 import static com.android.cts.net.hostside.app2.Common.ACTION_FINISH_ACTIVITY;
 import static com.android.cts.net.hostside.app2.Common.TAG;
 import static com.android.cts.net.hostside.app2.Common.TEST_PKG;
+import static com.android.cts.net.hostside.app2.Common.TYPE_COMPONENT_ACTIVTY;
 
 import android.app.Activity;
 import android.content.BroadcastReceiver;
@@ -42,7 +43,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log.d(TAG, "MyActivity.onCreate()");
-        Common.notifyNetworkStateObserver(this, getIntent());
+        Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
         finishCommandReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
index aa54075..c9ae16f 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
@@ -201,7 +201,7 @@
         Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType);
         final Intent serviceIntent = new Intent(context, MyService.class);
         final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
-                notificationId);
+                PendingIntent.FLAG_MUTABLE);
         final Bundle bundle = new Bundle();
         bundle.putCharSequence("parcelable", "I am not");
 
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
index ff4ba65..b55761c 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
@@ -17,6 +17,7 @@
 
 import static com.android.cts.net.hostside.app2.Common.TAG;
 import static com.android.cts.net.hostside.app2.Common.TEST_PKG;
+import static com.android.cts.net.hostside.app2.Common.TYPE_COMPONENT_FOREGROUND_SERVICE;
 
 import android.R;
 import android.app.Notification;
@@ -58,7 +59,7 @@
                 startForeground(42, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(R.drawable.ic_dialog_alert) // any icon is fine
                         .build());
-                Common.notifyNetworkStateObserver(this, intent);
+                Common.notifyNetworkStateObserver(this, intent, TYPE_COMPONENT_FOREGROUND_SERVICE);
                 break;
             case FLAG_STOP_FOREGROUND:
                 Log.d(TAG, "Stopping foreground");
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyJobService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyJobService.java
new file mode 100644
index 0000000..51c3157
--- /dev/null
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyJobService.java
@@ -0,0 +1,81 @@
+/*
+ * 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.cts.net.hostside.app2;
+
+import static com.android.cts.net.hostside.app2.Common.ACTION_FINISH_JOB;
+import static com.android.cts.net.hostside.app2.Common.TAG;
+import static com.android.cts.net.hostside.app2.Common.TYPE_COMPONENT_EXPEDITED_JOB;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+public class MyJobService extends JobService {
+
+    private BroadcastReceiver mFinishCommandReceiver = null;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Log.v(TAG, "MyJobService.onCreate()");
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        Log.v(TAG, "MyJobService.onStartJob()");
+        Common.notifyNetworkStateObserver(this, params.getTransientExtras(),
+                TYPE_COMPONENT_EXPEDITED_JOB);
+        mFinishCommandReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Log.v(TAG, "Finishing MyJobService");
+                try {
+                    jobFinished(params, /*wantsReschedule=*/ false);
+                } finally {
+                    if (mFinishCommandReceiver != null) {
+                        unregisterReceiver(mFinishCommandReceiver);
+                        mFinishCommandReceiver = null;
+                    }
+                }
+            }
+        };
+        registerReceiver(mFinishCommandReceiver, new IntentFilter(ACTION_FINISH_JOB));
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        // If this job is stopped before it had a chance to send network status via
+        // INetworkStateObserver, the test will fail. It could happen either due to test timing out
+        // or this app moving to a lower proc_state and losing network access.
+        Log.v(TAG, "MyJobService.onStopJob()");
+        if (mFinishCommandReceiver != null) {
+            unregisterReceiver(mFinishCommandReceiver);
+            mFinishCommandReceiver = null;
+        }
+        return false;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        Log.v(TAG, "MyJobService.onDestroy()");
+    }
+}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 1c9ff05..9e10d41 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -24,6 +24,8 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.Service;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -154,6 +156,13 @@
                 mNetworkCallback = null;
             }
         }
+
+        @Override
+        public void scheduleJob(JobInfo jobInfo) {
+            final JobScheduler jobScheduler = getApplicationContext()
+                    .getSystemService(JobScheduler.class);
+            jobScheduler.schedule(jobInfo);
+        }
       };
 
     private NetworkRequest makeNetworkRequest() {
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index 37420bf..89c79d3 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -80,7 +80,7 @@
             DeviceNotAvailableException {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
         assertNull(getDevice().installPackage(buildHelper.getTestFile(apk),
-                false /* reinstall */, true /* grantPermissions */));
+                false /* reinstall */, true /* grantPermissions */, "-t"));
     }
 
     protected void uninstallPackage(String packageName, boolean shouldSucceed)
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index 0e25d5e..d026fe0 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -318,11 +318,23 @@
     /**************************
      * Restricted mode tests. *
      **************************/
-    public void testRestrictedMode_networkAccess() throws Exception {
+    public void testNetworkAccess_restrictedMode() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
                 "testNetworkAccess");
     }
 
+    /************************
+     * Expedited job tests. *
+     ************************/
+
+    public void testMeteredNetworkAccess_expeditedJob() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".ExpeditedJobMeteredTest");
+    }
+
+    public void testNonMeteredNetworkAccess_expeditedJob() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".ExpeditedJobNonMeteredTest");
+    }
+
     /*******************
      * Helper methods. *
      *******************/
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 4a1020d..cd69b13 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -39,10 +39,6 @@
         "src/**/*.java",
         "src/**/*.kt",
     ],
-    exclude_srcs: [
-        // TODO: delete this after mainline-prod snaps to sc-dev.
-        "src/android/net/cts/NetworkAgentTest.kt",
-    ],
     jarjar_rules: "jarjar-rules-shared.txt",
     static_libs: [
         "bouncycastle-unbundled",
@@ -68,7 +64,6 @@
 // devices.
 android_test {
     name: "CtsNetTestCases",
-    enabled: false, // Disabled in mainline-prod
     defaults: ["CtsNetTestCasesDefaults"],
     // TODO: CTS should not depend on the entirety of the networkstack code.
     static_libs: [
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 78a01e2..474eefe 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -21,6 +21,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+    <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 30c4e72..9906c30 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -39,17 +39,20 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
-import android.net.UidRange;
 import android.net.wifi.WifiNetworkSpecifier;
 import android.os.Build;
 import android.os.PatternMatcher;
 import android.os.Process;
 import android.util.ArraySet;
+import android.util.Range;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.modules.utils.build.SdkLevel;
 import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.NetworkRequestShimImpl;
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
@@ -57,6 +60,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Set;
+
 @RunWith(AndroidJUnit4.class)
 public class NetworkRequestTest {
     @Rule
@@ -225,6 +230,14 @@
         assertTrue(requestCellularInternet.canBeSatisfiedBy(capCellularVpnMmsInternet));
     }
 
+    private void setUids(NetworkRequest.Builder builder, Set<Range<Integer>> ranges)
+            throws UnsupportedApiLevelException {
+        if (SdkLevel.isAtLeastS()) {
+            final NetworkRequestShim networkRequestShim = NetworkRequestShimImpl.newInstance();
+            networkRequestShim.setUids(builder, ranges);
+        }
+    }
+
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testInvariantInCanBeSatisfiedBy() {
@@ -232,15 +245,26 @@
         // NetworkCapabilities.satisfiedByNetworkCapabilities().
         final LocalNetworkSpecifier specifier1 = new LocalNetworkSpecifier(1234 /* id */);
         final int uid = Process.myUid();
-        final ArraySet<UidRange> ranges = new ArraySet<>();
-        ranges.add(new UidRange(uid, uid));
-        final NetworkRequest requestCombination = new NetworkRequest.Builder()
+        final NetworkRequest.Builder nrBuilder = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .setLinkUpstreamBandwidthKbps(1000)
                 .setNetworkSpecifier(specifier1)
-                .setSignalStrength(-123)
-                .setUids(ranges).build();
+                .setSignalStrength(-123);
+
+        // The uid ranges should be set into the request, but setUids() takes a set of UidRange
+        // that is hidden and inaccessible from shims. Before, S setUids will be a no-op. But
+        // because NetworkRequest.Builder sets the UID of the request to the current UID, the
+        // request contains the current UID both on S and before S.
+        final Set<Range<Integer>> ranges = new ArraySet<>();
+        ranges.add(new Range<Integer>(uid, uid));
+        try {
+            setUids(nrBuilder, ranges);
+        } catch (UnsupportedApiLevelException e) {
+            // Not supported before API31.
+        }
+        final NetworkRequest requestCombination = nrBuilder.build();
+
         final NetworkCapabilities capCell = new NetworkCapabilities.Builder()
                 .addTransportType(TRANSPORT_CELLULAR).build();
         assertCorrectlySatisfies(false, requestCombination, capCell);