Merge "Cleanup the public API getActiveNetworkForUid on AOSP"
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 874704e..7ad5e05 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -277,6 +277,10 @@
     backend: {
         rust: {
             enabled: true,
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.virt", // for virtualizationservice
+            ],
         },
     },
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index fb9942a..7ceb304 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -742,7 +742,7 @@
                             new DataOutputStream(sessionSocket.getOutputStream());
                     Credentials peerCredentials = sessionSocket.getPeerCredentials();
                     tmpArgBuffer = new ZygoteCommandBuffer(sessionSocket);
-                    args = ZygoteArguments.getInstance(argBuffer);
+                    args = ZygoteArguments.getInstance(tmpArgBuffer);
                     applyUidSecurityPolicy(args, peerCredentials);
                     // TODO (chriswailes): Should this only be run for debug builds?
                     validateUsapCommand(args);
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index f379ba0..5814a18 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -877,7 +877,7 @@
             continue;
         }
 
-        sizeKb += importer_info->second.size;
+        sizeKb += importer_info->second.size / 1024;
     }
 
     return sizeKb;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2b7a579..10600e3 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -480,6 +480,10 @@
     <uses-permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" />
     <uses-permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
 
+    <!-- Permission required to run the `vm` tool which manages on-device virtual machines -->
+    <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+    <uses-permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b500e16..8d2363b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2293,23 +2293,30 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        final int userId = UserHandle.getUserId(newPackage.getUid());
-        int numRequestedPermissions = newPackage.getRequestedPermissions().size();
-        for (int i = 0; i < numRequestedPermissions; i++) {
-            PermissionInfo permInfo = getPermissionInfo(newPackage.getRequestedPermissions().get(i),
-                    newPackage.getPackageName(), 0);
-            if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) {
-                continue;
+        for (int userId: mUserManagerInt.getUserIds()) {
+            int numRequestedPermissions = newPackage.getRequestedPermissions().size();
+            for (int i = 0; i < numRequestedPermissions; i++) {
+                PermissionInfo permInfo = getPermissionInfo(
+                        newPackage.getRequestedPermissions().get(i),
+                        newPackage.getPackageName(), 0);
+                if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) {
+                    continue;
+                }
+
+                EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(),
+                        "Revoking permission " + permInfo.name + " from package "
+                                + newPackage.getPackageName() + " as either the sdk downgraded "
+                                + downgradedSdk + " or newly requested legacy full storage "
+                                + newlyRequestsLegacy);
+
+                try {
+                    revokeRuntimePermissionInternal(permInfo.name, newPackage.getPackageName(),
+                            false, callingUid, userId, null, permissionCallback);
+                } catch (IllegalStateException | SecurityException e) {
+                    Log.e(TAG, "unable to revoke " + permInfo.name + " for "
+                            + newPackage.getPackageName() + " user " + userId, e);
+                }
             }
-
-            EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(),
-                    "Revoking permission " + permInfo.name + " from package "
-                            + newPackage.getPackageName() + " as either the sdk downgraded "
-                            + downgradedSdk + " or newly requested legacy full storage "
-                            + newlyRequestsLegacy);
-
-            revokeRuntimePermissionInternal(permInfo.name, newPackage.getPackageName(),
-                    false, callingUid, userId, null, permissionCallback);
         }
 
     }
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index ed4a7bf..1b4e36b 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -968,6 +968,12 @@
         }
         try {
             CompressedApexInfoList apexInfoList = getCompressedApexInfoList(packageFile);
+            if (apexInfoList == null) {
+                Log.i(TAG, "apex_info.pb not present in OTA package. "
+                            + "Assuming device doesn't support compressed"
+                            + "APEX, continueing without allocating space.");
+                return true;
+            }
             ApexManager apexManager = ApexManager.getInstance();
             apexManager.reserveSpaceForCompressedApex(apexInfoList);
             return true;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 81878e7..8ec1bf8 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -554,8 +554,10 @@
                     PackageInstaller.SessionInfo session = mContext.getPackageManager()
                             .getPackageInstaller().getSessionInfo(rollback.getStagedSessionId());
                     if (session == null || session.isStagedSessionFailed()) {
-                        iter.remove();
-                        rollback.delete(mAppDataRollbackHelper);
+                        if (rollback.isEnabling()) {
+                            iter.remove();
+                            rollback.delete(mAppDataRollbackHelper);
+                        }
                         continue;
                     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index eaf7693..ddad1db 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3372,7 +3372,7 @@
     }
 
     /**
-     * Find all visible task stacks containing {@param userId} and intercept them with an activity
+     * Find all task stacks containing {@param userId} and intercept them with an activity
      * to block out the contents and possibly start a credential-confirming intent.
      *
      * @param userId user handle for the locked managed profile.
@@ -3380,42 +3380,18 @@
     void lockAllProfileTasks(@UserIdInt int userId) {
         mService.deferWindowLayout();
         try {
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    RootWindowContainer::taskTopActivityIsUser, this, PooledLambda.__(Task.class),
-                    userId);
-            forAllLeafTasks(c, true /* traverseTopToBottom */);
-            c.recycle();
+            forAllLeafTasks(task -> {
+                if (task.getActivity(activity -> !activity.finishing && activity.mUserId == userId)
+                        != null) {
+                    mService.getTaskChangeNotificationController().notifyTaskProfileLocked(
+                            task.mTaskId, userId);
+                }
+            }, true /* traverseTopToBottom */);
         } finally {
             mService.continueWindowLayout();
         }
     }
 
-    /**
-     * Detects whether we should show a lock screen in front of this task for a locked user.
-     * <p>
-     * We'll do this if either of the following holds:
-     * <ul>
-     *   <li>The top activity explicitly belongs to {@param userId}.</li>
-     *   <li>The top activity returns a result to an activity belonging to {@param userId}.</li>
-     * </ul>
-     *
-     * @return {@code true} if the top activity looks like it belongs to {@param userId}.
-     */
-    private void taskTopActivityIsUser(Task task, @UserIdInt int userId) {
-        // To handle the case that work app is in the task but just is not the top one.
-        final ActivityRecord activityRecord = task.getTopNonFinishingActivity();
-        final ActivityRecord resultTo = (activityRecord != null ? activityRecord.resultTo : null);
-
-        // Check the task for a top activity belonging to userId, or returning a
-        // result to an activity belonging to userId. Example case: a document
-        // picker for personal files, opened by a work app, should still get locked.
-        if ((activityRecord != null && activityRecord.mUserId == userId)
-                || (resultTo != null && resultTo.mUserId == userId)) {
-            mService.getTaskChangeNotificationController().notifyTaskProfileLocked(
-                    task.mTaskId, userId);
-        }
-    }
-
     void cancelInitializingActivities() {
         for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
             final DisplayContent display = getChildAt(displayNdx);
diff --git a/services/net/Android.bp b/services/net/Android.bp
index fb3db8b..5a5f504 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -23,7 +23,6 @@
     ],
     static_libs: [
         "netd-client",
-        "netlink-client",
         "networkstack-client",
         "net-utils-services-common",
     ],
@@ -37,6 +36,7 @@
     name: "services.net-module-wifi",
     srcs: [
         ":framework-services-net-module-wifi-shared-srcs",
+        ":net-module-utils-srcs",
         ":net-utils-services-common-srcs",
     ],
     sdk_version: "module_current",
@@ -44,13 +44,15 @@
     libs: [
         "unsupportedappusage",
         "framework-wifi-util-lib",
-        "framework-connectivity"
+        "framework-connectivity",
+        "modules-utils-build_system",
     ],
     static_libs: [
         // All the classes in netd_aidl_interface must be jarjar so they do not conflict with the
         // classes generated by netd_aidl_interfaces-platform-java above.
         "netd_aidl_interface-V3-java",
         "networkstack-client",
+        "modules-utils-build_system",
     ],
     apex_available: [
         "com.android.wifi",
@@ -71,7 +73,7 @@
     ],
     visibility: [
         "//frameworks/base/packages/Tethering",
-        "//packages/modules/Connectivity/Tethering"
+        "//packages/modules/Connectivity/Tethering",
     ],
 }
 
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 11d050c..5ecf6bb 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -38,6 +38,7 @@
         "services.people",
         "services.usage",
         "guava",
+        "guava-android-testlib",
         "androidx.test.core",
         "androidx.test.ext.truth",
         "androidx.test.runner",
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index e0bada31..3e5cbea 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -35,6 +35,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.google.common.testing.EqualsTester;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -131,6 +133,24 @@
     }
 
     @Test
+    public void testEqualsActiveSource() {
+        int logicalAddress = 0;
+        int physicalAddress = 0x0000;
+        new EqualsTester()
+                .addEqualityGroup(
+                        new HdmiCecLocalDevice.ActiveSource(logicalAddress, physicalAddress),
+                        new HdmiCecLocalDevice.ActiveSource(logicalAddress, physicalAddress))
+                .addEqualityGroup(
+                        new HdmiCecLocalDevice.ActiveSource(logicalAddress, physicalAddress + 1))
+                .addEqualityGroup(
+                        new HdmiCecLocalDevice.ActiveSource(logicalAddress + 1, physicalAddress))
+                .addEqualityGroup(
+                        new HdmiCecLocalDevice.ActiveSource(
+                                logicalAddress + 1, physicalAddress + 1))
+                .testEquals();
+    }
+
+    @Test
     public void dispatchMessage_desNotValid() {
         HdmiCecMessage msg =
                 new HdmiCecMessage(
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 35d1b17..1aff8a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.ActivityStack.ActivityState.FINISHING;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
@@ -36,10 +37,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
 
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -169,5 +173,34 @@
         activity.setState(FINISHING, "test FINISHING");
         assertThat(mWm.mRoot.allPausedActivitiesComplete()).isTrue();
     }
+
+    @Test
+    public void testLockAllProfileTasks() {
+        // Make an activity visible with the user id set to 0
+        DisplayContent displayContent = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY);
+        TaskDisplayArea taskDisplayArea = displayContent.getTaskDisplayAreaAt(0);
+        final ActivityStack stack = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD, displayContent);
+        final ActivityRecord activity = new ActivityTestsBase.ActivityBuilder(stack.mAtmService)
+                .setStack(stack)
+                .setUid(0)
+                .setCreateTask(true)
+                .build();
+
+        // Create another activity on top and the user id is 1
+        Task task = activity.getTask();
+        final ActivityRecord topActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                .setStack(stack)
+                .setUid(UserHandle.PER_USER_RANGE + 1)
+                .setTask(task)
+                .build();
+
+        // Make sure the listeners will be notified for putting the task to locked state
+        TaskChangeNotificationController controller =
+                mWm.mAtmService.getTaskChangeNotificationController();
+        spyOn(controller);
+        mWm.mRoot.lockAllProfileTasks(0);
+        verify(controller).notifyTaskProfileLocked(eq(task.mTaskId), eq(0));
+    }
 }
 
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index e1bcb5f..e3485de 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -34,7 +34,10 @@
  * <ul>
  *  <li>The component name of the associated connection service.</li>
  *  <li>A string identifier that is unique across {@code PhoneAccountHandle}s with the same
- *      component name.</li>
+ *      component name. Apps registering {@link PhoneAccountHandle}s should ensure that the
+ *      {@link #getId()} provided does not expose personally identifying information.  A
+ *      {@link ConnectionService} should use an opaque token as the {@link PhoneAccountHandle}
+ *      identifier.</li>
  * </ul>
  *
  * Note: This Class requires a non-null {@link ComponentName} and {@link UserHandle} to operate
@@ -49,12 +52,35 @@
     private final String mId;
     private final UserHandle mUserHandle;
 
+    /**
+     * Creates a new {@link PhoneAccountHandle}.
+     *
+     * @param componentName The {@link ComponentName} of the {@link ConnectionService} which
+     *                      services this {@link PhoneAccountHandle}.
+     * @param id A string identifier that is unique across {@code PhoneAccountHandle}s with the same
+     *           component name. Apps registering {@link PhoneAccountHandle}s should ensure that the
+     *           ID provided does not expose personally identifying information.  A
+     *           {@link ConnectionService} should use an opaque token as the
+     *           {@link PhoneAccountHandle} identifier.
+     */
     public PhoneAccountHandle(
             @NonNull ComponentName componentName,
             @NonNull String id) {
         this(componentName, id, Process.myUserHandle());
     }
 
+    /**
+     * Creates a new {@link PhoneAccountHandle}.
+     *
+     * @param componentName The {@link ComponentName} of the {@link ConnectionService} which
+     *                      services this {@link PhoneAccountHandle}.
+     * @param id A string identifier that is unique across {@code PhoneAccountHandle}s with the same
+     *           component name. Apps registering {@link PhoneAccountHandle}s should ensure that the
+     *           ID provided does not expose personally identifying information.  A
+     *           {@link ConnectionService} should use an opaque token as the
+     *           {@link PhoneAccountHandle} identifier.
+     * @param userHandle The {@link UserHandle} associated with this {@link PhoneAccountHandle}.
+     */
     public PhoneAccountHandle(
             @NonNull ComponentName componentName,
             @NonNull String id,
@@ -80,17 +106,17 @@
      * others supported by the connection service that created it.
      * <p>
      * A connection service must select identifiers that are stable for the lifetime of
-     * their users' relationship with their service, across many Android devices. For example, a
-     * good set of identifiers might be the email addresses with which with users registered for
-     * their accounts with a particular service. Depending on how a service chooses to operate,
-     * a bad set of identifiers might be an increasing series of integers
-     * ({@code 0}, {@code 1}, {@code 2}, ...) that are generated locally on each phone and could
-     * collide with values generated on other phones or after a data wipe of a given phone.
-     *
+     * their users' relationship with their service, across many Android devices.  The identifier
+     * should be a stable opaque token which uniquely identifies the user within the service.
+     * Depending on how a service chooses to operate, a bad set of identifiers might be an
+     * increasing series of integers ({@code 0}, {@code 1}, {@code 2}, ...) that are generated
+     * locally on each phone and could collide with values generated on other phones or after a data
+     * wipe of a given phone.
+     * <p>
      * Important: A non-unique identifier could cause non-deterministic call-log backup/restore
      * behavior.
      *
-     * @return A service-specific unique identifier for this {@code PhoneAccountHandle}.
+     * @return A service-specific unique opaque identifier for this {@code PhoneAccountHandle}.
      */
     public String getId() {
         return mId;
@@ -157,7 +183,8 @@
         }
     }
 
-    public static final @android.annotation.NonNull Creator<PhoneAccountHandle> CREATOR = new Creator<PhoneAccountHandle>() {
+    public static final @android.annotation.NonNull Creator<PhoneAccountHandle> CREATOR =
+            new Creator<PhoneAccountHandle>() {
         @Override
         public PhoneAccountHandle createFromParcel(Parcel in) {
             return new PhoneAccountHandle(in);
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 96e715e..1d7a476 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -62,6 +62,7 @@
         switch (transportType) {
             case TRANSPORT_TYPE_WWAN: return "WWAN";
             case TRANSPORT_TYPE_WLAN: return "WLAN";
+            case TRANSPORT_TYPE_INVALID: return "INVALID";
             default: return Integer.toString(transportType);
         }
     }
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index bb90fb1..be1502a 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1582,16 +1582,6 @@
     }
 
     /**
-     * Same as {@link #getApnTypeString(int)}, but returns "Unknown" instead of an empty string
-     * when provided with an invalid int for compatibility purposes.
-     * @hide
-     */
-    public static @NonNull String getApnTypeStringInternal(@ApnType int apnType) {
-        String result = getApnTypeString(apnType);
-        return TextUtils.isEmpty(result) ? "Unknown" : result;
-    }
-
-    /**
      * Converts the string representation of an APN type to its integer representation.
      *
      * @param apnType APN type as a string
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 00bd4cf..eaf9c7b4 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -482,6 +482,33 @@
     }
 
     @Test
+    public void testExpireSession_Phase1_Install() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
+    }
+
+    @Test
+    public void testExpireSession_Phase2_VerifyInstall() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                rm.getAvailableRollbacks(), TestApp.A);
+        assertThat(rollback).isNotNull();
+        assertThat(rollback).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(rollback.isStaged()).isTrue();
+    }
+
+    @Test
+    public void testExpireSession_Phase3_VerifyRollback() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                rm.getAvailableRollbacks(), TestApp.A);
+        assertThat(rollback).isNotNull();
+    }
+
+    @Test
     public void hasMainlineModule() throws Exception {
         String pkgName = getModuleMetadataPackageName();
         boolean existed =  InstrumentationRegistry.getInstrumentation().getContext()
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index be74e33..bd6b813 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -39,7 +39,9 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.time.Instant;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -445,6 +447,27 @@
         after.forEach(dir -> assertDirectoryIsEmpty(dir));
     }
 
+    /**
+     * Tests an available rollback shouldn't be deleted when its session expires.
+     */
+    @Test
+    public void testExpireSession() throws Exception {
+        runPhase("testExpireSession_Phase1_Install");
+        getDevice().reboot();
+        runPhase("testExpireSession_Phase2_VerifyInstall");
+
+        // Advance system clock by 7 days to expire the staged session
+        Instant t1 = Instant.ofEpochMilli(getDevice().getDeviceDate());
+        Instant t2 = t1.plusMillis(TimeUnit.DAYS.toMillis(7));
+        runAsRoot(() -> getDevice().setDate(Date.from(t2)));
+
+        // Somehow we need to wait for a while before reboot. Otherwise the change to the
+        // system clock will be reset after reboot.
+        Thread.sleep(3000);
+        getDevice().reboot();
+        runPhase("testExpireSession_Phase3_VerifyRollback");
+    }
+
     private void pushTestApex() throws Exception {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
@@ -525,4 +548,18 @@
             return false;
         }
     }
+
+    @FunctionalInterface
+    private interface ExceptionalRunnable {
+        void run() throws Exception;
+    }
+
+    private void runAsRoot(ExceptionalRunnable runnable) throws Exception {
+        try {
+            getDevice().enableAdbRoot();
+            runnable.run();
+        } finally {
+            getDevice().disableAdbRoot();
+        }
+    }
 }
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
new file mode 100644
index 0000000..649f71d
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.net.INetworkMonitorCallbacks
+import android.net.Network
+import android.net.metrics.IpConnectivityLog
+import android.net.util.SharedLog
+import android.os.IBinder
+import com.android.networkstack.netlink.TcpSocketTracker
+import com.android.server.NetworkStackService
+import com.android.server.NetworkStackService.NetworkMonitorConnector
+import com.android.server.NetworkStackService.NetworkStackConnector
+import com.android.server.connectivity.NetworkMonitor
+import com.android.server.net.integrationtests.NetworkStackInstrumentationService.InstrumentationConnector
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import java.net.HttpURLConnection
+import java.net.URL
+import java.net.URLConnection
+
+private const val TEST_NETID = 42
+
+/**
+ * Android service that can return an [android.net.INetworkStackConnector] which can be instrumented
+ * through [NetworkStackInstrumentationService].
+ * Useful in tests to create test instrumented NetworkStack components that can receive
+ * instrumentation commands through [NetworkStackInstrumentationService].
+ */
+class TestNetworkStackService : Service() {
+    override fun onBind(intent: Intent): IBinder = TestNetworkStackConnector(makeTestContext())
+
+    private fun makeTestContext() = spy(applicationContext).also {
+        doReturn(mock(IBinder::class.java)).`when`(it).getSystemService(Context.NETD_SERVICE)
+    }
+
+    private class TestPermissionChecker : NetworkStackService.PermissionChecker() {
+        override fun enforceNetworkStackCallingPermission() = Unit
+    }
+
+    private class NetworkMonitorDeps(private val privateDnsBypassNetwork: Network) :
+            NetworkMonitor.Dependencies() {
+        override fun getPrivateDnsBypassNetwork(network: Network?) = privateDnsBypassNetwork
+    }
+
+    private inner class TestNetworkStackConnector(context: Context) : NetworkStackConnector(
+            context, TestPermissionChecker(), NetworkStackService.Dependencies()) {
+
+        private val network = Network(TEST_NETID)
+        private val privateDnsBypassNetwork = TestNetwork(TEST_NETID)
+
+        private inner class TestNetwork(netId: Int) : Network(netId) {
+            override fun openConnection(url: URL): URLConnection {
+                val response = InstrumentationConnector.processRequest(url)
+
+                val connection = mock(HttpURLConnection::class.java)
+                doReturn(response.responseCode).`when`(connection).responseCode
+                doReturn(response.contentLength).`when`(connection).contentLengthLong
+                doReturn(response.redirectUrl).`when`(connection).getHeaderField("location")
+                return connection
+            }
+        }
+
+        override fun makeNetworkMonitor(
+            network: Network,
+            name: String?,
+            cb: INetworkMonitorCallbacks
+        ) {
+            val nm = NetworkMonitor(this@TestNetworkStackService, cb,
+                    this.network,
+                    mock(IpConnectivityLog::class.java), mock(SharedLog::class.java),
+                    mock(NetworkStackService.NetworkStackServiceManager::class.java),
+                    NetworkMonitorDeps(privateDnsBypassNetwork),
+                    mock(TcpSocketTracker::class.java))
+            cb.onNetworkMonitorCreated(NetworkMonitorConnector(nm, TestPermissionChecker()))
+        }
+    }
+}