Merge "Add flags to control generation of Smart Suggestions in Notifications"
diff --git a/Android.bp b/Android.bp
index e63463c..ab7d709 100644
--- a/Android.bp
+++ b/Android.bp
@@ -732,10 +732,13 @@
         "ext",
     ],
 
+    jarjar_rules: ":framework-hidl-jarjar",
+
     static_libs: [
         "apex_aidl_interface-java",
         "networkstack-aidl-interfaces-java",
         "framework-protos",
+        "game-driver-protos",
         "mediaplayer2-protos",
         "android.hidl.base-V1.0-java",
         "android.hardware.cas-V1.0-java",
@@ -790,6 +793,11 @@
     ],
 }
 
+filegroup {
+    name: "framework-hidl-jarjar",
+    srcs: ["jarjar_rules_hidl.txt"],
+}
+
 java_library {
     name: "framework",
     defaults: ["framework-defaults"],
@@ -800,11 +808,7 @@
     name: "framework-annotation-proc",
     defaults: ["framework-defaults"],
     // Use UsedByApps annotation processor
-    annotation_processors: ["unsupportedappusage-annotation-processor"],
-    // b/25860419: annotation processors must be explicitly specified for grok
-    annotation_processor_classes: [
-        "android.processor.unsupportedappusage.UsedByAppsProcessor",
-    ],
+    plugins: ["unsupportedappusage-annotation-processor"],
 }
 
 // A host library including just UnsupportedAppUsage.java so that the annotation
diff --git a/api/current.txt b/api/current.txt
index 30599a5..e1a3608 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6823,6 +6823,7 @@
     field public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
     field public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
     field public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
+    field @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) public static final String EXTRA_PASSWORD_COMPLEXITY = "android.app.extra.PASSWORD_COMPLEXITY";
     field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
     field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
     field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME";
@@ -10347,6 +10348,7 @@
     field public static final int EXTRA_DOCK_STATE_LE_DESK = 3; // 0x3
     field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0
     field public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
+    field public static final String EXTRA_DURATION_MILLIS = "android.intent.extra.DURATION_MILLIS";
     field public static final String EXTRA_EMAIL = "android.intent.extra.EMAIL";
     field public static final String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS";
     field public static final String EXTRA_FROM_STORAGE = "android.intent.extra.FROM_STORAGE";
@@ -11498,7 +11500,7 @@
     method public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
     method public abstract android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
     method public abstract android.graphics.drawable.Drawable getApplicationBanner(String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public abstract int getApplicationEnabledSetting(String);
+    method public abstract int getApplicationEnabledSetting(@NonNull String);
     method public abstract android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo);
     method public abstract android.graphics.drawable.Drawable getApplicationIcon(String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.ApplicationInfo getApplicationInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -11506,7 +11508,7 @@
     method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
     method public abstract android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
-    method public abstract int getComponentEnabledSetting(android.content.ComponentName);
+    method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
     method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
     method public abstract android.graphics.drawable.Drawable getDrawable(String, @DrawableRes int, android.content.pm.ApplicationInfo);
     method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
@@ -11572,8 +11574,8 @@
     method public abstract android.content.pm.ProviderInfo resolveContentProvider(String, int);
     method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
     method public abstract void setApplicationCategoryHint(@NonNull String, int);
-    method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(String, int, int);
-    method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+    method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int);
+    method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
     method public abstract void setInstallerPackageName(String, String);
     method public abstract void updateInstantAppCookie(@Nullable byte[]);
     method public abstract void verifyPendingInstall(int, int);
@@ -14617,8 +14619,8 @@
   public class Picture {
     ctor public Picture();
     ctor public Picture(android.graphics.Picture);
-    method public android.graphics.Canvas beginRecording(int, int);
-    method public void draw(android.graphics.Canvas);
+    method @NonNull public android.graphics.Canvas beginRecording(int, int);
+    method public void draw(@NonNull android.graphics.Canvas);
     method public void endRecording();
     method public int getHeight();
     method public int getWidth();
@@ -14870,7 +14872,7 @@
   }
 
   public final class RenderNode {
-    ctor public RenderNode(String);
+    ctor public RenderNode(@Nullable String);
     method public int computeApproximateMemoryUsage();
     method public void discardDisplayList();
     method public void endRecording();
@@ -22255,7 +22257,7 @@
     method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo);
     method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo);
     method public void onUpdateSelection(int, int, int, int, int, int);
-    method public void onViewClicked(boolean);
+    method @Deprecated public void onViewClicked(boolean);
     method public void onWindowHidden();
     method public void onWindowShown();
     method public void requestHideSelf(int);
@@ -24637,6 +24639,7 @@
     method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]);
     method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID);
     method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String);
+    method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int);
     method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException;
     method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException;
     method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException;
@@ -27459,7 +27462,6 @@
 
   public static final class MediaSessionManager.RemoteUserInfo {
     ctor public MediaSessionManager.RemoteUserInfo(@NonNull String, int, int);
-    ctor public MediaSessionManager.RemoteUserInfo(String, int, int, android.os.IBinder);
     method public String getPackageName();
     method public int getPid();
     method public int getUid();
@@ -44194,6 +44196,7 @@
   public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
     method public int getAsuLevel();
+    method public int getBitErrorRate();
     method public int getDbm();
     method public int getLevel();
     method public int getTimingAdvance();
@@ -44495,16 +44498,16 @@
 
   public class SignalStrength implements android.os.Parcelable {
     method public int describeContents();
-    method public int getCdmaDbm();
-    method public int getCdmaEcio();
+    method @Deprecated public int getCdmaDbm();
+    method @Deprecated public int getCdmaEcio();
     method @NonNull public java.util.List<android.telephony.CellSignalStrength> getCellSignalStrengths();
-    method public int getEvdoDbm();
-    method public int getEvdoEcio();
-    method public int getEvdoSnr();
-    method public int getGsmBitErrorRate();
-    method public int getGsmSignalStrength();
+    method @Deprecated public int getEvdoDbm();
+    method @Deprecated public int getEvdoEcio();
+    method @Deprecated public int getEvdoSnr();
+    method @Deprecated public int getGsmBitErrorRate();
+    method @Deprecated public int getGsmSignalStrength();
     method public int getLevel();
-    method public boolean isGsm();
+    method @Deprecated public boolean isGsm();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int INVALID = 2147483647; // 0x7fffffff
   }
@@ -52634,12 +52637,12 @@
     method @NonNull public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext);
     method public final void destroy();
     method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
-    method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, int);
-    method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, int);
+    method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long);
+    method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, long);
     method public final void notifyViewAppeared(@NonNull android.view.ViewStructure);
     method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId);
     method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence, int);
-    method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull int[]);
+    method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull long[]);
   }
 
   public final class ContentCaptureSessionId implements android.os.Parcelable {
@@ -52997,7 +53000,7 @@
     method public void updateCursorAnchorInfo(android.view.View, android.view.inputmethod.CursorAnchorInfo);
     method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText);
     method public void updateSelection(android.view.View, int, int, int, int);
-    method public void viewClicked(android.view.View);
+    method @Deprecated public void viewClicked(android.view.View);
     field public static final int HIDE_IMPLICIT_ONLY = 1; // 0x1
     field public static final int HIDE_NOT_ALWAYS = 2; // 0x2
     field public static final int RESULT_HIDDEN = 3; // 0x3
@@ -56844,7 +56847,7 @@
     method public boolean isCursorVisible();
     method public boolean isElegantTextHeight();
     method public boolean isFallbackLineSpacing();
-    method public final boolean isHorizontallyScrolling();
+    method public final boolean isHorizontallyScrollable();
     method public boolean isInputMethodTarget();
     method public boolean isSingleLine();
     method public boolean isSuggestionsEnabled();
diff --git a/api/removed.txt b/api/removed.txt
index e232227..9f4b041 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -239,8 +239,8 @@
   }
 
   public class Picture {
-    method @Deprecated public static android.graphics.Picture createFromStream(java.io.InputStream);
-    method @Deprecated public void writeToStream(java.io.OutputStream);
+    method @Deprecated public static android.graphics.Picture createFromStream(@NonNull java.io.InputStream);
+    method @Deprecated public void writeToStream(@NonNull java.io.OutputStream);
   }
 
   @Deprecated public class PixelXorXfermode extends android.graphics.Xfermode {
diff --git a/api/system-current.txt b/api/system-current.txt
index cdd2d80..b731a3d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1095,6 +1095,8 @@
   public static final class UsageEvents.Event {
     method public int getInstanceId();
     method public String getNotificationChannelId();
+    method @Nullable public String getTaskRootClassName();
+    method @Nullable public String getTaskRootPackageName();
     field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc
     field public static final int NOTIFICATION_SEEN = 10; // 0xa
     field public static final int SLICE_PINNED = 14; // 0xe
@@ -1109,6 +1111,7 @@
   public final class UsageStatsManager {
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
+    method public int getUsageSource();
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
     method public void reportUsageStart(@NonNull android.app.Activity, @NonNull String);
@@ -1124,6 +1127,8 @@
     field public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
     field public static final int STANDBY_BUCKET_EXEMPTED = 5; // 0x5
     field public static final int STANDBY_BUCKET_NEVER = 50; // 0x32
+    field public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2; // 0x2
+    field public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1; // 0x1
   }
 
 }
@@ -1672,22 +1677,17 @@
 package android.content.rollback {
 
   public final class PackageRollbackInfo implements android.os.Parcelable {
-    ctor public PackageRollbackInfo(String, android.content.rollback.PackageRollbackInfo.PackageVersion, android.content.rollback.PackageRollbackInfo.PackageVersion);
     method public int describeContents();
+    method public String getPackageName();
+    method public android.content.pm.VersionedPackage getVersionRolledBackFrom();
+    method public android.content.pm.VersionedPackage getVersionRolledBackTo();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.rollback.PackageRollbackInfo> CREATOR;
-    field public final android.content.rollback.PackageRollbackInfo.PackageVersion higherVersion;
-    field public final android.content.rollback.PackageRollbackInfo.PackageVersion lowerVersion;
-    field public final String packageName;
-  }
-
-  public static class PackageRollbackInfo.PackageVersion {
-    ctor public PackageRollbackInfo.PackageVersion(long);
-    field public final long versionCode;
   }
 
   public final class RollbackInfo implements android.os.Parcelable {
     method public int describeContents();
+    method public int getRollbackId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
     field public final android.content.rollback.PackageRollbackInfo targetPackage;
@@ -3394,6 +3394,15 @@
     method @NonNull public android.media.TimedMetaData.Builder setTimedMetaData(long, @NonNull byte[]);
   }
 
+  public abstract class VolumeProvider {
+    method public void setCallback(android.media.VolumeProvider.Callback);
+  }
+
+  public abstract static class VolumeProvider.Callback {
+    ctor public VolumeProvider.Callback();
+    method public abstract void onVolumeChanged(android.media.VolumeProvider);
+  }
+
 }
 
 package android.media.audiopolicy {
@@ -3569,6 +3578,10 @@
     method public void onSetMediaButtonEventDelegate(@NonNull android.media.session.MediaSessionEngine.MediaButtonEventDelegate);
   }
 
+  public static final class MediaSession.Token implements android.os.Parcelable {
+    method public android.media.session.ControllerLink getControllerLink();
+  }
+
   public final class MediaSessionEngine implements java.lang.AutoCloseable {
     ctor public MediaSessionEngine(@NonNull android.content.Context, @NonNull android.media.session.SessionLink, @NonNull android.media.session.SessionCallbackLink, @NonNull android.media.session.MediaSessionEngine.CallbackStub, int);
     method public void close();
@@ -5556,6 +5569,12 @@
     method public void onPropertyChanged(String, String, String);
   }
 
+  public static interface DeviceConfig.Telephony {
+    field public static final String NAMESPACE = "telephony";
+    field public static final String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
+    field public static final String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration";
+  }
+
   public final class DocumentsContract {
     method public static boolean isManageMode(android.net.Uri);
     method public static android.net.Uri setManageMode(android.net.Uri);
@@ -6012,10 +6031,7 @@
     method public int getTaskId();
   }
 
-  public final class FillResponse implements android.os.Parcelable {
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR;
+  public final class FillResponse {
   }
 
   public static final class FillResponse.Builder {
@@ -6029,7 +6045,6 @@
     ctor public FillWindow();
     method public void destroy();
     method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
-    field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
   }
 
   public abstract class PresentationParams {
@@ -7641,6 +7656,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
     method public int getSimApplicationState();
     method public int getSimCardState();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getSimLocale();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSupportedRadioAccessFamily();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccCardInfo[] getUiccCardsInfo();
@@ -9073,6 +9089,18 @@
 
 }
 
+package android.view.autofill {
+
+  public final class AutofillManager {
+    method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities();
+    method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages();
+    method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean);
+    method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>);
+    method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean);
+  }
+
+}
+
 package android.view.contentcapture {
 
   public final class ContentCaptureContext implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index eca7a7a..c746882 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -352,6 +352,10 @@
     method public boolean isReservedSupported(@NonNull java.util.UUID);
   }
 
+  public final class UsageStatsManager {
+    method public void forceUsageSourceSettingRead();
+  }
+
 }
 
 package android.bluetooth {
@@ -1684,10 +1688,7 @@
     method public int getTaskId();
   }
 
-  public final class FillResponse implements android.os.Parcelable {
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR;
+  public final class FillResponse {
   }
 
   public static final class FillResponse.Builder {
@@ -1701,7 +1702,6 @@
     ctor public FillWindow();
     method public void destroy();
     method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
-    field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
   }
 
   public abstract class PresentationParams {
@@ -2273,6 +2273,10 @@
     method public static int getLongPressTooltipHideTimeout();
   }
 
+  public class ViewDebug {
+    method @Nullable public static AutoCloseable startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>);
+  }
+
   public interface WindowManager extends android.view.ViewManager {
     method public default void setShouldShowIme(int, boolean);
     method public default void setShouldShowSystemDecors(int, boolean);
@@ -2333,6 +2337,12 @@
   }
 
   public final class AutofillManager {
+    method @NonNull public java.util.Set<android.content.ComponentName> getAugmentedAutofillDisabledActivities();
+    method @NonNull public java.util.Set<java.lang.String> getAugmentedAutofillDisabledPackages();
+    method public void setActivityAugmentedAutofillEnabled(@NonNull android.content.ComponentName, boolean);
+    method public void setAugmentedAutofillWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>);
+    method public void setPackageAugmentedAutofillEnabled(@NonNull String, boolean);
+    field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
     field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
   }
 
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1942b2e..60b2e25 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -27,6 +27,7 @@
 import "frameworks/base/core/proto/android/bluetooth/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto";
+import "frameworks/base/core/proto/android/debug/enums.proto";
 import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
 import "frameworks/base/core/proto/android/os/enums.proto";
 import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
@@ -204,6 +205,7 @@
         SeOmapiReported se_omapi_reported = 141;
         BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142;
         AttentionManagerServiceResultReported attention_manager_service_result_reported = 143;
+        AdbConnectionChanged adb_connection_changed = 144;
     }
 
     // Pulled events will start at field 10000.
@@ -4466,7 +4468,7 @@
   // SIMx or eSEx.
   optional string terminal = 2;
 
-  optional string packageName = 3;
+  optional string package_name = 3;
 }
 
 /**
@@ -4497,3 +4499,25 @@
     }
     optional AttentionCheckResult attention_check_result = 1 [default = UNKNOWN];
 }
+
+/**
+ * Logs when an adb connection changes state.
+ *
+ * Logged from:
+ *     frameworks/base/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+ */
+message AdbConnectionChanged {
+    // The last time this system connected via adb, or 0 if the 'always allow' option was not
+    // previously selected for this system.
+    optional int64 last_connection_time_millis = 1;
+
+    // The time in ms within which a subsequent connection from an 'always allow' system is allowed
+    // to reconnect via adb without user interaction.
+    optional int64 auth_window_millis = 2;
+
+    // The state of the adb connection from frameworks/base/core/proto/android/debug/enums.proto.
+    optional android.debug.AdbConnectionStateEnum state = 3;
+
+    // True if the 'always allow' option was selected for this system.
+    optional bool always_allow = 4;
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1063be4..92f47e7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8218,10 +8218,10 @@
             final AutofillId autofillId = autofillIds[i];
             final View view = autofillClientFindViewByAutofillIdTraversal(autofillId);
             if (view != null) {
-                if (!autofillId.isVirtual()) {
+                if (!autofillId.isVirtualInt()) {
                     visible[i] = view.isVisibleToUser();
                 } else {
-                    visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildId());
+                    visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildIntId());
                 }
             }
         }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 5cac048..86e658d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -219,9 +219,11 @@
      * @param userId
      * @param event
      * @param appToken ActivityRecord's appToken.
+     * @param taskRoot TaskRecord's root
      */
     public abstract void updateActivityUsageStats(
-            ComponentName activity, int userId, int event, IBinder appToken);
+            ComponentName activity, int userId, int event, IBinder appToken,
+            ComponentName taskRoot);
     public abstract void updateForegroundTimeIfOnBattery(
             String packageName, int uid, long cpuTimeDiff);
     public abstract void sendForegroundProfileChanged(int userId);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5868771f..ab2430c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -27,6 +27,7 @@
 import android.annotation.UnsupportedAppUsage;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.database.ContentObserver;
 import android.media.AudioAttributes.AttributeUsage;
@@ -41,9 +42,9 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.ArrayMap;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import android.util.SparseArray;
 import com.android.internal.app.IAppOpsActiveCallback;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
@@ -60,8 +61,8 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
 /**
@@ -1489,7 +1490,7 @@
             AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS
             AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS
             AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS
-            AppOpsManager.MODE_DEFAULT, // SYSTEM_ALERT_WINDOW
+            getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW
             AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS
             AppOpsManager.MODE_ALLOWED, // CAMERA
             AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO
@@ -4816,4 +4817,21 @@
             }
         }
     }
+
+    private static int getSystemAlertWindowDefault() {
+        final Context context = ActivityThread.currentApplication();
+        if (context == null) {
+            return AppOpsManager.MODE_DEFAULT;
+        }
+
+        // system alert window is disable on low ram phones starting from Q
+        final PackageManager pm = context.getPackageManager();
+        // TVs are constantly plugged in and has less concern for memory/power
+        if (ActivityManager.isLowRamDeviceStatic()
+                && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0)) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+
+        return AppOpsManager.MODE_DEFAULT;
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7cc953c..55a3acb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1362,16 +1362,23 @@
     public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
 
     /**
-     * Activity action: have the user enter a new password. This activity should
-     * be launched after using {@link #setPasswordQuality(ComponentName, int)},
-     * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user
-     * enter a new password that meets the current requirements. You can use
-     * {@link #isActivePasswordSufficient()} to determine whether you need to
-     * have the user select a new password in order to meet the current
-     * constraints. Upon being resumed from this activity, you can check the new
+     * Activity action: have the user enter a new password.
+     *
+     * <p>For admin apps, this activity should be launched after using {@link
+     * #setPasswordQuality(ComponentName, int)}, or {@link
+     * #setPasswordMinimumLength(ComponentName, int)} to have the user enter a new password that
+     * meets the current requirements. You can use {@link #isActivePasswordSufficient()} to
+     * determine whether you need to have the user select a new password in order to meet the
+     * current constraints. Upon being resumed from this activity, you can check the new
      * password characteristics to see if they are sufficient.
      *
-     * If the intent is launched from within a managed profile with a profile
+     * <p>Non-admin apps can use {@link #getPasswordComplexity()} to check the current screen lock
+     * complexity, and use this activity with extra {@link #EXTRA_PASSWORD_COMPLEXITY} to suggest
+     * to users how complex the app wants the new screen lock to be. Note that both {@link
+     * #getPasswordComplexity()} and the extra {@link #EXTRA_PASSWORD_COMPLEXITY} require the
+     * calling app to have the permission {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}.
+     *
+     * <p>If the intent is launched from within a managed profile with a profile
      * owner built against {@link android.os.Build.VERSION_CODES#M} or before,
      * this will trigger entering a new password for the parent of the profile.
      * For all other cases it will trigger entering a new password for the user
@@ -1384,6 +1391,24 @@
             = "android.app.action.SET_NEW_PASSWORD";
 
     /**
+     * An integer indicating the complexity level of the new password an app would like the user to
+     * set when launching the action {@link #ACTION_SET_NEW_PASSWORD}.
+     *
+     * <p>Must be one of
+     * <ul>
+     *     <li>{@link #PASSWORD_COMPLEXITY_HIGH}
+     *     <li>{@link #PASSWORD_COMPLEXITY_MEDIUM}
+     *     <li>{@link #PASSWORD_COMPLEXITY_LOW}
+     *     <li>{@link #PASSWORD_COMPLEXITY_NONE}
+     * </ul>
+     *
+     * <p>If an invalid value is used, it will be treated as {@link #PASSWORD_COMPLEXITY_NONE}.
+     */
+    @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY)
+    public static final String EXTRA_PASSWORD_COMPLEXITY =
+            "android.app.extra.PASSWORD_COMPLEXITY";
+
+    /**
      * Constant for {@link #getPasswordComplexity()}: no password.
      *
      * <p>Note that these complexity constants are ordered so that higher values are more complex.
@@ -9493,18 +9518,12 @@
     }
 
     /**
-     * Allows the device owner or profile owner to enable or disable the backup service.
+     * Allows the device owner to enable or disable the backup service.
      *
-     * <p> Each user has its own backup service which manages the backup and restore mechanisms in
-     * that user. Disabling the backup service will prevent data from being backed up or restored.
+     * <p> Backup service manages all backup and restore mechanisms on the device. Setting this to
+     * false will prevent data from being backed up or restored.
      *
-     * <p> Device owner calls this API to control backup services across all users on the device.
-     * Profile owner can use this API to enable or disable the profile's backup service. However,
-     * for a managed profile its backup functionality is only enabled if both the device owner
-     * and the profile owner have enabled the backup service.
-     *
-     * <p> By default, backup service is disabled on a device with device owner, and within a
-     * managed profile.
+     * <p> Backup service is off by default when device owner is present.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param enabled {@code true} to enable the backup service, {@code false} to disable it.
@@ -9520,12 +9539,7 @@
     }
 
     /**
-     * Return whether the backup service is enabled by the device owner or profile owner for the
-     * current user, as previously set by {@link #setBackupServiceEnabled(ComponentName, boolean)}.
-     *
-     * <p> Whether the backup functionality is actually enabled or not depends on settings from both
-     * the current user and the device owner, please see
-     * {@link #setBackupServiceEnabled(ComponentName, boolean)} for details.
+     * Return whether the backup service is enabled by the device owner.
      *
      * <p> Backup service manages all backup and restore mechanisms on the device.
      *
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 8b41755..e5df2c7 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -20,6 +20,11 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -27,6 +32,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -85,6 +92,101 @@
         nonLetter = in.readInt();
     }
 
+    /** Returns the min quality allowed by {@code complexityLevel}. */
+    public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) {
+        // this would be the quality of the first metrics since mMetrics is sorted in ascending
+        // order of quality
+        return PasswordComplexityBucket
+                .complexityLevelToBucket(complexityLevel).mMetrics[0].quality;
+    }
+
+    /**
+     * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet
+     * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code
+     * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements,
+     * and {@code complexityLevel}.
+     *
+     * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are
+     * more than one set of metrics to meet the minimum complexity requirement and inspecting what
+     * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics
+     * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or
+     * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
+     * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
+     * an alphanumeric password so we would update the min complexity required min length to 6.
+     */
+    public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel,
+            int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric,
+            boolean requiresLettersOrSymbols) {
+        int targetQuality = Math.max(
+                userEnteredPasswordQuality,
+                getActualRequiredQuality(
+                        requestedQuality, requiresNumeric, requiresLettersOrSymbols));
+        return getTargetQualityMetrics(complexityLevel, targetQuality);
+    }
+
+    /**
+     * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality
+     * is the same as {@code targetQuality}.
+     *
+     * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics
+     * with the min quality at {@code complexityLevel}.
+     */
+    // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+    @VisibleForTesting
+    public static PasswordMetrics getTargetQualityMetrics(
+            @PasswordComplexity int complexityLevel, int targetQuality) {
+        PasswordComplexityBucket targetBucket =
+                PasswordComplexityBucket.complexityLevelToBucket(complexityLevel);
+        for (PasswordMetrics metrics : targetBucket.mMetrics) {
+            if (targetQuality == metrics.quality) {
+                return metrics;
+            }
+        }
+        // none of the metrics at complexityLevel has targetQuality, return metrics with min quality
+        // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where
+        // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and
+        // requested quality is NUMERIC
+        return targetBucket.mMetrics[0];
+    }
+
+    /**
+     * Finds out the actual quality requirement based on whether quality is {@link
+     * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are
+     * required.
+     */
+    @VisibleForTesting
+    // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
+    public static int getActualRequiredQuality(
+            int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) {
+        if (requestedQuality != PASSWORD_QUALITY_COMPLEX) {
+            return requestedQuality;
+        }
+
+        // find out actual password quality from complex requirements
+        if (requiresNumeric && requiresLettersOrSymbols) {
+            return PASSWORD_QUALITY_ALPHANUMERIC;
+        }
+        if (requiresLettersOrSymbols) {
+            return PASSWORD_QUALITY_ALPHABETIC;
+        }
+        if (requiresNumeric) {
+            // cannot specify numeric complex using complex quality so this must be numeric
+            return PASSWORD_QUALITY_NUMERIC;
+        }
+
+        // reaching here means dpm sets quality to complex without specifying any requirements
+        return PASSWORD_QUALITY_UNSPECIFIED;
+    }
+
+    /**
+     * Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}
+     * if {@code complexityLevel} is not valid.
+     */
+    @PasswordComplexity
+    public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) {
+        return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel;
+    }
+
     public boolean isDefault() {
         return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
                 && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
@@ -280,7 +382,7 @@
     @PasswordComplexity
     public int determineComplexity() {
         for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
-            if (satisfiesBucket(bucket.getMetrics())) {
+            if (satisfiesBucket(bucket.mMetrics)) {
                 return bucket.mComplexityLevel;
             }
         }
@@ -290,7 +392,7 @@
     /**
      * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
      */
-    public static class PasswordComplexityBucket {
+    private static class PasswordComplexityBucket {
         /**
          * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
          * {@link PasswordMetrics}.
@@ -299,12 +401,13 @@
                 new PasswordComplexityBucket(
                         PASSWORD_COMPLEXITY_HIGH,
                         new PasswordMetrics(
-                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6),
+                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+                                8),
                         new PasswordMetrics(
                                 DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
                         new PasswordMetrics(
-                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
-                                8));
+                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
+                                6));
 
         /**
          * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
@@ -314,11 +417,12 @@
                 new PasswordComplexityBucket(
                         PASSWORD_COMPLEXITY_MEDIUM,
                         new PasswordMetrics(
-                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4),
+                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+                                4),
                         new PasswordMetrics(
                                 DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
                         new PasswordMetrics(
-                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
                                 4));
 
         /**
@@ -328,11 +432,11 @@
         private static final PasswordComplexityBucket LOW =
                 new PasswordComplexityBucket(
                         PASSWORD_COMPLEXITY_LOW,
-                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC),
-                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
-                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING),
                         new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
-                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING));
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC));
 
         /**
          * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
@@ -348,19 +452,27 @@
         private final int mComplexityLevel;
         private final PasswordMetrics[] mMetrics;
 
+        /**
+         * @param metricsArray must be sorted in ascending order of {@link #quality}.
+         */
         private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
-                PasswordMetrics... metrics) {
-            this.mComplexityLevel = complexityLevel;
-            this.mMetrics = metrics;
-        }
+                PasswordMetrics... metricsArray) {
+            int previousQuality = PASSWORD_QUALITY_UNSPECIFIED;
+            for (PasswordMetrics metrics : metricsArray) {
+                if (metrics.quality < previousQuality) {
+                    throw new IllegalArgumentException("metricsArray must be sorted in ascending"
+                            + " order of quality");
+                }
+                previousQuality = metrics.quality;
+            }
 
-        /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */
-        public PasswordMetrics[] getMetrics() {
-            return mMetrics;
+            this.mMetrics = metricsArray;
+            this.mComplexityLevel = complexityLevel;
+
         }
 
         /** Returns the bucket that {@code complexityLevel} represents. */
-        public static PasswordComplexityBucket complexityLevelToBucket(
+        private static PasswordComplexityBucket complexityLevelToBucket(
                 @PasswordComplexity int complexityLevel) {
             for (PasswordComplexityBucket bucket : BUCKETS) {
                 if (bucket.mComplexityLevel == complexityLevel) {
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 7d03f00..6006ad2 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -894,7 +894,7 @@
             }
             if (mAutofillId != null) {
                 autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID;
-                if (mAutofillId.isVirtual()) {
+                if (mAutofillId.isVirtualInt()) {
                     autofillFlags |= AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID;
                 }
             }
@@ -961,8 +961,9 @@
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
                     out.writeInt(mAutofillId.getViewId());
                     if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIRTUAL_VIEW_ID) != 0) {
-                        out.writeInt(mAutofillId.getVirtualChildId());
+                        out.writeInt(mAutofillId.getVirtualChildIntId());
                     }
+                    // TODO(b/113593220): write session id as well
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_TYPE) != 0) {
                     out.writeInt(mAutofillType);
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index c983d4f..24580b4 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.Nullable;
 import android.app.IBackupAgent;
 import android.app.QueuedWork;
 import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
@@ -181,6 +182,8 @@
 
     Handler mHandler = null;
 
+    @Nullable private UserHandle mUser;
+
     Handler getHandler() {
         if (mHandler == null) {
             mHandler = new Handler(Looper.getMainLooper());
@@ -232,6 +235,8 @@
      */
     public void onCreate(UserHandle user) {
         onCreate();
+
+        mUser = user;
     }
 
     /**
@@ -528,6 +533,10 @@
     public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
     }
 
+    private int getBackupUserId() {
+        return mUser == null ? super.getUserId() : mUser.getIdentifier();
+    }
+
     /**
      * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
      * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
@@ -1033,7 +1042,7 @@
 
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, 0);
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
                 } catch (RemoteException e) {
                     // we'll time out anyway, so we're safe
                 }
@@ -1082,7 +1091,7 @@
 
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, 0);
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
                 } catch (RemoteException e) {
                     // we'll time out anyway, so we're safe
                 }
@@ -1112,7 +1121,8 @@
             } finally {
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, measureOutput.getSize());
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token,
+                            measureOutput.getSize());
                 } catch (RemoteException e) {
                     // timeout, so we're safe
                 }
@@ -1137,7 +1147,7 @@
 
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, 0);
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
                 } catch (RemoteException e) {
                     // we'll time out anyway, so we're safe
                 }
@@ -1162,7 +1172,7 @@
 
                 Binder.restoreCallingIdentity(ident);
                 try {
-                    callbackBinder.opComplete(token, 0);
+                    callbackBinder.opCompleteForUser(getBackupUserId(), token, 0);
                 } catch (RemoteException e) {
                     // we'll time out anyway, so we're safe
                 }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index f8c5a81..eda8981 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -549,6 +549,19 @@
 
     /**
      * Notify the backup manager that a BackupAgent has completed the operation
+     * corresponding to the given token and user id.
+     *
+     * @param userId User id for which the operation has been completed.
+     * @param token The transaction token passed to the BackupAgent method being
+     *        invoked.
+     * @param result In the case of a full backup measure operation, the estimated
+     *        total file size that would result from the operation. Unused in all other
+     *        cases.
+     */
+    void opCompleteForUser(int userId, int token, long result);
+
+    /**
+     * Notify the backup manager that a BackupAgent has completed the operation
      * corresponding to the given token.
      *
      * @param token The transaction token passed to the BackupAgent method being
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index bbae7d3..d2934b9 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -59,4 +59,6 @@
     void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
             String callingPackage);
     void reportUsageStop(in IBinder activity, String token, String callingPackage);
+    int getUsageSource();
+    void forceUsageSourceSettingRead();
 }
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 2c5fe04..451f44b 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -16,6 +16,7 @@
 package android.app.usage;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.res.Configuration;
@@ -286,7 +287,6 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public String mClass;
 
-
         /**
          * {@hide}
          */
@@ -295,6 +295,16 @@
         /**
          * {@hide}
          */
+        public String mTaskRootPackage;
+
+        /**
+         * {@hide}
+         */
+        public String mTaskRootClass;
+
+        /**
+         * {@hide}
+         */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public long mTimeStamp;
 
@@ -373,6 +383,8 @@
             mPackage = orig.mPackage;
             mClass = orig.mClass;
             mInstanceId = orig.mInstanceId;
+            mTaskRootPackage = orig.mTaskRootPackage;
+            mTaskRootClass = orig.mTaskRootClass;
             mTimeStamp = orig.mTimeStamp;
             mEventType = orig.mEventType;
             mConfiguration = orig.mConfiguration;
@@ -411,6 +423,28 @@
         }
 
         /**
+         * The package name of the task root when this event was reported.
+         * Or {@code null} for queries from apps without {@link
+         * android.Manifest.permission#PACKAGE_USAGE_STATS}
+         * @hide
+         */
+        @SystemApi
+        public @Nullable String getTaskRootPackageName() {
+            return mTaskRootPackage;
+        }
+
+        /**
+         * The class name of the task root when this event was reported.
+         * Or {@code null} for queries from apps without {@link
+         * android.Manifest.permission#PACKAGE_USAGE_STATS}
+         * @hide
+         */
+        @SystemApi
+        public @Nullable String getTaskRootClassName() {
+            return mTaskRootClass;
+        }
+
+        /**
          * The time at which this event occurred, measured in milliseconds since the epoch.
          * <p/>
          * See {@link System#currentTimeMillis()}.
@@ -522,6 +556,9 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private int mIndex = 0;
 
+    // Only used when parceling events. If false, task roots will be omitted from the parcel
+    private final boolean mIncludeTaskRoots;
+
     /*
      * In order to save space, since ComponentNames will be duplicated everywhere,
      * we use a map and index into it.
@@ -552,6 +589,7 @@
             mParcel.setDataSize(mParcel.dataPosition());
             mParcel.setDataPosition(positionInParcel);
         }
+        mIncludeTaskRoots = true;
     }
 
     /**
@@ -560,16 +598,27 @@
      */
     UsageEvents() {
         mEventCount = 0;
+        mIncludeTaskRoots = true;
+    }
+
+    /**
+     * Construct the iterator in preparation for writing it to a parcel.
+     * Defaults to excluding task roots from the parcel.
+     * {@hide}
+     */
+    public UsageEvents(List<Event> events, String[] stringPool) {
+        this(events, stringPool, false);
     }
 
     /**
      * Construct the iterator in preparation for writing it to a parcel.
      * {@hide}
      */
-    public UsageEvents(List<Event> events, String[] stringPool) {
+    public UsageEvents(List<Event> events, String[] stringPool, boolean includeTaskRoots) {
         mStringPool = stringPool;
         mEventCount = events.size();
         mEventsToWrite = events;
+        mIncludeTaskRoots = includeTaskRoots;
     }
 
     /**
@@ -645,9 +694,25 @@
         } else {
             classIndex = -1;
         }
+
+        final int taskRootPackageIndex;
+        if (mIncludeTaskRoots && event.mTaskRootPackage != null) {
+            taskRootPackageIndex = findStringIndex(event.mTaskRootPackage);
+        } else {
+            taskRootPackageIndex = -1;
+        }
+
+        final int taskRootClassIndex;
+        if (mIncludeTaskRoots && event.mTaskRootClass != null) {
+            taskRootClassIndex = findStringIndex(event.mTaskRootClass);
+        } else {
+            taskRootClassIndex = -1;
+        }
         p.writeInt(packageIndex);
         p.writeInt(classIndex);
         p.writeInt(event.mInstanceId);
+        p.writeInt(taskRootPackageIndex);
+        p.writeInt(taskRootClassIndex);
         p.writeInt(event.mEventType);
         p.writeLong(event.mTimeStamp);
 
@@ -691,6 +756,21 @@
             eventOut.mClass = null;
         }
         eventOut.mInstanceId = p.readInt();
+
+        final int taskRootPackageIndex = p.readInt();
+        if (taskRootPackageIndex >= 0) {
+            eventOut.mTaskRootPackage = mStringPool[taskRootPackageIndex];
+        } else {
+            eventOut.mTaskRootPackage = null;
+        }
+
+        final int taskRootClassIndex = p.readInt();
+        if (taskRootClassIndex >= 0) {
+            eventOut.mTaskRootClass = mStringPool[taskRootClassIndex];
+        } else {
+            eventOut.mTaskRootClass = null;
+        }
+
         eventOut.mEventType = p.readInt();
         eventOut.mTimeStamp = p.readLong();
 
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 605deac..d2de887 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -22,6 +22,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -234,6 +235,29 @@
     @SystemApi
     public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
 
+
+    /**
+     * App usage observers will consider the task root package the source of usage.
+     * @hide
+     */
+    @SystemApi
+    public static final int USAGE_SOURCE_TASK_ROOT_ACTIVITY = 1;
+
+    /**
+     * App usage observers will consider the visible activity's package the source of usage.
+     * @hide
+     */
+    @SystemApi
+    public static final int USAGE_SOURCE_CURRENT_ACTIVITY = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "USAGE_SOURCE_" }, value = {
+            USAGE_SOURCE_TASK_ROOT_ACTIVITY,
+            USAGE_SOURCE_CURRENT_ACTIVITY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UsageSource {}
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private static final UsageEvents sEmptyResults = new UsageEvents();
 
@@ -776,6 +800,38 @@
         }
     }
 
+    /**
+     * Get what App Usage Observers will consider the source of usage for an activity. Usage Source
+     * is decided at boot and will not change until next boot.
+     * @see #USAGE_SOURCE_TASK_ROOT_ACTIVITY
+     * @see #USAGE_SOURCE_CURRENT_ACTIVITY
+     *
+     * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+     *                           is not the profile owner of this user.
+     * @hide
+     */
+    @SystemApi
+    public @UsageSource int getUsageSource() {
+        try {
+            return mService.getUsageSource();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Force the Usage Source be reread from global settings.
+     * @hide
+     */
+    @TestApi
+    public void forceUsageSourceSettingRead() {
+        try {
+            mService.forceUsageSourceSettingRead();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     public static String reasonToString(int standbyReason) {
         StringBuilder sb = new StringBuilder();
@@ -845,6 +901,22 @@
         return sb.toString();
     }
 
+    /** @hide */
+    public static String usageSourceToString(int usageSource) {
+        switch (usageSource) {
+            case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+                return "TASK_ROOT_ACTIVITY";
+            case USAGE_SOURCE_CURRENT_ACTIVITY:
+                return "CURRENT_ACTIVITY";
+            default:
+                StringBuilder sb = new StringBuilder();
+                sb.append("UNKNOWN(");
+                sb.append(usageSource);
+                sb.append(")");
+                return sb.toString();
+        }
+    }
+
     /**
      * {@hide}
      * Temporarily whitelist the specified app for a short duration. This is to allow an app
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index cc3ab00..d2d0cf9 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -40,9 +40,11 @@
      *                  {@link UsageEvents}
      * @param instanceId For activity, hashCode of ActivityRecord's appToken.
      *                   For non-activity, it is not used.
+     * @param taskRoot For activity, the name of the package at the root of the task
+     *                 For non-activity, it is not used.
      */
     public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType,
-            int instanceId);
+            int instanceId, ComponentName taskRoot);
 
     /**
      * Reports an event to the UsageStatsManager.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3c487a1..b46203c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2008,6 +2008,15 @@
             "android.intent.extra.PERMISSION_GROUP_NAME";
 
     /**
+     * Intent extra: The number of milliseconds.
+     * <p>
+     * Type: long
+     * </p>
+     */
+    public static final String EXTRA_DURATION_MILLIS =
+            "android.intent.extra.DURATION_MILLIS";
+
+    /**
      * Activity action: Launch UI to review app uses of permissions.
      * <p>
      * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission name
@@ -2020,11 +2029,16 @@
      * {@link #EXTRA_PERMISSION_NAME}.
      * </p>
      * <p>
+     * Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent
+     * activity to show (optional).  Must be non-negative.
+     * </p>
+     * <p>
      * Output: Nothing.
      * </p>
      *
      * @see #EXTRA_PERMISSION_NAME
      * @see #EXTRA_PERMISSION_GROUP_NAME
+     * @see #EXTRA_DURATION_MILLIS
      *
      * @hide
      */
@@ -2363,7 +2377,6 @@
     /**
      * Broadcast Action: An existing version of an application package has been
      * rolled back to a previous version.
-     * The data contains the name of the package.
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4efcd30..9e2f316 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5772,7 +5772,7 @@
      */
     @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
             conditional = true)
-    public abstract void setComponentEnabledSetting(ComponentName componentName,
+    public abstract void setComponentEnabledSetting(@NonNull ComponentName componentName,
             @EnabledState int newState, @EnabledFlags int flags);
 
     /**
@@ -5786,7 +5786,7 @@
      * @return Returns the current enabled state for the component.
      */
     public abstract @EnabledState int getComponentEnabledSetting(
-            ComponentName componentName);
+            @NonNull ComponentName componentName);
 
     /**
      * Set the enabled setting for an application
@@ -5801,7 +5801,7 @@
      */
     @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE,
             conditional = true)
-    public abstract void setApplicationEnabledSetting(String packageName,
+    public abstract void setApplicationEnabledSetting(@NonNull String packageName,
             @EnabledState int newState, @EnabledFlags int flags);
 
     /**
@@ -5815,7 +5815,7 @@
      * @return Returns the current enabled state for the application.
      * @throws IllegalArgumentException if the named package does not exist.
      */
-    public abstract @EnabledState int getApplicationEnabledSetting(String packageName);
+    public abstract @EnabledState int getApplicationEnabledSetting(@NonNull String packageName);
 
     /**
      * Flush the package restrictions for a given user to disk. This forces the package restrictions
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 2040024..4644a83 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -17,11 +17,10 @@
 package android.content.rollback;
 
 import android.annotation.SystemApi;
+import android.content.pm.VersionedPackage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.Objects;
-
 /**
  * Information about a rollback available for a particular package.
  *
@@ -29,59 +28,41 @@
  */
 @SystemApi
 public final class PackageRollbackInfo implements Parcelable {
-    /**
-     * The name of a package being rolled back.
-     */
-    public final String packageName;
+
+    private final VersionedPackage mVersionRolledBackFrom;
+    private final VersionedPackage mVersionRolledBackTo;
 
     /**
-     * The version the package was rolled back from.
+     * Returns the name of the package to roll back from.
      */
-    public final PackageVersion higherVersion;
-
-    /**
-     * The version the package was rolled back to.
-     */
-    public final PackageVersion lowerVersion;
-
-    /**
-     * Represents a version of a package.
-     */
-    public static class PackageVersion {
-        public final long versionCode;
-
-        // TODO(b/120200473): Include apk sha or some other way to distinguish
-        // between two different apks with the same version code.
-        public PackageVersion(long versionCode) {
-            this.versionCode = versionCode;
-        }
-
-        @Override
-        public boolean equals(Object other) {
-            if (other instanceof PackageVersion)  {
-                PackageVersion otherVersion = (PackageVersion) other;
-                return versionCode == otherVersion.versionCode;
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(versionCode);
-        }
+    public String getPackageName() {
+        return mVersionRolledBackFrom.getPackageName();
     }
 
-    public PackageRollbackInfo(String packageName,
-            PackageVersion higherVersion, PackageVersion lowerVersion) {
-        this.packageName = packageName;
-        this.higherVersion = higherVersion;
-        this.lowerVersion = lowerVersion;
+    /**
+     * Returns the version of the package rolled back from.
+     */
+    public VersionedPackage getVersionRolledBackFrom() {
+        return mVersionRolledBackFrom;
+    }
+
+    /**
+     * Returns the version of the package rolled back to.
+     */
+    public VersionedPackage getVersionRolledBackTo() {
+        return mVersionRolledBackTo;
+    }
+
+    /** @hide */
+    public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
+            VersionedPackage packageRolledBackTo) {
+        this.mVersionRolledBackFrom = packageRolledBackFrom;
+        this.mVersionRolledBackTo = packageRolledBackTo;
     }
 
     private PackageRollbackInfo(Parcel in) {
-        this.packageName = in.readString();
-        this.higherVersion = new PackageVersion(in.readLong());
-        this.lowerVersion = new PackageVersion(in.readLong());
+        this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
+        this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
     }
 
     @Override
@@ -91,9 +72,8 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeString(packageName);
-        out.writeLong(higherVersion.versionCode);
-        out.writeLong(lowerVersion.versionCode);
+        mVersionRolledBackFrom.writeToParcel(out, flags);
+        mVersionRolledBackTo.writeToParcel(out, flags);
     }
 
     public static final Parcelable.Creator<PackageRollbackInfo> CREATOR =
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 66df4fe..0803a7c 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -30,6 +30,11 @@
 public final class RollbackInfo implements Parcelable {
 
     /**
+     * A unique identifier for the rollback.
+     */
+    private final int mRollbackId;
+
+    /**
      * The package that needs to be rolled back.
      */
     public final PackageRollbackInfo targetPackage;
@@ -40,12 +45,21 @@
     // staged installs is supported.
 
     /** @hide */
-    public RollbackInfo(PackageRollbackInfo targetPackage) {
+    public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
+        this.mRollbackId = rollbackId;
         this.targetPackage = targetPackage;
     }
 
     private RollbackInfo(Parcel in) {
-        this.targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+        mRollbackId = in.readInt();
+        targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+    }
+
+    /**
+     * Returns a unique identifier for this rollback.
+     */
+    public int getRollbackId() {
+        return mRollbackId;
     }
 
     @Override
@@ -55,6 +69,7 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mRollbackId);
         targetPackage.writeToParcel(out, flags);
     }
 
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d3509d5..38ddc16 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -2087,7 +2087,14 @@
      * protocol, so applications with custom text editing written before this method appeared will
      * not call to inform the IME of this interaction.
      * @param focusChanged true if the user changed the focused view by this click.
+     * @see InputMethodManager#viewClicked(View)
+     * @deprecated The method may not be called for composite {@link View} that works as a giant
+     *             "Canvas", which can host its own UI hierarchy and sub focus state.
+     *             {@link android.webkit.WebView} is a good example. Application / IME developers
+     *             should not rely on this method. If your goal is just being notified when an
+     *             on-going input is interrupted, simply monitor {@link #onFinishInput()}.
      */
+    @Deprecated
     public void onViewClicked(boolean focusChanged) {
         // Intentionally empty
     }
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index b7af374..f0fe92e 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -24,7 +24,7 @@
  *
  * @hide
  */
-interface INetworkManagementEventObserver {
+oneway interface INetworkManagementEventObserver {
     /**
      * Interface configuration status has changed.
      *
diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl
index 2df8ab7..8b64f1c 100644
--- a/core/java/android/net/INetworkStackConnector.aidl
+++ b/core/java/android/net/INetworkStackConnector.aidl
@@ -18,10 +18,12 @@
 import android.net.INetworkMonitorCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
 
 /** @hide */
 oneway interface INetworkStackConnector {
     void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
         in IDhcpServerCallbacks cb);
     void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb);
+    void makeIpClient(in String ifName, in IIpClientCallbacks callbacks);
 }
\ No newline at end of file
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index af043ee..d277034 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -27,6 +27,7 @@
 import android.content.ServiceConnection;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Process;
@@ -84,6 +85,21 @@
     }
 
     /**
+     * Create an IpClient on the specified interface.
+     *
+     * <p>The IpClient will be returned asynchronously through the provided callbacks.
+     */
+    public void makeIpClient(String ifName, IIpClientCallbacks cb) {
+        requestConnector(connector -> {
+            try {
+                connector.makeIpClient(ifName, cb);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        });
+    }
+
+    /**
      * Create a NetworkMonitor.
      *
      * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java
similarity index 100%
rename from services/net/java/android/net/util/MultinetworkPolicyTracker.java
rename to core/java/android/net/util/MultinetworkPolicyTracker.java
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 950f381..6daa5b4 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -103,7 +103,7 @@
         String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
                 Build.SUPPORTED_ABIS[0];
         try {
-            mZygote = Process.zygoteProcess.startChildZygote(
+            mZygote = Process.ZYGOTE_PROCESS.startChildZygote(
                     "com.android.internal.os.AppZygoteInit",
                     mAppInfo.processName + "_zygote",
                     mZygoteUid,
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index c5a51f1..518528d 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -79,23 +79,27 @@
          * Called when taking bugreport resulted in an error.
          *
          * @param errorCode the error that occurred. Possible values are
-         *     {@code BUGREPORT_ERROR_INVALID_INPUT}, {@code BUGREPORT_ERROR_RUNTIME}.
+         *     {@code BUGREPORT_ERROR_INVALID_INPUT},
+         *     {@code BUGREPORT_ERROR_RUNTIME},
+         *     {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}.
          */
         void onError(@BugreportErrorCode int errorCode);
 
         /**
-         * Called when taking bugreport finishes successfully
-         *
-         * @param durationMs time capturing bugreport took in milliseconds
-         * @param title title for the bugreport; helpful in reminding the user why they took it
-         * @param description detailed description for the bugreport
+         * Called when taking bugreport finishes successfully.
          */
-        void onFinished(long durationMs, @NonNull String title,
-                @NonNull String description);
+        void onFinished();
     }
 
     /**
-     * Starts a bugreport asynchronously.
+     * Starts a bugreport.
+     *
+     * <p>This starts a bugreport in the background. However the call itself can take several
+     * seconds to return in the worst case. {@code listener} will receive progress and status
+     * updates.
+     *
+     * <p>The bugreport artifacts will be copied over to the given file descriptors only if the
+     * user consents to sharing with the calling app.
      *
      * @param bugreportFd file to write the bugreport. This should be opened in write-only,
      *     append mode.
@@ -107,7 +111,7 @@
     @RequiresPermission(android.Manifest.permission.DUMP)
     public void startBugreport(@NonNull FileDescriptor bugreportFd,
             @Nullable FileDescriptor screenshotFd,
-            @NonNull BugreportParams params, @Nullable BugreportListener listener) {
+            @NonNull BugreportParams params, @NonNull BugreportListener listener) {
         // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary.
         DumpstateListener dsListener = new DumpstateListener(listener);
 
@@ -121,6 +125,18 @@
         }
     }
 
+    /*
+     * Cancels a currently running bugreport.
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void cancelBugreport() {
+        try {
+            mBinder.cancelBugreport();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private final class DumpstateListener extends IDumpstateListener.Stub
             implements DeathRecipient {
         private final BugreportListener mListener;
@@ -145,9 +161,13 @@
         }
 
         @Override
-        public void onFinished(long durationMs, String title, String description)
-                throws RemoteException {
-            mListener.onFinished(durationMs, title, description);
+        public void onFinished() throws RemoteException {
+            try {
+                mListener.onFinished();
+            } finally {
+                // The bugreport has finished. Let's shutdown the service to minimize its footprint.
+                cancelBugreport();
+            }
         }
 
         // Old methods; should go away
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index f51ba9a..5bf9095 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -23,10 +23,15 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
+import android.gamedriver.GameDriverProto.Blacklist;
+import android.gamedriver.GameDriverProto.Blacklists;
 import android.opengl.EGL14;
 import android.provider.Settings;
+import android.util.Base64;
 import android.util.Log;
 
+import com.android.framework.protobuf.InvalidProtocolBufferException;
+
 import dalvik.system.VMRuntime;
 
 import java.io.BufferedReader;
@@ -62,6 +67,8 @@
     private static final String ANGLE_RULES_FILE = "a4a_rules.json";
     private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
     private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
+    private static final String GAME_DRIVER_BLACKLIST_FLAG = "blacklist";
+    private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP;
 
     private ClassLoader mClassLoader;
     private String mLayerPath;
@@ -480,6 +487,24 @@
             return;
         }
 
+        ApplicationInfo driverInfo;
+        try {
+            driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName,
+                    PackageManager.MATCH_SYSTEM_ONLY);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
+            return;
+        }
+
+        // O drivers are restricted to the sphal linker namespace, so don't try to use
+        // packages unless they declare they're compatible with that restriction.
+        if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+            if (DEBUG) {
+                Log.w(TAG, "updated driver package is not known to be compatible with O");
+            }
+            return;
+        }
+
         // To minimize risk of driver updates crippling the device beyond user repair, never use an
         // updated driver for privileged or non-updated system apps. Presumably pre-installed apps
         // were tested thoroughly with the pre-installed driver.
@@ -491,7 +516,7 @@
 
         // GUP_DEV_ALL_APPS
         // 0: Default (Invalid values fallback to default as well)
-        // 1: All apps use Game Update Package
+        // 1: All apps use Game Driver
         // 2: All apps use system graphics driver
         int gupDevAllApps = coreSettings.getInt(Settings.Global.GUP_DEV_ALL_APPS, 0);
         if (gupDevAllApps == 2) {
@@ -510,33 +535,45 @@
                 }
                 return;
             }
+            boolean isDevOptIn = getGlobalSettingsString(coreSettings,
+                                                         Settings.Global.GUP_DEV_OPT_IN_APPS)
+                              .contains(ai.packageName);
 
-            if (!getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_IN_APPS)
-                            .contains(ai.packageName)
-                    && !onWhitelist(context, driverPackageName, ai.packageName)) {
+            if (!isDevOptIn && !onWhitelist(context, driverPackageName, ai.packageName)) {
                 if (DEBUG) {
                     Log.w(TAG, ai.packageName + " is not on the whitelist.");
                 }
                 return;
             }
-        }
 
-        ApplicationInfo driverInfo;
-        try {
-            driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName,
-                    PackageManager.MATCH_SYSTEM_ONLY);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
-            return;
-        }
-
-        // O drivers are restricted to the sphal linker namespace, so don't try to use
-        // packages unless they declare they're compatible with that restriction.
-        if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) {
-            if (DEBUG) {
-                Log.w(TAG, "updated driver package is not known to be compatible with O");
+            if (!isDevOptIn) {
+                // At this point, the application is on the whitelist only, check whether it's
+                // on the blacklist, terminate early when it's on the blacklist.
+                try {
+                    // TODO(b/121350991) Switch to DeviceConfig with property listener.
+                    String base64String = coreSettings.getString(Settings.Global.GUP_BLACKLIST);
+                    if (base64String != null && !base64String.isEmpty()) {
+                        Blacklists blacklistsProto = Blacklists.parseFrom(
+                                Base64.decode(base64String, BASE64_FLAGS));
+                        List<Blacklist> blacklists = blacklistsProto.getBlacklistsList();
+                        long driverVersionCode = driverInfo.longVersionCode;
+                        for (Blacklist blacklist : blacklists) {
+                            if (blacklist.getVersionCode() == driverVersionCode) {
+                                for (String packageName : blacklist.getPackageNamesList()) {
+                                    if (packageName == ai.packageName) {
+                                        return;
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    }
+                } catch (InvalidProtocolBufferException e) {
+                    if (DEBUG) {
+                        Log.w(TAG, "Can't parse blacklist, skip and continue...");
+                    }
+                }
             }
-            return;
         }
 
         String abi = chooseAbi(driverInfo);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index f2a9adb..07c4933 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -32,16 +32,6 @@
     private static final String LOG_TAG = "Process";
 
     /**
-     * @hide for internal use only.
-     */
-    public static final String ZYGOTE_SOCKET = "zygote";
-
-    /**
-     * @hide for internal use only.
-     */
-    public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary";
-
-    /**
      * An invalid UID value.
      */
     public static final int INVALID_UID = -1;
@@ -479,8 +469,7 @@
      * State associated with the zygote process.
      * @hide
      */
-    public static final ZygoteProcess zygoteProcess =
-            new ZygoteProcess(ZYGOTE_SOCKET, SECONDARY_ZYGOTE_SOCKET);
+    public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
 
     /**
      * Start a new process.
@@ -538,7 +527,7 @@
                                   @Nullable String[] packagesForUid,
                                   @Nullable String[] visibleVols,
                                   @Nullable String[] zygoteArgs) {
-        return zygoteProcess.start(processClass, niceName, uid, gid, gids,
+        return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
                     packagesForUid, visibleVols, zygoteArgs);
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index ec77821..4cb9c5b 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -62,87 +62,119 @@
  * {@hide}
  */
 public class ZygoteProcess {
+
+    /**
+     * @hide for internal use only.
+     */
+    public static final String ZYGOTE_SOCKET_NAME = "zygote";
+
+    /**
+     * @hide for internal use only.
+     */
+    public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary";
+
+    /**
+     * @hide for internal use only
+     */
     private static final String LOG_TAG = "ZygoteProcess";
 
     /**
      * The name of the socket used to communicate with the primary zygote.
      */
-    private final LocalSocketAddress mSocket;
+    private final LocalSocketAddress mZygoteSocketAddress;
 
     /**
      * The name of the secondary (alternate ABI) zygote socket.
      */
-    private final LocalSocketAddress mSecondarySocket;
+    private final LocalSocketAddress mZygoteSecondarySocketAddress;
 
-    public ZygoteProcess(String primarySocket, String secondarySocket) {
-        this(new LocalSocketAddress(primarySocket, LocalSocketAddress.Namespace.RESERVED),
-                new LocalSocketAddress(secondarySocket, LocalSocketAddress.Namespace.RESERVED));
+    public ZygoteProcess() {
+        mZygoteSocketAddress =
+                new LocalSocketAddress(ZYGOTE_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED);
+        mZygoteSecondarySocketAddress =
+                new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME,
+                                       LocalSocketAddress.Namespace.RESERVED);
     }
 
-    public ZygoteProcess(LocalSocketAddress primarySocket, LocalSocketAddress secondarySocket) {
-        mSocket = primarySocket;
-        mSecondarySocket = secondarySocket;
+    public ZygoteProcess(LocalSocketAddress primarySocketAddress,
+                         LocalSocketAddress secondarySocketAddress) {
+        mZygoteSocketAddress = primarySocketAddress;
+        mZygoteSecondarySocketAddress = secondarySocketAddress;
     }
 
     public LocalSocketAddress getPrimarySocketAddress() {
-        return mSocket;
+        return mZygoteSocketAddress;
     }
 
     /**
      * State for communicating with the zygote process.
      */
     public static class ZygoteState {
-        final LocalSocket socket;
-        final DataInputStream inputStream;
-        final BufferedWriter writer;
-        final List<String> abiList;
+        final LocalSocketAddress mZygoteSocketAddress;
 
-        boolean mClosed;
+        private final LocalSocket mZygoteSessionSocket;
 
-        private ZygoteState(LocalSocket socket, DataInputStream inputStream,
-                BufferedWriter writer, List<String> abiList) {
-            this.socket = socket;
-            this.inputStream = inputStream;
-            this.writer = writer;
-            this.abiList = abiList;
+        final DataInputStream mZygoteInputStream;
+        final BufferedWriter mZygoteOutputWriter;
+
+        private final List<String> mABIList;
+
+        private boolean mClosed;
+
+        private ZygoteState(LocalSocketAddress zygoteSocketAddress,
+                            LocalSocket zygoteSessionSocket,
+                            DataInputStream zygoteInputStream,
+                            BufferedWriter zygoteOutputWriter,
+                            List<String> abiList) {
+            this.mZygoteSocketAddress = zygoteSocketAddress;
+            this.mZygoteSessionSocket = zygoteSessionSocket;
+            this.mZygoteInputStream = zygoteInputStream;
+            this.mZygoteOutputWriter = zygoteOutputWriter;
+            this.mABIList = abiList;
         }
 
-        public static ZygoteState connect(LocalSocketAddress address) throws IOException {
+        /**
+         * Create a new ZygoteState object by connecting to the given Zygote socket.
+         *
+         * @param zygoteSocketAddress  Zygote socket to connect to
+         * @return  A new ZygoteState object containing a session socket for the given Zygote socket
+         * address
+         * @throws IOException
+         */
+        public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress)
+                throws IOException {
+
             DataInputStream zygoteInputStream = null;
-            BufferedWriter zygoteWriter = null;
-            final LocalSocket zygoteSocket = new LocalSocket();
+            BufferedWriter zygoteOutputWriter = null;
+            final LocalSocket zygoteSessionSocket = new LocalSocket();
 
             try {
-                zygoteSocket.connect(address);
-
-                zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
-
-                zygoteWriter = new BufferedWriter(new OutputStreamWriter(
-                        zygoteSocket.getOutputStream()), 256);
+                zygoteSessionSocket.connect(zygoteSocketAddress);
+                zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
+                zygoteOutputWriter =
+                        new BufferedWriter(
+                                new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
+                                256);
             } catch (IOException ex) {
                 try {
-                    zygoteSocket.close();
-                } catch (IOException ignore) {
-                }
+                    zygoteSessionSocket.close();
+                } catch (IOException ignore) { }
 
                 throw ex;
             }
 
-            String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
-            Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/"
-                    + address.getName() + " opened, supported ABIS: " + abiListString);
-
-            return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
-                    Arrays.asList(abiListString.split(",")));
+            return new ZygoteState(zygoteSocketAddress,
+                                   zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
+                                   getAbiList(zygoteOutputWriter, zygoteInputStream));
         }
 
         boolean matches(String abi) {
-            return abiList.contains(abi);
+            return mABIList.contains(abi);
         }
 
         public void close() {
             try {
-                socket.close();
+                mZygoteSessionSocket.close();
             } catch (IOException ex) {
                 Log.e(LOG_TAG,"I/O exception on routine close", ex);
             }
@@ -241,7 +273,7 @@
         try {
             return startViaZygote(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
-                    abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */,
+                    abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/false,
                     packageName, packagesForUid, visibleVols, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
@@ -260,7 +292,7 @@
      * @throws ZygoteStartFailedEx if the query failed.
      */
     @GuardedBy("mLock")
-    private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
+    private static List<String> getAbiList(BufferedWriter writer, DataInputStream inputStream)
             throws IOException {
         // Each query starts with the argument count (1 in this case)
         writer.write("1");
@@ -276,7 +308,9 @@
         byte[] bytes = new byte[numBytes];
         inputStream.readFully(bytes);
 
-        return new String(bytes, StandardCharsets.US_ASCII);
+        String rawList = new String(bytes, StandardCharsets.US_ASCII);
+
+        return Arrays.asList(rawList.split(","));
     }
 
     /**
@@ -310,8 +344,8 @@
              * the child or -1 on failure, followed by boolean to
              * indicate whether a wrapper process was used.
              */
-            final BufferedWriter writer = zygoteState.writer;
-            final DataInputStream inputStream = zygoteState.inputStream;
+            final BufferedWriter writer = zygoteState.mZygoteOutputWriter;
+            final DataInputStream inputStream = zygoteState.mZygoteInputStream;
 
             writer.write(Integer.toString(args.size()));
             writer.newLine();
@@ -528,18 +562,18 @@
                 ZygoteState state = openZygoteSocketIfNeeded(abi);
 
                 // Each query starts with the argument count (1 in this case)
-                state.writer.write("1");
+                state.mZygoteOutputWriter.write("1");
                 // ... followed by a new-line.
-                state.writer.newLine();
+                state.mZygoteOutputWriter.newLine();
                 // ... followed by our only argument.
-                state.writer.write("--get-pid");
-                state.writer.newLine();
-                state.writer.flush();
+                state.mZygoteOutputWriter.write("--get-pid");
+                state.mZygoteOutputWriter.newLine();
+                state.mZygoteOutputWriter.flush();
 
                 // The response is a length prefixed stream of ASCII bytes.
-                int numBytes = state.inputStream.readInt();
+                int numBytes = state.mZygoteInputStream.readInt();
                 byte[] bytes = new byte[numBytes];
-                state.inputStream.readFully(bytes);
+                state.mZygoteInputStream.readFully(bytes);
 
                 return Integer.parseInt(new String(bytes, StandardCharsets.US_ASCII));
             }
@@ -593,16 +627,16 @@
             return true;
         }
         try {
-            state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
-            state.writer.newLine();
-            state.writer.write("--set-api-blacklist-exemptions");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--set-api-blacklist-exemptions");
+            state.mZygoteOutputWriter.newLine();
             for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) {
-                state.writer.write(mApiBlacklistExemptions.get(i));
-                state.writer.newLine();
+                state.mZygoteOutputWriter.write(mApiBlacklistExemptions.get(i));
+                state.mZygoteOutputWriter.newLine();
             }
-            state.writer.flush();
-            int status = state.inputStream.readInt();
+            state.mZygoteOutputWriter.flush();
+            int status = state.mZygoteInputStream.readInt();
             if (status != 0) {
                 Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status);
             }
@@ -622,13 +656,13 @@
             return;
         }
         try {
-            state.writer.write(Integer.toString(1));
-            state.writer.newLine();
-            state.writer.write("--hidden-api-log-sampling-rate="
+            state.mZygoteOutputWriter.write(Integer.toString(1));
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--hidden-api-log-sampling-rate="
                     + Integer.toString(mHiddenApiAccessLogSampleRate));
-            state.writer.newLine();
-            state.writer.flush();
-            int status = state.inputStream.readInt();
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.flush();
+            int status = state.mZygoteInputStream.readInt();
             if (status != 0) {
                 Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate; status " + status);
             }
@@ -638,22 +672,29 @@
     }
 
     /**
-     * Tries to open socket to Zygote process if not already open. If
-     * already open, does nothing.  May block and retry.  Requires that mLock be held.
+     * Tries to open a session socket to a Zygote process with a compatible ABI if one is not
+     * already open. If a compatible session socket is already open that session socket is returned.
+     * This function may block and may have to try connecting to multiple Zygotes to find the
+     * appropriate one.  Requires that mLock be held.
      */
     @GuardedBy("mLock")
-    private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
+    private ZygoteState openZygoteSocketIfNeeded(String abi)
+            throws ZygoteStartFailedEx {
+
         Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");
 
         if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
             try {
-                primaryZygoteState = ZygoteState.connect(mSocket);
+                primaryZygoteState =
+                    ZygoteState.connect(mZygoteSocketAddress);
             } catch (IOException ioe) {
                 throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
             }
+
             maybeSetApiBlacklistExemptions(primaryZygoteState, false);
             maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
         }
+
         if (primaryZygoteState.matches(abi)) {
             return primaryZygoteState;
         }
@@ -661,10 +702,12 @@
         // The primary zygote didn't match. Try the secondary.
         if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
             try {
-                secondaryZygoteState = ZygoteState.connect(mSecondarySocket);
+                secondaryZygoteState =
+                    ZygoteState.connect(mZygoteSecondarySocketAddress);
             } catch (IOException ioe) {
                 throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
             }
+
             maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
             maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
         }
@@ -685,11 +728,11 @@
                                                                           IOException {
         synchronized (mLock) {
             ZygoteState state = openZygoteSocketIfNeeded(abi);
-            state.writer.write("2");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write("2");
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write("--preload-app");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write("--preload-app");
+            state.mZygoteOutputWriter.newLine();
 
             // Zygote args needs to be strings, so in order to pass ApplicationInfo,
             // write it to a Parcel, and base64 the raw Parcel bytes to the other side.
@@ -697,12 +740,12 @@
             appInfo.writeToParcel(parcel, 0 /* flags */);
             String encodedParcelData = Base64.getEncoder().encodeToString(parcel.marshall());
             parcel.recycle();
-            state.writer.write(encodedParcelData);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(encodedParcelData);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.flush();
+            state.mZygoteOutputWriter.flush();
 
-            return (state.inputStream.readInt() == 0);
+            return (state.mZygoteInputStream.readInt() == 0);
         }
     }
 
@@ -715,27 +758,27 @@
                                                                             IOException {
         synchronized(mLock) {
             ZygoteState state = openZygoteSocketIfNeeded(abi);
-            state.writer.write("5");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write("5");
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write("--preload-package");
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write("--preload-package");
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write(packagePath);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(packagePath);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write(libsPath);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(libsPath);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write(libFileName);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(libFileName);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.write(cacheKey);
-            state.writer.newLine();
+            state.mZygoteOutputWriter.write(cacheKey);
+            state.mZygoteOutputWriter.newLine();
 
-            state.writer.flush();
+            state.mZygoteOutputWriter.flush();
 
-            return (state.inputStream.readInt() == 0);
+            return (state.mZygoteInputStream.readInt() == 0);
         }
     }
 
@@ -749,13 +792,13 @@
         synchronized (mLock) {
             ZygoteState state = openZygoteSocketIfNeeded(abi);
             // Each query starts with the argument count (1 in this case)
-            state.writer.write("1");
-            state.writer.newLine();
-            state.writer.write("--preload-default");
-            state.writer.newLine();
-            state.writer.flush();
+            state.mZygoteOutputWriter.write("1");
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--preload-default");
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.flush();
 
-            return (state.inputStream.readInt() == 0);
+            return (state.mZygoteInputStream.readInt() == 0);
         }
     }
 
@@ -763,20 +806,21 @@
      * Try connecting to the Zygote over and over again until we hit a time-out.
      * @param socketName The name of the socket to connect to.
      */
-    public static void waitForConnectionToZygote(String socketName) {
-        final LocalSocketAddress address =
-                new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.RESERVED);
-        waitForConnectionToZygote(address);
+    public static void waitForConnectionToZygote(String zygoteSocketName) {
+        final LocalSocketAddress zygoteSocketAddress =
+                new LocalSocketAddress(zygoteSocketName, LocalSocketAddress.Namespace.RESERVED);
+        waitForConnectionToZygote(zygoteSocketAddress);
     }
 
     /**
      * Try connecting to the Zygote over and over again until we hit a time-out.
      * @param address The name of the socket to connect to.
      */
-    public static void waitForConnectionToZygote(LocalSocketAddress address) {
+    public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) {
         for (int n = 20; n >= 0; n--) {
             try {
-                final ZygoteState zs = ZygoteState.connect(address);
+                final ZygoteState zs =
+                        ZygoteState.connect(zygoteSocketAddress);
                 zs.close();
                 return;
             } catch (IOException ioe) {
@@ -789,7 +833,8 @@
             } catch (InterruptedException ie) {
             }
         }
-        Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName());
+        Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket "
+                + zygoteSocketAddress.getName());
     }
 
     /**
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index cd3efb4..87efbf3 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -103,6 +103,24 @@
     @SystemApi
     public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
 
+    /**
+     * Telephony related properties definitions.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface Telephony {
+        String NAMESPACE = "telephony";
+        /**
+         * Whether to apply ramping ringer on incoming phone calls.
+         */
+        String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
+        /**
+         * Ringer ramping time in milliseconds.
+         */
+        String PROPERTY_RAMPING_RINGER_DURATION = "ramping_duration";
+    }
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 707778f..fa3f54a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11244,8 +11244,6 @@
          * service_max_inactivity               (long)
          * service_bg_start_timeout             (long)
          * process_start_async                  (boolean)
-         * use_mem_aware_service_restarts       (boolean)
-         * service_restart_delay_duration       (long)
          * </pre>
          *
          * <p>
@@ -11508,6 +11506,20 @@
         public static final String DISPLAY_PANEL_LPM = "display_panel_lpm";
 
         /**
+         * App time limit usage source setting.
+         * This controls which app in a task will be considered the source of usage when
+         * calculating app usage time limits.
+         *
+         * 1 -> task root app
+         * 2 -> current app
+         * Any other value defaults to task root app.
+         *
+         * Need to reboot the device for this setting to take effect.
+         * @hide
+         */
+        public static final String APP_TIME_LIMIT_USAGE_SOURCE = "app_time_limit_usage_source";
+
+        /**
          * App standby (app idle) specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
          * <p>
@@ -11869,6 +11881,28 @@
         public static final String KEEP_PROFILE_IN_BACKGROUND = "keep_profile_in_background";
 
         /**
+         * The default time in ms within which a subsequent connection from an always allowed system
+         * is allowed to reconnect without user interaction.
+         *
+         * @hide
+         */
+        public static final long DEFAULT_ADB_ALLOWED_CONNECTION_TIME = 604800000;
+
+        /**
+         * When the user first connects their device to a system a prompt is displayed to allow
+         * the adb connection with an option to 'Always allow' connections from this system. If the
+         * user selects this always allow option then the connection time is stored for the system.
+         * This setting is the time in ms within which a subsequent connection from an always
+         * allowed system is allowed to reconnect without user interaction.
+         *
+         * Type: long
+         *
+         * @hide
+         */
+        public static final String ADB_ALLOWED_CONNECTION_TIME =
+                "adb_allowed_connection_time";
+
+        /**
          * Get the key that retrieves a bluetooth headset's priority.
          * @hide
          */
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index aaba85b..8e0f522 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -67,7 +67,7 @@
 
     private static final String TAG = AugmentedAutofillService.class.getSimpleName();
 
-    // TODO(b/111330312): STOPSHIP use dynamic value, or change to false
+    // TODO(b/123100811): STOPSHIP use dynamic value, or change to false
     static final boolean DEBUG = true;
     static final boolean VERBOSE = false;
 
@@ -127,8 +127,6 @@
         return false;
     }
 
-    // TODO(b/111330312): add methods to disable autofill per app / activity?
-
     /**
      * Asks the service to handle an "augmented" autofill request.
      *
@@ -175,12 +173,11 @@
                     focusedValue, requestTime, callback);
             mAutofillProxies.put(sessionId,  proxy);
         } else {
-            // TODO(b/111330312): figure out if it's ok to reuse the proxy; add logging
-            // TODO(b/111330312): also make sure to cover scenario on CTS test
+            // TODO(b/123099468): figure out if it's ok to reuse the proxy; add logging
             if (DEBUG) Log.d(TAG, "Reusing proxy for session " + sessionId);
             proxy.update(focusedId, focusedValue);
         }
-        // TODO(b/111330312): set cancellation signal
+        // TODO(b/123101711): set cancellation signal
         final CancellationSignal cancellationSignal = null;
         onFillRequest(new FillRequest(proxy), cancellationSignal, new FillController(proxy),
                 new FillCallback(proxy));
@@ -193,7 +190,7 @@
                 final int sessionId = mAutofillProxies.keyAt(i);
                 final AutofillProxy proxy = mAutofillProxies.valueAt(i);
                 if (proxy == null) {
-                    // TODO(b/111330312): this might be fine, in which case we should logv it
+                    // TODO(b/123100811): this might be fine, in which case we should logv it
                     Log.w(TAG, "No proxy for session " + sessionId);
                     return;
                 }
@@ -303,7 +300,7 @@
             this.mFocusedId = focusedId;
             this.mFocusedValue = focusedValue;
             this.mRequestTime = requestTime;
-            // TODO(b/111330312): linkToDeath
+            // TODO(b/123099468): linkToDeath
         }
 
         @NonNull
@@ -366,7 +363,7 @@
 
         private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) {
             synchronized (mLock) {
-                // TODO(b/111330312): should we close the popupwindow if the focused id changed?
+                // TODO(b/123099468): should we close the popupwindow if the focused id changed?
                 mFocusedId = focusedId;
                 mFocusedValue = focusedValue;
             }
@@ -425,7 +422,7 @@
                 default:
                     Slog.w(TAG, "invalid event reported: " + event);
             }
-            // TODO(b/111330312): log metrics as well
+            // TODO(b/122858578): log metrics as well
         }
 
         public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index bfb4aad..f2a7a35 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -59,7 +59,8 @@
         if (fillWindow != null) {
             fillWindow.show();
         }
-        // TODO(b/111330312): properly implement on server-side by updating the Session state
-        // accordingly (and adding CTS tests)
+        // TODO(b/123099468): must notify the server so it can update the session state to avoid
+        // showing conflicting UIs (for example, if a new request is made to the main autofill
+        // service and it now wants to show something).
     }
 }
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index dad5067..af9905f 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -29,7 +29,7 @@
  * @hide
  */
 @SystemApi
-// TODO(b/111330312): pass a requestId and/or sessionId
+// TODO(b/123100811): pass a requestId and/or sessionId?
 @TestApi
 // TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
 // in the same package as the test, and that module is compiled with SDK=test_current
diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java
index 5285132..f1e904a 100644
--- a/core/java/android/service/autofill/augmented/FillResponse.java
+++ b/core/java/android/service/autofill/augmented/FillResponse.java
@@ -19,8 +19,6 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.view.autofill.AutofillId;
 
 import java.util.List;
@@ -34,7 +32,7 @@
 @TestApi
 //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
 //in the same package as the test, and that module is compiled with SDK=test_current
-public final class FillResponse implements Parcelable {
+public final class FillResponse {
 
     private final FillWindow mFillWindow;
 
@@ -70,8 +68,8 @@
          * @return this builder
          */
         public Builder setFillWindow(@NonNull FillWindow fillWindow) {
-            // TODO(b/111330312): implement / check not null / unit test
-            // TODO(b/111330312): throw exception if FillWindow not updated yet
+            // TODO(b/123100712): check not null / unit test / throw exception if FillWindow not
+            // updated yet
             mFillWindow = fillWindow;
             return this;
         }
@@ -85,7 +83,7 @@
          * @return this builder
          */
         public Builder setIgnoredIds(@NonNull List<AutofillId> ids) {
-            // TODO(b/111330312): implement / check not null / unit test
+            // TODO(b/123100695): implement / check not null / unit test
             return this;
         }
 
@@ -102,37 +100,10 @@
          * @return A built response.
          */
         public FillResponse build() {
-            // TODO(b/111330312): check conditions / add unit test
+            // TODO(b/123100712): check conditions / add unit test
             return new FillResponse(this);
         }
-
-        // TODO(b/111330312): add methods to disable app / activity, either here or on manager
     }
 
-    // TODO(b/111330312): implement to String
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        // TODO(b/111330312): implement
-    }
-
-    public static final Parcelable.Creator<FillResponse> CREATOR =
-            new Parcelable.Creator<FillResponse>() {
-
-                @Override
-                public FillResponse createFromParcel(Parcel parcel) {
-                    // TODO(b/111330312): implement
-                    return null;
-                }
-
-                @Override
-                public FillResponse[] newArray(int size) {
-                    return new FillResponse[size];
-                }
-    };
+    // TODO(b/123100811): implement to String
 }
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 51b0f01..40e3a12 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -20,7 +20,6 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
-import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -42,8 +41,6 @@
 import dalvik.system.CloseGuard;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 
 /**
  * Handle to a window used to display the augmented autofill UI.
@@ -71,18 +68,6 @@
 public final class FillWindow implements AutoCloseable {
     private static final String TAG = "FillWindow";
 
-    /** Indicates the data being shown is a physical address */
-    public static final long FLAG_METADATA_ADDRESS = 0x1;
-
-    // TODO(b/111330312): add more flags
-
-    /** @hide */
-    @LongDef(prefix = { "FLAG" }, value = {
-            FLAG_METADATA_ADDRESS,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface Flags{}
-
     private final Object mLock = new Object();
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
@@ -108,29 +93,22 @@
      *
      * @param rootView new root view
      * @param area coordinates to render the view.
-     * @param flags optional flags such as metadata of what will be rendered in the window. The
-     * Smart Suggestion host might decide whether or not to render the UI based on them.
+     * @param flags currently not used.
      *
      * @return boolean whether the window was updated or not.
      *
      * @throws IllegalArgumentException if the area is not compatible with this window
      */
-    public boolean update(@NonNull Area area, @NonNull View rootView, @Flags long flags) {
+    public boolean update(@NonNull Area area, @NonNull View rootView, long flags) {
         if (DEBUG) {
             Log.d(TAG, "Updating " + area + " + with " + rootView);
         }
-        // TODO(b/111330312): add test case for null
+        // TODO(b/123100712): add test case for null
         Preconditions.checkNotNull(area);
         Preconditions.checkNotNull(rootView);
-        // TODO(b/111330312): must check the area is a valid object returned by
+        // TODO(b/123100712): must check the area is a valid object returned by
         // SmartSuggestionParams, throw IAE if not
 
-        // TODO(b/111330312): must some how pass metadata to the SmartSuggestiongs provider
-
-
-        // TODO(b/111330312): use a SurfaceControl approach; for now, we're manually creating
-        // the window underneath the existing view.
-
         final PresentationParams smartSuggestion = area.proxy.getSmartSuggestionParams();
         if (smartSuggestion == null) {
             Log.w(TAG, "No SmartSuggestionParams");
@@ -148,12 +126,12 @@
 
             mProxy = area.proxy;
 
-            // TODO(b/111330312): once we have the SurfaceControl approach, we should update the
+            // TODO(b/123227534): once we have the SurfaceControl approach, we should update the
             // window instead of destroying. In fact, it might be better to allocate a full window
             // initially, which is transparent (and let touches get through) everywhere but in the
             // rect boundaries.
 
-            // TODO(b/111330312): make sure all touch events are handled, window is always closed,
+            // TODO(b/123099468): make sure all touch events are handled, window is always closed,
             // etc.
 
             mWm = rootView.getContext().getSystemService(WindowManager.class);
@@ -181,7 +159,7 @@
 
     /** @hide */
     void show() {
-        // TODO(b/111330312): check if updated first / throw exception
+        // TODO(b/123100712): check if updated first / throw exception
         if (DEBUG) Log.d(TAG, "show()");
         synchronized (mLock) {
             checkNotDestroyedLocked();
diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl
index dac7590..2b072664 100644
--- a/core/java/android/service/autofill/augmented/IFillCallback.aidl
+++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl
@@ -24,8 +24,7 @@
  * @hide
  */
 interface IFillCallback {
-    // TODO(b/111330312): add cancellation (after we have CTS tests, so we can test it)
+    // TODO(b/123101711): add cancellation (after we have CTS tests, so we can test it)
 //    void onCancellable(in ICancellationSignal cancellation);
-    // TODO(b/111330312): might need to pass the response (once IME implements Smart Suggestions)
     void onSuccess();
 }
diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java
index b60064e..1fb9032 100644
--- a/core/java/android/service/autofill/augmented/PresentationParams.java
+++ b/core/java/android/service/autofill/augmented/PresentationParams.java
@@ -190,7 +190,7 @@
          */
         @Nullable
         public Area getSubArea(@NonNull Rect bounds) {
-            // TODO(b/111330312): implement / check boundaries / throw IAE / add unit test
+            // TODO(b/123100712): implement / check boundaries / throw IAE / add unit test
             return null;
         }
 
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7c1465b..7291d0b 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -122,7 +122,8 @@
         mController.scheduleApplyChangeInsets();
     }
 
-    void applyChangeInsets(InsetsState state) {
+    @VisibleForTesting
+    public void applyChangeInsets(InsetsState state) {
         final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
         ArrayList<SurfaceParams> params = new ArrayList<>();
         if (offset.left != 0) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4b1d1ec..dd6231d 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -231,15 +231,18 @@
         mState.dump(prefix + "  ", pw);
     }
 
-    void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
+    @VisibleForTesting
+    public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
         mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
     }
 
-    void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+    @VisibleForTesting
+    public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
         mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
     }
 
-    void scheduleApplyChangeInsets() {
+    @VisibleForTesting
+    public void scheduleApplyChangeInsets() {
         if (!mAnimCallbackScheduled) {
             mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
                     mAnimCallback, null /* token*/);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 34d076f..47b206ca 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.HardwareRenderer;
+import android.graphics.Picture;
 import android.graphics.Point;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
@@ -553,6 +554,10 @@
         dumpProfileInfo(fd, flags);
     }
 
+    Picture captureRenderingCommands() {
+        return null;
+    }
+
     @Override
     public boolean loadSystemProperties() {
         boolean changed = super.loadSystemProperties();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2014ec2..9d0c9f4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8203,10 +8203,10 @@
      * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
      * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence, int)}
      * respectively. The structure for the a child must be created using
-     * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, int)}, and the
+     * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the
      * {@code autofillId} for a child can be obtained either through
      * {@code childStructure.getAutofillId()} or
-     * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}.
+     * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}.
      *
      * <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
      * <ul>
@@ -8608,7 +8608,7 @@
         if (isAttachedToWindow()) {
             throw new IllegalStateException("Cannot set autofill id when view is attached");
         }
-        if (id != null && id.isVirtual()) {
+        if (id != null && !id.isNonVirtual()) {
             throw new IllegalStateException("Cannot set autofill id assigned to virtual views");
         }
         if (id == null && (mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) == 0) {
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 292e933..5afc07f 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -17,17 +17,21 @@
 package android.view;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.HardwareRenderer;
 import android.graphics.Picture;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
 import android.graphics.RenderNode;
 import android.os.Debug;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -48,16 +52,20 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
 
 /**
  * Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -741,6 +749,123 @@
         root.getViewRootImpl().outputDisplayList(target);
     }
 
+    private static class PictureCallbackHandler implements AutoCloseable,
+            HardwareRenderer.PictureCapturedCallback, Runnable {
+        private final HardwareRenderer mRenderer;
+        private final Function<Picture, Boolean> mCallback;
+        private final Executor mExecutor;
+        private final ReentrantLock mLock = new ReentrantLock(false);
+        private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
+        private boolean mStopListening;
+        private Thread mRenderThread;
+
+        private PictureCallbackHandler(HardwareRenderer renderer,
+                Function<Picture, Boolean> callback, Executor executor) {
+            mRenderer = renderer;
+            mCallback = callback;
+            mExecutor = executor;
+            mRenderer.setPictureCaptureCallback(this);
+        }
+
+        @Override
+        public void close() {
+            mLock.lock();
+            mStopListening = true;
+            mLock.unlock();
+            mRenderer.setPictureCaptureCallback(null);
+        }
+
+        @Override
+        public void onPictureCaptured(Picture picture) {
+            mLock.lock();
+            if (mStopListening) {
+                mLock.unlock();
+                mRenderer.setPictureCaptureCallback(null);
+                return;
+            }
+            if (mRenderThread == null) {
+                mRenderThread = Thread.currentThread();
+            }
+            Picture toDestroy = null;
+            if (mQueue.size() == 3) {
+                toDestroy = mQueue.removeLast();
+            }
+            mQueue.add(picture);
+            mLock.unlock();
+            if (toDestroy == null) {
+                mExecutor.execute(this);
+            } else {
+                toDestroy.close();
+            }
+        }
+
+        @Override
+        public void run() {
+            mLock.lock();
+            final Picture picture = mQueue.poll();
+            final boolean isStopped = mStopListening;
+            mLock.unlock();
+            if (Thread.currentThread() == mRenderThread) {
+                close();
+                throw new IllegalStateException(
+                        "ViewDebug#startRenderingCommandsCapture must be given an executor that "
+                        + "invokes asynchronously");
+            }
+            if (isStopped) {
+                picture.close();
+                return;
+            }
+            final boolean keepReceiving = mCallback.apply(picture);
+            if (!keepReceiving) {
+                close();
+            }
+        }
+    }
+
+    /**
+     * Begins capturing the entire rendering commands for the view tree referenced by the given
+     * view. The view passed may be any View in the tree as long as it is attached. That is,
+     * {@link View#isAttachedToWindow()} must be true.
+     *
+     * Every time a frame is rendered a Picture will be passed to the given callback via the given
+     * executor. As long as the callback returns 'true' it will continue to receive new frames.
+     * The system will only invoke the callback at a rate that the callback is able to keep up with.
+     * That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
+     * then the callback will only receive 33% of the frames produced.
+     *
+     * This method must be called on the same thread as the View tree.
+     *
+     * @param tree The View tree to capture the rendering commands.
+     * @param callback The callback to invoke on every frame produced. Should return true to
+     *                 continue receiving new frames, false to stop capturing.
+     * @param executor The executor to invoke the callback on. Recommend using a background thread
+     *                 to avoid stalling the UI thread. Must be an asynchronous invoke or an
+     *                 exception will be thrown.
+     * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
+     * that the callback may continue to receive another frame or two depending on thread timings.
+     * Returns null if the capture stream cannot be started, such as if there's no
+     * HardwareRenderer for the given view tree.
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
+            Function<Picture, Boolean> callback) {
+        final View.AttachInfo attachInfo = tree.mAttachInfo;
+        if (attachInfo == null) {
+            throw new IllegalArgumentException("Given view isn't attached");
+        }
+        if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
+            throw new IllegalStateException("Called on the wrong thread."
+                    + " Must be called on the thread that owns the given View");
+        }
+        final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
+        if (renderer != null) {
+            return new PictureCallbackHandler(renderer, callback, executor);
+        }
+        return null;
+    }
+
     private static void capture(View root, final OutputStream clientStream, String parameter)
             throws IOException {
 
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 9c935af..f1c7b69 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -29,12 +29,14 @@
     /** @hide */
     public static final int NO_SESSION = 0;
 
-    private static final int FLAG_IS_VIRTUAL = 0x1;
-    private static final int FLAG_HAS_SESSION = 0x2;
+    private static final int FLAG_IS_VIRTUAL_INT = 0x1;
+    private static final int FLAG_IS_VIRTUAL_LONG = 0x2;
+    private static final int FLAG_HAS_SESSION = 0x4;
 
     private final int mViewId;
     private final int mFlags;
-    private final int mVirtualId;
+    private final int mVirtualIntId;
+    private final long mVirtualLongId;
     private final int mSessionId;
 
     /** @hide */
@@ -46,40 +48,89 @@
     /** @hide */
     @TestApi
     public AutofillId(@NonNull AutofillId parent, int virtualChildId) {
-        this(FLAG_IS_VIRTUAL, parent.mViewId, virtualChildId, NO_SESSION);
+        this(FLAG_IS_VIRTUAL_INT, parent.mViewId, virtualChildId, NO_SESSION);
     }
 
     /** @hide */
     public AutofillId(int parentId, int virtualChildId) {
-        this(FLAG_IS_VIRTUAL, parentId, virtualChildId, NO_SESSION);
+        this(FLAG_IS_VIRTUAL_INT, parentId, virtualChildId, NO_SESSION);
     }
 
     /** @hide */
-    public AutofillId(@NonNull AutofillId parent, int virtualChildId, int sessionId) {
-        this(FLAG_IS_VIRTUAL | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
+    public AutofillId(@NonNull AutofillId parent, long virtualChildId, int sessionId) {
+        this(FLAG_IS_VIRTUAL_LONG | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
     }
 
-    private AutofillId(int flags, int parentId, int virtualChildId, int sessionId) {
+    private AutofillId(int flags, int parentId, long virtualChildId, int sessionId) {
         mFlags = flags;
         mViewId = parentId;
-        mVirtualId = virtualChildId;
+        mVirtualIntId = ((flags & FLAG_IS_VIRTUAL_INT) != 0) ? (int) virtualChildId : View.NO_ID;
+        mVirtualLongId = ((flags & FLAG_IS_VIRTUAL_LONG) != 0) ? virtualChildId : View.NO_ID;
         mSessionId = sessionId;
     }
 
-
     /** @hide */
     public int getViewId() {
         return mViewId;
     }
 
-    /** @hide */
-    public int getVirtualChildId() {
-        return mVirtualId;
+    /**
+     * Gets the virtual child id.
+     *
+     * <p>Should only be used on subsystems where such id is represented by an {@code int}
+     * (Assist and Autofill).
+     *
+     * @hide
+     */
+    public int getVirtualChildIntId() {
+        return mVirtualIntId;
     }
 
-    /** @hide */
-    public boolean isVirtual() {
-        return (mFlags & FLAG_IS_VIRTUAL) != 0;
+    /**
+     * Gets the virtual child id.
+     *
+     * <p>Should only be used on subsystems where such id is represented by a {@code long}
+     * (ContentCapture).
+     *
+     * @hide
+     */
+    public long getVirtualChildLongId() {
+        return mVirtualLongId;
+    }
+
+    /**
+     * Checks whether this node represents a virtual child, whose id is represented by an
+     * {@code int}.
+     *
+     * <p>Should only be used on subsystems where such id is represented by an {@code int}
+     * (Assist and Autofill).
+     *
+     * @hide
+     */
+    public boolean isVirtualInt() {
+        return (mFlags & FLAG_IS_VIRTUAL_INT) != 0;
+    }
+
+    /**
+     * Checks whether this node represents a virtual child, whose id is represented by an
+     * {@code long}.
+     *
+     * <p>Should only be used on subsystems where such id is represented by a {@code long}
+     * (ContentCapture).
+     *
+     * @hide
+     */
+    public boolean isVirtualLong() {
+        return (mFlags & FLAG_IS_VIRTUAL_LONG) != 0;
+    }
+
+    /**
+     * Checks whether this node represents a non-virtual child.
+     *
+     * @hide
+     */
+    public boolean isNonVirtual() {
+        return !isVirtualInt() && !isVirtualLong();
     }
 
     private boolean hasSession() {
@@ -100,7 +151,8 @@
         final int prime = 31;
         int result = 1;
         result = prime * result + mViewId;
-        result = prime * result + mVirtualId;
+        result = prime * result + mVirtualIntId;
+        result = prime * result + (int) (mVirtualLongId ^ (mVirtualLongId >>> 32));
         result = prime * result + mSessionId;
         return result;
     }
@@ -112,7 +164,8 @@
         if (getClass() != obj.getClass()) return false;
         final AutofillId other = (AutofillId) obj;
         if (mViewId != other.mViewId) return false;
-        if (mVirtualId != other.mVirtualId) return false;
+        if (mVirtualIntId != other.mVirtualIntId) return false;
+        if (mVirtualLongId != other.mVirtualLongId) return false;
         if (mSessionId != other.mSessionId) return false;
         return true;
     }
@@ -120,9 +173,12 @@
     @Override
     public String toString() {
         final StringBuilder builder = new StringBuilder().append(mViewId);
-        if (isVirtual()) {
-            builder.append(':').append(mVirtualId);
+        if (isVirtualInt()) {
+            builder.append(':').append(mVirtualIntId);
+        } else if (isVirtualLong()) {
+            builder.append(':').append(mVirtualLongId);
         }
+
         if (hasSession()) {
             builder.append('@').append(mSessionId);
         }
@@ -138,12 +194,14 @@
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(mViewId);
         parcel.writeInt(mFlags);
-        if (isVirtual()) {
-            parcel.writeInt(mVirtualId);
-        }
         if (hasSession()) {
             parcel.writeInt(mSessionId);
         }
+        if (isVirtualInt()) {
+            parcel.writeInt(mVirtualIntId);
+        } else if (isVirtualLong()) {
+            parcel.writeLong(mVirtualLongId);
+        }
     }
 
     public static final Parcelable.Creator<AutofillId> CREATOR =
@@ -152,9 +210,14 @@
         public AutofillId createFromParcel(Parcel source) {
             final int viewId = source.readInt();
             final int flags = source.readInt();
-            final int virtualId = (flags & FLAG_IS_VIRTUAL) != 0 ? source.readInt() : View.NO_ID;
             final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION;
-            return new AutofillId(flags, viewId, virtualId, sessionId);
+            if ((flags & FLAG_IS_VIRTUAL_INT) != 0) {
+                return new AutofillId(flags, viewId, source.readInt(), sessionId);
+            }
+            if ((flags & FLAG_IS_VIRTUAL_LONG) != 0) {
+                return new AutofillId(flags, viewId, source.readLong(), sessionId);
+            }
+            return new AutofillId(flags, viewId, View.NO_ID, sessionId);
         }
 
         @Override
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 888a4c5..64c34f61 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -17,6 +17,7 @@
 package android.view.autofill;
 
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.util.DebugUtils.flagsToString;
 import static android.view.autofill.Helper.sDebug;
 import static android.view.autofill.Helper.sVerbose;
 
@@ -25,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.content.ComponentName;
@@ -77,6 +79,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 //TODO: use java.lang.ref.Cleaner once Android supports Java 9
 import sun.misc.Cleaner;
@@ -336,6 +339,25 @@
     public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
 
     /**
+     * Displays the Augment Autofill window using the same mechanism (such as a popup-window
+     * attached to the focused view) as the standard autofill.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x1;
+
+    /** @hide */ // TODO(b/123233342): remove when not used anymore
+    public static final int FLAG_SMART_SUGGESTION_LEGACY = 0x2;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = {
+            FLAG_SMART_SUGGESTION_SYSTEM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SmartSuggestionMode {}
+
+    /**
      * Makes an authentication id from a request id and a dataset id.
      *
      * @param requestId The request id.
@@ -1686,7 +1708,7 @@
                 final IAutoFillManager service = mService;
                 final IAutoFillManagerClient serviceClient = mServiceClient;
                 mServiceClientCleaner = Cleaner.create(this, () -> {
-                    // TODO(b/111330312): call service to also remove reference to
+                    // TODO(b/123100811): call service to also remove reference to
                     // mAugmentedAutofillServiceClient
                     try {
                         service.removeClient(serviceClient, userId);
@@ -1746,6 +1768,108 @@
         }
     }
 
+    /**
+     * Defines whether augmented autofill should be triggered for activities with such
+     * {@link android.content.ComponentName}.
+     *
+     * <p>Useful to blacklist a particular activity.
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+    //in the same package as the test, and that module is compiled with SDK=test_current
+    public void setActivityAugmentedAutofillEnabled(@NonNull ComponentName activity,
+            boolean enabled) {
+        // TODO(b/123100824): implement
+    }
+
+    /**
+     * Defines whether augmented autofill should be triggered for activities of the app with such
+     * {@code packageName}.
+     *
+     * <p>Useful to blacklist any activity from a particular app.
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+    //in the same package as the test, and that module is compiled with SDK=test_current
+    public void setPackageAugmentedAutofillEnabled(@NonNull String packageName, boolean enabled) {
+        // TODO(b/123100824): implement
+    }
+
+    /**
+     * Explicitly limits augmented autofill to the given packages and activities.
+     *
+     * <p>When the whitelist is set, it overrides the values passed to
+     * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)}
+     * and {@link #setPackageAugmentedAutofillEnabled(String, boolean)}.
+     *
+     * <p>To reset the whitelist, call it passing {@code null} to both arguments.
+     *
+     * <p>Useful when the service wants to restrict augmented autofill to a category of apps, like
+     * apps that uses addresses. For example, if the service wants to support augmented autofill on
+     * all activities of app {@code AddressApp1} and just activities {@code act1} and {@code act2}
+     * of {@code AddressApp2}, it would call:
+     * {@code setAugmentedAutofillWhitelist(Arrays.asList("AddressApp1"),
+     * Arrays.asList(new ComponentName("AddressApp2", "act1"),
+     * new ComponentName("AddressApp2", "act2")));}
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+    //in the same package as the test, and that module is compiled with SDK=test_current
+    public void setAugmentedAutofillWhitelist(@Nullable List<String> packages,
+            @Nullable List<ComponentName> activities) {
+        // TODO(b/123100824): implement
+    }
+
+    /**
+     * Gets the activities where augmented autofill was disabled by
+     * {@link #setActivityAugmentedAutofillEnabled(ComponentName, boolean)}.
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @NonNull
+    public Set<ComponentName> getAugmentedAutofillDisabledActivities() {
+        return null; // TODO(b/123100824): implement
+    }
+
+    /**
+     * Gets the apps where content capture was disabled by
+     * {@link #setPackageAugmentedAutofillEnabled(String, boolean)}.
+     *
+     * <p><b>Note:</b> This method should only be called by the app providing the augmented autofill
+     * service, and it's ignored if the caller isn't it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @NonNull
+    public Set<String> getAugmentedAutofillDisabledPackages() {
+        return null; // TODO(b/123100824): implement
+    }
+
     private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
             Rect anchorBounds, IAutofillWindowPresenter presenter) {
         final View anchor = findView(id);
@@ -1769,8 +1893,8 @@
         }
 
         if (callback != null) {
-            if (id.isVirtual()) {
-                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+            if (id.isVirtualInt()) {
+                callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
                         AutofillCallback.EVENT_INPUT_SHOWN);
             } else {
                 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
@@ -1896,7 +2020,7 @@
                     failedIds.add(id);
                     continue;
                 }
-                if (id.isVirtual()) {
+                if (id.isVirtualInt()) {
                     if (virtualValues == null) {
                         // Most likely there will be just one view with virtual children.
                         virtualValues = new ArrayMap<>(1);
@@ -1907,7 +2031,7 @@
                         valuesByParent = new SparseArray<>(5);
                         virtualValues.put(view, valuesByParent);
                     }
-                    valuesByParent.put(id.getVirtualChildId(), value);
+                    valuesByParent.put(id.getVirtualChildIntId(), value);
                 } else {
                     // Mark the view as to be autofilled with 'value'
                     if (mLastAutofilledData == null) {
@@ -2142,8 +2266,8 @@
         }
 
         if (callback != null) {
-            if (id.isVirtual()) {
-                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+            if (id.isVirtualInt()) {
+                callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
                         AutofillCallback.EVENT_INPUT_HIDDEN);
             } else {
                 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
@@ -2169,8 +2293,8 @@
         }
 
         if (callback != null) {
-            if (id.isVirtual()) {
-                callback.onAutofillEvent(anchor, id.getVirtualChildId(),
+            if (id.isVirtualInt()) {
+                callback.onAutofillEvent(anchor, id.getVirtualChildIntId(),
                         AutofillCallback.EVENT_INPUT_UNAVAILABLE);
             } else {
                 callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
@@ -2296,6 +2420,11 @@
         }
     }
 
+    /** @hide */
+    public static String getSmartSuggestionModeToString(@SmartSuggestionMode int flags) {
+        return flagsToString(AutofillManager.class, "FLAG_SMART_SUGGESTION_", flags);
+    }
+
     @GuardedBy("mLock")
     private boolean isActiveLocked() {
         return mState == STATE_ACTIVE;
@@ -2972,7 +3101,6 @@
 
         @Override
         public Rect getViewCoordinates(@NonNull AutofillId id) {
-            // TODO(b/111330312): use handler / callback?
             final AutofillManager afm = mAfm.get();
             if (afm == null) return null;
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index b620ab1..a1e581e 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -352,14 +352,14 @@
      * @throws IllegalArgumentException if {@code virtualIds} is empty
      */
     public final void notifyViewsDisappeared(@NonNull AutofillId hostId,
-            @NonNull int[] virtualIds) {
-        Preconditions.checkArgument(!hostId.isVirtual(), "parent cannot be virtual");
+            @NonNull long[] virtualIds) {
+        Preconditions.checkArgument(hostId.isNonVirtual(), "parent cannot be virtual");
         Preconditions.checkArgument(!ArrayUtils.isEmpty(virtualIds), "virtual ids cannot be empty");
         if (!isContentCaptureEnabled()) return;
 
         // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is
         // parcelized
-        for (int id : virtualIds) {
+        for (long id : virtualIds) {
             internalNotifyViewDisappeared(new AutofillId(hostId, id, getIdAsInt()));
         }
     }
@@ -405,9 +405,9 @@
      *
      * @throws IllegalArgumentException if the {@code parentId} is a virtual child id.
      */
-    public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) {
+    public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, long virtualChildId) {
         Preconditions.checkNotNull(parentId);
-        Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children");
+        Preconditions.checkArgument(parentId.isNonVirtual(), "virtual ids cannot have children");
         return new AutofillId(parentId, virtualChildId, getIdAsInt());
     }
 
@@ -423,7 +423,7 @@
      */
     @NonNull
     public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
-            int virtualId) {
+            long virtualId) {
         return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt());
     }
 
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index cbc946b..0cabafa 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -617,7 +617,7 @@
         }
 
         @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
-        public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId, int sessionId) {
+        public ViewStructureImpl(@NonNull AutofillId parentId, long virtualId, int sessionId) {
             mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
             mNode.mAutofillId = new AutofillId(parentId, virtualId, sessionId);
         }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 10b99ec..15088d2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -27,6 +27,7 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -1881,7 +1882,15 @@
 
     /**
      * Notify the event when the user tapped or clicked the text view.
+     *
+     * @param view {@link View} which is being clicked.
+     * @see InputMethodService#onViewClicked(boolean)
+     * @deprecated The semantics of this method can never be defined well for composite {@link View}
+     *             that works as a giant "Canvas", which can host its own UI hierarchy and sub focus
+     *             state. {@link android.webkit.WebView} is a good example. Application / IME
+     *             developers should not rely on this method.
      */
+    @Deprecated
     public void viewClicked(View view) {
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
@@ -2484,11 +2493,48 @@
      */
     @RequiresPermission(WRITE_SECURE_SETTINGS)
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
-        try {
-            return mService.setCurrentInputMethodSubtype(subtype);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because "
+                    + "almost always it is a bug under multi-user / multi-profile environment. "
+                    + "Consider directly interacting with InputMethodManagerService "
+                    + "via LocalServices.");
+            return false;
         }
+        if (subtype == null) {
+            // See the JavaDoc. This is how this method has worked.
+            return false;
+        }
+        final Context fallbackContext = ActivityThread.currentApplication();
+        if (fallbackContext == null) {
+            return false;
+        }
+        if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+                != PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+        final ContentResolver contentResolver = fallbackContext.getContentResolver();
+        final String imeId = Settings.Secure.getString(contentResolver,
+                Settings.Secure.DEFAULT_INPUT_METHOD);
+        if (ComponentName.unflattenFromString(imeId) == null) {
+            // Null or invalid IME ID format.
+            return false;
+        }
+        final List<InputMethodSubtype> enabledSubtypes;
+        try {
+            enabledSubtypes = mService.getEnabledInputMethodSubtypeList(imeId, true);
+        } catch (RemoteException e) {
+            return false;
+        }
+        final int numSubtypes = enabledSubtypes.size();
+        for (int i = 0; i < numSubtypes; ++i) {
+            final InputMethodSubtype enabledSubtype = enabledSubtypes.get(i);
+            if (enabledSubtype.equals(subtype)) {
+                Settings.Secure.putInt(contentResolver,
+                        Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, enabledSubtype.hashCode());
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 29b3b3c..3a1c457 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -150,7 +150,7 @@
         }
 
         try {
-            sZygote = Process.zygoteProcess.startChildZygote(
+            sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
                     "com.android.internal.os.WebViewZygoteInit",
                     "webview_zygote",
                     Process.WEBVIEW_ZYGOTE_UID,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1085e5d..780fe8d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5117,7 +5117,7 @@
      * @attr ref android.R.styleable#TextView_scrollHorizontally
      * @see #setHorizontallyScrolling(boolean)
      */
-    public final boolean isHorizontallyScrolling() {
+    public final boolean isHorizontallyScrollable() {
         return mHorizontallyScrolling;
     }
 
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index b4d8322..30137e38 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -103,7 +103,9 @@
      * binding to every ChooserTargetService implementation.
      */
     // TODO(b/121287573): Replace with a system flag (setprop?)
-    private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = false;
+    private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
+    private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
+
     // TODO(b/121287224): Re-evaluate this limit
     private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
 
@@ -136,6 +138,7 @@
     private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
     private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
     private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3;
+    private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;
 
     private final Handler mChooserHandler = new Handler() {
         @Override
@@ -182,6 +185,9 @@
                         mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
                                 resultInfo.resultTargets);
                     }
+                    break;
+
+                case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
                     sendVoiceChoicesIfNeeded();
                     mChooserListAdapter.setShowServiceTargets(true);
                     break;
@@ -630,6 +636,7 @@
             // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
             // for direct share targets. After ShareSheet is refactored we should use the
             // ShareShortcutInfos directly.
+            boolean resultMessageSent = false;
             for (int i = 0; i < driList.size(); i++) {
                 List<ChooserTarget> chooserTargets = new ArrayList<>();
                 for (int j = 0; j < resultList.size(); j++) {
@@ -646,6 +653,13 @@
                 msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
                 msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
                 mChooserHandler.sendMessage(msg);
+                resultMessageSent = true;
+            }
+
+            if (resultMessageSent) {
+                final Message msg = Message.obtain();
+                msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
+                mChooserHandler.sendMessage(msg);
             }
         });
     }
@@ -1178,13 +1192,17 @@
                     mTargetsNeedPruning = true;
                 }
             }
+
             if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
                 if (DEBUG) {
                     Log.d(TAG, "querying direct share targets from ShortcutManager");
                 }
                 queryDirectShareTargets(this);
-            } else {
-                if (DEBUG) Log.d(TAG, "List built querying services");
+            }
+            if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
+                if (DEBUG) {
+                    Log.d(TAG, "List built querying services");
+                }
                 queryTargetServices(this);
             }
         }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index b28f4cd..f609f2f 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -16,13 +16,33 @@
 
 package com.android.internal.os;
 
+import static android.system.OsConstants.O_CLOEXEC;
+
+import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
+
+import android.net.Credentials;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.FactoryTest;
 import android.os.IVold;
+import android.os.Process;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.util.Log;
 
 import dalvik.system.ZygoteHooks;
 
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
 /** @hide */
 public final class Zygote {
     /*
@@ -94,6 +114,18 @@
     /** Read-write external storage should be mounted instead of package sandbox */
     public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL;
 
+    /** Number of bytes sent to the Zygote over blastula pipes or the pool event FD */
+    public static final int BLASTULA_MANAGEMENT_MESSAGE_BYTES = 8;
+
+    /** If the blastula pool should be created and used to start applications */
+    public static final boolean BLASTULA_POOL_ENABLED = false;
+
+    /**
+     * File descriptor used for communication between the signal handler and the ZygoteServer poll
+     * loop.
+     * */
+    protected static FileDescriptor sBlastulaPoolEventFD;
+
     private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
 
     /**
@@ -123,6 +155,43 @@
      */
     public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end=";
 
+    /** Prefix prepended to socket names created by init */
+    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
+
+    /**
+     * The maximum value that the sBlastulaPoolMax variable may take.  This value
+     * is a mirror of BLASTULA_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp.
+     */
+    static final int BLASTULA_POOL_MAX_LIMIT = 10;
+
+    /**
+     * The minimum value that the sBlastulaPoolMin variable may take.
+     */
+    static final int BLASTULA_POOL_MIN_LIMIT = 1;
+
+    /**
+     * The runtime-adjustable maximum Blastula pool size.
+     */
+    static int sBlastulaPoolMax = BLASTULA_POOL_MAX_LIMIT;
+
+    /**
+     * The runtime-adjustable minimum Blastula pool size.
+     */
+    static int sBlastulaPoolMin = BLASTULA_POOL_MIN_LIMIT;
+
+    /**
+     * The runtime-adjustable value used to determine when to re-fill the
+     * blastula pool.  The pool will be re-filled when
+     * (sBlastulaPoolMax - gBlastulaPoolCount) >= sBlastulaPoolRefillThreshold.
+     */
+    // TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax.
+    static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2);
+
+    private static LocalServerSocket sBlastulaPoolSocket = null;
+
+    /** a prototype instance for a future List.toArray() */
+    protected static final int[][] INT_ARRAY_2D = new int[0][0];
+
     private Zygote() {}
 
     /** Called for some security initialization before any fork. */
@@ -189,6 +258,51 @@
             int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
             String appDataDir, String packageName, String[] packagesForUid, String[] visibleVolIds);
 
+    /**
+     * Specialize a Blastula instance.  The current VM must have been started
+     * with the -Xzygote flag.
+     *
+     * @param uid  The UNIX uid that the new process should setuid() to before spawning any threads
+     * @param gid  The UNIX gid that the new process should setgid() to before spawning any threads
+     * @param gids null-ok;  A list of UNIX gids that the new process should
+     * setgroups() to before spawning any threads
+     * @param runtimeFlags  Bit flags that enable ART features
+     * @param rlimits null-ok  An array of rlimit tuples, with the second
+     * dimension having a length of 3 and representing
+     * (resource, rlim_cur, rlim_max). These are set via the posix
+     * setrlimit(2) call.
+     * @param seInfo null-ok  A string specifying SELinux information for
+     * the new process.
+     * @param niceName null-ok  A string specifying the process name.
+     * @param startChildZygote  If true, the new child process will itself be a
+     * new zygote process.
+     * @param instructionSet null-ok  The instruction set to use.
+     * @param appDataDir null-ok  The data directory of the app.
+     */
+    public static void specializeBlastula(int uid, int gid, int[] gids, int runtimeFlags,
+            int[][] rlimits, int mountExternal, String seInfo, String niceName,
+            boolean startChildZygote, String instructionSet, String appDataDir,
+            String packageName, String[] packagesForUid, String[] visibleVolIds) {
+
+        nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
+                                 niceName, startChildZygote, instructionSet, appDataDir,
+                                 packageName, packagesForUid, visibleVolIds);
+
+        // Enable tracing as soon as possible for the child process.
+        Trace.setTracingEnabled(true, runtimeFlags);
+
+        // Note that this event ends at the end of handleChildProc.
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
+
+        /*
+         * This is called here (instead of after the fork but before the specialize) to maintain
+         * consistancy with the code paths for forkAndSpecialize.
+         *
+         * TODO (chriswailes): Look into moving this to immediately after the fork.
+         */
+        VM_HOOKS.postForkCommon();
+    }
+
     private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
@@ -259,20 +373,483 @@
      */
     protected static native void nativeUnmountStorageOnInit();
 
+    /**
+     * Get socket file descriptors (opened by init) from the environment and
+     * store them for access from native code later.
+     *
+     * @param isPrimary  True if this is the zygote process, false if it is zygote_secondary
+     */
+    public static void getSocketFDs(boolean isPrimary) {
+        nativeGetSocketFDs(isPrimary);
+    }
+
     protected static native void nativeGetSocketFDs(boolean isPrimary);
 
+    /**
+     * Initialize the blastula pool and fill it with the desired number of
+     * processes.
+     */
+    protected static Runnable initBlastulaPool() {
+        if (BLASTULA_POOL_ENABLED) {
+            sBlastulaPoolEventFD = getBlastulaPoolEventFD();
+
+            return fillBlastulaPool(null);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Checks to see if the current policy says that pool should be refilled, and spawns new
+     * blastulas if necessary.
+     *
+     * NOTE: This function doesn't need to be guarded with BLASTULA_POOL_ENABLED because it is
+     *       only called from contexts that are only valid if the pool is enabled.
+     *
+     * @param sessionSocketRawFDs  Anonymous session sockets that are currently open
+     * @return In the Zygote process this function will always return null; in blastula processes
+     *         this function will return a Runnable object representing the new application that is
+     *         passed up from blastulaMain.
+     */
+    protected static Runnable fillBlastulaPool(int[] sessionSocketRawFDs) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool");
+
+        int blastulaPoolCount = getBlastulaPoolCount();
+
+        int numBlastulasToSpawn = sBlastulaPoolMax - blastulaPoolCount;
+
+        if (blastulaPoolCount < sBlastulaPoolMin
+                || numBlastulasToSpawn >= sBlastulaPoolRefillThreshold) {
+
+            // Disable some VM functionality and reset some system values
+            // before forking.
+            VM_HOOKS.preFork();
+            resetNicePriority();
+
+            while (blastulaPoolCount++ < sBlastulaPoolMax) {
+                Runnable caller = forkBlastula(sessionSocketRawFDs);
+
+                if (caller != null) {
+                    return caller;
+                }
+            }
+
+            // Re-enable runtime services for the Zygote.  Blastula services
+            // are re-enabled in specializeBlastula.
+            VM_HOOKS.postForkCommon();
+
+            Log.i("zygote", "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn);
+        }
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        return null;
+    }
+
+    /**
+     * @return Number of blastulas currently in the pool
+     */
+    private static int getBlastulaPoolCount() {
+        return nativeGetBlastulaPoolCount();
+    }
+
     private static native int nativeGetBlastulaPoolCount();
 
+    /**
+     * @return The event FD used for communication between the signal handler and the ZygoteServer
+     *         poll loop
+     */
+    private static FileDescriptor getBlastulaPoolEventFD() {
+        FileDescriptor fd = new FileDescriptor();
+        fd.setInt$(nativeGetBlastulaPoolEventFD());
+
+        return fd;
+    }
+
     private static native int nativeGetBlastulaPoolEventFD();
 
+    /**
+     * Fork a new blastula process from the zygote
+     *
+     * @param sessionSocketRawFDs  Anonymous session sockets that are currently open
+     * @return In the Zygote process this function will always return null; in blastula processes
+     *         this function will return a Runnable object representing the new application that is
+     *         passed up from blastulaMain.
+     */
+    private static Runnable forkBlastula(int[] sessionSocketRawFDs) {
+        FileDescriptor[] pipeFDs = null;
+
+        try {
+            pipeFDs = Os.pipe2(O_CLOEXEC);
+        } catch (ErrnoException errnoEx) {
+            throw new IllegalStateException("Unable to create blastula pipe.", errnoEx);
+        }
+
+        int pid =
+                nativeForkBlastula(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs);
+
+        if (pid == 0) {
+            IoUtils.closeQuietly(pipeFDs[0]);
+            return blastulaMain(pipeFDs[1]);
+        } else {
+            // The read-end of the pipe will be closed by the native code.
+            // See removeBlastulaTableEntry();
+            IoUtils.closeQuietly(pipeFDs[1]);
+            return null;
+        }
+    }
+
     private static native int nativeForkBlastula(int readPipeFD,
                                                  int writePipeFD,
                                                  int[] sessionSocketRawFDs);
 
+    /**
+     * This function is used by blastulas to wait for specialization requests from the system
+     * server.
+     *
+     * @param writePipe  The write end of the reporting pipe used to communicate with the poll loop
+     *                   of the ZygoteServer.
+     * @return A runnable oject representing the new application.
+     */
+    static Runnable blastulaMain(FileDescriptor writePipe) {
+        final int pid = Process.myPid();
+
+        LocalSocket sessionSocket = null;
+        DataOutputStream blastulaOutputStream = null;
+        Credentials peerCredentials = null;
+        String[] argStrings = null;
+
+        while (true) {
+            try {
+                sessionSocket = sBlastulaPoolSocket.accept();
+
+                BufferedReader blastulaReader =
+                        new BufferedReader(new InputStreamReader(sessionSocket.getInputStream()));
+                blastulaOutputStream =
+                        new DataOutputStream(sessionSocket.getOutputStream());
+
+                peerCredentials = sessionSocket.getPeerCredentials();
+
+                argStrings = readArgumentList(blastulaReader);
+
+                if (argStrings != null) {
+                    break;
+                } else {
+                    Log.e("Blastula", "Truncated command received.");
+                    IoUtils.closeQuietly(sessionSocket);
+                }
+            } catch (IOException ioEx) {
+                Log.e("Blastula", "Failed to read command: " + ioEx.getMessage());
+                IoUtils.closeQuietly(sessionSocket);
+            }
+        }
+
+        ZygoteArguments args = new ZygoteArguments(argStrings);
+
+        // TODO (chriswailes): Should this only be run for debug builds?
+        validateBlastulaCommand(args);
+
+        applyUidSecurityPolicy(args, peerCredentials);
+        applyDebuggerSystemProperty(args);
+
+        int[][] rlimits = null;
+
+        if (args.mRLimits != null) {
+            rlimits = args.mRLimits.toArray(INT_ARRAY_2D);
+        }
+
+        // This must happen before the SELinux policy for this process is
+        // changed when specializing.
+        try {
+            // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
+            // Process.ProcessStartResult object.
+            blastulaOutputStream.writeInt(pid);
+        } catch (IOException ioEx) {
+            Log.e("Blastula", "Failed to write response to session socket: " + ioEx.getMessage());
+            System.exit(-1);
+        } finally {
+            IoUtils.closeQuietly(sessionSocket);
+            IoUtils.closeQuietly(sBlastulaPoolSocket);
+        }
+
+        try {
+            ByteArrayOutputStream buffer =
+                    new ByteArrayOutputStream(Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES);
+            DataOutputStream outputStream = new DataOutputStream(buffer);
+
+            // This is written as a long so that the blastula reporting pipe and blastula pool
+            // event FD handlers in ZygoteServer.runSelectLoop can be unified.  These two cases
+            // should both send/receive 8 bytes.
+            outputStream.writeLong(pid);
+            outputStream.flush();
+
+            Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
+        } catch (Exception ex) {
+            Log.e("Blastula",
+                    String.format("Failed to write PID (%d) to pipe (%d): %s",
+                            pid, writePipe.getInt$(), ex.getMessage()));
+            System.exit(-1);
+        } finally {
+            IoUtils.closeQuietly(writePipe);
+        }
+
+        specializeBlastula(args.mUid, args.mGid, args.mGids,
+                           args.mRuntimeFlags, rlimits, args.mMountExternal,
+                           args.mSeInfo, args.mNiceName, args.mStartChildZygote,
+                           args.mInstructionSet, args.mAppDataDir, args.mPackageName,
+                           args.mPackagesForUid, args.mVisibleVolIds);
+
+        if (args.mNiceName != null) {
+            Process.setArgV0(args.mNiceName);
+        }
+
+        // End of the postFork event.
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
+                                     args.mRemainingArgs,
+                                     null /* classLoader */);
+    }
+
+    private static final String BLASTULA_ERROR_PREFIX = "Invalid command to blastula: ";
+
+    /**
+     * Checks a set of zygote arguments to see if they can be handled by a blastula.  Throws an
+     * exception if an invalid arugment is encountered.
+     * @param args  The arguments to test
+     */
+    static void validateBlastulaCommand(ZygoteArguments args) {
+        if (args.mAbiListQuery) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--query-abi-list");
+        } else if (args.mPidQuery) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--get-pid");
+        } else if (args.mPreloadDefault) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-default");
+        } else if (args.mPreloadPackage != null) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-package");
+        } else if (args.mPreloadApp != null) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--preload-app");
+        } else if (args.mStartChildZygote) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--start-child-zygote");
+        } else if (args.mApiBlacklistExemptions != null) {
+            throw new IllegalArgumentException(
+                BLASTULA_ERROR_PREFIX + "--set-api-blacklist-exemptions");
+        } else if (args.mHiddenApiAccessLogSampleRate != -1) {
+            throw new IllegalArgumentException(
+                BLASTULA_ERROR_PREFIX + "--hidden-api-log-sampling-rate=");
+        } else if (args.mInvokeWith != null) {
+            throw new IllegalArgumentException(BLASTULA_ERROR_PREFIX + "--invoke-with");
+        } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) {
+            throw new ZygoteSecurityException("Client may not specify capabilities: "
+                + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities)
+                + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities));
+        }
+    }
+
+    /**
+     * @return  Raw file descriptors for the read-end of blastula reporting pipes.
+     */
+    protected static int[] getBlastulaPipeFDs() {
+        return nativeGetBlastulaPipeFDs();
+    }
+
     private static native int[] nativeGetBlastulaPipeFDs();
 
+    /**
+     * Remove the blastula table entry for the provided process ID.
+     *
+     * @param blastulaPID  Process ID of the entry to remove
+     * @return True if the entry was removed; false if it doesn't exist
+     */
+    protected static boolean removeBlastulaTableEntry(int blastulaPID) {
+        return nativeRemoveBlastulaTableEntry(blastulaPID);
+    }
+
     private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID);
 
+    /**
+     * uid 1000 (Process.SYSTEM_UID) may specify any uid &gt; 1000 in normal
+     * operation. It may also specify any gid and setgroups() list it chooses.
+     * In factory test mode, it may specify any UID.
+     *
+     * @param args non-null; zygote spawner arguments
+     * @param peer non-null; peer credentials
+     * @throws ZygoteSecurityException
+     */
+    protected static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer)
+            throws ZygoteSecurityException {
+
+        if (peer.getUid() == Process.SYSTEM_UID) {
+            /* In normal operation, SYSTEM_UID can only specify a restricted
+             * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
+             */
+            boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
+
+            if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) {
+                throw new ZygoteSecurityException(
+                        "System UID may not launch process with UID < "
+                        + Process.SYSTEM_UID);
+            }
+        }
+
+        // If not otherwise specified, uid and gid are inherited from peer
+        if (!args.mUidSpecified) {
+            args.mUid = peer.getUid();
+            args.mUidSpecified = true;
+        }
+        if (!args.mGidSpecified) {
+            args.mGid = peer.getGid();
+            args.mGidSpecified = true;
+        }
+    }
+
+    /**
+     * Applies debugger system properties to the zygote arguments.
+     *
+     * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
+     * the debugger state is specified via the "--enable-jdwp" flag
+     * in the spawn request.
+     *
+     * @param args non-null; zygote spawner args
+     */
+    protected static void applyDebuggerSystemProperty(ZygoteArguments args) {
+        if (RoSystemProperties.DEBUGGABLE) {
+            args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
+        }
+    }
+
+    /**
+     * Applies zygote security policy.
+     * Based on the credentials of the process issuing a zygote command:
+     * <ol>
+     * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
+     * wrapper command.
+     * <li> Any other uid may not specify any invoke-with argument.
+     * </ul>
+     *
+     * @param args non-null; zygote spawner arguments
+     * @param peer non-null; peer credentials
+     * @throws ZygoteSecurityException
+     */
+    protected static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer)
+            throws ZygoteSecurityException {
+        int peerUid = peer.getUid();
+
+        if (args.mInvokeWith != null && peerUid != 0
+                && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
+            throw new ZygoteSecurityException("Peer is permitted to specify an"
+                + "explicit invoke-with wrapper command only for debuggable"
+                + "applications.");
+        }
+    }
+
+    /**
+     * Applies invoke-with system properties to the zygote arguments.
+     *
+     * @param args non-null; zygote args
+     */
+    protected static void applyInvokeWithSystemProperty(ZygoteArguments args) {
+        if (args.mInvokeWith == null && args.mNiceName != null) {
+            String property = "wrap." + args.mNiceName;
+            args.mInvokeWith = SystemProperties.get(property);
+            if (args.mInvokeWith != null && args.mInvokeWith.length() == 0) {
+                args.mInvokeWith = null;
+            }
+        }
+    }
+
+    /**
+     * Reads an argument list from the provided socket
+     * @return Argument list or null if EOF is reached
+     * @throws IOException passed straight through
+     */
+    static String[] readArgumentList(BufferedReader socketReader) throws IOException {
+
+        /**
+         * See android.os.Process.zygoteSendArgsAndGetPid()
+         * Presently the wire format to the zygote process is:
+         * a) a count of arguments (argc, in essence)
+         * b) a number of newline-separated argument strings equal to count
+         *
+         * After the zygote process reads these it will write the pid of
+         * the child or -1 on failure.
+         */
+
+        int argc;
+
+        try {
+            String argc_string = socketReader.readLine();
+
+            if (argc_string == null) {
+                // EOF reached.
+                return null;
+            }
+            argc = Integer.parseInt(argc_string);
+
+        } catch (NumberFormatException ex) {
+            Log.e("Zygote", "Invalid Zygote wire format: non-int at argc");
+            throw new IOException("Invalid wire format");
+        }
+
+        // See bug 1092107: large argc can be used for a DOS attack
+        if (argc > MAX_ZYGOTE_ARGC) {
+            throw new IOException("Max arg count exceeded");
+        }
+
+        String[] args = new String[argc];
+        for (int arg_index = 0; arg_index < argc; arg_index++) {
+            args[arg_index] = socketReader.readLine();
+            if (args[arg_index] == null) {
+                // We got an unexpected EOF.
+                throw new IOException("Truncated request");
+            }
+        }
+
+        return args;
+    }
+
+    /**
+     * Creates a managed object representing the Blastula pool socket that has
+     * already been initialized and bound by init.
+     *
+     * TODO (chriswailes): Move the name selection logic into this function.
+     *
+     * @throws RuntimeException when open fails
+     */
+    static void createBlastulaSocket(String socketName) {
+        if (BLASTULA_POOL_ENABLED && sBlastulaPoolSocket == null) {
+            sBlastulaPoolSocket = createManagedSocketFromInitSocket(socketName);
+        }
+    }
+
+    /**
+     * Creates a managed LocalServerSocket object using a file descriptor
+     * created by an init.rc script.  The init scripts that specify the
+     * sockets name can be found in system/core/rootdir.  The socket is bound
+     * to the file system in the /dev/sockets/ directory, and the file
+     * descriptor is shared via the ANDROID_SOCKET_<socketName> environment
+     * variable.
+     */
+    static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {
+        int fileDesc;
+        final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
+
+        try {
+            String env = System.getenv(fullSocketName);
+            fileDesc = Integer.parseInt(env);
+        } catch (RuntimeException ex) {
+            throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex);
+        }
+
+        try {
+            FileDescriptor fd = new FileDescriptor();
+            fd.setInt$(fileDesc);
+            return new LocalServerSocket(fd);
+        } catch (IOException ex) {
+            throw new RuntimeException(
+                "Error building socket from file descriptor: " + fileDesc, ex);
+        }
+    }
 
     private static void callPostForkSystemServerHooks() {
         // SystemServer specific post fork hooks run before child post fork hooks.
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
new file mode 100644
index 0000000..df89b26
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Handles argument parsing for args related to the zygote spawner.
+ *
+ * Current recognized args:
+ * <ul>
+ *   <li> --setuid=<i>uid of child process, defaults to 0</i>
+ *   <li> --setgid=<i>gid of child process, defaults to 0</i>
+ *   <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
+ *   <li> --capabilities=<i>a pair of comma-separated integer strings
+ * indicating Linux capabilities(2) set for child. The first string
+ * represents the <code>permitted</code> set, and the second the
+ * <code>effective</code> set. Precede each with 0 or
+ * 0x for octal or hexidecimal value. If unspecified, both default to 0.
+ * This parameter is only applied if the uid of the new process will
+ * be non-0. </i>
+ *   <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
+ *    <code>r</code> is the resource, <code>c</code> and <code>m</code>
+ *    are the settings for current and max value.</i>
+ *   <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate.
+ *   <li> --nice-name=<i>nice name to appear in ps</i>
+ *   <li> --package-name=<i>package name this process belongs to</i>
+ *   <li> --runtime-args indicates that the remaining arg list should
+ * be handed off to com.android.internal.os.RuntimeInit, rather than
+ * processed directly.
+ * Android runtime startup (eg, Binder initialization) is also eschewed.
+ *   <li> [--] &lt;args for RuntimeInit &gt;
+ * </ul>
+ */
+class ZygoteArguments {
+
+    /**
+     * from --setuid
+     */
+    int mUid = 0;
+    boolean mUidSpecified;
+
+    /**
+     * from --setgid
+     */
+    int mGid = 0;
+    boolean mGidSpecified;
+
+    /**
+     * from --setgroups
+     */
+    int[] mGids;
+
+    /**
+     * From --runtime-flags.
+     */
+    int mRuntimeFlags;
+
+    /**
+     * From --mount-external
+     */
+    int mMountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+
+    /**
+     * from --target-sdk-version.
+     */
+    int mTargetSdkVersion;
+    boolean mTargetSdkVersionSpecified;
+
+    /**
+     * from --nice-name
+     */
+    String mNiceName;
+
+    /**
+     * from --capabilities
+     */
+    boolean mCapabilitiesSpecified;
+    long mPermittedCapabilities;
+    long mEffectiveCapabilities;
+
+    /**
+     * from --seinfo
+     */
+    boolean mSeInfoSpecified;
+    String mSeInfo;
+
+    /**
+     * from all --rlimit=r,c,m
+     */
+    ArrayList<int[]> mRLimits;
+
+    /**
+     * from --invoke-with
+     */
+    String mInvokeWith;
+
+    /** from --package-name */
+    String mPackageName;
+
+    /** from --packages-for-uid */
+    String[] mPackagesForUid;
+
+    /** from --visible-vols */
+    String[] mVisibleVolIds;
+
+    /**
+     * Any args after and including the first non-option arg (or after a '--')
+     */
+    String[] mRemainingArgs;
+
+    /**
+     * Whether the current arguments constitute an ABI list query.
+     */
+    boolean mAbiListQuery;
+
+    /**
+     * The instruction set to use, or null when not important.
+     */
+    String mInstructionSet;
+
+    /**
+     * The app data directory. May be null, e.g., for the system server. Note that this might not be
+     * reliable in the case of process-sharing apps.
+     */
+    String mAppDataDir;
+
+    /**
+     * The APK path of the package to preload, when using --preload-package.
+     */
+    String mPreloadPackage;
+
+    /**
+     * A Base64 string representing a serialize ApplicationInfo Parcel,
+     when using --preload-app.
+     */
+    String mPreloadApp;
+
+    /**
+     * The native library path of the package to preload, when using --preload-package.
+     */
+    String mPreloadPackageLibs;
+
+    /**
+     * The filename of the native library to preload, when using --preload-package.
+     */
+    String mPreloadPackageLibFileName;
+
+    /**
+     * The cache key under which to enter the preloaded package into the classloader cache, when
+     * using --preload-package.
+     */
+    String mPreloadPackageCacheKey;
+
+    /**
+     * Whether this is a request to start preloading the default resources and classes. This
+     * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started
+     * with --enable-lazy-preload).
+     */
+    boolean mPreloadDefault;
+
+    /**
+     * Whether this is a request to start a zygote process as a child of this zygote. Set with
+     * --start-child-zygote. The remaining arguments must include the CHILD_ZYGOTE_SOCKET_NAME_ARG
+     * flag to indicate the abstract socket name that should be used for communication.
+     */
+    boolean mStartChildZygote;
+
+    /**
+     * Whether the current arguments constitute a request for the zygote's PID.
+     */
+    boolean mPidQuery;
+
+    /**
+     * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, or
+     * when they change, via --set-api-blacklist-exemptions.
+     */
+    String[] mApiBlacklistExemptions;
+
+    /**
+     * Sampling rate for logging hidden API accesses to the event log. This is sent to the
+     * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
+     */
+    int mHiddenApiAccessLogSampleRate = -1;
+
+    /**
+     * Constructs instance and parses args
+     *
+     * @param args zygote command-line args
+     */
+    ZygoteArguments(String[] args) throws IllegalArgumentException {
+        parseArgs(args);
+    }
+
+    /**
+     * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and
+     * "--setgid=") and creates an array containing the remaining args.
+     *
+     * Per security review bug #1112214, duplicate args are disallowed in critical cases to make
+     * injection harder.
+     */
+    private void parseArgs(String[] args)
+            throws IllegalArgumentException {
+        int curArg = 0;
+
+        boolean seenRuntimeArgs = false;
+
+        boolean expectRuntimeArgs = true;
+        for ( /* curArg */ ; curArg < args.length; curArg++) {
+            String arg = args[curArg];
+
+            if (arg.equals("--")) {
+                curArg++;
+                break;
+            } else if (arg.startsWith("--setuid=")) {
+                if (mUidSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mUidSpecified = true;
+                mUid = Integer.parseInt(
+                    arg.substring(arg.indexOf('=') + 1));
+            } else if (arg.startsWith("--setgid=")) {
+                if (mGidSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mGidSpecified = true;
+                mGid = Integer.parseInt(
+                    arg.substring(arg.indexOf('=') + 1));
+            } else if (arg.startsWith("--target-sdk-version=")) {
+                if (mTargetSdkVersionSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate target-sdk-version specified");
+                }
+                mTargetSdkVersionSpecified = true;
+                mTargetSdkVersion = Integer.parseInt(
+                    arg.substring(arg.indexOf('=') + 1));
+            } else if (arg.equals("--runtime-args")) {
+                seenRuntimeArgs = true;
+            } else if (arg.startsWith("--runtime-flags=")) {
+                mRuntimeFlags = Integer.parseInt(
+                    arg.substring(arg.indexOf('=') + 1));
+            } else if (arg.startsWith("--seinfo=")) {
+                if (mSeInfoSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mSeInfoSpecified = true;
+                mSeInfo = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.startsWith("--capabilities=")) {
+                if (mCapabilitiesSpecified) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mCapabilitiesSpecified = true;
+                String capString = arg.substring(arg.indexOf('=') + 1);
+
+                String[] capStrings = capString.split(",", 2);
+
+                if (capStrings.length == 1) {
+                    mEffectiveCapabilities = Long.decode(capStrings[0]);
+                    mPermittedCapabilities = mEffectiveCapabilities;
+                } else {
+                    mPermittedCapabilities = Long.decode(capStrings[0]);
+                    mEffectiveCapabilities = Long.decode(capStrings[1]);
+                }
+            } else if (arg.startsWith("--rlimit=")) {
+                // Duplicate --rlimit arguments are specifically allowed.
+                String[] limitStrings = arg.substring(arg.indexOf('=') + 1).split(",");
+
+                if (limitStrings.length != 3) {
+                    throw new IllegalArgumentException(
+                        "--rlimit= should have 3 comma-delimited ints");
+                }
+                int[] rlimitTuple = new int[limitStrings.length];
+
+                for (int i = 0; i < limitStrings.length; i++) {
+                    rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
+                }
+
+                if (mRLimits == null) {
+                    mRLimits = new ArrayList();
+                }
+
+                mRLimits.add(rlimitTuple);
+            } else if (arg.startsWith("--setgroups=")) {
+                if (mGids != null) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+
+                String[] params = arg.substring(arg.indexOf('=') + 1).split(",");
+
+                mGids = new int[params.length];
+
+                for (int i = params.length - 1; i >= 0; i--) {
+                    mGids[i] = Integer.parseInt(params[i]);
+                }
+            } else if (arg.equals("--invoke-with")) {
+                if (mInvokeWith != null) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                try {
+                    mInvokeWith = args[++curArg];
+                } catch (IndexOutOfBoundsException ex) {
+                    throw new IllegalArgumentException(
+                        "--invoke-with requires argument");
+                }
+            } else if (arg.startsWith("--nice-name=")) {
+                if (mNiceName != null) {
+                    throw new IllegalArgumentException(
+                        "Duplicate arg specified");
+                }
+                mNiceName = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.equals("--mount-external-default")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
+            } else if (arg.equals("--mount-external-read")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_READ;
+            } else if (arg.equals("--mount-external-write")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
+            } else if (arg.equals("--mount-external-full")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_FULL;
+            }  else if (arg.equals("--mount-external-installer")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
+            } else if (arg.equals("--query-abi-list")) {
+                mAbiListQuery = true;
+            } else if (arg.equals("--get-pid")) {
+                mPidQuery = true;
+            } else if (arg.startsWith("--instruction-set=")) {
+                mInstructionSet = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.startsWith("--app-data-dir=")) {
+                mAppDataDir = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.equals("--preload-app")) {
+                mPreloadApp = args[++curArg];
+            } else if (arg.equals("--preload-package")) {
+                mPreloadPackage = args[++curArg];
+                mPreloadPackageLibs = args[++curArg];
+                mPreloadPackageLibFileName = args[++curArg];
+                mPreloadPackageCacheKey = args[++curArg];
+            } else if (arg.equals("--preload-default")) {
+                mPreloadDefault = true;
+                expectRuntimeArgs = false;
+            } else if (arg.equals("--start-child-zygote")) {
+                mStartChildZygote = true;
+            } else if (arg.equals("--set-api-blacklist-exemptions")) {
+                // consume all remaining args; this is a stand-alone command, never included
+                // with the regular fork command.
+                mApiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
+                curArg = args.length;
+                expectRuntimeArgs = false;
+            } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
+                String rateStr = arg.substring(arg.indexOf('=') + 1);
+                try {
+                    mHiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
+                } catch (NumberFormatException nfe) {
+                    throw new IllegalArgumentException(
+                        "Invalid log sampling rate: " + rateStr, nfe);
+                }
+                expectRuntimeArgs = false;
+            } else if (arg.startsWith("--package-name=")) {
+                if (mPackageName != null) {
+                    throw new IllegalArgumentException("Duplicate arg specified");
+                }
+                mPackageName = arg.substring(arg.indexOf('=') + 1);
+            } else if (arg.startsWith("--packages-for-uid=")) {
+                mPackagesForUid = arg.substring(arg.indexOf('=') + 1).split(",");
+            } else if (arg.startsWith("--visible-vols=")) {
+                mVisibleVolIds = arg.substring(arg.indexOf('=') + 1).split(",");
+            } else {
+                break;
+            }
+        }
+
+        if (mAbiListQuery || mPidQuery) {
+            if (args.length - curArg > 0) {
+                throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
+            }
+        } else if (mPreloadPackage != null) {
+            if (args.length - curArg > 0) {
+                throw new IllegalArgumentException(
+                    "Unexpected arguments after --preload-package.");
+            }
+        } else if (mPreloadApp != null) {
+            if (args.length - curArg > 0) {
+                throw new IllegalArgumentException(
+                    "Unexpected arguments after --preload-app.");
+            }
+        } else if (expectRuntimeArgs) {
+            if (!seenRuntimeArgs) {
+                throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
+            }
+
+            mRemainingArgs = new String[args.length - curArg];
+            System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length);
+        }
+
+        if (mStartChildZygote) {
+            boolean seenChildSocketArg = false;
+            for (String arg : mRemainingArgs) {
+                if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+                    seenChildSocketArg = true;
+                    break;
+                }
+            }
+            if (!seenChildSocketArg) {
+                throw new IllegalArgumentException("--start-child-zygote specified "
+                        + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ced798c..78ecee1 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -24,16 +24,13 @@
 import static android.system.OsConstants.STDOUT_FILENO;
 
 import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS;
-import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
 import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
 
 import android.content.pm.ApplicationInfo;
 import android.net.Credentials;
 import android.net.LocalSocket;
-import android.os.FactoryTest;
 import android.os.Parcel;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -52,8 +49,6 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Base64;
 
 /**
@@ -62,9 +57,6 @@
 class ZygoteConnection {
     private static final String TAG = "Zygote";
 
-    /** a prototype instance for a future List.toArray() */
-    private static final int[][] intArray2d = new int[0][0];
-
     /**
      * The command socket.
      *
@@ -113,7 +105,7 @@
      *
      * @return null-ok; file descriptor
      */
-    FileDescriptor getFileDesciptor() {
+    FileDescriptor getFileDescriptor() {
         return mSocket.getFileDescriptor();
     }
 
@@ -127,11 +119,13 @@
      */
     Runnable processOneCommand(ZygoteServer zygoteServer) {
         String args[];
-        Arguments parsedArgs = null;
+        ZygoteArguments parsedArgs = null;
         FileDescriptor[] descriptors;
 
         try {
-            args = readArgumentList();
+            args = Zygote.readArgumentList(mSocketReader);
+
+            // TODO (chriswailes): Remove this and add an assert.
             descriptors = mSocket.getAncillaryFileDescriptors();
         } catch (IOException ex) {
             throw new IllegalStateException("IOException on command socket", ex);
@@ -148,26 +142,26 @@
         FileDescriptor childPipeFd = null;
         FileDescriptor serverPipeFd = null;
 
-        parsedArgs = new Arguments(args);
+        parsedArgs = new ZygoteArguments(args);
 
-        if (parsedArgs.abiListQuery) {
+        if (parsedArgs.mAbiListQuery) {
             handleAbiListQuery();
             return null;
         }
 
-        if (parsedArgs.pidQuery) {
+        if (parsedArgs.mPidQuery) {
             handlePidQuery();
             return null;
         }
 
-        if (parsedArgs.preloadDefault) {
+        if (parsedArgs.mPreloadDefault) {
             handlePreload();
             return null;
         }
 
-        if (parsedArgs.preloadPackage != null) {
-            handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
-                    parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey);
+        if (parsedArgs.mPreloadPackage != null) {
+            handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs,
+                    parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey);
             return null;
         }
 
@@ -186,37 +180,37 @@
             return null;
         }
 
-        if (parsedArgs.apiBlacklistExemptions != null) {
-            handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions);
+        if (parsedArgs.mApiBlacklistExemptions != null) {
+            handleApiBlacklistExemptions(parsedArgs.mApiBlacklistExemptions);
             return null;
         }
 
-        if (parsedArgs.hiddenApiAccessLogSampleRate != -1) {
-            handleHiddenApiAccessLogSampleRate(parsedArgs.hiddenApiAccessLogSampleRate);
+        if (parsedArgs.mHiddenApiAccessLogSampleRate != -1) {
+            handleHiddenApiAccessLogSampleRate(parsedArgs.mHiddenApiAccessLogSampleRate);
             return null;
         }
 
-        if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
-            throw new ZygoteSecurityException("Client may not specify capabilities: " +
-                    "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
-                    ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
+        if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) {
+            throw new ZygoteSecurityException("Client may not specify capabilities: "
+                    + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
+                    + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities));
         }
 
-        applyUidSecurityPolicy(parsedArgs, peer);
-        applyInvokeWithSecurityPolicy(parsedArgs, peer);
+        Zygote.applyUidSecurityPolicy(parsedArgs, peer);
+        Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
 
-        applyDebuggerSystemProperty(parsedArgs);
-        applyInvokeWithSystemProperty(parsedArgs);
+        Zygote.applyDebuggerSystemProperty(parsedArgs);
+        Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
         int[][] rlimits = null;
 
-        if (parsedArgs.rlimits != null) {
-            rlimits = parsedArgs.rlimits.toArray(intArray2d);
+        if (parsedArgs.mRLimits != null) {
+            rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
         }
 
         int[] fdsToIgnore = null;
 
-        if (parsedArgs.invokeWith != null) {
+        if (parsedArgs.mInvokeWith != null) {
             try {
                 FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
                 childPipeFd = pipeFds[1];
@@ -256,11 +250,11 @@
 
         fd = null;
 
-        pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
-                parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
-                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
-                parsedArgs.instructionSet, parsedArgs.appDataDir, parsedArgs.packageName,
-                parsedArgs.packagesForUid, parsedArgs.visibleVolIds);
+        pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
+                parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
+                parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
+                parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mPackageName,
+                parsedArgs.mPackagesForUid, parsedArgs.mVisibleVolIds);
 
         try {
             if (pid == 0) {
@@ -272,7 +266,7 @@
                 serverPipeFd = null;
 
                 return handleChildProc(parsedArgs, descriptors, childPipeFd,
-                        parsedArgs.startChildZygote);
+                        parsedArgs.mStartChildZygote);
             } else {
                 // In the parent. A pid < 0 indicates a failure and will be handled in
                 // handleParentProc.
@@ -387,541 +381,6 @@
     }
 
     /**
-     * Handles argument parsing for args related to the zygote spawner.
-     *
-     * Current recognized args:
-     * <ul>
-     *   <li> --setuid=<i>uid of child process, defaults to 0</i>
-     *   <li> --setgid=<i>gid of child process, defaults to 0</i>
-     *   <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
-     *   <li> --capabilities=<i>a pair of comma-separated integer strings
-     * indicating Linux capabilities(2) set for child. The first string
-     * represents the <code>permitted</code> set, and the second the
-     * <code>effective</code> set. Precede each with 0 or
-     * 0x for octal or hexidecimal value. If unspecified, both default to 0.
-     * This parameter is only applied if the uid of the new process will
-     * be non-0. </i>
-     *   <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
-     *    <code>r</code> is the resource, <code>c</code> and <code>m</code>
-     *    are the settings for current and max value.</i>
-     *   <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate.
-     *   <li> --nice-name=<i>nice name to appear in ps</i>
-     *   <li> --package-name=<i>package name this process belongs to</i>
-     *   <li> --runtime-args indicates that the remaining arg list should
-     * be handed off to com.android.internal.os.RuntimeInit, rather than
-     * processed directly.
-     * Android runtime startup (eg, Binder initialization) is also eschewed.
-     *   <li> [--] &lt;args for RuntimeInit &gt;
-     * </ul>
-     */
-    static class Arguments {
-        /** from --setuid */
-        int uid = 0;
-        boolean uidSpecified;
-
-        /** from --setgid */
-        int gid = 0;
-        boolean gidSpecified;
-
-        /** from --setgroups */
-        int[] gids;
-
-        /**
-         * From --runtime-flags.
-         */
-        int runtimeFlags;
-
-        /** From --mount-external */
-        int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
-
-        /** from --target-sdk-version. */
-        int targetSdkVersion;
-        boolean targetSdkVersionSpecified;
-
-        /** from --nice-name */
-        String niceName;
-
-        /** from --capabilities */
-        boolean capabilitiesSpecified;
-        long permittedCapabilities;
-        long effectiveCapabilities;
-
-        /** from --seinfo */
-        boolean seInfoSpecified;
-        String seInfo;
-
-        /** from all --rlimit=r,c,m */
-        ArrayList<int[]> rlimits;
-
-        /** from --invoke-with */
-        String invokeWith;
-
-        /** from --package-name */
-        String packageName;
-
-        /** from --packages-for-uid */
-        String[] packagesForUid;
-
-        /** from --visible-vols */
-        String[] visibleVolIds;
-
-        /**
-         * Any args after and including the first non-option arg
-         * (or after a '--')
-         */
-        String remainingArgs[];
-
-        /**
-         * Whether the current arguments constitute an ABI list query.
-         */
-        boolean abiListQuery;
-
-        /**
-         * The instruction set to use, or null when not important.
-         */
-        String instructionSet;
-
-        /**
-         * The app data directory. May be null, e.g., for the system server. Note that this might
-         * not be reliable in the case of process-sharing apps.
-         */
-        String appDataDir;
-
-        /**
-         * The APK path of the package to preload, when using --preload-package.
-         */
-        String preloadPackage;
-
-        /**
-         * A Base64 string representing a serialize ApplicationInfo Parcel,
-           when using --preload-app.
-          */
-        String mPreloadApp;
-
-        /**
-         * The native library path of the package to preload, when using --preload-package.
-         */
-        String preloadPackageLibs;
-
-        /**
-         * The filename of the native library to preload, when using --preload-package.
-         */
-        String preloadPackageLibFileName;
-
-        /**
-         * The cache key under which to enter the preloaded package into the classloader cache,
-         * when using --preload-package.
-         */
-        String preloadPackageCacheKey;
-
-        /**
-         * Whether this is a request to start preloading the default resources and classes.
-         * This argument only makes sense when the zygote is in lazy preload mode (i.e, when
-         * it's started with --enable-lazy-preload).
-         */
-        boolean preloadDefault;
-
-        /**
-         * Whether this is a request to start a zygote process as a child of this zygote.
-         * Set with --start-child-zygote. The remaining arguments must include the
-         * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that
-         * should be used for communication.
-         */
-        boolean startChildZygote;
-
-        /**
-         * Whether the current arguments constitute a request for the zygote's PID.
-         */
-        boolean pidQuery;
-
-        /**
-         * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time,
-         * or when they change, via --set-api-blacklist-exemptions.
-         */
-        String[] apiBlacklistExemptions;
-
-        /**
-         * Sampling rate for logging hidden API accesses to the event log. This is sent to the
-         * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
-         */
-        int hiddenApiAccessLogSampleRate = -1;
-
-        /**
-         * Constructs instance and parses args
-         * @param args zygote command-line args
-         * @throws IllegalArgumentException
-         */
-        Arguments(String args[]) throws IllegalArgumentException {
-            parseArgs(args);
-        }
-
-        /**
-         * Parses the commandline arguments intended for the Zygote spawner
-         * (such as "--setuid=" and "--setgid=") and creates an array
-         * containing the remaining args.
-         *
-         * Per security review bug #1112214, duplicate args are disallowed in
-         * critical cases to make injection harder.
-         */
-        private void parseArgs(String args[])
-                throws IllegalArgumentException {
-            int curArg = 0;
-
-            boolean seenRuntimeArgs = false;
-
-            boolean expectRuntimeArgs = true;
-            for ( /* curArg */ ; curArg < args.length; curArg++) {
-                String arg = args[curArg];
-
-                if (arg.equals("--")) {
-                    curArg++;
-                    break;
-                } else if (arg.startsWith("--setuid=")) {
-                    if (uidSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    uidSpecified = true;
-                    uid = Integer.parseInt(
-                            arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.startsWith("--setgid=")) {
-                    if (gidSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    gidSpecified = true;
-                    gid = Integer.parseInt(
-                            arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.startsWith("--target-sdk-version=")) {
-                    if (targetSdkVersionSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate target-sdk-version specified");
-                    }
-                    targetSdkVersionSpecified = true;
-                    targetSdkVersion = Integer.parseInt(
-                            arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.equals("--runtime-args")) {
-                    seenRuntimeArgs = true;
-                } else if (arg.startsWith("--runtime-flags=")) {
-                    runtimeFlags = Integer.parseInt(
-                            arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.startsWith("--seinfo=")) {
-                    if (seInfoSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    seInfoSpecified = true;
-                    seInfo = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.startsWith("--capabilities=")) {
-                    if (capabilitiesSpecified) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    capabilitiesSpecified = true;
-                    String capString = arg.substring(arg.indexOf('=')+1);
-
-                    String[] capStrings = capString.split(",", 2);
-
-                    if (capStrings.length == 1) {
-                        effectiveCapabilities = Long.decode(capStrings[0]);
-                        permittedCapabilities = effectiveCapabilities;
-                    } else {
-                        permittedCapabilities = Long.decode(capStrings[0]);
-                        effectiveCapabilities = Long.decode(capStrings[1]);
-                    }
-                } else if (arg.startsWith("--rlimit=")) {
-                    // Duplicate --rlimit arguments are specifically allowed.
-                    String[] limitStrings
-                            = arg.substring(arg.indexOf('=')+1).split(",");
-
-                    if (limitStrings.length != 3) {
-                        throw new IllegalArgumentException(
-                                "--rlimit= should have 3 comma-delimited ints");
-                    }
-                    int[] rlimitTuple = new int[limitStrings.length];
-
-                    for(int i=0; i < limitStrings.length; i++) {
-                        rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
-                    }
-
-                    if (rlimits == null) {
-                        rlimits = new ArrayList();
-                    }
-
-                    rlimits.add(rlimitTuple);
-                } else if (arg.startsWith("--setgroups=")) {
-                    if (gids != null) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-
-                    String[] params
-                            = arg.substring(arg.indexOf('=') + 1).split(",");
-
-                    gids = new int[params.length];
-
-                    for (int i = params.length - 1; i >= 0 ; i--) {
-                        gids[i] = Integer.parseInt(params[i]);
-                    }
-                } else if (arg.equals("--invoke-with")) {
-                    if (invokeWith != null) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    try {
-                        invokeWith = args[++curArg];
-                    } catch (IndexOutOfBoundsException ex) {
-                        throw new IllegalArgumentException(
-                                "--invoke-with requires argument");
-                    }
-                } else if (arg.startsWith("--nice-name=")) {
-                    if (niceName != null) {
-                        throw new IllegalArgumentException(
-                                "Duplicate arg specified");
-                    }
-                    niceName = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.equals("--mount-external-default")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
-                } else if (arg.equals("--mount-external-read")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_READ;
-                } else if (arg.equals("--mount-external-write")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
-                } else if (arg.equals("--mount-external-full")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
-                } else if (arg.equals("--mount-external-installer")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
-                } else if (arg.equals("--mount-external-legacy")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
-                } else if (arg.equals("--query-abi-list")) {
-                    abiListQuery = true;
-                } else if (arg.equals("--get-pid")) {
-                    pidQuery = true;
-                } else if (arg.startsWith("--instruction-set=")) {
-                    instructionSet = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.startsWith("--app-data-dir=")) {
-                    appDataDir = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.equals("--preload-app")) {
-                    mPreloadApp = args[++curArg];
-                } else if (arg.equals("--preload-package")) {
-                    preloadPackage = args[++curArg];
-                    preloadPackageLibs = args[++curArg];
-                    preloadPackageLibFileName = args[++curArg];
-                    preloadPackageCacheKey = args[++curArg];
-                } else if (arg.equals("--preload-default")) {
-                    preloadDefault = true;
-                    expectRuntimeArgs = false;
-                } else if (arg.equals("--start-child-zygote")) {
-                    startChildZygote = true;
-                } else if (arg.equals("--set-api-blacklist-exemptions")) {
-                    // consume all remaining args; this is a stand-alone command, never included
-                    // with the regular fork command.
-                    apiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
-                    curArg = args.length;
-                    expectRuntimeArgs = false;
-                } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
-                    String rateStr = arg.substring(arg.indexOf('=') + 1);
-                    try {
-                        hiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(
-                                "Invalid log sampling rate: " + rateStr, nfe);
-                    }
-                    expectRuntimeArgs = false;
-                } else if (arg.startsWith("--package-name=")) {
-                    if (packageName != null) {
-                        throw new IllegalArgumentException("Duplicate arg specified");
-                    }
-                    packageName = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.startsWith("--packages-for-uid=")) {
-                    packagesForUid = arg.substring(arg.indexOf('=') + 1).split(",");
-                } else if (arg.startsWith("--visible-vols=")) {
-                    visibleVolIds = arg.substring(arg.indexOf('=') + 1).split(",");
-                } else {
-                    break;
-                }
-            }
-
-            if (abiListQuery || pidQuery) {
-                if (args.length - curArg > 0) {
-                    throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
-                }
-            } else if (preloadPackage != null) {
-                if (args.length - curArg > 0) {
-                    throw new IllegalArgumentException(
-                            "Unexpected arguments after --preload-package.");
-                }
-            } else if (mPreloadApp != null) {
-                if (args.length - curArg > 0) {
-                    throw new IllegalArgumentException(
-                            "Unexpected arguments after --preload-app.");
-                }
-            } else if (expectRuntimeArgs) {
-                if (!seenRuntimeArgs) {
-                    throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
-                }
-
-                remainingArgs = new String[args.length - curArg];
-                System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length);
-            }
-
-            if (startChildZygote) {
-                boolean seenChildSocketArg = false;
-                for (String arg : remainingArgs) {
-                    if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
-                        seenChildSocketArg = true;
-                        break;
-                    }
-                }
-                if (!seenChildSocketArg) {
-                    throw new IllegalArgumentException("--start-child-zygote specified " +
-                            "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
-                }
-            }
-        }
-    }
-
-    /**
-     * Reads an argument list from the command socket/
-     * @return Argument list or null if EOF is reached
-     * @throws IOException passed straight through
-     */
-    private String[] readArgumentList()
-            throws IOException {
-
-        /**
-         * See android.os.Process.zygoteSendArgsAndGetPid()
-         * Presently the wire format to the zygote process is:
-         * a) a count of arguments (argc, in essence)
-         * b) a number of newline-separated argument strings equal to count
-         *
-         * After the zygote process reads these it will write the pid of
-         * the child or -1 on failure.
-         */
-
-        int argc;
-
-        try {
-            String s = mSocketReader.readLine();
-
-            if (s == null) {
-                // EOF reached.
-                return null;
-            }
-            argc = Integer.parseInt(s);
-        } catch (NumberFormatException ex) {
-            Log.e(TAG, "invalid Zygote wire format: non-int at argc");
-            throw new IOException("invalid wire format");
-        }
-
-        // See bug 1092107: large argc can be used for a DOS attack
-        if (argc > MAX_ZYGOTE_ARGC) {
-            throw new IOException("max arg count exceeded");
-        }
-
-        String[] result = new String[argc];
-        for (int i = 0; i < argc; i++) {
-            result[i] = mSocketReader.readLine();
-            if (result[i] == null) {
-                // We got an unexpected EOF.
-                throw new IOException("truncated request");
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * uid 1000 (Process.SYSTEM_UID) may specify any uid &gt; 1000 in normal
-     * operation. It may also specify any gid and setgroups() list it chooses.
-     * In factory test mode, it may specify any UID.
-     *
-     * @param args non-null; zygote spawner arguments
-     * @param peer non-null; peer credentials
-     * @throws ZygoteSecurityException
-     */
-    private static void applyUidSecurityPolicy(Arguments args, Credentials peer)
-            throws ZygoteSecurityException {
-
-        if (peer.getUid() == Process.SYSTEM_UID) {
-            /* In normal operation, SYSTEM_UID can only specify a restricted
-             * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
-             */
-            boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
-
-            if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) {
-                throw new ZygoteSecurityException(
-                        "System UID may not launch process with UID < "
-                        + Process.SYSTEM_UID);
-            }
-        }
-
-        // If not otherwise specified, uid and gid are inherited from peer
-        if (!args.uidSpecified) {
-            args.uid = peer.getUid();
-            args.uidSpecified = true;
-        }
-        if (!args.gidSpecified) {
-            args.gid = peer.getGid();
-            args.gidSpecified = true;
-        }
-    }
-
-    /**
-     * Applies debugger system properties to the zygote arguments.
-     *
-     * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
-     * the debugger state is specified via the "--enable-jdwp" flag
-     * in the spawn request.
-     *
-     * @param args non-null; zygote spawner args
-     */
-    public static void applyDebuggerSystemProperty(Arguments args) {
-        if (RoSystemProperties.DEBUGGABLE) {
-            args.runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
-        }
-    }
-
-    /**
-     * Applies zygote security policy.
-     * Based on the credentials of the process issuing a zygote command:
-     * <ol>
-     * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
-     * wrapper command.
-     * <li> Any other uid may not specify any invoke-with argument.
-     * </ul>
-     *
-     * @param args non-null; zygote spawner arguments
-     * @param peer non-null; peer credentials
-     * @throws ZygoteSecurityException
-     */
-    private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer)
-            throws ZygoteSecurityException {
-        int peerUid = peer.getUid();
-
-        if (args.invokeWith != null && peerUid != 0 &&
-            (args.runtimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
-            throw new ZygoteSecurityException("Peer is permitted to specify an"
-                    + "explicit invoke-with wrapper command only for debuggable"
-                    + "applications.");
-        }
-    }
-
-    /**
-     * Applies invoke-with system properties to the zygote arguments.
-     *
-     * @param args non-null; zygote args
-     */
-    public static void applyInvokeWithSystemProperty(Arguments args) {
-        if (args.invokeWith == null && args.niceName != null) {
-            String property = "wrap." + args.niceName;
-            args.invokeWith = SystemProperties.get(property);
-            if (args.invokeWith != null && args.invokeWith.length() == 0) {
-                args.invokeWith = null;
-            }
-        }
-    }
-
-    /**
      * Handles post-fork setup of child proc, closing sockets as appropriate,
      * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
      * if successful or returning if failed.
@@ -931,7 +390,7 @@
      * @param pipeFd null-ok; pipe for communication back to Zygote.
      * @param isZygote whether this new child process is itself a new Zygote.
      */
-    private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
+    private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor[] descriptors,
             FileDescriptor pipeFd, boolean isZygote) {
         /**
          * By the time we get here, the native code has closed the two actual Zygote
@@ -954,27 +413,27 @@
             }
         }
 
-        if (parsedArgs.niceName != null) {
-            Process.setArgV0(parsedArgs.niceName);
+        if (parsedArgs.mNiceName != null) {
+            Process.setArgV0(parsedArgs.mNiceName);
         }
 
         // End of the postFork event.
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        if (parsedArgs.invokeWith != null) {
-            WrapperInit.execApplication(parsedArgs.invokeWith,
-                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
+        if (parsedArgs.mInvokeWith != null) {
+            WrapperInit.execApplication(parsedArgs.mInvokeWith,
+                    parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
                     VMRuntime.getCurrentInstructionSet(),
-                    pipeFd, parsedArgs.remainingArgs);
+                    pipeFd, parsedArgs.mRemainingArgs);
 
             // Should not get here.
             throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
         } else {
             if (!isZygote) {
-                return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
-                        null /* classLoader */);
+                return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+                        parsedArgs.mRemainingArgs, null /* classLoader */);
             } else {
-                return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,
-                        parsedArgs.remainingArgs, null /* classLoader */);
+                return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion,
+                        parsedArgs.mRemainingArgs, null /* classLoader */);
             }
         }
     }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index c2c6ae6..2f00c07 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -21,7 +21,6 @@
 
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.opengl.EGL14;
 import android.os.Build;
 import android.os.Environment;
 import android.os.IInstalld;
@@ -71,16 +70,16 @@
 /**
  * Startup class for the zygote process.
  *
- * Pre-initializes some classes, and then waits for commands on a UNIX domain
- * socket. Based on these commands, forks off child processes that inherit
- * the initial state of the VM.
+ * Pre-initializes some classes, and then waits for commands on a UNIX domain socket. Based on these
+ * commands, forks off child processes that inherit the initial state of the VM.
  *
- * Please see {@link ZygoteConnection.Arguments} for documentation on the
- * client protocol.
+ * Please see {@link ZygoteArguments} for documentation on the client protocol.
  *
  * @hide
  */
 public class ZygoteInit {
+
+    // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
     private static final String TAG = "Zygote";
 
     private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
@@ -89,11 +88,15 @@
     private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
     private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
 
-    /** when preloading, GC after allocating this many bytes */
+    /**
+     * when preloading, GC after allocating this many bytes
+     */
     private static final int PRELOAD_GC_THRESHOLD = 50000;
 
     private static final String ABI_LIST_ARG = "--abi-list=";
 
+    // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a
+    // --blastula-socket-name parameter.
     private static final String SOCKET_NAME_ARG = "--socket-name=";
 
     /**
@@ -106,7 +109,9 @@
      */
     private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
 
-    /** Controls whether we should preload resources during zygote init. */
+    /**
+     * Controls whether we should preload resources during zygote init.
+     */
     public static final boolean PRELOAD_RESOURCES = true;
 
     private static final int UNPRIVILEGED_UID = 9999;
@@ -173,6 +178,7 @@
     }
 
     native private static void nativePreloadAppProcessHALs();
+
     native private static void nativePreloadOpenGL();
 
     private static void preloadOpenGL() {
@@ -191,8 +197,8 @@
     /**
      * Register AndroidKeyStoreProvider and warm up the providers that are already registered.
      *
-     * By doing it here we avoid that each app does it when requesting a service from the
-     * provider for the first time.
+     * By doing it here we avoid that each app does it when requesting a service from the provider
+     * for the first time.
      */
     private static void warmUpJcaProviders() {
         long startTime = SystemClock.uptimeMillis();
@@ -218,11 +224,10 @@
     }
 
     /**
-     * Performs Zygote process initialization. Loads and initializes
-     * commonly used classes.
+     * Performs Zygote process initialization. Loads and initializes commonly used classes.
      *
-     * Most classes only cause a few hundred bytes to be allocated, but
-     * a few will allocate a dozen Kbytes (in one case, 500+K).
+     * Most classes only cause a few hundred bytes to be allocated, but a few will allocate a dozen
+     * Kbytes (in one case, 500+K).
      */
     private static void preloadClasses() {
         final VMRuntime runtime = VMRuntime.getRuntime();
@@ -263,8 +268,8 @@
         runtime.setTargetHeapUtilization(0.8f);
 
         try {
-            BufferedReader br
-                = new BufferedReader(new InputStreamReader(is), 256);
+            BufferedReader br =
+                    new BufferedReader(new InputStreamReader(is), 256);
 
             int count = 0;
             String line;
@@ -305,7 +310,7 @@
             }
 
             Log.i(TAG, "...preloaded " + count + " classes in "
-                    + (SystemClock.uptimeMillis()-startTime) + "ms.");
+                    + (SystemClock.uptimeMillis() - startTime) + "ms.");
         } catch (IOException e) {
             Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
         } finally {
@@ -331,11 +336,10 @@
     }
 
     /**
-     * Load in commonly used resources, so they can be shared across
-     * processes.
+     * Load in commonly used resources, so they can be shared across processes.
      *
-     * These tend to be a few Kbytes, but are frequently in the 20-40K
-     * range, and occasionally even larger.
+     * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even
+     * larger.
      */
     private static void preloadResources() {
         final VMRuntime runtime = VMRuntime.getRuntime();
@@ -352,7 +356,7 @@
                 int N = preloadDrawables(ar);
                 ar.recycle();
                 Log.i(TAG, "...preloaded " + N + " resources in "
-                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
+                        + (SystemClock.uptimeMillis() - startTime) + "ms.");
 
                 startTime = SystemClock.uptimeMillis();
                 ar = mResources.obtainTypedArray(
@@ -360,7 +364,7 @@
                 N = preloadColorStateLists(ar);
                 ar.recycle();
                 Log.i(TAG, "...preloaded " + N + " resources in "
-                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
+                        + (SystemClock.uptimeMillis() - startTime) + "ms.");
 
                 if (mResources.getBoolean(
                         com.android.internal.R.bool.config_freeformWindowManagement)) {
@@ -381,7 +385,7 @@
 
     private static int preloadColorStateLists(TypedArray ar) {
         int N = ar.length();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             int id = ar.getResourceId(i, 0);
             if (false) {
                 Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
@@ -390,8 +394,8 @@
                 if (mResources.getColorStateList(id, null) == null) {
                     throw new IllegalArgumentException(
                             "Unable to find preloaded color resource #0x"
-                            + Integer.toHexString(id)
-                            + " (" + ar.getString(i) + ")");
+                                    + Integer.toHexString(id)
+                                    + " (" + ar.getString(i) + ")");
                 }
             }
         }
@@ -401,7 +405,7 @@
 
     private static int preloadDrawables(TypedArray ar) {
         int N = ar.length();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             int id = ar.getResourceId(i, 0);
             if (false) {
                 Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
@@ -410,8 +414,8 @@
                 if (mResources.getDrawable(id, null) == null) {
                     throw new IllegalArgumentException(
                             "Unable to find preloaded drawable resource #0x"
-                            + Integer.toHexString(id)
-                            + " (" + ar.getString(i) + ")");
+                                    + Integer.toHexString(id)
+                                    + " (" + ar.getString(i) + ")");
                 }
             }
         }
@@ -419,9 +423,8 @@
     }
 
     /**
-     * Runs several special GCs to try to clean up a few generations of
-     * softly- and final-reachable objects, along with any other garbage.
-     * This is only useful just before a fork().
+     * Runs several special GCs to try to clean up a few generations of softly- and final-reachable
+     * objects, along with any other garbage. This is only useful just before a fork().
      */
     private static void gcAndFinalize() {
         ZygoteHooks.gcAndFinalize();
@@ -430,12 +433,12 @@
     /**
      * Finish remaining work for the newly forked system server process.
      */
-    private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
+    private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
         // set umask to 0077 so new files and directories will default to owner-only permissions.
         Os.umask(S_IRWXG | S_IRWXO);
 
-        if (parsedArgs.niceName != null) {
-            Process.setArgV0(parsedArgs.niceName);
+        if (parsedArgs.mNiceName != null) {
+            Process.setArgV0(parsedArgs.mNiceName);
         }
 
         final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
@@ -454,8 +457,8 @@
             }
         }
 
-        if (parsedArgs.invokeWith != null) {
-            String[] args = parsedArgs.remainingArgs;
+        if (parsedArgs.mInvokeWith != null) {
+            String[] args = parsedArgs.mRemainingArgs;
             // If we have a non-null system server class path, we'll have to duplicate the
             // existing arguments and append the classpath to it. ART will handle the classpath
             // correctly when we exec a new process.
@@ -467,15 +470,15 @@
                 args = amendedArgs;
             }
 
-            WrapperInit.execApplication(parsedArgs.invokeWith,
-                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
+            WrapperInit.execApplication(parsedArgs.mInvokeWith,
+                    parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
                     VMRuntime.getCurrentInstructionSet(), null, args);
 
             throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
         } else {
             ClassLoader cl = null;
             if (systemServerClasspath != null) {
-                cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
+                cl = createPathClassLoader(systemServerClasspath, parsedArgs.mTargetSdkVersion);
 
                 Thread.currentThread().setContextClassLoader(cl);
             }
@@ -483,16 +486,17 @@
             /*
              * Pass the remaining arguments to SystemServer.
              */
-            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+            return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+                    parsedArgs.mRemainingArgs, cl);
         }
 
         /* should never reach here */
     }
 
     /**
-     * Note that preparing the profiles for system server does not require special
-     * selinux permissions. From the installer perspective the system server is a regular package
-     * which can capture profile information.
+     * Note that preparing the profiles for system server does not require special selinux
+     * permissions. From the installer perspective the system server is a regular package which can
+     * capture profile information.
      */
     private static void prepareSystemServerProfile(String systemServerClasspath)
             throws RemoteException {
@@ -544,8 +548,8 @@
     }
 
     /**
-     * Performs dex-opt on the elements of {@code classPath}, if needed. We
-     * choose the instruction set of the current runtime.
+     * Performs dex-opt on the elements of {@code classPath}, if needed. We choose the instruction
+     * set of the current runtime.
      */
     private static void performSystemServerDexOpt(String classPath) {
         final String[] classPathElements = classPath.split(":");
@@ -563,8 +567,9 @@
             int dexoptNeeded;
             try {
                 dexoptNeeded = DexFile.getDexOptNeeded(
-                    classPathElement, instructionSet, systemServerFilter,
-                    null /* classLoaderContext */, false /* newProfile */, false /* downgrade */);
+                        classPathElement, instructionSet, systemServerFilter,
+                        null /* classLoaderContext */, false /* newProfile */,
+                        false /* downgrade */);
             } catch (FileNotFoundException ignored) {
                 // Do not add to the classpath.
                 Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
@@ -607,8 +612,8 @@
     }
 
     /**
-     * Encodes the system server class loader context in a format that is accepted by dexopt.
-     * This assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}.
+     * Encodes the system server class loader context in a format that is accepted by dexopt. This
+     * assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}.
      *
      * Note that ideally we would use the {@code DexoptUtils} to compute this. However we have no
      * dependency here on the server so we hard code the logic again.
@@ -619,10 +624,11 @@
 
     /**
      * Encodes the class path in a format accepted by dexopt.
-     * @param classPath the old class path (may be empty).
-     * @param newElement the new class path elements
-     * @return the class path encoding resulted from appending {@code newElement} to
-     * {@code classPath}.
+     *
+     * @param classPath  The old class path (may be empty).
+     * @param newElement  The new class path elements
+     * @return The class path encoding resulted from appending {@code newElement} to {@code
+     * classPath}.
      */
     private static String encodeSystemServerClassPath(String classPath, String newElement) {
         return (classPath == null || classPath.isEmpty())
@@ -633,25 +639,25 @@
     /**
      * Prepare the arguments and forks for the system server process.
      *
-     * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
-     * child process, and {@code null} in the parent.
+     * @return A {@code Runnable} that provides an entrypoint into system_server code in the child
+     * process; {@code null} in the parent.
      */
     private static Runnable forkSystemServer(String abiList, String socketName,
             ZygoteServer zygoteServer) {
         long capabilities = posixCapabilitiesAsBits(
-            OsConstants.CAP_IPC_LOCK,
-            OsConstants.CAP_KILL,
-            OsConstants.CAP_NET_ADMIN,
-            OsConstants.CAP_NET_BIND_SERVICE,
-            OsConstants.CAP_NET_BROADCAST,
-            OsConstants.CAP_NET_RAW,
-            OsConstants.CAP_SYS_MODULE,
-            OsConstants.CAP_SYS_NICE,
-            OsConstants.CAP_SYS_PTRACE,
-            OsConstants.CAP_SYS_TIME,
-            OsConstants.CAP_SYS_TTY_CONFIG,
-            OsConstants.CAP_WAKE_ALARM,
-            OsConstants.CAP_BLOCK_SUSPEND
+                OsConstants.CAP_IPC_LOCK,
+                OsConstants.CAP_KILL,
+                OsConstants.CAP_NET_ADMIN,
+                OsConstants.CAP_NET_BIND_SERVICE,
+                OsConstants.CAP_NET_BROADCAST,
+                OsConstants.CAP_NET_RAW,
+                OsConstants.CAP_SYS_MODULE,
+                OsConstants.CAP_SYS_NICE,
+                OsConstants.CAP_SYS_PTRACE,
+                OsConstants.CAP_SYS_TIME,
+                OsConstants.CAP_SYS_TTY_CONFIG,
+                OsConstants.CAP_WAKE_ALARM,
+                OsConstants.CAP_BLOCK_SUSPEND
         );
         /* Containers run without some capabilities, so drop any caps that are not available. */
         StructCapUserHeader header = new StructCapUserHeader(
@@ -666,38 +672,39 @@
 
         /* Hardcoded command line to start the system server */
         String args[] = {
-            "--setuid=1000",
-            "--setgid=1000",
-            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
-            "--capabilities=" + capabilities + "," + capabilities,
-            "--nice-name=system_server",
-            "--runtime-args",
-            "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
-            "com.android.server.SystemServer",
+                "--setuid=1000",
+                "--setgid=1000",
+                "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
+                        + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
+                "--capabilities=" + capabilities + "," + capabilities,
+                "--nice-name=system_server",
+                "--runtime-args",
+                "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
+                "com.android.server.SystemServer",
         };
-        ZygoteConnection.Arguments parsedArgs = null;
+        ZygoteArguments parsedArgs = null;
 
         int pid;
 
         try {
-            parsedArgs = new ZygoteConnection.Arguments(args);
-            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
-            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
+            parsedArgs = new ZygoteArguments(args);
+            Zygote.applyDebuggerSystemProperty(parsedArgs);
+            Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
             boolean profileSystemServer = SystemProperties.getBoolean(
                     "dalvik.vm.profilesystemserver", false);
             if (profileSystemServer) {
-                parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
+                parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
             }
 
             /* Request to fork the system server process */
             pid = Zygote.forkSystemServer(
-                    parsedArgs.uid, parsedArgs.gid,
-                    parsedArgs.gids,
-                    parsedArgs.runtimeFlags,
+                    parsedArgs.mUid, parsedArgs.mGid,
+                    parsedArgs.mGids,
+                    parsedArgs.mRuntimeFlags,
                     null,
-                    parsedArgs.permittedCapabilities,
-                    parsedArgs.effectiveCapabilities);
+                    parsedArgs.mPermittedCapabilities,
+                    parsedArgs.mEffectiveCapabilities);
         } catch (IllegalArgumentException ex) {
             throw new RuntimeException(ex);
         }
@@ -785,10 +792,10 @@
             if (!enableLazyPreload) {
                 bootTimingsTraceLog.traceBegin("ZygotePreload");
                 EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
-                    SystemClock.uptimeMillis());
+                        SystemClock.uptimeMillis());
                 preload(bootTimingsTraceLog);
                 EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
-                    SystemClock.uptimeMillis());
+                        SystemClock.uptimeMillis());
                 bootTimingsTraceLog.traceEnd(); // ZygotePreload
             } else {
                 Zygote.resetNicePriority();
@@ -844,17 +851,16 @@
     /**
      * Return {@code true} if this device configuration has another zygote.
      *
-     * We determine this by comparing the device ABI list with this zygotes
-     * list. If this zygote supports all ABIs this device supports, there won't
-     * be another zygote.
+     * We determine this by comparing the device ABI list with this zygotes list. If this zygote
+     * supports all ABIs this device supports, there won't be another zygote.
      */
     private static boolean hasSecondZygote(String abiList) {
         return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList);
     }
 
     private static void waitForSecondaryZygote(String socketName) {
-        String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ?
-                Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET;
+        String otherZygoteName = ZygoteProcess.ZYGOTE_SOCKET_NAME.equals(socketName)
+                ? ZygoteProcess.ZYGOTE_SECONDARY_SOCKET_NAME : ZygoteProcess.ZYGOTE_SOCKET_NAME;
         ZygoteProcess.waitForConnectionToZygote(otherZygoteName);
     }
 
@@ -869,9 +875,8 @@
     }
 
     /**
-     * The main function called when started through the zygote process. This
-     * could be unified with main(), if the native code in nativeFinishInit()
-     * were rationalized with Zygote startup.<p>
+     * The main function called when started through the zygote process. This could be unified with
+     * main(), if the native code in nativeFinishInit() were rationalized with Zygote startup.<p>
      *
      * Current recognized args:
      * <ul>
@@ -881,7 +886,8 @@
      * @param targetSdkVersion target SDK version
      * @param argv arg strings
      */
-    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
+    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,
+            ClassLoader classLoader) {
         if (RuntimeInit.DEBUG) {
             Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
         }
@@ -895,9 +901,9 @@
     }
 
     /**
-     * The main function called when starting a child zygote process. This is used as an
-     * alternative to zygoteInit(), which skips calling into initialization routines that
-     * start the Binder threadpool.
+     * The main function called when starting a child zygote process. This is used as an alternative
+     * to zygoteInit(), which skips calling into initialization routines that start the Binder
+     * threadpool.
      */
     static final Runnable childZygoteInit(
             int targetSdkVersion, String[] argv, ClassLoader classLoader) {
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index fecf9b9..c1bfde1 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -20,14 +20,14 @@
 
 import android.net.LocalServerSocket;
 import android.net.LocalSocket;
-import android.system.Os;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.StructPollfd;
 import android.util.Log;
-
 import android.util.Slog;
-import java.io.IOException;
+
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.util.ArrayList;
 
 /**
@@ -63,8 +63,7 @@
      */
     private boolean mIsForkChild;
 
-    ZygoteServer() {
-    }
+    ZygoteServer() { }
 
     void setForkChild() {
         mIsForkChild = true;
@@ -197,7 +196,7 @@
                 if (i == 0) {
                     ZygoteConnection newPeer = acceptCommandPeer(abiList);
                     peers.add(newPeer);
-                    fds.add(newPeer.getFileDesciptor());
+                    fds.add(newPeer.getFileDescriptor());
                 } else {
                     try {
                         ZygoteConnection connection = peers.get(i);
diff --git a/core/java/com/android/internal/statusbar/NotificationVisibility.java b/core/java/com/android/internal/statusbar/NotificationVisibility.java
index a7203e7..24bb789 100644
--- a/core/java/com/android/internal/statusbar/NotificationVisibility.java
+++ b/core/java/com/android/internal/statusbar/NotificationVisibility.java
@@ -21,6 +21,8 @@
 import android.os.Parcelable;
 import android.util.Log;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import java.util.ArrayDeque;
 import java.util.Collection;
 
@@ -33,18 +35,53 @@
     public int rank;
     public int count;
     public boolean visible = true;
+    /** The visible location of the notification, could be e.g. notification shade or HUN. */
+    public NotificationLocation location;
     /*package*/ int id;
 
+    /**
+     * The UI location of the notification.
+     *
+     * There is a one-to-one mapping between this enum and
+     * MetricsProto.MetricsEvent.NotificationLocation.
+     */
+    public enum NotificationLocation {
+        LOCATION_UNKNOWN(MetricsEvent.LOCATION_UNKNOWN),
+        LOCATION_FIRST_HEADS_UP(MetricsEvent.LOCATION_FIRST_HEADS_UP), // visible heads-up
+        LOCATION_HIDDEN_TOP(MetricsEvent.LOCATION_HIDDEN_TOP), // hidden/scrolled away on the top
+        LOCATION_MAIN_AREA(MetricsEvent.LOCATION_MAIN_AREA), // visible in the shade
+        // in the bottom stack, and peeking
+        LOCATION_BOTTOM_STACK_PEEKING(MetricsEvent.LOCATION_BOTTOM_STACK_PEEKING),
+        // in the bottom stack, and hidden
+        LOCATION_BOTTOM_STACK_HIDDEN(MetricsEvent.LOCATION_BOTTOM_STACK_HIDDEN),
+        LOCATION_GONE(MetricsEvent.LOCATION_GONE); // the view isn't laid out at all
+
+        private final int mMetricsEventNotificationLocation;
+
+        NotificationLocation(int metricsEventNotificationLocation) {
+            mMetricsEventNotificationLocation = metricsEventNotificationLocation;
+        }
+
+        /**
+         * Returns the field from MetricsEvent.NotificationLocation that corresponds to this object.
+         */
+        public int toMetricsEventEnum() {
+            return mMetricsEventNotificationLocation;
+        }
+    }
+
     private NotificationVisibility() {
         id = sNexrId++;
     }
 
-    private NotificationVisibility(String key, int rank, int count, boolean visibile) {
+    private NotificationVisibility(String key, int rank, int count, boolean visible,
+            NotificationLocation location) {
         this();
         this.key = key;
         this.rank = rank;
         this.count = count;
-        this.visible = visibile;
+        this.visible = visible;
+        this.location = location;
     }
 
     @Override
@@ -54,12 +91,13 @@
                 + " rank=" + rank
                 + " count=" + count
                 + (visible?" visible":"")
+                + " location=" + location.name()
                 + " )";
     }
 
     @Override
     public NotificationVisibility clone() {
-        return obtain(this.key, this.rank, this.count, this.visible);
+        return obtain(this.key, this.rank, this.count, this.visible, this.location);
     }
 
     @Override
@@ -89,6 +127,7 @@
         out.writeInt(this.rank);
         out.writeInt(this.count);
         out.writeInt(this.visible ? 1 : 0);
+        out.writeString(this.location.name());
     }
 
     private void readFromParcel(Parcel in) {
@@ -96,18 +135,28 @@
         this.rank = in.readInt();
         this.count = in.readInt();
         this.visible = in.readInt() != 0;
+        this.location = NotificationLocation.valueOf(in.readString());
     }
 
     /**
-     * Return a new NotificationVisibility instance from the global pool. Allows us to
-     * avoid allocating new objects in many cases.
+     * Create a new NotificationVisibility object.
      */
     public static NotificationVisibility obtain(String key, int rank, int count, boolean visible) {
+        return obtain(key, rank, count, visible,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+    }
+
+    /**
+     * Create a new NotificationVisibility object.
+     */
+    public static NotificationVisibility obtain(String key, int rank, int count, boolean visible,
+            NotificationLocation location) {
         NotificationVisibility vo = obtain();
         vo.key = key;
         vo.rank = rank;
         vo.count = count;
         vo.visible = visible;
+        vo.location = location;
         return vo;
     }
 
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 8194a92..0752efe 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -64,7 +64,6 @@
     void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
     boolean isInputMethodPickerShownForTest();
     InputMethodSubtype getCurrentInputMethodSubtype();
-    boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
     void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
     // This is kept due to @UnsupportedAppUsage.
     // TODO(Bug 113914148): Consider removing this.
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index fd1d87f..d29857d 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -37,6 +37,12 @@
     }
 }
 
+Picture::Picture(sk_sp<SkPicture>&& src) {
+    mPicture = std::move(src);
+    mWidth = 0;
+    mHeight = 0;
+}
+
 Canvas* Picture::beginRecording(int width, int height) {
     mPicture.reset(NULL);
     mRecorder.reset(new SkPictureRecorder);
diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h
index 3068631..536f651 100644
--- a/core/jni/android/graphics/Picture.h
+++ b/core/jni/android/graphics/Picture.h
@@ -37,6 +37,7 @@
 class Picture {
 public:
     explicit Picture(const Picture* src = NULL);
+    explicit Picture(sk_sp<SkPicture>&& src);
 
     Canvas* beginRecording(int width, int height);
 
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 5a8ab3c..318ec9b 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -48,6 +48,7 @@
 #include <FrameInfo.h>
 #include <FrameMetricsObserver.h>
 #include <IContextFactory.h>
+#include <Picture.h>
 #include <Properties.h>
 #include <PropertyValuesAnimatorSet.h>
 #include <RenderNode.h>
@@ -71,6 +72,11 @@
 } gFrameMetricsObserverClassInfo;
 
 struct {
+    jclass clazz;
+    jmethodID invokePictureCapturedCallback;
+} gHardwareRenderer;
+
+struct {
     jmethodID onFrameDraw;
 } gFrameDrawingCallback;
 
@@ -905,6 +911,27 @@
     jobject mObject;
 };
 
+static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* env,
+        jobject clazz, jlong proxyPtr, jobject pictureCallback) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    if (!pictureCallback) {
+        proxy->setPictureCapturedCallback(nullptr);
+    } else {
+        JavaVM* vm = nullptr;
+        LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+        auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
+                env->NewGlobalRef(pictureCallback));
+        proxy->setPictureCapturedCallback([globalCallbackRef](sk_sp<SkPicture>&& picture) {
+            JNIEnv* env = getenv(globalCallbackRef->vm());
+            Picture* wrapper = new Picture{std::move(picture)};
+            env->CallStaticVoidMethod(gHardwareRenderer.clazz,
+                    gHardwareRenderer.invokePictureCapturedCallback,
+                    static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)),
+                    globalCallbackRef->object());
+        });
+    }
+}
+
 static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
         jobject clazz, jlong proxyPtr, jobject frameCallback) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -1145,6 +1172,8 @@
     { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
     { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
     { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
+    { "nSetPictureCaptureCallback", "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V",
+            (void*) android_view_ThreadedRenderer_setPictureCapturedCallbackJNI },
     { "nSetFrameCallback", "(JLandroid/graphics/HardwareRenderer$FrameDrawingCallback;)V",
             (void*)android_view_ThreadedRenderer_setFrameCallback},
     { "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V",
@@ -1198,6 +1227,13 @@
     gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
             env, metricsClass, "mTimingData", "[J");
 
+    jclass hardwareRenderer = FindClassOrDie(env,
+            "android/graphics/HardwareRenderer");
+    gHardwareRenderer.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(hardwareRenderer));
+    gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
+            "invokePictureCapturedCallback",
+            "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
+
     jclass frameCallbackClass = FindClassOrDie(env,
             "android/graphics/HardwareRenderer$FrameDrawingCallback");
     gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index f68c760..6cdba33 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2179,4 +2179,8 @@
     // OPEN: Settings > Network & internet > Click Mobile network to land on a page with a list of
     // SIM/eSIM subscriptions.
     MOBILE_NETWORK_LIST = 1627;
+
+    // OPEN: Settings > Display > Adaptive sleep
+    // OS: Q
+    SETTINGS_ADAPTIVE_SLEEP = 1628;
 }
diff --git a/core/proto/android/debug/enums.proto b/core/proto/android/debug/enums.proto
new file mode 100644
index 0000000..6747bb7
--- /dev/null
+++ b/core/proto/android/debug/enums.proto
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.debug;
+
+option java_outer_classname = "AdbProtoEnums";
+option java_multiple_files = true;
+
+/**
+ * adb connection state used to track adb connection changes in AdbDebuggingManager.java.
+ */
+enum AdbConnectionStateEnum {
+    UNKNOWN = 0;
+
+    /**
+     * The adb connection is waiting for approval from the user.
+     */
+    AWAITING_USER_APPROVAL = 1;
+
+    /**
+     * The user allowed the adb connection from the system.
+     */
+    USER_ALLOWED = 2;
+
+    /**
+     * The user denied the adb connection from the system.
+     */
+    USER_DENIED = 3;
+
+    /**
+     * The adb connection was automatically allowed without user interaction due to the system
+     * being previously allowed by the user with the 'always allow' option selected, and the adb
+     * grant has not yet expired.
+     */
+    AUTOMATICALLY_ALLOWED = 4;
+
+    /**
+     * An empty or invalid base64 encoded key was provided to the framework; the connection was
+     * automatically denied.
+     */
+    DENIED_INVALID_KEY = 5;
+
+    /**
+     * vold decrypt has not yet occurred; the connection was automatically denied.
+     */
+    DENIED_VOLD_DECRYPT = 6;
+
+    /**
+     * The adb session has been disconnected.
+     */
+    DISCONNECTED = 7;
+}
+
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index 528c1a4..050ec7a 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -88,6 +88,11 @@
     // If class field is an Activity, instance_id is a unique id of the
     // Activity object.
     optional int32 instance_id = 14;
+    // task_root_package_index contains the index + 1 of the task root package name in the string
+    // pool
+    optional int32 task_root_package_index = 15;
+    // task_root_class_index contains the index + 1 of the task root class name in the string pool
+    optional int32 task_root_class_index = 16;
   }
 
   // The following fields contain supplemental data used to build IntervalStats, such as a string
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 16d1d1a..1a21306 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -530,6 +530,9 @@
     <!-- Boolean indicating whether the wifi chipset has dual frequency band support -->
     <bool translatable="false" name="config_wifi_dual_band_support">false</bool>
 
+    <!-- Maximum number of concurrent WiFi interfaces in AP mode -->
+    <integer translatable="false" name="config_wifi_max_ap_interfaces">1</integer>
+
     <!-- Boolean indicating whether the wifi chipset requires the softap band be -->
     <!-- converted from 5GHz to ANY due to hardware restrictions -->
     <bool translatable="false" name="config_wifi_convert_apband_5ghz_to_any">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7517218..83982c7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1879,6 +1879,7 @@
   <java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
   <java-symbol type="bool" name="config_wifi_background_scan_support" />
   <java-symbol type="bool" name="config_wifi_dual_band_support" />
+  <java-symbol type="integer" name="config_wifi_max_ap_interfaces" />
   <java-symbol type="bool" name="config_wifi_convert_apband_5ghz_to_any" />
   <java-symbol type="bool" name="config_wifi_local_only_hotspot_5ghz" />
   <java-symbol type="bool" name="config_wifi_connected_mac_randomization_supported" />
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 8d42c74..5731daa 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -20,12 +20,23 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality;
+import static android.app.admin.PasswordMetrics.getActualRequiredQuality;
+import static android.app.admin.PasswordMetrics.getMinimumMetrics;
+import static android.app.admin.PasswordMetrics.getTargetQualityMetrics;
+import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
-import android.app.admin.PasswordMetrics.PasswordComplexityBucket;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -109,7 +120,7 @@
         assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
                 PasswordMetrics.computeForPassword("1").quality);
         // contains a long sequence so isn't complex
-        assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+        assertEquals(PASSWORD_QUALITY_NUMERIC,
                 PasswordMetrics.computeForPassword("1234").quality);
         assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
                 PasswordMetrics.computeForPassword("").quality);
@@ -145,7 +156,7 @@
                 new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5));
 
         assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
-                new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 4));
+                new PasswordMetrics(PASSWORD_QUALITY_COMPLEX, 4));
 
         metrics0 = PasswordMetrics.computeForPassword("1234abcd,./");
         metrics1 = PasswordMetrics.computeForPassword("1234abcd,./");
@@ -176,9 +187,9 @@
     @Test
     public void testConstructQuality() {
         PasswordMetrics expected = new PasswordMetrics();
-        expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+        expected.quality = PASSWORD_QUALITY_COMPLEX;
 
-        PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+        PasswordMetrics actual = new PasswordMetrics(PASSWORD_QUALITY_COMPLEX);
 
         assertEquals(expected, actual);
     }
@@ -256,42 +267,178 @@
     }
 
     @Test
-    public void testComplexityLevelToBucket_none() {
-        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
-                PASSWORD_COMPLEXITY_NONE).getMetrics();
+    public void testSanitizeComplexityLevel_none() {
+        assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_NONE));
 
-        for (PasswordMetrics metrics : bucket) {
-            assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity());
-        }
     }
 
     @Test
-    public void testComplexityLevelToBucket_low() {
-        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
-                PASSWORD_COMPLEXITY_LOW).getMetrics();
-
-        for (PasswordMetrics metrics : bucket) {
-            assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity());
-        }
+    public void testSanitizeComplexityLevel_low() {
+        assertEquals(PASSWORD_COMPLEXITY_LOW, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_LOW));
     }
 
     @Test
-    public void testComplexityLevelToBucket_medium() {
-        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
-                PASSWORD_COMPLEXITY_MEDIUM).getMetrics();
-
-        for (PasswordMetrics metrics : bucket) {
-            assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity());
-        }
+    public void testSanitizeComplexityLevel_medium() {
+        assertEquals(
+                PASSWORD_COMPLEXITY_MEDIUM, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_MEDIUM));
     }
 
     @Test
-    public void testComplexityLevelToBucket_high() {
-        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
-                PASSWORD_COMPLEXITY_HIGH).getMetrics();
+    public void testSanitizeComplexityLevel_high() {
+        assertEquals(PASSWORD_COMPLEXITY_HIGH, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_HIGH));
+    }
 
-        for (PasswordMetrics metrics : bucket) {
-            assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity());
-        }
+    @Test
+    public void testSanitizeComplexityLevel_invalid() {
+        assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(-1));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_none() {
+        assertEquals(PASSWORD_QUALITY_UNSPECIFIED,
+                complexityLevelToMinQuality(PASSWORD_COMPLEXITY_NONE));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_low() {
+        assertEquals(PASSWORD_QUALITY_SOMETHING,
+                complexityLevelToMinQuality(PASSWORD_COMPLEXITY_LOW));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_medium() {
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX,
+                complexityLevelToMinQuality(PASSWORD_COMPLEXITY_MEDIUM));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_high() {
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX,
+                complexityLevelToMinQuality(PASSWORD_COMPLEXITY_HIGH));
+    }
+
+    @Test
+    public void testComplexityLevelToMinQuality_invalid() {
+        assertEquals(PASSWORD_QUALITY_UNSPECIFIED, complexityLevelToMinQuality(-1));
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() {
+        PasswordMetrics metrics =
+                getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC);
+
+        assertTrue(metrics.isDefault());
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_qualityNotAllowedReturnsMinQualityMetrics() {
+        PasswordMetrics metrics =
+                getTargetQualityMetrics(PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_NUMERIC);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+        assertEquals(/* expected= */ 4, metrics.length);
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_highComplexityNumericComplex() {
+        PasswordMetrics metrics = getTargetQualityMetrics(
+                PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+        assertEquals(/* expected= */ 8, metrics.length);
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() {
+        PasswordMetrics metrics = getTargetQualityMetrics(
+                PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC);
+
+        assertEquals(PASSWORD_QUALITY_ALPHABETIC, metrics.quality);
+        assertEquals(/* expected= */ 4, metrics.length);
+    }
+
+    @Test
+    public void testGetTargetQualityMetrics_lowComplexityAlphanumeric() {
+        PasswordMetrics metrics = getTargetQualityMetrics(
+                PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHANUMERIC);
+
+        assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
+        assertEquals(/* expected= */ 4, metrics.length);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_nonComplex() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_NUMERIC_COMPLEX,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, actual);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_complexRequiresNone() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_COMPLEX,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_UNSPECIFIED, actual);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_complexRequiresNumeric() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_COMPLEX,
+                /* requiresNumeric= */ true,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC, actual);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_complexRequiresLetters() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_COMPLEX,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ true);
+
+        assertEquals(PASSWORD_QUALITY_ALPHABETIC, actual);
+    }
+
+    @Test
+    public void testGetActualRequiredQuality_complexRequiresNumericAndLetters() {
+        int actual = getActualRequiredQuality(
+                PASSWORD_QUALITY_COMPLEX,
+                /* requiresNumeric= */ true,
+                /* requiresLettersOrSymbols= */ true);
+
+        assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, actual);
+    }
+
+    @Test
+    public void testGetMinimumMetrics_userInputStricter() {
+        PasswordMetrics metrics = getMinimumMetrics(
+                PASSWORD_COMPLEXITY_HIGH,
+                PASSWORD_QUALITY_ALPHANUMERIC,
+                PASSWORD_QUALITY_NUMERIC,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
+        assertEquals(/* expected= */ 6, metrics.length);
+    }
+
+    @Test
+    public void testGetMinimumMetrics_actualRequiredQualityStricter() {
+        PasswordMetrics metrics = getMinimumMetrics(
+                PASSWORD_COMPLEXITY_HIGH,
+                PASSWORD_QUALITY_UNSPECIFIED,
+                PASSWORD_QUALITY_NUMERIC,
+                /* requiresNumeric= */ false,
+                /* requiresLettersOrSymbols= */ false);
+
+        assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
+        assertEquals(/* expected= */ 8, metrics.length);
     }
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 87ad3d1..1431741 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -100,6 +100,7 @@
                     Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
                     Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED,
                     Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
+                    Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
                     Settings.Global.ADB_ENABLED,
                     Settings.Global.ADD_USERS_WHEN_LOCKED,
                     Settings.Global.AIRPLANE_MODE_ON,
@@ -120,6 +121,7 @@
                     Settings.Global.APP_IDLE_CONSTANTS,
                     Settings.Global.APP_OPS_CONSTANTS,
                     Settings.Global.APP_STANDBY_ENABLED,
+                    Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE,
                     Settings.Global.ASSISTED_GPS_ENABLED,
                     Settings.Global.AUDIO_SAFE_VOLUME_STATE,
                     Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 7f00ad9..7cd3c44 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -101,6 +101,7 @@
     @Test
     public void testChangeInsets() {
         mController.changeInsets(Insets.of(0, 30, 40, 0));
+        mController.applyChangeInsets(new InsetsState());
         assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
 
         ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class);
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
index 33bc593..2f17b32 100644
--- a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
+++ b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
@@ -34,26 +34,57 @@
     public void testNonVirtual() {
         final AutofillId id = new AutofillId(42);
         assertThat(id.getViewId()).isEqualTo(42);
-        assertThat(id.isVirtual()).isFalse();
-        assertThat(id.getVirtualChildId()).isEqualTo(View.NO_ID);
+        assertThat(id.isNonVirtual()).isTrue();
+        assertThat(id.isVirtualInt()).isFalse();
+        assertThat(id.isVirtualLong()).isFalse();
+        assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+        assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID);
 
         final AutofillId clone = cloneThroughParcel(id);
         assertThat(clone.getViewId()).isEqualTo(42);
-        assertThat(clone.isVirtual()).isFalse();
-        assertThat(clone.getVirtualChildId()).isEqualTo(View.NO_ID);
+        assertThat(clone.isNonVirtual()).isTrue();
+        assertThat(clone.isVirtualInt()).isFalse();
+        assertThat(clone.isVirtualLong()).isFalse();
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+        assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID);
     }
 
     @Test
-    public void testVirtual() {
+    public void testVirtual_int() {
         final AutofillId id = new AutofillId(42, 108);
         assertThat(id.getViewId()).isEqualTo(42);
-        assertThat(id.isVirtual()).isTrue();
-        assertThat(id.getVirtualChildId()).isEqualTo(108);
+        assertThat(id.isVirtualInt()).isTrue();
+        assertThat(id.isVirtualLong()).isFalse();
+        assertThat(id.isNonVirtual()).isFalse();
+        assertThat(id.getVirtualChildIntId()).isEqualTo(108);
+        assertThat(id.getVirtualChildLongId()).isEqualTo(View.NO_ID);
 
         final AutofillId clone = cloneThroughParcel(id);
         assertThat(clone.getViewId()).isEqualTo(42);
-        assertThat(clone.isVirtual()).isTrue();
-        assertThat(clone.getVirtualChildId()).isEqualTo(108);
+        assertThat(clone.isVirtualLong()).isFalse();
+        assertThat(clone.isVirtualInt()).isTrue();
+        assertThat(clone.isNonVirtual()).isFalse();
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(108);
+        assertThat(clone.getVirtualChildLongId()).isEqualTo(View.NO_ID);
+    }
+
+    @Test
+    public void testVirtual_long() {
+        final AutofillId id = new AutofillId(new AutofillId(42), 4815162342L, 108);
+        assertThat(id.getViewId()).isEqualTo(42);
+        assertThat(id.isVirtualLong()).isTrue();
+        assertThat(id.isVirtualInt()).isFalse();
+        assertThat(id.isNonVirtual()).isFalse();
+        assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+        assertThat(id.getVirtualChildLongId()).isEqualTo(4815162342L);
+
+        final AutofillId clone = cloneThroughParcel(id);
+        assertThat(clone.getViewId()).isEqualTo(42);
+        assertThat(clone.isVirtualLong()).isTrue();
+        assertThat(clone.isVirtualInt()).isFalse();
+        assertThat(clone.isNonVirtual()).isFalse();
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
+        assertThat(clone.getVirtualChildLongId()).isEqualTo(4815162342L);
     }
 
     @Test
@@ -62,27 +93,33 @@
 
         final AutofillId id = new AutofillId(new AutofillId(42), 108);
         assertThat(id.getViewId()).isEqualTo(42);
-        assertThat(id.isVirtual()).isTrue();
-        assertThat(id.getVirtualChildId()).isEqualTo(108);
+        assertThat(id.isVirtualInt()).isTrue();
+        assertThat(id.getVirtualChildIntId()).isEqualTo(108);
 
         final AutofillId clone = cloneThroughParcel(id);
         assertThat(clone.getViewId()).isEqualTo(42);
-        assertThat(clone.isVirtual()).isTrue();
-        assertThat(clone.getVirtualChildId()).isEqualTo(108);
+        assertThat(clone.isVirtualInt()).isTrue();
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(108);
     }
 
     @Test
     public void testVirtual_withSession() {
-        final AutofillId id = new AutofillId(new AutofillId(42), 108, 666);
+        final AutofillId id = new AutofillId(new AutofillId(42), 108L, 666);
         assertThat(id.getViewId()).isEqualTo(42);
-        assertThat(id.isVirtual()).isTrue();
-        assertThat(id.getVirtualChildId()).isEqualTo(108);
+        assertThat(id.isVirtualLong()).isTrue();
+        assertThat(id.isVirtualInt()).isFalse();
+        assertThat(id.isNonVirtual()).isFalse();
+        assertThat(id.getVirtualChildLongId()).isEqualTo(108L);
+        assertThat(id.getVirtualChildIntId()).isEqualTo(View.NO_ID);
         assertThat(id.getSessionId()).isEqualTo(666);
 
         final AutofillId clone = cloneThroughParcel(id);
         assertThat(clone.getViewId()).isEqualTo(42);
-        assertThat(clone.isVirtual()).isTrue();
-        assertThat(clone.getVirtualChildId()).isEqualTo(108);
+        assertThat(clone.isVirtualLong()).isTrue();
+        assertThat(clone.isVirtualInt()).isFalse();
+        assertThat(clone.isNonVirtual()).isFalse();
+        assertThat(clone.getVirtualChildLongId()).isEqualTo(108L);
+        assertThat(clone.getVirtualChildIntId()).isEqualTo(View.NO_ID);
         assertThat(clone.getSessionId()).isEqualTo(666);
     }
 
@@ -118,13 +155,14 @@
         assertThat(virtualIdDifferentParent).isNotEqualTo(virtualIdDifferentChild);
         assertThat(virtualIdDifferentChild).isNotEqualTo(virtualIdDifferentParent);
 
-        final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+        final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1L, 108);
         assertThat(virtualIdDifferentSession).isNotEqualTo(virtualId);
         assertThat(virtualId).isNotEqualTo(virtualIdDifferentSession);
         assertThat(virtualIdDifferentSession).isNotEqualTo(realId);
         assertThat(realId).isNotEqualTo(virtualIdDifferentSession);
 
-        final AutofillId sameVirtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+        final AutofillId sameVirtualIdDifferentSession =
+                new AutofillId(new AutofillId(42), 1L, 108);
         assertThat(sameVirtualIdDifferentSession).isEqualTo(virtualIdDifferentSession);
         assertThat(virtualIdDifferentSession).isEqualTo(sameVirtualIdDifferentSession);
         assertThat(sameVirtualIdDifferentSession.hashCode())
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index ff97aa1..c2eb18b 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -48,17 +48,18 @@
 
     @Test
     public void testNewAutofillId_invalid() {
-        assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42));
+        assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42L));
         assertThrows(IllegalArgumentException.class,
-                () -> mSession1.newAutofillId(new AutofillId(42, 42), 42));
+                () -> mSession1.newAutofillId(new AutofillId(42, 42), 42L));
     }
 
     @Test
     public void testNewAutofillId_valid() {
         final AutofillId parentId = new AutofillId(42);
-        final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+        final AutofillId childId = mSession1.newAutofillId(parentId, 108L);
         assertThat(childId.getViewId()).isEqualTo(42);
-        assertThat(childId.getVirtualChildId()).isEqualTo(108);
+        assertThat(childId.getVirtualChildLongId()).isEqualTo(108L);
+        assertThat(childId.getVirtualChildIntId()).isEqualTo(View.NO_ID);
         assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt());
     }
 
@@ -66,8 +67,8 @@
     public void testNewAutofillId_differentSessions() {
         assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check
         final AutofillId parentId = new AutofillId(42);
-        final AutofillId childId1 = mSession1.newAutofillId(parentId, 108);
-        final AutofillId childId2 = mSession2.newAutofillId(parentId, 108);
+        final AutofillId childId1 = mSession1.newAutofillId(parentId, 108L);
+        final AutofillId childId2 = mSession2.newAutofillId(parentId, 108L);
         assertThat(childId1).isNotEqualTo(childId2);
         assertThat(childId2).isNotEqualTo(childId1);
     }
@@ -91,9 +92,9 @@
     @Test
     public void testNewVirtualViewStructure() {
         final AutofillId parentId = new AutofillId(42);
-        final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108);
+        final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108L);
         assertThat(structure).isNotNull();
-        final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+        final AutofillId childId = mSession1.newAutofillId(parentId, 108L);
         assertThat(structure.getAutofillId()).isEqualTo(childId);
     }
 
@@ -101,16 +102,16 @@
     public void testNotifyViewsDisappeared_invalid() {
         // Null parent
         assertThrows(NullPointerException.class,
-                () -> mSession1.notifyViewsDisappeared(null, new int[] {42}));
+                () -> mSession1.notifyViewsDisappeared(null, new long[] {42}));
         // Null child
         assertThrows(IllegalArgumentException.class,
                 () -> mSession1.notifyViewsDisappeared(new AutofillId(42), null));
         // Empty child
         assertThrows(IllegalArgumentException.class,
-                () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new int[] {}));
+                () -> mSession1.notifyViewsDisappeared(new AutofillId(42), new long[] {}));
         // Virtual parent
         assertThrows(IllegalArgumentException.class,
-                () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new int[] {666}));
+                () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new long[] {666}));
     }
 
     // Cannot use @Spy because we need to pass the session id on constructor
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java
new file mode 100644
index 0000000..b740ecc
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/statusbar/NotificationVisibilityTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.internal.statusbar;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NotificationVisibilityTest {
+
+    @Test
+    public void testNotificationLocation_sameValuesAsMetricsProto() throws Exception {
+        for (NotificationVisibility.NotificationLocation location :
+                NotificationVisibility.NotificationLocation.values()) {
+            Field locationField = MetricsEvent.class.getField(location.name());
+            int metricsValue = locationField.getInt(null);
+            assertThat(metricsValue, is(location.toMetricsEventEnum()));
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index e402055..c4ddd50 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -20,6 +20,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.os.IBinder;
@@ -667,6 +668,17 @@
         nSetContentDrawBounds(mNativeProxy, left, top, right, bottom);
     }
 
+    /** @hide */
+    public void setPictureCaptureCallback(@Nullable PictureCapturedCallback callback) {
+        nSetPictureCaptureCallback(mNativeProxy, callback);
+    }
+
+    /** called by native */
+    static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) {
+        Picture picture = new Picture(picturePtr);
+        callback.onPictureCaptured(picture);
+    }
+
     /**
      * Interface used to receive callbacks when a frame is being drawn.
      *
@@ -695,6 +707,17 @@
         void onFrameComplete(long frameNr);
     }
 
+    /**
+     * Interface for listening to picture captures
+     * @hide
+     */
+    @TestApi
+    public interface PictureCapturedCallback {
+        /** @hide */
+        @TestApi
+        void onPictureCaptured(Picture picture);
+    }
+
     private static void validateAlpha(float alpha, String argumentName) {
         if (!(alpha >= 0.0f && alpha <= 1.0f)) {
             throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
@@ -998,6 +1021,9 @@
     private static native void nSetContentDrawBounds(long nativeProxy, int left,
             int top, int right, int bottom);
 
+    private static native void nSetPictureCaptureCallback(long nativeProxy,
+            PictureCapturedCallback callback);
+
     private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback);
 
     private static native void nSetFrameCompleteCallback(long nativeProxy,
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index f6d801b..8d12cbf 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 
 import java.io.InputStream;
@@ -34,7 +35,8 @@
  */
 public class Picture {
     private PictureCanvas mRecordingCanvas;
-    @UnsupportedAppUsage
+    // TODO: Figure out if this was a false-positive
+    @UnsupportedAppUsage(maxTargetSdk = 28)
     private long mNativePicture;
     private boolean mRequiresHwAcceleration;
 
@@ -56,23 +58,43 @@
         this(nativeConstructor(src != null ? src.mNativePicture : 0));
     }
 
-    private Picture(long nativePicture) {
+    /** @hide */
+    public Picture(long nativePicture) {
         if (nativePicture == 0) {
-            throw new RuntimeException();
+            throw new IllegalArgumentException();
         }
         mNativePicture = nativePicture;
     }
 
+    /**
+     * Immediately releases the backing data of the Picture. This object will no longer
+     * be usable after calling this, and any further calls on the Picture will throw an
+     * IllegalStateException.
+     * // TODO: Support?
+     * @hide
+     */
+    public void close() {
+        if (mNativePicture != 0) {
+            nativeDestructor(mNativePicture);
+            mNativePicture = 0;
+        }
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
-            nativeDestructor(mNativePicture);
-            mNativePicture = 0;
+            close();
         } finally {
             super.finalize();
         }
     }
 
+    private void verifyValid() {
+        if (mNativePicture == 0) {
+            throw new IllegalStateException("Picture is destroyed");
+        }
+    }
+
     /**
      * To record a picture, call beginRecording() and then draw into the Canvas
      * that is returned. Nothing we appear on screen, but all of the draw
@@ -81,7 +103,9 @@
      * that was returned must no longer be used, and nothing should be drawn
      * into it.
      */
+    @NonNull
     public Canvas beginRecording(int width, int height) {
+        verifyValid();
         if (mRecordingCanvas != null) {
             throw new IllegalStateException("Picture already recording, must call #endRecording()");
         }
@@ -98,6 +122,7 @@
      * or {@link Canvas#drawPicture(Picture)} is called.
      */
     public void endRecording() {
+        verifyValid();
         if (mRecordingCanvas != null) {
             mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
             mRecordingCanvas = null;
@@ -110,7 +135,8 @@
      * does not reflect (per se) the content of the picture.
      */
     public int getWidth() {
-      return nativeGetWidth(mNativePicture);
+        verifyValid();
+        return nativeGetWidth(mNativePicture);
     }
 
     /**
@@ -118,7 +144,8 @@
      * does not reflect (per se) the content of the picture.
      */
     public int getHeight() {
-      return nativeGetHeight(mNativePicture);
+        verifyValid();
+        return nativeGetHeight(mNativePicture);
     }
 
     /**
@@ -133,6 +160,7 @@
      *         false otherwise.
      */
     public boolean requiresHardwareAcceleration() {
+        verifyValid();
         return mRequiresHwAcceleration;
     }
 
@@ -149,7 +177,8 @@
      *
      * @param canvas  The picture is drawn to this canvas
      */
-    public void draw(Canvas canvas) {
+    public void draw(@NonNull Canvas canvas) {
+        verifyValid();
         if (mRecordingCanvas != null) {
             endRecording();
         }
@@ -172,7 +201,7 @@
      * raw or compressed pixels.
      */
     @Deprecated
-    public static Picture createFromStream(InputStream stream) {
+    public static Picture createFromStream(@NonNull InputStream stream) {
         return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
     }
 
@@ -188,10 +217,11 @@
      * Bitmap from which you can persist it as raw or compressed pixels.
      */
     @Deprecated
-    public void writeToStream(OutputStream stream) {
+    public void writeToStream(@NonNull OutputStream stream) {
+        verifyValid();
         // do explicit check before calling the native method
         if (stream == null) {
-            throw new NullPointerException();
+            throw new IllegalArgumentException("stream cannot be null");
         }
         if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
             throw new RuntimeException();
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 3b1d44b..09b18b7 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -184,7 +184,7 @@
      *
      * @param name The name of the RenderNode, used for debugging purpose. May be null.
      */
-    public RenderNode(String name) {
+    public RenderNode(@Nullable String name) {
         this(name, null);
     }
 
diff --git a/graphics/proto/Android.bp b/graphics/proto/Android.bp
new file mode 100644
index 0000000..1d06348
--- /dev/null
+++ b/graphics/proto/Android.bp
@@ -0,0 +1,11 @@
+java_library_static {
+    name: "game-driver-protos",
+    host_supported: true,
+    proto: {
+        type: "lite",
+    },
+    srcs: ["game_driver.proto"],
+    no_framework_libs: true,
+    jarjar_rules: "jarjar-rules.txt",
+    sdk_version: "28",
+}
diff --git a/graphics/proto/game_driver.proto b/graphics/proto/game_driver.proto
new file mode 100644
index 0000000..fd7ffcc
--- /dev/null
+++ b/graphics/proto/game_driver.proto
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.
+ */
+
+syntax = "proto2";
+
+package android.gamedriver;
+
+option java_package = "android.gamedriver";
+option java_outer_classname = "GameDriverProto";
+
+message Blacklist {
+    optional int64 version_code = 1;
+    repeated string package_names = 2;
+}
+
+message Blacklists {
+    repeated Blacklist blacklists = 1;
+}
diff --git a/graphics/proto/jarjar-rules.txt b/graphics/proto/jarjar-rules.txt
new file mode 100644
index 0000000..4e40637
--- /dev/null
+++ b/graphics/proto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.** com.android.framework.protobuf.@1
diff --git a/jarjar_rules_hidl.txt b/jarjar_rules_hidl.txt
new file mode 100644
index 0000000..4b2331d
--- /dev/null
+++ b/jarjar_rules_hidl.txt
@@ -0,0 +1 @@
+rule android.hidl.** android.internal.hidl.@1
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index dd62bbb..7265692 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -551,6 +551,19 @@
     SkPaint paint = inPaint;
     paint.setAlpha(mProperties.getRootAlpha() * 255);
 
+    if (canvas->getGrContext() == nullptr) {
+        // Recording to picture, don't use the SkSurface which won't work off of renderthread.
+        Bitmap& bitmap = getBitmapUpdateIfDirty();
+        SkBitmap skiaBitmap;
+        bitmap.getSkBitmap(&skiaBitmap);
+
+        int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
+        int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
+        canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
+                               &paint, SkCanvas::kFast_SrcRectConstraint);
+        return;
+    }
+
     SkRect src;
     sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
     if (vdSurface) {
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 240efb4..60c8057 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -74,7 +74,13 @@
 
 void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
     if (canvas->getGrContext() == nullptr) {
-        SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface"));
+        // We're dumping a picture, render a light-blue rectangle instead
+        // TODO: Draw the WebView text on top? Seemingly complicated as SkPaint doesn't
+        // seem to have a default typeface that works. We only ever use drawGlyphs, which
+        // requires going through minikin & hwui's canvas which we don't have here.
+        SkPaint paint;
+        paint.setColor(0xFF81D4FA);
+        canvas->drawRect(mBounds, paint);
         return;
     }
 
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index df82243..47c9094 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -111,7 +111,7 @@
 
             const Rect& layerDamage = layers.entries()[i].damage;
 
-            SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
+            SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
 
             int saveCount = layerCanvas->save();
             SkASSERT(saveCount == 1);
@@ -139,8 +139,6 @@
             layerCanvas->restoreToCount(saveCount);
             mLightCenter = savedLightCenter;
 
-            endCapture(layerNode->getLayerSurface());
-
             // cache the current context so that we can defer flushing it until
             // either all the layers have been rendered or the context changes
             GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
@@ -244,6 +242,7 @@
     }
 
     virtual void onProcess(const sp<Task<bool>>& task) override {
+        ATRACE_NAME("SavePictureTask");
         SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
 
         if (0 == access(t->filename.c_str(), F_OK)) {
@@ -265,46 +264,56 @@
 
 SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
     if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
-        bool recordingPicture = mCaptureSequence > 0;
         char prop[PROPERTY_VALUE_MAX] = {'\0'};
-        if (!recordingPicture) {
+        if (mCaptureSequence <= 0) {
             property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
-            recordingPicture = prop[0] != '0' &&
-                               mCapturedFile != prop;  // ensure we capture only once per filename
-            if (recordingPicture) {
+            if (prop[0] != '0' && mCapturedFile != prop) {
                 mCapturedFile = prop;
                 mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
             }
         }
-        if (recordingPicture) {
+        if (mCaptureSequence > 0 || mPictureCapturedCallback) {
             mRecorder.reset(new SkPictureRecorder());
-            return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
-                                             SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+            SkCanvas* pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
+                                                                SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+            mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
+            mNwayCanvas->addCanvas(surface->getCanvas());
+            mNwayCanvas->addCanvas(pictureCanvas);
+            return mNwayCanvas.get();
         }
     }
     return surface->getCanvas();
 }
 
 void SkiaPipeline::endCapture(SkSurface* surface) {
+    mNwayCanvas.reset();
     if (CC_UNLIKELY(mRecorder.get())) {
+        ATRACE_CALL();
         sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
-        surface->getCanvas()->drawPicture(picture);
         if (picture->approximateOpCount() > 0) {
-            auto data = picture->serialize();
+            if (mCaptureSequence > 0) {
+                ATRACE_BEGIN("picture->serialize");
+                auto data = picture->serialize();
+                ATRACE_END();
 
-            // offload saving to file in a different thread
-            if (!mSavePictureProcessor.get()) {
-                TaskManager* taskManager = getTaskManager();
-                mSavePictureProcessor = new SavePictureProcessor(
-                        taskManager->canRunTasks() ? taskManager : nullptr);
+                // offload saving to file in a different thread
+                if (!mSavePictureProcessor.get()) {
+                    TaskManager* taskManager = getTaskManager();
+                    mSavePictureProcessor = new SavePictureProcessor(
+                            taskManager->canRunTasks() ? taskManager : nullptr);
+                }
+                if (1 == mCaptureSequence) {
+                    mSavePictureProcessor->savePicture(data, mCapturedFile);
+                } else {
+                    mSavePictureProcessor->savePicture(
+                            data,
+                            mCapturedFile + "_" + std::to_string(mCaptureSequence));
+                }
+                mCaptureSequence--;
             }
-            if (1 == mCaptureSequence) {
-                mSavePictureProcessor->savePicture(data, mCapturedFile);
-            } else {
-                mSavePictureProcessor->savePicture(
-                        data, mCapturedFile + "_" + std::to_string(mCaptureSequence));
+            if (mPictureCapturedCallback) {
+                std::invoke(mPictureCapturedCallback, std::move(picture));
             }
-            mCaptureSequence--;
         }
         mRecorder.reset();
     }
@@ -314,6 +323,11 @@
                                const std::vector<sp<RenderNode>>& nodes, bool opaque,
                                const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
                                const SkMatrix& preTransform) {
+    bool previousSkpEnabled = Properties::skpCaptureEnabled;
+    if (mPictureCapturedCallback) {
+        Properties::skpCaptureEnabled = true;
+    }
+
     renderVectorDrawableCache();
 
     // draw all layers up front
@@ -334,6 +348,8 @@
 
     ATRACE_NAME("flush commands");
     surface->getCanvas()->flush();
+
+    Properties::skpCaptureEnabled = previousSkpEnabled;
 }
 
 namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index cf6f5b2..e9957df 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -105,6 +105,11 @@
         mLightCenter = lightGeometry.center;
     }
 
+    void setPictureCapturedCallback(
+            const std::function<void(sk_sp<SkPicture>&&)>& callback) override {
+        mPictureCapturedCallback = callback;
+    }
+
 protected:
     void dumpResourceCacheUsage() const;
     void setSurfaceColorProperties(renderthread::ColorMode colorMode);
@@ -163,6 +168,8 @@
      *  parallel tryCapture calls (not really needed).
      */
     std::unique_ptr<SkPictureRecorder> mRecorder;
+    std::unique_ptr<SkNWayCanvas> mNwayCanvas;
+    std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback;
 
     static float mLightRadius;
     static uint8_t mAmbientShadowAlpha;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 9e7abf4..db97763 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -184,6 +184,10 @@
         mFrameCompleteCallbacks.push_back(std::move(func));
     }
 
+    void setPictureCapturedCallback(const std::function<void(sk_sp<SkPicture>&&)>& callback) {
+        mRenderPipeline->setPictureCapturedCallback(callback);
+    }
+
     void setForceDark(bool enable) {
         mUseForceDark = enable;
     }
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index d4dd629..2cfc8df3 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -59,15 +59,15 @@
     virtual MakeCurrentResult makeCurrent() = 0;
     virtual Frame getFrame() = 0;
     virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
-                      const LightGeometry& lightGeometry,
-                      LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
-                      bool opaque, const LightInfo& lightInfo,
+                      const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+                      const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
                       const std::vector<sp<RenderNode>>& renderNodes,
                       FrameInfoVisualizer* profiler) = 0;
     virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
                              FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
     virtual DeferredLayerUpdater* createTextureLayer() = 0;
-    virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
+    virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior,
+                            ColorMode colorMode) = 0;
     virtual void onStop() = 0;
     virtual bool isSurfaceReady() = 0;
     virtual bool isContextReady() = 0;
@@ -85,6 +85,8 @@
     virtual SkColorType getSurfaceColorType() const = 0;
     virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
     virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
+    virtual void setPictureCapturedCallback(
+            const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
 
     virtual ~IRenderPipeline() {}
 };
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index aa6af23..ab59af7 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -21,6 +21,7 @@
 #include "Properties.h"
 #include "Readback.h"
 #include "Rect.h"
+#include "WebViewFunctorManager.h"
 #include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/VectorDrawableAtlas.h"
 #include "renderstate/RenderState.h"
@@ -30,7 +31,6 @@
 #include "renderthread/RenderThread.h"
 #include "utils/Macros.h"
 #include "utils/TimeUtils.h"
-#include "WebViewFunctorManager.h"
 
 #include <ui/GraphicBuffer.h>
 
@@ -147,9 +147,7 @@
 void RenderProxy::destroyFunctor(int functor) {
     ATRACE_CALL();
     RenderThread& thread = RenderThread::getInstance();
-    thread.queue().post([=]() {
-        WebViewFunctorManager::instance().destroyFunctor(functor);
-    });
+    thread.queue().post([=]() { WebViewFunctorManager::instance().destroyFunctor(functor); });
 }
 
 DeferredLayerUpdater* RenderProxy::createTextureLayer() {
@@ -164,9 +162,9 @@
 
 bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) {
     auto& thread = RenderThread::getInstance();
-    return thread.queue().runSync(
-            [&]() -> bool { return thread.readback().copyLayerInto(layer, &bitmap)
-                                   == CopyResult::Success; });
+    return thread.queue().runSync([&]() -> bool {
+        return thread.readback().copyLayerInto(layer, &bitmap) == CopyResult::Success;
+    });
 }
 
 void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -204,9 +202,8 @@
 }
 
 int RenderProxy::maxTextureSize() {
-    static int maxTextureSize = RenderThread::getInstance().queue().runSync([]() {
-        return DeviceInfo::get()->maxTextureSize();
-    });
+    static int maxTextureSize = RenderThread::getInstance().queue().runSync(
+            []() { return DeviceInfo::get()->maxTextureSize(); });
     return maxTextureSize;
 }
 
@@ -281,6 +278,12 @@
     mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
 }
 
+void RenderProxy::setPictureCapturedCallback(
+        const std::function<void(sk_sp<SkPicture>&&)>& callback) {
+    mRenderThread.queue().post(
+            [ this, cb = callback ]() { mContext->setPictureCapturedCallback(cb); });
+}
+
 void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
     mDrawFrameTask.setFrameCallback(std::move(callback));
 }
@@ -302,9 +305,7 @@
 }
 
 void RenderProxy::setForceDark(bool enable) {
-    mRenderThread.queue().post([this, enable]() {
-        mContext->setForceDark(enable);
-    });
+    mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
 }
 
 int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
@@ -348,9 +349,8 @@
         // TODO: fix everything that hits this. We should never be triggering a readback ourselves.
         return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
     } else {
-        return thread.queue().runSync([&]() -> int {
-            return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
-        });
+        return thread.queue().runSync(
+                [&]() -> int { return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); });
     }
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 9dc9181..6e1bfd7 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -114,6 +114,8 @@
     ANDROID_API void removeRenderNode(RenderNode* node);
     ANDROID_API void drawRenderNode(RenderNode* node);
     ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
+    ANDROID_API void setPictureCapturedCallback(
+            const std::function<void(sk_sp<SkPicture>&&)>& callback);
     ANDROID_API void setFrameCallback(std::function<void(int64_t)>&& callback);
     ANDROID_API void setFrameCompleteCallback(std::function<void(int64_t)>&& callback);
 
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index bfc10da5..2d2c4a8 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -176,7 +176,8 @@
      * @param uuid The UUID of the crypto scheme.
      */
     public static final boolean isCryptoSchemeSupported(@NonNull UUID uuid) {
-        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null);
+        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null,
+                SECURITY_LEVEL_UNKNOWN);
     }
 
     /**
@@ -189,7 +190,25 @@
      */
     public static final boolean isCryptoSchemeSupported(
             @NonNull UUID uuid, @NonNull String mimeType) {
-        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType);
+        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid),
+                mimeType, SECURITY_LEVEL_UNKNOWN);
+    }
+
+    /**
+     * Query if the given scheme identified by its UUID is supported on
+     * this device, and whether the DRM plugin is able to handle the
+     * media container format specified by mimeType at the requested
+     * security level.
+     *
+     * @param uuid The UUID of the crypto scheme.
+     * @param mimeType The MIME type of the media container, e.g. "video/mp4"
+     *   or "video/webm"
+     * @param securityLevel the security level requested
+     */
+    public static final boolean isCryptoSchemeSupported(
+            @NonNull UUID uuid, @NonNull String mimeType, @SecurityLevel int securityLevel) {
+        return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType,
+                securityLevel);
     }
 
     private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
@@ -206,7 +225,7 @@
     }
 
     private static final native boolean isCryptoSchemeSupportedNative(
-            @NonNull byte[] uuid, @Nullable String mimeType);
+            @NonNull byte[] uuid, @Nullable String mimeType, @SecurityLevel int securityLevel);
 
     private EventHandler createHandler() {
         Looper looper;
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 42597aa..73d3d88 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -138,7 +138,7 @@
         mAudioAttributes = attributes;
         // The audio attributes have to be set before the media player is prepared.
         // Re-initialize it.
-        setUri(mUri);
+        setUri(mUri, mVolumeShaperConfig);
     }
 
     /**
@@ -415,6 +415,7 @@
             mLocalPlayer.reset();
             mLocalPlayer.release();
             mLocalPlayer = null;
+            mVolumeShaper = null;
             synchronized (sActiveRingtones) {
                 sActiveRingtones.remove(this);
             }
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index 1c017c5..0297406 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -16,6 +16,7 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
 import android.media.session.MediaSession;
 
 import java.lang.annotation.Retention;
@@ -147,6 +148,7 @@
      * Sets a callback to receive volume changes.
      * @hide
      */
+    @SystemApi
     public void setCallback(Callback callback) {
         mCallback = callback;
     }
@@ -155,6 +157,7 @@
      * Listens for changes to the volume.
      * @hide
      */
+    @SystemApi
     public static abstract class Callback {
         public abstract void onVolumeChanged(VolumeProvider volumeProvider);
     }
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java
index 28845e4..adc14a5 100644
--- a/media/java/android/media/session/ControllerCallbackLink.java
+++ b/media/java/android/media/session/ControllerCallbackLink.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession.QueueItem;
 import android.os.Binder;
@@ -127,7 +128,8 @@
     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void notifyQueueChanged(@Nullable List<QueueItem> queue) {
         try {
-            mIControllerCallback.notifyQueueChanged(queue);
+            mIControllerCallback.notifyQueueChanged(queue == null ? null :
+                    new MediaParceledListSlice(queue));
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
@@ -275,11 +277,11 @@
         }
 
         @Override
-        public void notifyQueueChanged(List<QueueItem> queue) {
+        public void notifyQueueChanged(MediaParceledListSlice queue) {
             ensureMediaControlPermission();
             final long token = Binder.clearCallingIdentity();
             try {
-                mCallbackStub.onQueueChanged(queue);
+                mCallbackStub.onQueueChanged(queue == null ? null : queue.getList());
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -313,12 +315,6 @@
         }
 
         private void ensureMediaControlPermission() {
-            // Allow API calls from the System UI
-            if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
-                    == PackageManager.PERMISSION_GRANTED) {
-                return;
-            }
-
             // Check if it's system server or has MEDIA_CONTENT_CONTROL.
             // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
             // check here.
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index 5c02e7c..56ae852 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -16,8 +16,8 @@
 package android.media.session;
 
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.session.MediaController;
-import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 
@@ -31,7 +31,7 @@
     // These callbacks are for the TransportController
     void notifyPlaybackStateChanged(in PlaybackState state);
     void notifyMetadataChanged(in MediaMetadata metadata);
-    void notifyQueueChanged(in List<MediaSession.QueueItem> queue);
+    void notifyQueueChanged(in MediaParceledListSlice queue);
     void notifyQueueTitleChanged(CharSequence title);
     void notifyExtrasChanged(in Bundle extras);
     void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info);
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 7166616..1a185e9 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -116,7 +116,7 @@
 
     private final MediaSessionEngine mImpl;
 
-    // Do not change the name of mCallbackWrapper. Support lib accesses this by using reflection.
+    // Do not change the name of mCallback. Support lib accesses this by using reflection.
     @UnsupportedAppUsage
     private Object mCallback;
 
@@ -160,6 +160,7 @@
      * @param callback The callback object
      */
     public void setCallback(@Nullable Callback callback) {
+        mCallback = callback == null ? null : new Object();
         mImpl.setCallback(callback);
     }
 
@@ -173,6 +174,7 @@
      * @param handler The handler that events should be posted on.
      */
     public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
+        mCallback = callback == null ? null : new Object();
         mImpl.setCallback(callback, handler);
     }
 
@@ -479,7 +481,7 @@
          * @hide
          */
         @SystemApi
-        ControllerLink getControllerLink() {
+        public ControllerLink getControllerLink() {
             return mControllerLink;
         }
 
diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/java/android/media/session/MediaSessionEngine.java
index c4634a9..f159a95 100644
--- a/media/java/android/media/session/MediaSessionEngine.java
+++ b/media/java/android/media/session/MediaSessionEngine.java
@@ -989,10 +989,8 @@
     public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
         private WeakReference<MediaSessionEngine> mSessionImpl;
 
-        private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
-                ControllerCallbackLink caller) {
-            return new RemoteUserInfo(packageName, pid, uid,
-                    caller != null ? caller.getBinder() : null);
+        private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid) {
+            return new RemoteUserInfo(packageName, pid, uid);
         }
 
         public CallbackStub() {
@@ -1003,7 +1001,7 @@
                 ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
-                sessionImpl.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
+                sessionImpl.dispatchCommand(createRemoteUserInfo(packageName, pid, uid),
                         command, args, cb);
             }
         }
@@ -1015,7 +1013,7 @@
             try {
                 if (sessionImpl != null) {
                     sessionImpl.dispatchMediaButton(
-                            createRemoteUserInfo(packageName, pid, uid, null), mediaButtonIntent);
+                            createRemoteUserInfo(packageName, pid, uid), mediaButtonIntent);
                 }
             } finally {
                 if (cb != null) {
@@ -1029,7 +1027,7 @@
                 ControllerCallbackLink caller, Intent mediaButtonIntent) {
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
-                sessionImpl.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
+                sessionImpl.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid),
                         mediaButtonIntent);
             }
         }
@@ -1039,7 +1037,7 @@
                 ControllerCallbackLink caller) {
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
-                sessionImpl.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
+                sessionImpl.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid));
             }
         }
 
@@ -1050,7 +1048,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchPrepareFromMediaId(
-                        createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras);
+                        createRemoteUserInfo(packageName, pid, uid), mediaId, extras);
             }
         }
 
@@ -1061,7 +1059,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchPrepareFromSearch(
-                        createRemoteUserInfo(packageName, pid, uid, caller), query, extras);
+                        createRemoteUserInfo(packageName, pid, uid), query, extras);
             }
         }
 
@@ -1071,7 +1069,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchPrepareFromUri(
-                        createRemoteUserInfo(packageName, pid, uid, caller), uri, extras);
+                        createRemoteUserInfo(packageName, pid, uid), uri, extras);
             }
         }
 
@@ -1080,7 +1078,7 @@
                 ControllerCallbackLink caller) {
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
-                sessionImpl.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
+                sessionImpl.dispatchPlay(createRemoteUserInfo(packageName, pid, uid));
             }
         }
 
@@ -1091,7 +1089,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchPlayFromMediaId(
-                        createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras);
+                        createRemoteUserInfo(packageName, pid, uid), mediaId, extras);
             }
         }
 
@@ -1102,7 +1100,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchPlayFromSearch(
-                        createRemoteUserInfo(packageName, pid, uid, caller), query, extras);
+                        createRemoteUserInfo(packageName, pid, uid), query, extras);
             }
         }
 
@@ -1112,7 +1110,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchPlayFromUri(
-                        createRemoteUserInfo(packageName, pid, uid, caller), uri, extras);
+                        createRemoteUserInfo(packageName, pid, uid), uri, extras);
             }
         }
 
@@ -1122,7 +1120,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchSkipToItem(
-                        createRemoteUserInfo(packageName, pid, uid, caller), id);
+                        createRemoteUserInfo(packageName, pid, uid), id);
             }
         }
 
@@ -1131,7 +1129,7 @@
                 ControllerCallbackLink caller) {
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
-                sessionImpl.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
+                sessionImpl.dispatchPause(createRemoteUserInfo(packageName, pid, uid));
             }
         }
 
@@ -1140,7 +1138,7 @@
                 ControllerCallbackLink caller) {
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
-                sessionImpl.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
+                sessionImpl.dispatchStop(createRemoteUserInfo(packageName, pid, uid));
             }
         }
 
@@ -1149,7 +1147,7 @@
                 ControllerCallbackLink caller) {
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
-                sessionImpl.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
+                sessionImpl.dispatchNext(createRemoteUserInfo(packageName, pid, uid));
             }
         }
 
@@ -1158,7 +1156,7 @@
                 ControllerCallbackLink caller) {
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
-                sessionImpl.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
+                sessionImpl.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid));
             }
         }
 
@@ -1168,7 +1166,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchFastForward(
-                        createRemoteUserInfo(packageName, pid, uid, caller));
+                        createRemoteUserInfo(packageName, pid, uid));
             }
         }
 
@@ -1177,7 +1175,7 @@
                 ControllerCallbackLink caller) {
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
-                sessionImpl.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
+                sessionImpl.dispatchRewind(createRemoteUserInfo(packageName, pid, uid));
             }
         }
 
@@ -1187,7 +1185,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchSeekTo(
-                        createRemoteUserInfo(packageName, pid, uid, caller), pos);
+                        createRemoteUserInfo(packageName, pid, uid), pos);
             }
         }
 
@@ -1197,7 +1195,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchRate(
-                        createRemoteUserInfo(packageName, pid, uid, caller), rating);
+                        createRemoteUserInfo(packageName, pid, uid), rating);
             }
         }
 
@@ -1207,7 +1205,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchCustomAction(
-                        createRemoteUserInfo(packageName, pid, uid, caller), action, args);
+                        createRemoteUserInfo(packageName, pid, uid), action, args);
             }
         }
 
@@ -1217,7 +1215,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchAdjustVolume(
-                        createRemoteUserInfo(packageName, pid, uid, caller), direction);
+                        createRemoteUserInfo(packageName, pid, uid), direction);
             }
         }
 
@@ -1227,7 +1225,7 @@
             MediaSessionEngine sessionImpl = mSessionImpl.get();
             if (sessionImpl != null) {
                 sessionImpl.dispatchSetVolumeTo(
-                        createRemoteUserInfo(packageName, pid, uid, caller), value);
+                        createRemoteUserInfo(packageName, pid, uid), value);
             }
         }
 
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 3f4fbb9..c64c452 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -28,7 +28,6 @@
 import android.media.IRemoteVolumeController;
 import android.media.MediaSession2;
 import android.media.Session2Token;
-import android.media.browse.MediaBrowser;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -37,6 +36,7 @@
 import android.os.UserHandle;
 import android.service.media.MediaBrowserService;
 import android.service.notification.NotificationListenerService;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -797,7 +797,6 @@
         private final String mPackageName;
         private final int mPid;
         private final int mUid;
-        private final IBinder mCallerBinder;
 
         /**
          * Create a new remote user information.
@@ -807,22 +806,9 @@
          * @param uid The uid of the remote user
          */
         public RemoteUserInfo(@NonNull String packageName, int pid, int uid) {
-            this(packageName, pid, uid, null);
-        }
-
-        /**
-         * Create a new remote user information.
-         *
-         * @param packageName The package name of the remote user
-         * @param pid The pid of the remote user
-         * @param uid The uid of the remote user
-         * @param callerBinder The binder of the remote user. Can be {@code null}.
-         */
-        public RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder) {
             mPackageName = packageName;
             mPid = pid;
             mUid = uid;
-            mCallerBinder = callerBinder;
         }
 
         /**
@@ -847,13 +833,8 @@
         }
 
         /**
-         * Returns equality of two RemoteUserInfo. Two RemoteUserInfos are the same only if they're
-         * sent to the same controller (either {@link MediaController} or
-         * {@link MediaBrowser}. If it's not nor one of them is triggered by the key presses, they
-         * would be considered as different one.
-         * <p>
-         * If you only want to compare the caller's package, compare them with the
-         * {@link #getPackageName()}, {@link #getPid()}, and/or {@link #getUid()} directly.
+         * Returns equality of two RemoteUserInfo. Two RemoteUserInfo objects are equal
+         * if and only if they have the same package name, same pid, and same uid.
          *
          * @param obj the reference object with which to compare.
          * @return {@code true} if equals, {@code false} otherwise
@@ -867,8 +848,9 @@
                 return true;
             }
             RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj;
-            return (mCallerBinder == null || otherUserInfo.mCallerBinder == null) ? false
-                    : mCallerBinder.equals(otherUserInfo.mCallerBinder);
+            return TextUtils.equals(mPackageName, otherUserInfo.mPackageName)
+                    && mPid == otherUserInfo.mPid
+                    && mUid == otherUserInfo.mUid;
         }
 
         @Override
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java
index 0dbf427..3bcb65c 100644
--- a/media/java/android/media/session/SessionCallbackLink.java
+++ b/media/java/android/media/session/SessionCallbackLink.java
@@ -944,12 +944,6 @@
         }
 
         private void ensureMediaControlPermission() {
-            // Allow API calls from the System UI
-            if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
-                    == PackageManager.PERMISSION_GRANTED) {
-                return;
-            }
-
             // Check if it's system server or has MEDIA_CONTENT_CONTROL.
             // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
             // check here.
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index d19d117..2fbc699 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -541,8 +541,7 @@
             throw new IllegalStateException("This should be called inside of onGetRoot or"
                     + " onLoadChildren or onLoadItem methods");
         }
-        return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid,
-                mCurConnection.callbacks.asBinder());
+        return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid);
     }
 
     /**
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 42c5b05..81fce8a 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -542,14 +542,15 @@
 
 
 // static
-bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) {
+bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType,
+                                   DrmPlugin::SecurityLevel securityLevel) {
     sp<IDrm> drm = MakeDrm();
 
     if (drm == NULL) {
         return false;
     }
 
-    return drm->isCryptoSchemeSupported(uuid, mimeType);
+    return drm->isCryptoSchemeSupported(uuid, mimeType, securityLevel);
 }
 
 status_t JDrm::initCheck() const {
@@ -930,8 +931,30 @@
     setDrm(env, thiz, drm);
 }
 
+DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) {
+    DrmPlugin::SecurityLevel level;
+
+    if (jlevel == gSecurityLevels.kSecurityLevelMax) {
+        level = DrmPlugin::kSecurityLevelMax;
+    }  else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) {
+        level = DrmPlugin::kSecurityLevelSwSecureCrypto;
+    } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) {
+        level = DrmPlugin::kSecurityLevelSwSecureDecode;
+    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) {
+        level = DrmPlugin::kSecurityLevelHwSecureCrypto;
+    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) {
+        level = DrmPlugin::kSecurityLevelHwSecureDecode;
+    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) {
+        level = DrmPlugin::kSecurityLevelHwSecureAll;
+    } else {
+        level = DrmPlugin::kSecurityLevelUnknown;
+    }
+    return level;
+}
+
 static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative(
-    JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType) {
+        JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType,
+        jint jSecurityLevel) {
 
     if (uuidObj == NULL) {
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
@@ -952,8 +975,9 @@
     if (jmimeType != NULL) {
         mimeType = JStringToString8(env, jmimeType);
     }
+    DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel);
 
-    return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType);
+    return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType, securityLevel);
 }
 
 static jbyteArray android_media_MediaDrm_openSession(
@@ -965,21 +989,8 @@
     }
 
     Vector<uint8_t> sessionId;
-    DrmPlugin::SecurityLevel level;
-
-    if (jlevel == gSecurityLevels.kSecurityLevelMax) {
-        level = DrmPlugin::kSecurityLevelMax;
-    }  else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureCrypto) {
-        level = DrmPlugin::kSecurityLevelSwSecureCrypto;
-    } else if (jlevel == gSecurityLevels.kSecurityLevelSwSecureDecode) {
-        level = DrmPlugin::kSecurityLevelSwSecureDecode;
-    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureCrypto) {
-        level = DrmPlugin::kSecurityLevelHwSecureCrypto;
-    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureDecode) {
-        level = DrmPlugin::kSecurityLevelHwSecureDecode;
-    } else if (jlevel == gSecurityLevels.kSecurityLevelHwSecureAll) {
-        level = DrmPlugin::kSecurityLevelHwSecureAll;
-    } else {
+    DrmPlugin::SecurityLevel level = jintToSecurityLevel(jlevel);
+    if (level == DrmPlugin::kSecurityLevelUnknown) {
         jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid security level");
         return NULL;
     }
@@ -1903,7 +1914,7 @@
     { "native_setup", "(Ljava/lang/Object;[BLjava/lang/String;)V",
       (void *)android_media_MediaDrm_native_setup },
 
-    { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;)Z",
+    { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;I)Z",
       (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative },
 
     { "openSession", "(I)[B",
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
index b9356f3..9338861 100644
--- a/media/jni/android_media_MediaDrm.h
+++ b/media/jni/android_media_MediaDrm.h
@@ -37,7 +37,9 @@
 };
 
 struct JDrm : public BnDrmClient {
-    static bool IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType);
+    static bool IsCryptoSchemeSupported(const uint8_t uuid[16],
+                                        const String8 &mimeType,
+                                        DrmPlugin::SecurityLevel level);
 
     JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16], const String8 &appPackageName);
 
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 2f7d599..a2da0a0 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -24,7 +24,7 @@
         ":services-networkstack-shared-srcs",
     ],
     static_libs: [
-        "dhcp-packet-lib",
+        "services-netlink-lib",
     ]
 }
 
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index 7f8bb93..5ab833b 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <!-- Launch captive portal app as specific user -->
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.NETWORK_STACK" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
     <application
         android:label="NetworkStack"
         android:defaultToDeviceProtectedStorage="true"
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
similarity index 98%
rename from services/net/java/android/net/apf/ApfFilter.java
rename to packages/NetworkStack/src/android/net/apf/ApfFilter.java
index 4943952..50c4dfc 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -16,10 +16,6 @@
 
 package android.net.apf;
 
-import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION;
 import static android.net.util.SocketUtils.makePacketSocketAddress;
 import static android.system.OsConstants.AF_PACKET;
 import static android.system.OsConstants.ARPHRD_ETHER;
@@ -35,6 +31,10 @@
 import static com.android.internal.util.BitUtils.getUint32;
 import static com.android.internal.util.BitUtils.getUint8;
 import static com.android.internal.util.BitUtils.uint32;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
 
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -46,7 +46,7 @@
 import android.net.NetworkUtils;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpClientCallbacks;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
 import android.net.metrics.IpConnectivityLog;
@@ -337,7 +337,7 @@
     private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
 
     private final ApfCapabilities mApfCapabilities;
-    private final IpClientCallbacks mIpClientCallback;
+    private final IpClientCallbacksWrapper mIpClientCallback;
     private final InterfaceParams mInterfaceParams;
     private final IpConnectivityLog mMetricsLog;
 
@@ -378,7 +378,7 @@
 
     @VisibleForTesting
     ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
-            IpClientCallbacks ipClientCallback, IpConnectivityLog log) {
+            IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) {
         mApfCapabilities = config.apfCapabilities;
         mIpClientCallback = ipClientCallback;
         mInterfaceParams = ifParams;
@@ -1420,7 +1420,7 @@
      * filtering using APF programs.
      */
     public static ApfFilter maybeCreate(Context context, ApfConfiguration config,
-            InterfaceParams ifParams, IpClientCallbacks ipClientCallback) {
+            InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) {
         if (context == null || config == null || ifParams == null) return null;
         ApfCapabilities apfCapabilities =  config.apfCapabilities;
         if (apfCapabilities == null) return null;
diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java
similarity index 100%
rename from services/net/java/android/net/apf/ApfGenerator.java
rename to packages/NetworkStack/src/android/net/apf/ApfGenerator.java
diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpAckPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
new file mode 100644
index 0000000..04ac9a3
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
@@ -0,0 +1,1052 @@
+/*
+ * Copyright (C) 2015 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 android.net.dhcp;
+
+import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS;
+import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
+import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
+import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_MTU;
+import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_ROUTER;
+import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK;
+import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO;
+import static android.net.dhcp.DhcpPacket.INADDR_ANY;
+import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
+import static android.net.util.SocketUtils.makePacketSocketAddress;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_RAW;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BROADCAST;
+import static android.system.OsConstants.SO_RCVBUF;
+import static android.system.OsConstants.SO_REUSEADDR;
+
+import android.content.Context;
+import android.net.DhcpResults;
+import android.net.NetworkUtils;
+import android.net.TrafficStats;
+import android.net.ip.IpClient;
+import android.net.metrics.DhcpClientEvent;
+import android.net.metrics.DhcpErrorEvent;
+import android.net.metrics.IpConnectivityLog;
+import android.net.util.InterfaceParams;
+import android.net.util.SocketUtils;
+import android.os.Message;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.HexDump;
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+
+import libcore.io.IoBridge;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * A DHCPv4 client.
+ *
+ * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
+ * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
+ *
+ * TODO:
+ *
+ * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
+ * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
+ *   do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
+ *   given SSID), it requests the last-leased IP address on the same interface, causing a delay if
+ *   the server NAKs or a timeout if it doesn't.
+ *
+ * Known differences from current behaviour:
+ *
+ * - Does not request the "static routes" option.
+ * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
+ * - Requests the "broadcast" option, but does nothing with it.
+ * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
+ *
+ * @hide
+ */
+public class DhcpClient extends StateMachine {
+
+    private static final String TAG = "DhcpClient";
+    private static final boolean DBG = true;
+    private static final boolean STATE_DBG = false;
+    private static final boolean MSG_DBG = false;
+    private static final boolean PACKET_DBG = false;
+
+    // Timers and timeouts.
+    private static final int SECONDS = 1000;
+    private static final int FIRST_TIMEOUT_MS   =   2 * SECONDS;
+    private static final int MAX_TIMEOUT_MS     = 128 * SECONDS;
+
+    // This is not strictly needed, since the client is asynchronous and implements exponential
+    // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
+    // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
+    // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
+    private static final int DHCP_TIMEOUT_MS    =  36 * SECONDS;
+
+    // DhcpClient uses IpClient's handler.
+    private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE;
+
+    /* Commands from controller to start/stop DHCP */
+    public static final int CMD_START_DHCP                  = PUBLIC_BASE + 1;
+    public static final int CMD_STOP_DHCP                   = PUBLIC_BASE + 2;
+
+    /* Notification from DHCP state machine prior to DHCP discovery/renewal */
+    public static final int CMD_PRE_DHCP_ACTION             = PUBLIC_BASE + 3;
+    /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
+     * success/failure */
+    public static final int CMD_POST_DHCP_ACTION            = PUBLIC_BASE + 4;
+    /* Notification from DHCP state machine before quitting */
+    public static final int CMD_ON_QUIT                     = PUBLIC_BASE + 5;
+
+    /* Command from controller to indicate DHCP discovery/renewal can continue
+     * after pre DHCP action is complete */
+    public static final int CMD_PRE_DHCP_ACTION_COMPLETE    = PUBLIC_BASE + 6;
+
+    /* Command and event notification to/from IpManager requesting the setting
+     * (or clearing) of an IPv4 LinkAddress.
+     */
+    public static final int CMD_CLEAR_LINKADDRESS           = PUBLIC_BASE + 7;
+    public static final int CMD_CONFIGURE_LINKADDRESS       = PUBLIC_BASE + 8;
+    public static final int EVENT_LINKADDRESS_CONFIGURED    = PUBLIC_BASE + 9;
+
+    /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
+    public static final int DHCP_SUCCESS = 1;
+    public static final int DHCP_FAILURE = 2;
+
+    // Internal messages.
+    private static final int PRIVATE_BASE         = IpClient.DHCPCLIENT_CMD_BASE + 100;
+    private static final int CMD_KICK             = PRIVATE_BASE + 1;
+    private static final int CMD_RECEIVED_PACKET  = PRIVATE_BASE + 2;
+    private static final int CMD_TIMEOUT          = PRIVATE_BASE + 3;
+    private static final int CMD_RENEW_DHCP       = PRIVATE_BASE + 4;
+    private static final int CMD_REBIND_DHCP      = PRIVATE_BASE + 5;
+    private static final int CMD_EXPIRE_DHCP      = PRIVATE_BASE + 6;
+
+    // For message logging.
+    private static final Class[] sMessageClasses = { DhcpClient.class };
+    private static final SparseArray<String> sMessageNames =
+            MessageUtils.findMessageNames(sMessageClasses);
+
+    // DHCP parameters that we request.
+    /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
+        DHCP_SUBNET_MASK,
+        DHCP_ROUTER,
+        DHCP_DNS_SERVER,
+        DHCP_DOMAIN_NAME,
+        DHCP_MTU,
+        DHCP_BROADCAST_ADDRESS,  // TODO: currently ignored.
+        DHCP_LEASE_TIME,
+        DHCP_RENEWAL_TIME,
+        DHCP_REBINDING_TIME,
+        DHCP_VENDOR_INFO,
+    };
+
+    // DHCP flag that means "yes, we support unicast."
+    private static final boolean DO_UNICAST   = false;
+
+    // System services / libraries we use.
+    private final Context mContext;
+    private final Random mRandom;
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+
+    // Sockets.
+    // - We use a packet socket to receive, because servers send us packets bound for IP addresses
+    //   which we have not yet configured, and the kernel protocol stack drops these.
+    // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
+    //   be off-link as well as on-link).
+    private FileDescriptor mPacketSock;
+    private FileDescriptor mUdpSock;
+    private ReceiveThread mReceiveThread;
+
+    // State variables.
+    private final StateMachine mController;
+    private final WakeupMessage mKickAlarm;
+    private final WakeupMessage mTimeoutAlarm;
+    private final WakeupMessage mRenewAlarm;
+    private final WakeupMessage mRebindAlarm;
+    private final WakeupMessage mExpiryAlarm;
+    private final String mIfaceName;
+
+    private boolean mRegisteredForPreDhcpNotification;
+    private InterfaceParams mIface;
+    // TODO: MacAddress-ify more of this class hierarchy.
+    private byte[] mHwAddr;
+    private SocketAddress mInterfaceBroadcastAddr;
+    private int mTransactionId;
+    private long mTransactionStartMillis;
+    private DhcpResults mDhcpLease;
+    private long mDhcpLeaseExpiry;
+    private DhcpResults mOffer;
+
+    // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState.
+    private long mLastInitEnterTime;
+    private long mLastBoundExitTime;
+
+    // States.
+    private State mStoppedState = new StoppedState();
+    private State mDhcpState = new DhcpState();
+    private State mDhcpInitState = new DhcpInitState();
+    private State mDhcpSelectingState = new DhcpSelectingState();
+    private State mDhcpRequestingState = new DhcpRequestingState();
+    private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
+    private State mConfiguringInterfaceState = new ConfiguringInterfaceState();
+    private State mDhcpBoundState = new DhcpBoundState();
+    private State mDhcpRenewingState = new DhcpRenewingState();
+    private State mDhcpRebindingState = new DhcpRebindingState();
+    private State mDhcpInitRebootState = new DhcpInitRebootState();
+    private State mDhcpRebootingState = new DhcpRebootingState();
+    private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
+    private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
+
+    private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
+        cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
+        return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
+    }
+
+    // TODO: Take an InterfaceParams instance instead of an interface name String.
+    private DhcpClient(Context context, StateMachine controller, String iface) {
+        super(TAG, controller.getHandler());
+
+        mContext = context;
+        mController = controller;
+        mIfaceName = iface;
+
+        addState(mStoppedState);
+        addState(mDhcpState);
+            addState(mDhcpInitState, mDhcpState);
+            addState(mWaitBeforeStartState, mDhcpState);
+            addState(mDhcpSelectingState, mDhcpState);
+            addState(mDhcpRequestingState, mDhcpState);
+            addState(mDhcpHaveLeaseState, mDhcpState);
+                addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
+                addState(mDhcpBoundState, mDhcpHaveLeaseState);
+                addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
+                addState(mDhcpRenewingState, mDhcpHaveLeaseState);
+                addState(mDhcpRebindingState, mDhcpHaveLeaseState);
+            addState(mDhcpInitRebootState, mDhcpState);
+            addState(mDhcpRebootingState, mDhcpState);
+
+        setInitialState(mStoppedState);
+
+        mRandom = new Random();
+
+        // Used to schedule packet retransmissions.
+        mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
+        // Used to time out PacketRetransmittingStates.
+        mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
+        // Used to schedule DHCP reacquisition.
+        mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
+        mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP);
+        mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP);
+    }
+
+    public void registerForPreDhcpNotification() {
+        mRegisteredForPreDhcpNotification = true;
+    }
+
+    public static DhcpClient makeDhcpClient(
+            Context context, StateMachine controller, InterfaceParams ifParams) {
+        DhcpClient client = new DhcpClient(context, controller, ifParams.name);
+        client.mIface = ifParams;
+        client.start();
+        return client;
+    }
+
+    private boolean initInterface() {
+        if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName);
+        if (mIface == null) {
+            Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName);
+            return false;
+        }
+
+        mHwAddr = mIface.macAddr.toByteArray();
+        mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST);
+        return true;
+    }
+
+    private void startNewTransaction() {
+        mTransactionId = mRandom.nextInt();
+        mTransactionStartMillis = SystemClock.elapsedRealtime();
+    }
+
+    private boolean initSockets() {
+        return initPacketSocket() && initUdpSocket();
+    }
+
+    private boolean initPacketSocket() {
+        try {
+            mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
+            SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index);
+            Os.bind(mPacketSock, addr);
+            NetworkUtils.attachDhcpFilter(mPacketSock);
+        } catch(SocketException|ErrnoException e) {
+            Log.e(TAG, "Error creating packet socket", e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean initUdpSocket() {
+        final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP);
+        try {
+            mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+            SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName);
+            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
+            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
+            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
+            Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
+        } catch(SocketException|ErrnoException e) {
+            Log.e(TAG, "Error creating UDP socket", e);
+            return false;
+        } finally {
+            TrafficStats.setThreadStatsTag(oldTag);
+        }
+        return true;
+    }
+
+    private boolean connectUdpSock(Inet4Address to) {
+        try {
+            Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
+            return true;
+        } catch (SocketException|ErrnoException e) {
+            Log.e(TAG, "Error connecting UDP socket", e);
+            return false;
+        }
+    }
+
+    private static void closeQuietly(FileDescriptor fd) {
+        try {
+            IoBridge.closeAndSignalBlockedThreads(fd);
+        } catch (IOException ignored) {}
+    }
+
+    private void closeSockets() {
+        closeQuietly(mUdpSock);
+        closeQuietly(mPacketSock);
+    }
+
+    class ReceiveThread extends Thread {
+
+        private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
+        private volatile boolean mStopped = false;
+
+        public void halt() {
+            mStopped = true;
+            closeSockets();  // Interrupts the read() call the thread is blocked in.
+        }
+
+        @Override
+        public void run() {
+            if (DBG) Log.d(TAG, "Receive thread started");
+            while (!mStopped) {
+                int length = 0;  // Or compiler can't tell it's initialized if a parse error occurs.
+                try {
+                    length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
+                    DhcpPacket packet = null;
+                    packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
+                    if (DBG) Log.d(TAG, "Received packet: " + packet);
+                    sendMessage(CMD_RECEIVED_PACKET, packet);
+                } catch (IOException|ErrnoException e) {
+                    if (!mStopped) {
+                        Log.e(TAG, "Read error", e);
+                        logError(DhcpErrorEvent.RECEIVE_ERROR);
+                    }
+                } catch (DhcpPacket.ParseException e) {
+                    Log.e(TAG, "Can't parse packet: " + e.getMessage());
+                    if (PACKET_DBG) {
+                        Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
+                    }
+                    if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
+                        int snetTagId = 0x534e4554;
+                        String bugId = "31850211";
+                        int uid = -1;
+                        String data = DhcpPacket.ParseException.class.getName();
+                        EventLog.writeEvent(snetTagId, bugId, uid, data);
+                    }
+                    logError(e.errorCode);
+                }
+            }
+            if (DBG) Log.d(TAG, "Receive thread stopped");
+        }
+    }
+
+    private short getSecs() {
+        return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
+    }
+
+    private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) {
+        try {
+            if (encap == DhcpPacket.ENCAP_L2) {
+                if (DBG) Log.d(TAG, "Broadcasting " + description);
+                Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
+            } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) {
+                if (DBG) Log.d(TAG, "Broadcasting " + description);
+                // We only send L3-encapped broadcasts in DhcpRebindingState,
+                // where we have an IP address and an unconnected UDP socket.
+                //
+                // N.B.: We only need this codepath because DhcpRequestPacket
+                // hardcodes the source IP address to 0.0.0.0. We could reuse
+                // the packet socket if this ever changes.
+                Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
+            } else {
+                // It's safe to call getpeername here, because we only send unicast packets if we
+                // have an IP address, and we connect the UDP socket in DhcpBoundState#enter.
+                if (DBG) Log.d(TAG, String.format("Unicasting %s to %s",
+                        description, Os.getpeername(mUdpSock)));
+                Os.write(mUdpSock, buf);
+            }
+        } catch(ErrnoException|IOException e) {
+            Log.e(TAG, "Can't send packet: ", e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean sendDiscoverPacket() {
+        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
+                DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
+                DO_UNICAST, REQUESTED_PARAMS);
+        return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
+    }
+
+    private boolean sendRequestPacket(
+            Inet4Address clientAddress, Inet4Address requestedAddress,
+            Inet4Address serverAddress, Inet4Address to) {
+        // TODO: should we use the transaction ID from the server?
+        final int encap = INADDR_ANY.equals(clientAddress)
+                ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
+
+        ByteBuffer packet = DhcpPacket.buildRequestPacket(
+                encap, mTransactionId, getSecs(), clientAddress,
+                DO_UNICAST, mHwAddr, requestedAddress,
+                serverAddress, REQUESTED_PARAMS, null);
+        String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
+        String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
+                             " request=" + requestedAddress.getHostAddress() +
+                             " serverid=" + serverStr;
+        return transmitPacket(packet, description, encap, to);
+    }
+
+    private void scheduleLeaseTimers() {
+        if (mDhcpLeaseExpiry == 0) {
+            Log.d(TAG, "Infinite lease, no timer scheduling needed");
+            return;
+        }
+
+        final long now = SystemClock.elapsedRealtime();
+
+        // TODO: consider getting the renew and rebind timers from T1 and T2.
+        // See also:
+        //     https://tools.ietf.org/html/rfc2131#section-4.4.5
+        //     https://tools.ietf.org/html/rfc1533#section-9.9
+        //     https://tools.ietf.org/html/rfc1533#section-9.10
+        final long remainingDelay = mDhcpLeaseExpiry - now;
+        final long renewDelay = remainingDelay / 2;
+        final long rebindDelay = remainingDelay * 7 / 8;
+        mRenewAlarm.schedule(now + renewDelay);
+        mRebindAlarm.schedule(now + rebindDelay);
+        mExpiryAlarm.schedule(now + remainingDelay);
+        Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s");
+        Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s");
+        Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s");
+    }
+
+    private void notifySuccess() {
+        mController.sendMessage(
+                CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
+    }
+
+    private void notifyFailure() {
+        mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null);
+    }
+
+    private void acceptDhcpResults(DhcpResults results, String msg) {
+        mDhcpLease = results;
+        mOffer = null;
+        Log.d(TAG, msg + " lease: " + mDhcpLease);
+        notifySuccess();
+    }
+
+    private void clearDhcpState() {
+        mDhcpLease = null;
+        mDhcpLeaseExpiry = 0;
+        mOffer = null;
+    }
+
+    /**
+     * Quit the DhcpStateMachine.
+     *
+     * @hide
+     */
+    public void doQuit() {
+        Log.d(TAG, "doQuit");
+        quit();
+    }
+
+    @Override
+    protected void onQuitting() {
+        Log.d(TAG, "onQuitting");
+        mController.sendMessage(CMD_ON_QUIT);
+    }
+
+    abstract class LoggingState extends State {
+        private long mEnterTimeMs;
+
+        @Override
+        public void enter() {
+            if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
+            mEnterTimeMs = SystemClock.elapsedRealtime();
+        }
+
+        @Override
+        public void exit() {
+            long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs;
+            logState(getName(), (int) durationMs);
+        }
+
+        private String messageName(int what) {
+            return sMessageNames.get(what, Integer.toString(what));
+        }
+
+        private String messageToString(Message message) {
+            long now = SystemClock.uptimeMillis();
+            return new StringBuilder(" ")
+                    .append(message.getWhen() - now)
+                    .append(messageName(message.what))
+                    .append(" ").append(message.arg1)
+                    .append(" ").append(message.arg2)
+                    .append(" ").append(message.obj)
+                    .toString();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (MSG_DBG) {
+                Log.d(TAG, getName() + messageToString(message));
+            }
+            return NOT_HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            // All DhcpClient's states are inner classes with a well defined name.
+            // Use getSimpleName() and avoid super's getName() creating new String instances.
+            return getClass().getSimpleName();
+        }
+    }
+
+    // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
+    // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
+    abstract class WaitBeforeOtherState extends LoggingState {
+        protected State mOtherState;
+
+        @Override
+        public void enter() {
+            super.enter();
+            mController.sendMessage(CMD_PRE_DHCP_ACTION);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            super.processMessage(message);
+            switch (message.what) {
+                case CMD_PRE_DHCP_ACTION_COMPLETE:
+                    transitionTo(mOtherState);
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    class StoppedState extends State {
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case CMD_START_DHCP:
+                    if (mRegisteredForPreDhcpNotification) {
+                        transitionTo(mWaitBeforeStartState);
+                    } else {
+                        transitionTo(mDhcpInitState);
+                    }
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    class WaitBeforeStartState extends WaitBeforeOtherState {
+        public WaitBeforeStartState(State otherState) {
+            super();
+            mOtherState = otherState;
+        }
+    }
+
+    class WaitBeforeRenewalState extends WaitBeforeOtherState {
+        public WaitBeforeRenewalState(State otherState) {
+            super();
+            mOtherState = otherState;
+        }
+    }
+
+    class DhcpState extends State {
+        @Override
+        public void enter() {
+            clearDhcpState();
+            if (initInterface() && initSockets()) {
+                mReceiveThread = new ReceiveThread();
+                mReceiveThread.start();
+            } else {
+                notifyFailure();
+                transitionTo(mStoppedState);
+            }
+        }
+
+        @Override
+        public void exit() {
+            if (mReceiveThread != null) {
+                mReceiveThread.halt();  // Also closes sockets.
+                mReceiveThread = null;
+            }
+            clearDhcpState();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            super.processMessage(message);
+            switch (message.what) {
+                case CMD_STOP_DHCP:
+                    transitionTo(mStoppedState);
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    public boolean isValidPacket(DhcpPacket packet) {
+        // TODO: check checksum.
+        int xid = packet.getTransactionId();
+        if (xid != mTransactionId) {
+            Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
+            return false;
+        }
+        if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
+            Log.d(TAG, "MAC addr mismatch: got " +
+                    HexDump.toHexString(packet.getClientMac()) + ", expected " +
+                    HexDump.toHexString(packet.getClientMac()));
+            return false;
+        }
+        return true;
+    }
+
+    public void setDhcpLeaseExpiry(DhcpPacket packet) {
+        long leaseTimeMillis = packet.getLeaseTimeMillis();
+        mDhcpLeaseExpiry =
+                (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
+    }
+
+    /**
+     * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
+     * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
+     * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
+     * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
+     * state.
+     *
+     * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
+     * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
+     * sent by the receive thread. They may also set mTimeout and implement timeout.
+     */
+    abstract class PacketRetransmittingState extends LoggingState {
+
+        private int mTimer;
+        protected int mTimeout = 0;
+
+        @Override
+        public void enter() {
+            super.enter();
+            initTimer();
+            maybeInitTimeout();
+            sendMessage(CMD_KICK);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            super.processMessage(message);
+            switch (message.what) {
+                case CMD_KICK:
+                    sendPacket();
+                    scheduleKick();
+                    return HANDLED;
+                case CMD_RECEIVED_PACKET:
+                    receivePacket((DhcpPacket) message.obj);
+                    return HANDLED;
+                case CMD_TIMEOUT:
+                    timeout();
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+
+        @Override
+        public void exit() {
+            super.exit();
+            mKickAlarm.cancel();
+            mTimeoutAlarm.cancel();
+        }
+
+        abstract protected boolean sendPacket();
+        abstract protected void receivePacket(DhcpPacket packet);
+        protected void timeout() {}
+
+        protected void initTimer() {
+            mTimer = FIRST_TIMEOUT_MS;
+        }
+
+        protected int jitterTimer(int baseTimer) {
+            int maxJitter = baseTimer / 10;
+            int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
+            return baseTimer + jitter;
+        }
+
+        protected void scheduleKick() {
+            long now = SystemClock.elapsedRealtime();
+            long timeout = jitterTimer(mTimer);
+            long alarmTime = now + timeout;
+            mKickAlarm.schedule(alarmTime);
+            mTimer *= 2;
+            if (mTimer > MAX_TIMEOUT_MS) {
+                mTimer = MAX_TIMEOUT_MS;
+            }
+        }
+
+        protected void maybeInitTimeout() {
+            if (mTimeout > 0) {
+                long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
+                mTimeoutAlarm.schedule(alarmTime);
+            }
+        }
+    }
+
+    class DhcpInitState extends PacketRetransmittingState {
+        public DhcpInitState() {
+            super();
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            startNewTransaction();
+            mLastInitEnterTime = SystemClock.elapsedRealtime();
+        }
+
+        protected boolean sendPacket() {
+            return sendDiscoverPacket();
+        }
+
+        protected void receivePacket(DhcpPacket packet) {
+            if (!isValidPacket(packet)) return;
+            if (!(packet instanceof DhcpOfferPacket)) return;
+            mOffer = packet.toDhcpResults();
+            if (mOffer != null) {
+                Log.d(TAG, "Got pending lease: " + mOffer);
+                transitionTo(mDhcpRequestingState);
+            }
+        }
+    }
+
+    // Not implemented. We request the first offer we receive.
+    class DhcpSelectingState extends LoggingState {
+    }
+
+    class DhcpRequestingState extends PacketRetransmittingState {
+        public DhcpRequestingState() {
+            mTimeout = DHCP_TIMEOUT_MS / 2;
+        }
+
+        protected boolean sendPacket() {
+            return sendRequestPacket(
+                    INADDR_ANY,                                    // ciaddr
+                    (Inet4Address) mOffer.ipAddress.getAddress(),  // DHCP_REQUESTED_IP
+                    (Inet4Address) mOffer.serverAddress,           // DHCP_SERVER_IDENTIFIER
+                    INADDR_BROADCAST);                             // packet destination address
+        }
+
+        protected void receivePacket(DhcpPacket packet) {
+            if (!isValidPacket(packet)) return;
+            if ((packet instanceof DhcpAckPacket)) {
+                DhcpResults results = packet.toDhcpResults();
+                if (results != null) {
+                    setDhcpLeaseExpiry(packet);
+                    acceptDhcpResults(results, "Confirmed");
+                    transitionTo(mConfiguringInterfaceState);
+                }
+            } else if (packet instanceof DhcpNakPacket) {
+                // TODO: Wait a while before returning into INIT state.
+                Log.d(TAG, "Received NAK, returning to INIT");
+                mOffer = null;
+                transitionTo(mDhcpInitState);
+            }
+        }
+
+        @Override
+        protected void timeout() {
+            // After sending REQUESTs unsuccessfully for a while, go back to init.
+            transitionTo(mDhcpInitState);
+        }
+    }
+
+    class DhcpHaveLeaseState extends State {
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case CMD_EXPIRE_DHCP:
+                    Log.d(TAG, "Lease expired!");
+                    notifyFailure();
+                    transitionTo(mDhcpInitState);
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+
+        @Override
+        public void exit() {
+            // Clear any extant alarms.
+            mRenewAlarm.cancel();
+            mRebindAlarm.cancel();
+            mExpiryAlarm.cancel();
+            clearDhcpState();
+            // Tell IpManager to clear the IPv4 address. There is no need to
+            // wait for confirmation since any subsequent packets are sent from
+            // INADDR_ANY anyway (DISCOVER, REQUEST).
+            mController.sendMessage(CMD_CLEAR_LINKADDRESS);
+        }
+    }
+
+    class ConfiguringInterfaceState extends LoggingState {
+        @Override
+        public void enter() {
+            super.enter();
+            mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            super.processMessage(message);
+            switch (message.what) {
+                case EVENT_LINKADDRESS_CONFIGURED:
+                    transitionTo(mDhcpBoundState);
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    class DhcpBoundState extends LoggingState {
+        @Override
+        public void enter() {
+            super.enter();
+            if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) {
+                // There's likely no point in going into DhcpInitState here, we'll probably
+                // just repeat the transaction, get the same IP address as before, and fail.
+                //
+                // NOTE: It is observed that connectUdpSock() basically never fails, due to
+                // SO_BINDTODEVICE. Examining the local socket address shows it will happily
+                // return an IPv4 address from another interface, or even return "0.0.0.0".
+                //
+                // TODO: Consider deleting this check, following testing on several kernels.
+                notifyFailure();
+                transitionTo(mStoppedState);
+            }
+
+            scheduleLeaseTimers();
+            logTimeToBoundState();
+        }
+
+        @Override
+        public void exit() {
+            super.exit();
+            mLastBoundExitTime = SystemClock.elapsedRealtime();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            super.processMessage(message);
+            switch (message.what) {
+                case CMD_RENEW_DHCP:
+                    if (mRegisteredForPreDhcpNotification) {
+                        transitionTo(mWaitBeforeRenewalState);
+                    } else {
+                        transitionTo(mDhcpRenewingState);
+                    }
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+
+        private void logTimeToBoundState() {
+            long now = SystemClock.elapsedRealtime();
+            if (mLastBoundExitTime > mLastInitEnterTime) {
+                logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime));
+            } else {
+                logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime));
+            }
+        }
+    }
+
+    abstract class DhcpReacquiringState extends PacketRetransmittingState {
+        protected String mLeaseMsg;
+
+        @Override
+        public void enter() {
+            super.enter();
+            startNewTransaction();
+        }
+
+        abstract protected Inet4Address packetDestination();
+
+        protected boolean sendPacket() {
+            return sendRequestPacket(
+                    (Inet4Address) mDhcpLease.ipAddress.getAddress(),  // ciaddr
+                    INADDR_ANY,                                        // DHCP_REQUESTED_IP
+                    null,                                              // DHCP_SERVER_IDENTIFIER
+                    packetDestination());                              // packet destination address
+        }
+
+        protected void receivePacket(DhcpPacket packet) {
+            if (!isValidPacket(packet)) return;
+            if ((packet instanceof DhcpAckPacket)) {
+                final DhcpResults results = packet.toDhcpResults();
+                if (results != null) {
+                    if (!mDhcpLease.ipAddress.equals(results.ipAddress)) {
+                        Log.d(TAG, "Renewed lease not for our current IP address!");
+                        notifyFailure();
+                        transitionTo(mDhcpInitState);
+                    }
+                    setDhcpLeaseExpiry(packet);
+                    // Updating our notion of DhcpResults here only causes the
+                    // DNS servers and routes to be updated in LinkProperties
+                    // in IpManager and by any overridden relevant handlers of
+                    // the registered IpManager.Callback.  IP address changes
+                    // are not supported here.
+                    acceptDhcpResults(results, mLeaseMsg);
+                    transitionTo(mDhcpBoundState);
+                }
+            } else if (packet instanceof DhcpNakPacket) {
+                Log.d(TAG, "Received NAK, returning to INIT");
+                notifyFailure();
+                transitionTo(mDhcpInitState);
+            }
+        }
+    }
+
+    class DhcpRenewingState extends DhcpReacquiringState {
+        public DhcpRenewingState() {
+            mLeaseMsg = "Renewed";
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (super.processMessage(message) == HANDLED) {
+                return HANDLED;
+            }
+
+            switch (message.what) {
+                case CMD_REBIND_DHCP:
+                    transitionTo(mDhcpRebindingState);
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+
+        @Override
+        protected Inet4Address packetDestination() {
+            // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
+            // http://b/25343517 . Try to make things work anyway by using broadcast renews.
+            return (mDhcpLease.serverAddress != null) ?
+                    mDhcpLease.serverAddress : INADDR_BROADCAST;
+        }
+    }
+
+    class DhcpRebindingState extends DhcpReacquiringState {
+        public DhcpRebindingState() {
+            mLeaseMsg = "Rebound";
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+
+            // We need to broadcast and possibly reconnect the socket to a
+            // completely different server.
+            closeQuietly(mUdpSock);
+            if (!initUdpSocket()) {
+                Log.e(TAG, "Failed to recreate UDP socket");
+                transitionTo(mDhcpInitState);
+            }
+        }
+
+        @Override
+        protected Inet4Address packetDestination() {
+            return INADDR_BROADCAST;
+        }
+    }
+
+    class DhcpInitRebootState extends LoggingState {
+    }
+
+    class DhcpRebootingState extends LoggingState {
+    }
+
+    private void logError(int errorCode) {
+        mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
+    }
+
+    private void logState(String name, int durationMs) {
+        final DhcpClientEvent event = new DhcpClientEvent.Builder()
+                .setMsg(name)
+                .setDurationMs(durationMs)
+                .build();
+        mMetricsLog.log(mIfaceName, event);
+    }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpDeclinePacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpDiscoverPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpInformPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpNakPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpOfferPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpReleasePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpReleasePacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java
diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpRequestPacket.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java
diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
similarity index 100%
rename from services/net/java/android/net/ip/ConnectivityPacketTracker.java
rename to packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
new file mode 100644
index 0000000..ad7f85d
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -0,0 +1,1691 @@
+/*
+ * Copyright (C) 2017 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 android.net.ip;
+
+import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
+
+import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.DhcpResults;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ProvisioningConfigurationParcelable;
+import android.net.ProxyInfo;
+import android.net.ProxyInfoParcelable;
+import android.net.RouteInfo;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.dhcp.DhcpClient;
+import android.net.ip.IIpClientCallbacks;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.shared.InitialConfiguration;
+import android.net.shared.NetdService;
+import android.net.shared.ProvisioningConfiguration;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.os.ConditionVariable;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IState;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+import com.android.server.net.NetlinkTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+
+/**
+ * IpClient
+ *
+ * This class provides the interface to IP-layer provisioning and maintenance
+ * functionality that can be used by transport layers like Wi-Fi, Ethernet,
+ * et cetera.
+ *
+ * [ Lifetime ]
+ * IpClient is designed to be instantiated as soon as the interface name is
+ * known and can be as long-lived as the class containing it (i.e. declaring
+ * it "private final" is okay).
+ *
+ * @hide
+ */
+public class IpClient extends StateMachine {
+    private static final boolean DBG = false;
+
+    // For message logging.
+    private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
+    private static final SparseArray<String> sWhatToString =
+            MessageUtils.findMessageNames(sMessageClasses);
+    // Two static concurrent hashmaps of interface name to logging classes.
+    // One holds StateMachine logs and the other connectivity packet logs.
+    private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
+
+    /**
+     * Dump all state machine and connectivity packet logs to the specified writer.
+     * @param skippedIfaces Interfaces for which logs should not be dumped.
+     */
+    public static void dumpAllLogs(PrintWriter writer, Set<String> skippedIfaces) {
+        for (String ifname : sSmLogs.keySet()) {
+            if (skippedIfaces.contains(ifname)) continue;
+
+            writer.println(String.format("--- BEGIN %s ---", ifname));
+
+            final SharedLog smLog = sSmLogs.get(ifname);
+            if (smLog != null) {
+                writer.println("State machine log:");
+                smLog.dump(null, writer, null);
+            }
+
+            writer.println("");
+
+            final LocalLog pktLog = sPktLogs.get(ifname);
+            if (pktLog != null) {
+                writer.println("Connectivity packet log:");
+                pktLog.readOnlyLocalLog().dump(null, writer, null);
+            }
+
+            writer.println(String.format("--- END %s ---", ifname));
+        }
+    }
+
+    // Use a wrapper class to log in order to ensure complete and detailed
+    // logging. This method is lighter weight than annotations/reflection
+    // and has the following benefits:
+    //
+    //     - No invoked method can be forgotten.
+    //       Any new method added to IpClient.Callback must be overridden
+    //       here or it will never be called.
+    //
+    //     - No invoking call site can be forgotten.
+    //       Centralized logging in this way means call sites don't need to
+    //       remember to log, and therefore no call site can be forgotten.
+    //
+    //     - No variation in log format among call sites.
+    //       Encourages logging of any available arguments, and all call sites
+    //       are necessarily logged identically.
+    //
+    // NOTE: Log first because passed objects may or may not be thread-safe and
+    // once passed on to the callback they may be modified by another thread.
+    //
+    // TODO: Find an lighter weight approach.
+    public static class IpClientCallbacksWrapper {
+        private static final String PREFIX = "INVOKE ";
+        private final IIpClientCallbacks mCallback;
+        private final SharedLog mLog;
+
+        @VisibleForTesting
+        protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log) {
+            mCallback = callback;
+            mLog = log;
+        }
+
+        private void log(String msg) {
+            mLog.log(PREFIX + msg);
+        }
+
+        private void log(String msg, Throwable e) {
+            mLog.e(PREFIX + msg, e);
+        }
+
+        public void onPreDhcpAction() {
+            log("onPreDhcpAction()");
+            try {
+                mCallback.onPreDhcpAction();
+            } catch (RemoteException e) {
+                log("Failed to call onPreDhcpAction", e);
+            }
+        }
+
+        public void onPostDhcpAction() {
+            log("onPostDhcpAction()");
+            try {
+                mCallback.onPostDhcpAction();
+            } catch (RemoteException e) {
+                log("Failed to call onPostDhcpAction", e);
+            }
+        }
+
+        public void onNewDhcpResults(DhcpResults dhcpResults) {
+            log("onNewDhcpResults({" + dhcpResults + "})");
+            try {
+                mCallback.onNewDhcpResults(toStableParcelable(dhcpResults));
+            } catch (RemoteException e) {
+                log("Failed to call onNewDhcpResults", e);
+            }
+        }
+
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            log("onProvisioningSuccess({" + newLp + "})");
+            try {
+                mCallback.onProvisioningSuccess(toStableParcelable(newLp));
+            } catch (RemoteException e) {
+                log("Failed to call onProvisioningSuccess", e);
+            }
+        }
+
+        public void onProvisioningFailure(LinkProperties newLp) {
+            log("onProvisioningFailure({" + newLp + "})");
+            try {
+                mCallback.onProvisioningFailure(toStableParcelable(newLp));
+            } catch (RemoteException e) {
+                log("Failed to call onProvisioningFailure", e);
+            }
+        }
+
+        public void onLinkPropertiesChange(LinkProperties newLp) {
+            log("onLinkPropertiesChange({" + newLp + "})");
+            try {
+                mCallback.onLinkPropertiesChange(toStableParcelable(newLp));
+            } catch (RemoteException e) {
+                log("Failed to call onLinkPropertiesChange", e);
+            }
+        }
+
+        public void onReachabilityLost(String logMsg) {
+            log("onReachabilityLost(" + logMsg + ")");
+            try {
+                mCallback.onReachabilityLost(logMsg);
+            } catch (RemoteException e) {
+                log("Failed to call onReachabilityLost", e);
+            }
+        }
+
+        public void onQuit() {
+            log("onQuit()");
+            try {
+                mCallback.onQuit();
+            } catch (RemoteException e) {
+                log("Failed to call onQuit", e);
+            }
+        }
+
+        public void installPacketFilter(byte[] filter) {
+            log("installPacketFilter(byte[" + filter.length + "])");
+            try {
+                mCallback.installPacketFilter(filter);
+            } catch (RemoteException e) {
+                log("Failed to call installPacketFilter", e);
+            }
+        }
+
+        public void startReadPacketFilter() {
+            log("startReadPacketFilter()");
+            try {
+                mCallback.startReadPacketFilter();
+            } catch (RemoteException e) {
+                log("Failed to call startReadPacketFilter", e);
+            }
+        }
+
+        public void setFallbackMulticastFilter(boolean enabled) {
+            log("setFallbackMulticastFilter(" + enabled + ")");
+            try {
+                mCallback.setFallbackMulticastFilter(enabled);
+            } catch (RemoteException e) {
+                log("Failed to call setFallbackMulticastFilter", e);
+            }
+        }
+
+        public void setNeighborDiscoveryOffload(boolean enable) {
+            log("setNeighborDiscoveryOffload(" + enable + ")");
+            try {
+                mCallback.setNeighborDiscoveryOffload(enable);
+            } catch (RemoteException e) {
+                log("Failed to call setNeighborDiscoveryOffload", e);
+            }
+        }
+    }
+
+    public static final String DUMP_ARG_CONFIRM = "confirm";
+
+    private static final int CMD_TERMINATE_AFTER_STOP             = 1;
+    private static final int CMD_STOP                             = 2;
+    private static final int CMD_START                            = 3;
+    private static final int CMD_CONFIRM                          = 4;
+    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE       = 5;
+    // Triggered by NetlinkTracker to communicate netlink events.
+    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
+    private static final int CMD_UPDATE_TCP_BUFFER_SIZES          = 7;
+    private static final int CMD_UPDATE_HTTP_PROXY                = 8;
+    private static final int CMD_SET_MULTICAST_FILTER             = 9;
+    private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
+    private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
+    private static final int EVENT_READ_PACKET_FILTER_COMPLETE    = 12;
+
+    // Internal commands to use instead of trying to call transitionTo() inside
+    // a given State's enter() method. Calling transitionTo() from enter/exit
+    // encounters a Log.wtf() that can cause trouble on eng builds.
+    private static final int CMD_JUMP_STARTED_TO_RUNNING          = 100;
+    private static final int CMD_JUMP_RUNNING_TO_STOPPING         = 101;
+    private static final int CMD_JUMP_STOPPING_TO_STOPPED         = 102;
+
+    // IpClient shares a handler with DhcpClient: commands must not overlap
+    public static final int DHCPCLIENT_CMD_BASE = 1000;
+
+    private static final int MAX_LOG_RECORDS = 500;
+    private static final int MAX_PACKET_RECORDS = 100;
+
+    private static final boolean NO_CALLBACKS = false;
+    private static final boolean SEND_CALLBACKS = true;
+
+    // This must match the interface prefix in clatd.c.
+    // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
+    private static final String CLAT_PREFIX = "v4-";
+
+    private static final int IMMEDIATE_FAILURE_DURATION = 0;
+
+    private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1;
+    private static final int PROV_CHANGE_LOST_PROVISIONING = 2;
+    private static final int PROV_CHANGE_GAINED_PROVISIONING = 3;
+    private static final int PROV_CHANGE_STILL_PROVISIONED = 4;
+
+    private final State mStoppedState = new StoppedState();
+    private final State mStoppingState = new StoppingState();
+    private final State mStartedState = new StartedState();
+    private final State mRunningState = new RunningState();
+
+    private final String mTag;
+    private final Context mContext;
+    private final String mInterfaceName;
+    private final String mClatInterfaceName;
+    @VisibleForTesting
+    protected final IpClientCallbacksWrapper mCallback;
+    private final Dependencies mDependencies;
+    private final CountDownLatch mShutdownLatch;
+    private final ConnectivityManager mCm;
+    private final INetworkManagementService mNwService;
+    private final NetlinkTracker mNetlinkTracker;
+    private final WakeupMessage mProvisioningTimeoutAlarm;
+    private final WakeupMessage mDhcpActionTimeoutAlarm;
+    private final SharedLog mLog;
+    private final LocalLog mConnectivityPacketLog;
+    private final MessageHandlingLogger mMsgStateLogger;
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+    private final InterfaceController mInterfaceCtrl;
+
+    private InterfaceParams mInterfaceParams;
+
+    /**
+     * Non-final member variables accessed only from within our StateMachine.
+     */
+    private LinkProperties mLinkProperties;
+    private android.net.shared.ProvisioningConfiguration mConfiguration;
+    private IpReachabilityMonitor mIpReachabilityMonitor;
+    private DhcpClient mDhcpClient;
+    private DhcpResults mDhcpResults;
+    private String mTcpBufferSizes;
+    private ProxyInfo mHttpProxy;
+    private ApfFilter mApfFilter;
+    private boolean mMulticastFiltering;
+    private long mStartTimeMillis;
+
+    /**
+     * Reading the snapshot is an asynchronous operation initiated by invoking
+     * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
+     * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable
+     * signals when a new snapshot is ready.
+     */
+    private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable();
+
+    public static class Dependencies {
+        public INetworkManagementService getNMS() {
+            return INetworkManagementService.Stub.asInterface(
+                    ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+        }
+
+        public INetd getNetd() {
+            return NetdService.getInstance();
+        }
+
+        /**
+         * Get interface parameters for the specified interface.
+         */
+        public InterfaceParams getInterfaceParams(String ifname) {
+            return InterfaceParams.getByName(ifname);
+        }
+    }
+
+    public IpClient(Context context, String ifName, IIpClientCallbacks callback) {
+        this(context, ifName, callback, new Dependencies());
+    }
+
+    /**
+     * An expanded constructor, useful for dependency injection.
+     * TODO: migrate all test users to mock IpClient directly and remove this ctor.
+     */
+    public IpClient(Context context, String ifName, IIpClientCallbacks callback,
+            INetworkManagementService nwService) {
+        this(context, ifName, callback, new Dependencies() {
+            @Override
+            public INetworkManagementService getNMS() {
+                return nwService;
+            }
+        });
+    }
+
+    @VisibleForTesting
+    IpClient(Context context, String ifName, IIpClientCallbacks callback, Dependencies deps) {
+        super(IpClient.class.getSimpleName() + "." + ifName);
+        Preconditions.checkNotNull(ifName);
+        Preconditions.checkNotNull(callback);
+
+        mTag = getName();
+
+        mContext = context;
+        mInterfaceName = ifName;
+        mClatInterfaceName = CLAT_PREFIX + ifName;
+        mDependencies = deps;
+        mShutdownLatch = new CountDownLatch(1);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
+        mNwService = deps.getNMS();
+
+        sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
+        mLog = sSmLogs.get(mInterfaceName);
+        sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
+        mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
+        mMsgStateLogger = new MessageHandlingLogger();
+        mCallback = new IpClientCallbacksWrapper(callback, mLog);
+
+        // TODO: Consider creating, constructing, and passing in some kind of
+        // InterfaceController.Dependencies class.
+        mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog);
+
+        mNetlinkTracker = new NetlinkTracker(
+                mInterfaceName,
+                new NetlinkTracker.Callback() {
+                    @Override
+                    public void update() {
+                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+                    }
+                }) {
+            @Override
+            public void interfaceAdded(String iface) {
+                super.interfaceAdded(iface);
+                if (mClatInterfaceName.equals(iface)) {
+                    mCallback.setNeighborDiscoveryOffload(false);
+                } else if (!mInterfaceName.equals(iface)) {
+                    return;
+                }
+
+                final String msg = "interfaceAdded(" + iface + ")";
+                logMsg(msg);
+            }
+
+            @Override
+            public void interfaceRemoved(String iface) {
+                super.interfaceRemoved(iface);
+                // TODO: Also observe mInterfaceName going down and take some
+                // kind of appropriate action.
+                if (mClatInterfaceName.equals(iface)) {
+                    // TODO: consider sending a message to the IpClient main
+                    // StateMachine thread, in case "NDO enabled" state becomes
+                    // tied to more things that 464xlat operation.
+                    mCallback.setNeighborDiscoveryOffload(true);
+                } else if (!mInterfaceName.equals(iface)) {
+                    return;
+                }
+
+                final String msg = "interfaceRemoved(" + iface + ")";
+                logMsg(msg);
+            }
+
+            private void logMsg(String msg) {
+                Log.d(mTag, msg);
+                getHandler().post(() -> mLog.log("OBSERVED " + msg));
+            }
+        };
+
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+
+        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
+
+        // Anything the StateMachine may access must have been instantiated
+        // before this point.
+        configureAndStartStateMachine();
+
+        // Anything that may send messages to the StateMachine must only be
+        // configured to do so after the StateMachine has started (above).
+        startStateMachineUpdaters();
+    }
+
+    /**
+     * Make a IIpClient connector to communicate with this IpClient.
+     */
+    public IIpClient makeConnector() {
+        return new IpClientConnector();
+    }
+
+    class IpClientConnector extends IIpClient.Stub {
+        @Override
+        public void completedPreDhcpAction() {
+            checkNetworkStackCallingPermission();
+            IpClient.this.completedPreDhcpAction();
+        }
+        @Override
+        public void confirmConfiguration() {
+            checkNetworkStackCallingPermission();
+            IpClient.this.confirmConfiguration();
+        }
+        @Override
+        public void readPacketFilterComplete(byte[] data) {
+            checkNetworkStackCallingPermission();
+            IpClient.this.readPacketFilterComplete(data);
+        }
+        @Override
+        public void shutdown() {
+            checkNetworkStackCallingPermission();
+            IpClient.this.shutdown();
+        }
+        @Override
+        public void startProvisioning(ProvisioningConfigurationParcelable req) {
+            checkNetworkStackCallingPermission();
+            IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req));
+        }
+        @Override
+        public void stop() {
+            checkNetworkStackCallingPermission();
+            IpClient.this.stop();
+        }
+        @Override
+        public void setTcpBufferSizes(String tcpBufferSizes) {
+            checkNetworkStackCallingPermission();
+            IpClient.this.setTcpBufferSizes(tcpBufferSizes);
+        }
+        @Override
+        public void setHttpProxy(ProxyInfoParcelable proxyInfo) {
+            checkNetworkStackCallingPermission();
+            IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo));
+        }
+        @Override
+        public void setMulticastFilter(boolean enabled) {
+            checkNetworkStackCallingPermission();
+            IpClient.this.setMulticastFilter(enabled);
+        }
+    }
+
+    public String getInterfaceName() {
+        return mInterfaceName;
+    }
+
+    private void configureAndStartStateMachine() {
+        // CHECKSTYLE:OFF IndentationCheck
+        addState(mStoppedState);
+        addState(mStartedState);
+            addState(mRunningState, mStartedState);
+        addState(mStoppingState);
+        // CHECKSTYLE:ON IndentationCheck
+
+        setInitialState(mStoppedState);
+
+        super.start();
+    }
+
+    private void startStateMachineUpdaters() {
+        try {
+            mNwService.registerObserver(mNetlinkTracker);
+        } catch (RemoteException e) {
+            logError("Couldn't register NetlinkTracker: %s", e);
+        }
+    }
+
+    private void stopStateMachineUpdaters() {
+        try {
+            mNwService.unregisterObserver(mNetlinkTracker);
+        } catch (RemoteException e) {
+            logError("Couldn't unregister NetlinkTracker: %s", e);
+        }
+    }
+
+    @Override
+    protected void onQuitting() {
+        mCallback.onQuit();
+        mShutdownLatch.countDown();
+    }
+
+    /**
+     * Shut down this IpClient instance altogether.
+     */
+    public void shutdown() {
+        stop();
+        sendMessage(CMD_TERMINATE_AFTER_STOP);
+    }
+
+    /**
+     * Start provisioning with the provided parameters.
+     */
+    public void startProvisioning(ProvisioningConfiguration req) {
+        if (!req.isValid()) {
+            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+            return;
+        }
+
+        mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName);
+        if (mInterfaceParams == null) {
+            logError("Failed to find InterfaceParams for " + mInterfaceName);
+            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
+            return;
+        }
+
+        mCallback.setNeighborDiscoveryOffload(true);
+        sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
+    }
+
+    /**
+     * Stop this IpClient.
+     *
+     * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+     */
+    public void stop() {
+        sendMessage(CMD_STOP);
+    }
+
+    /**
+     * Confirm the provisioning configuration.
+     */
+    public void confirmConfiguration() {
+        sendMessage(CMD_CONFIRM);
+    }
+
+    /**
+     * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
+     * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
+     * proceed.
+     */
+    public void completedPreDhcpAction() {
+        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+    }
+
+    /**
+     * Indicate that packet filter read is complete.
+     */
+    public void readPacketFilterComplete(byte[] data) {
+        sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data);
+    }
+
+    /**
+     * Set the TCP buffer sizes to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setTcpBufferSizes(String tcpBufferSizes) {
+        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
+    }
+
+    /**
+     * Set the HTTP Proxy configuration to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setHttpProxy(ProxyInfo proxyInfo) {
+        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
+    }
+
+    /**
+     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
+     * if not, Callback.setFallbackMulticastFilter() is called.
+     */
+    public void setMulticastFilter(boolean enabled) {
+        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
+    }
+
+    /**
+     * Dump logs of this IpClient.
+     */
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+            // Execute confirmConfiguration() and take no further action.
+            confirmConfiguration();
+            return;
+        }
+
+        // Thread-unsafe access to mApfFilter but just used for debugging.
+        final ApfFilter apfFilter = mApfFilter;
+        final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration;
+        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+                ? provisioningConfig.mApfCapabilities : null;
+
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        pw.println(mTag + " APF dump:");
+        pw.increaseIndent();
+        if (apfFilter != null) {
+            if (apfCapabilities.hasDataAccess()) {
+                // Request a new snapshot, then wait for it.
+                mApfDataSnapshotComplete.close();
+                mCallback.startReadPacketFilter();
+                if (!mApfDataSnapshotComplete.block(1000)) {
+                    pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT");
+                }
+            }
+            apfFilter.dump(pw);
+
+        } else {
+            pw.print("No active ApfFilter; ");
+            if (provisioningConfig == null) {
+                pw.println("IpClient not yet started.");
+            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+                pw.println("Hardware does not support APF.");
+            } else {
+                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+            }
+        }
+        pw.decreaseIndent();
+        pw.println();
+        pw.println(mTag + " current ProvisioningConfiguration:");
+        pw.increaseIndent();
+        pw.println(Objects.toString(provisioningConfig, "N/A"));
+        pw.decreaseIndent();
+
+        final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
+        if (iprm != null) {
+            pw.println();
+            pw.println(mTag + " current IpReachabilityMonitor state:");
+            pw.increaseIndent();
+            iprm.dump(pw);
+            pw.decreaseIndent();
+        }
+
+        pw.println();
+        pw.println(mTag + " StateMachine dump:");
+        pw.increaseIndent();
+        mLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " connectivity packet log:");
+        pw.println();
+        pw.println("Debug with python and scapy via:");
+        pw.println("shell$ python");
+        pw.println(">>> from scapy import all as scapy");
+        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+        pw.println();
+
+        pw.increaseIndent();
+        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
+        pw.decreaseIndent();
+    }
+
+
+    /**
+     * Internals.
+     */
+
+    @Override
+    protected String getWhatToString(int what) {
+        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
+    }
+
+    @Override
+    protected String getLogRecString(Message msg) {
+        final String logLine = String.format(
+                "%s/%d %d %d %s [%s]",
+                mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index,
+                msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
+
+        final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
+        mLog.log(richerLogLine);
+        if (DBG) {
+            Log.d(mTag, richerLogLine);
+        }
+
+        mMsgStateLogger.reset();
+        return logLine;
+    }
+
+    @Override
+    protected boolean recordLogRec(Message msg) {
+        // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
+        // and we already log any LinkProperties change that results in an
+        // invocation of IpClient.Callback#onLinkPropertiesChange().
+        final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+        if (!shouldLog) {
+            mMsgStateLogger.reset();
+        }
+        return shouldLog;
+    }
+
+    private void logError(String fmt, Object... args) {
+        final String msg = "ERROR " + String.format(fmt, args);
+        Log.e(mTag, msg);
+        mLog.log(msg);
+    }
+
+    // This needs to be called with care to ensure that our LinkProperties
+    // are in sync with the actual LinkProperties of the interface. For example,
+    // we should only call this if we know for sure that there are no IP addresses
+    // assigned to the interface, etc.
+    private void resetLinkProperties() {
+        mNetlinkTracker.clearLinkProperties();
+        mConfiguration = null;
+        mDhcpResults = null;
+        mTcpBufferSizes = "";
+        mHttpProxy = null;
+
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+    }
+
+    private void recordMetric(final int type) {
+        // We may record error metrics prior to starting.
+        // Map this to IMMEDIATE_FAILURE_DURATION.
+        final long duration = (mStartTimeMillis > 0)
+                ? (SystemClock.elapsedRealtime() - mStartTimeMillis)
+                : IMMEDIATE_FAILURE_DURATION;
+        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
+    }
+
+    // For now: use WifiStateMachine's historical notion of provisioned.
+    @VisibleForTesting
+    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
+        // For historical reasons, we should connect even if all we have is
+        // an IPv4 address and nothing else.
+        if (lp.hasIPv4Address() || lp.isProvisioned()) {
+            return true;
+        }
+        if (config == null) {
+            return false;
+        }
+
+        // When an InitialConfiguration is specified, ignore any difference with previous
+        // properties and instead check if properties observed match the desired properties.
+        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
+    }
+
+    // TODO: Investigate folding all this into the existing static function
+    // LinkProperties.compareProvisioning() or some other single function that
+    // takes two LinkProperties objects and returns a ProvisioningChange
+    // object that is a correct and complete assessment of what changed, taking
+    // account of the asymmetries described in the comments in this function.
+    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
+    private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
+        int delta;
+        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
+        final boolean wasProvisioned = isProvisioned(oldLp, config);
+        final boolean isProvisioned = isProvisioned(newLp, config);
+
+        if (!wasProvisioned && isProvisioned) {
+            delta = PROV_CHANGE_GAINED_PROVISIONING;
+        } else if (wasProvisioned && isProvisioned) {
+            delta = PROV_CHANGE_STILL_PROVISIONED;
+        } else if (!wasProvisioned && !isProvisioned) {
+            delta = PROV_CHANGE_STILL_NOT_PROVISIONED;
+        } else {
+            // (wasProvisioned && !isProvisioned)
+            //
+            // Note that this is true even if we lose a configuration element
+            // (e.g., a default gateway) that would not be required to advance
+            // into provisioned state. This is intended: if we have a default
+            // router and we lose it, that's a sure sign of a problem, but if
+            // we connect to a network with no IPv4 DNS servers, we consider
+            // that to be a network without DNS servers and connect anyway.
+            //
+            // See the comment below.
+            delta = PROV_CHANGE_LOST_PROVISIONING;
+        }
+
+        final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
+        final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
+        final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
+
+        // If bad wifi avoidance is disabled, then ignore IPv6 loss of
+        // provisioning. Otherwise, when a hotspot that loses Internet
+        // access sends out a 0-lifetime RA to its clients, the clients
+        // will disconnect and then reconnect, avoiding the bad hotspot,
+        // instead of getting stuck on the bad hotspot. http://b/31827713 .
+        //
+        // This is incorrect because if the hotspot then regains Internet
+        // access with a different prefix, TCP connections on the
+        // deprecated addresses will remain stuck.
+        //
+        // Note that we can still be disconnected by IpReachabilityMonitor
+        // if the IPv6 default gateway (but not the IPv6 DNS servers; see
+        // accompanying code in IpReachabilityMonitor) is unreachable.
+        final boolean ignoreIPv6ProvisioningLoss =
+                mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
+                && mCm.getAvoidBadWifi();
+
+        // Additionally:
+        //
+        // Partial configurations (e.g., only an IPv4 address with no DNS
+        // servers and no default route) are accepted as long as DHCPv4
+        // succeeds. On such a network, isProvisioned() will always return
+        // false, because the configuration is not complete, but we want to
+        // connect anyway. It might be a disconnected network such as a
+        // Chromecast or a wireless printer, for example.
+        //
+        // Because on such a network isProvisioned() will always return false,
+        // delta will never be LOST_PROVISIONING. So check for loss of
+        // provisioning here too.
+        if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
+            delta = PROV_CHANGE_LOST_PROVISIONING;
+        }
+
+        // Additionally:
+        //
+        // If the previous link properties had a global IPv6 address and an
+        // IPv6 default route then also consider the loss of that default route
+        // to be a loss of provisioning. See b/27962810.
+        if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
+            delta = PROV_CHANGE_LOST_PROVISIONING;
+        }
+
+        return delta;
+    }
+
+    private void dispatchCallback(int delta, LinkProperties newLp) {
+        switch (delta) {
+            case PROV_CHANGE_GAINED_PROVISIONING:
+                if (DBG) {
+                    Log.d(mTag, "onProvisioningSuccess()");
+                }
+                recordMetric(IpManagerEvent.PROVISIONING_OK);
+                mCallback.onProvisioningSuccess(newLp);
+                break;
+
+            case PROV_CHANGE_LOST_PROVISIONING:
+                if (DBG) {
+                    Log.d(mTag, "onProvisioningFailure()");
+                }
+                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+                mCallback.onProvisioningFailure(newLp);
+                break;
+
+            default:
+                if (DBG) {
+                    Log.d(mTag, "onLinkPropertiesChange()");
+                }
+                mCallback.onLinkPropertiesChange(newLp);
+                break;
+        }
+    }
+
+    // Updates all IpClient-related state concerned with LinkProperties.
+    // Returns a ProvisioningChange for possibly notifying other interested
+    // parties that are not fronted by IpClient.
+    private int setLinkProperties(LinkProperties newLp) {
+        if (mApfFilter != null) {
+            mApfFilter.setLinkProperties(newLp);
+        }
+        if (mIpReachabilityMonitor != null) {
+            mIpReachabilityMonitor.updateLinkProperties(newLp);
+        }
+
+        int delta = compareProvisioning(mLinkProperties, newLp);
+        mLinkProperties = new LinkProperties(newLp);
+
+        if (delta == PROV_CHANGE_GAINED_PROVISIONING) {
+            // TODO: Add a proper ProvisionedState and cancel the alarm in
+            // its enter() method.
+            mProvisioningTimeoutAlarm.cancel();
+        }
+
+        return delta;
+    }
+
+    private LinkProperties assembleLinkProperties() {
+        // [1] Create a new LinkProperties object to populate.
+        LinkProperties newLp = new LinkProperties();
+        newLp.setInterfaceName(mInterfaceName);
+
+        // [2] Pull in data from netlink:
+        //         - IPv4 addresses
+        //         - IPv6 addresses
+        //         - IPv6 routes
+        //         - IPv6 DNS servers
+        //
+        // N.B.: this is fundamentally race-prone and should be fixed by
+        // changing NetlinkTracker from a hybrid edge/level model to an
+        // edge-only model, or by giving IpClient its own netlink socket(s)
+        // so as to track all required information directly.
+        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
+        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
+        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
+            newLp.addRoute(route);
+        }
+        addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
+
+        // [3] Add in data from DHCPv4, if available.
+        //
+        // mDhcpResults is never shared with any other owner so we don't have
+        // to worry about concurrent modification.
+        if (mDhcpResults != null) {
+            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+                newLp.addRoute(route);
+            }
+            addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
+            newLp.setDomains(mDhcpResults.domains);
+
+            if (mDhcpResults.mtu != 0) {
+                newLp.setMtu(mDhcpResults.mtu);
+            }
+        }
+
+        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
+        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+            newLp.setTcpBufferSizes(mTcpBufferSizes);
+        }
+        if (mHttpProxy != null) {
+            newLp.setHttpProxy(mHttpProxy);
+        }
+
+        // [5] Add data from InitialConfiguration
+        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
+            InitialConfiguration config = mConfiguration.mInitialConfig;
+            // Add InitialConfiguration routes and dns server addresses once all addresses
+            // specified in the InitialConfiguration have been observed with Netlink.
+            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
+                for (IpPrefix prefix : config.directlyConnectedRoutes) {
+                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
+                }
+            }
+            addAllReachableDnsServers(newLp, config.dnsServers);
+        }
+        final LinkProperties oldLp = mLinkProperties;
+        if (DBG) {
+            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
+                    netlinkLinkProperties, newLp, oldLp));
+        }
+
+        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
+        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
+        return newLp;
+    }
+
+    private static void addAllReachableDnsServers(
+            LinkProperties lp, Iterable<InetAddress> dnses) {
+        // TODO: Investigate deleting this reachability check.  We should be
+        // able to pass everything down to netd and let netd do evaluation
+        // and RFC6724-style sorting.
+        for (InetAddress dns : dnses) {
+            if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
+                lp.addDnsServer(dns);
+            }
+        }
+    }
+
+    // Returns false if we have lost provisioning, true otherwise.
+    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
+        final LinkProperties newLp = assembleLinkProperties();
+        if (Objects.equals(newLp, mLinkProperties)) {
+            return true;
+        }
+        final int delta = setLinkProperties(newLp);
+        if (sendCallbacks) {
+            dispatchCallback(delta, newLp);
+        }
+        return (delta != PROV_CHANGE_LOST_PROVISIONING);
+    }
+
+    private void handleIPv4Success(DhcpResults dhcpResults) {
+        mDhcpResults = new DhcpResults(dhcpResults);
+        final LinkProperties newLp = assembleLinkProperties();
+        final int delta = setLinkProperties(newLp);
+
+        if (DBG) {
+            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+        }
+        mCallback.onNewDhcpResults(dhcpResults);
+        dispatchCallback(delta, newLp);
+    }
+
+    private void handleIPv4Failure() {
+        // TODO: Investigate deleting this clearIPv4Address() call.
+        //
+        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+        // that could trigger a call to this function. If we missed handling
+        // that message in StartedState for some reason we would still clear
+        // any addresses upon entry to StoppedState.
+        mInterfaceCtrl.clearIPv4Address();
+        mDhcpResults = null;
+        if (DBG) {
+            Log.d(mTag, "onNewDhcpResults(null)");
+        }
+        mCallback.onNewDhcpResults(null);
+
+        handleProvisioningFailure();
+    }
+
+    private void handleProvisioningFailure() {
+        final LinkProperties newLp = assembleLinkProperties();
+        int delta = setLinkProperties(newLp);
+        // If we've gotten here and we're still not provisioned treat that as
+        // a total loss of provisioning.
+        //
+        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
+        // there was no usable IPv6 obtained before a non-zero provisioning
+        // timeout expired.
+        //
+        // Regardless: GAME OVER.
+        if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) {
+            delta = PROV_CHANGE_LOST_PROVISIONING;
+        }
+
+        dispatchCallback(delta, newLp);
+        if (delta == PROV_CHANGE_LOST_PROVISIONING) {
+            transitionTo(mStoppingState);
+        }
+    }
+
+    private void doImmediateProvisioningFailure(int failureType) {
+        logError("onProvisioningFailure(): %s", failureType);
+        recordMetric(failureType);
+        mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+    }
+
+    private boolean startIPv4() {
+        // If we have a StaticIpConfiguration attempt to apply it and
+        // handle the result accordingly.
+        if (mConfiguration.mStaticIpConfig != null) {
+            if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+            } else {
+                return false;
+            }
+        } else {
+            // Start DHCPv4.
+            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams);
+            mDhcpClient.registerForPreDhcpNotification();
+            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+        }
+
+        return true;
+    }
+
+    private boolean startIPv6() {
+        return mInterfaceCtrl.setIPv6PrivacyExtensions(true)
+                && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode)
+                && mInterfaceCtrl.enableIPv6();
+    }
+
+    private boolean applyInitialConfig(InitialConfiguration config) {
+        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
+        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
+            if (!mInterfaceCtrl.addAddress(addr)) return false;
+        }
+
+        return true;
+    }
+
+    private boolean startIpReachabilityMonitor() {
+        try {
+            // TODO: Fetch these parameters from settings, and install a
+            // settings observer to watch for update and re-program these
+            // parameters (Q: is this level of dynamic updatability really
+            // necessary or does reading from settings at startup suffice?).
+            final int numSolicits = 5;
+            final int interSolicitIntervalMs = 750;
+            setNeighborParameters(mDependencies.getNetd(), mInterfaceName,
+                    numSolicits, interSolicitIntervalMs);
+        } catch (Exception e) {
+            mLog.e("Failed to adjust neighbor parameters", e);
+            // Carry on using the system defaults (currently: 3, 1000);
+        }
+
+        try {
+            mIpReachabilityMonitor = new IpReachabilityMonitor(
+                    mContext,
+                    mInterfaceParams,
+                    getHandler(),
+                    mLog,
+                    new IpReachabilityMonitor.Callback() {
+                        @Override
+                        public void notifyLost(InetAddress ip, String logMsg) {
+                            mCallback.onReachabilityLost(logMsg);
+                        }
+                    },
+                    mConfiguration.mUsingMultinetworkPolicyTracker);
+        } catch (IllegalArgumentException iae) {
+            // Failed to start IpReachabilityMonitor. Log it and call
+            // onProvisioningFailure() immediately.
+            //
+            // See http://b/31038971.
+            logError("IpReachabilityMonitor failure: %s", iae);
+            mIpReachabilityMonitor = null;
+        }
+
+        return (mIpReachabilityMonitor != null);
+    }
+
+    private void stopAllIP() {
+        // We don't need to worry about routes, just addresses, because:
+        //     - disableIpv6() will clear autoconf IPv6 routes as well, and
+        //     - we don't get IPv4 routes from netlink
+        // so we neither react to nor need to wait for changes in either.
+
+        mInterfaceCtrl.disableIPv6();
+        mInterfaceCtrl.clearAllAddresses();
+    }
+
+    class StoppedState extends State {
+        @Override
+        public void enter() {
+            stopAllIP();
+
+            resetLinkProperties();
+            if (mStartTimeMillis > 0) {
+                // Completed a life-cycle; send a final empty LinkProperties
+                // (cleared in resetLinkProperties() above) and record an event.
+                mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties));
+                recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
+                mStartTimeMillis = 0;
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_TERMINATE_AFTER_STOP:
+                    stopStateMachineUpdaters();
+                    quit();
+                    break;
+
+                case CMD_STOP:
+                    break;
+
+                case CMD_START:
+                    mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj;
+                    transitionTo(mStartedState);
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_SET_MULTICAST_FILTER:
+                    mMulticastFiltering = (boolean) msg.obj;
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    // Everything is already stopped.
+                    logError("Unexpected CMD_ON_QUIT (already stopped).");
+                    break;
+
+                default:
+                    return NOT_HANDLED;
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    class StoppingState extends State {
+        @Override
+        public void enter() {
+            if (mDhcpClient == null) {
+                // There's no DHCPv4 for which to wait; proceed to stopped.
+                deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED));
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_JUMP_STOPPING_TO_STOPPED:
+                    transitionTo(mStoppedState);
+                    break;
+
+                case CMD_STOP:
+                    break;
+
+                case DhcpClient.CMD_CLEAR_LINKADDRESS:
+                    mInterfaceCtrl.clearIPv4Address();
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    mDhcpClient = null;
+                    transitionTo(mStoppedState);
+                    break;
+
+                default:
+                    deferMessage(msg);
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    class StartedState extends State {
+        @Override
+        public void enter() {
+            mStartTimeMillis = SystemClock.elapsedRealtime();
+
+            if (mConfiguration.mProvisioningTimeoutMs > 0) {
+                final long alarmTime = SystemClock.elapsedRealtime()
+                        + mConfiguration.mProvisioningTimeoutMs;
+                mProvisioningTimeoutAlarm.schedule(alarmTime);
+            }
+
+            if (readyToProceed()) {
+                deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING));
+            } else {
+                // Clear all IPv4 and IPv6 before proceeding to RunningState.
+                // Clean up any leftover state from an abnormal exit from
+                // tethering or during an IpClient restart.
+                stopAllIP();
+            }
+        }
+
+        @Override
+        public void exit() {
+            mProvisioningTimeoutAlarm.cancel();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_JUMP_STARTED_TO_RUNNING:
+                    transitionTo(mRunningState);
+                    break;
+
+                case CMD_STOP:
+                    transitionTo(mStoppingState);
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    if (readyToProceed()) {
+                        transitionTo(mRunningState);
+                    }
+                    break;
+
+                case EVENT_PROVISIONING_TIMEOUT:
+                    handleProvisioningFailure();
+                    break;
+
+                default:
+                    // It's safe to process messages out of order because the
+                    // only message that can both
+                    //     a) be received at this time and
+                    //     b) affect provisioning state
+                    // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
+                    deferMessage(msg);
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+
+        private boolean readyToProceed() {
+            return (!mLinkProperties.hasIPv4Address() && !mLinkProperties.hasGlobalIPv6Address());
+        }
+    }
+
+    class RunningState extends State {
+        private ConnectivityPacketTracker mPacketTracker;
+        private boolean mDhcpActionInFlight;
+
+        @Override
+        public void enter() {
+            ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
+            apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
+            apfConfig.multicastFilter = mMulticastFiltering;
+            // Get the Configuration for ApfFilter from Context
+            apfConfig.ieee802_3Filter =
+                    mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
+            apfConfig.ethTypeBlackList =
+                    mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
+            mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
+            // TODO: investigate the effects of any multicast filtering racing/interfering with the
+            // rest of this IP configuration startup.
+            if (mApfFilter == null) {
+                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+            }
+
+            mPacketTracker = createPacketTracker();
+            if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
+
+            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
+                enqueueJumpToStoppingState();
+                return;
+            }
+
+            if (mConfiguration.mEnableIPv4 && !startIPv4()) {
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
+                enqueueJumpToStoppingState();
+                return;
+            }
+
+            final InitialConfiguration config = mConfiguration.mInitialConfig;
+            if ((config != null) && !applyInitialConfig(config)) {
+                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+                enqueueJumpToStoppingState();
+                return;
+            }
+
+            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
+                doImmediateProvisioningFailure(
+                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
+                enqueueJumpToStoppingState();
+                return;
+            }
+        }
+
+        @Override
+        public void exit() {
+            stopDhcpAction();
+
+            if (mIpReachabilityMonitor != null) {
+                mIpReachabilityMonitor.stop();
+                mIpReachabilityMonitor = null;
+            }
+
+            if (mDhcpClient != null) {
+                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
+                mDhcpClient.doQuit();
+            }
+
+            if (mPacketTracker != null) {
+                mPacketTracker.stop();
+                mPacketTracker = null;
+            }
+
+            if (mApfFilter != null) {
+                mApfFilter.shutdown();
+                mApfFilter = null;
+            }
+
+            resetLinkProperties();
+        }
+
+        private void enqueueJumpToStoppingState() {
+            deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
+        }
+
+        private ConnectivityPacketTracker createPacketTracker() {
+            try {
+                return new ConnectivityPacketTracker(
+                        getHandler(), mInterfaceParams, mConnectivityPacketLog);
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+
+        private void ensureDhcpAction() {
+            if (!mDhcpActionInFlight) {
+                mCallback.onPreDhcpAction();
+                mDhcpActionInFlight = true;
+                final long alarmTime = SystemClock.elapsedRealtime()
+                        + mConfiguration.mRequestedPreDhcpActionMs;
+                mDhcpActionTimeoutAlarm.schedule(alarmTime);
+            }
+        }
+
+        private void stopDhcpAction() {
+            mDhcpActionTimeoutAlarm.cancel();
+            if (mDhcpActionInFlight) {
+                mCallback.onPostDhcpAction();
+                mDhcpActionInFlight = false;
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_JUMP_RUNNING_TO_STOPPING:
+                case CMD_STOP:
+                    transitionTo(mStoppingState);
+                    break;
+
+                case CMD_START:
+                    logError("ALERT: START received in StartedState. Please fix caller.");
+                    break;
+
+                case CMD_CONFIRM:
+                    // TODO: Possibly introduce a second type of confirmation
+                    // that both probes (a) on-link neighbors and (b) does
+                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
+                    // roams.
+                    if (mIpReachabilityMonitor != null) {
+                        mIpReachabilityMonitor.probeAll();
+                    }
+                    break;
+
+                case EVENT_PRE_DHCP_ACTION_COMPLETE:
+                    // It's possible to reach here if, for example, someone
+                    // calls completedPreDhcpAction() after provisioning with
+                    // a static IP configuration.
+                    if (mDhcpClient != null) {
+                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
+                    }
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
+                        transitionTo(mStoppingState);
+                    }
+                    break;
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
+
+                case CMD_SET_MULTICAST_FILTER: {
+                    mMulticastFiltering = (boolean) msg.obj;
+                    if (mApfFilter != null) {
+                        mApfFilter.setMulticastFilter(mMulticastFiltering);
+                    } else {
+                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+                    }
+                    break;
+                }
+
+                case EVENT_READ_PACKET_FILTER_COMPLETE: {
+                    if (mApfFilter != null) {
+                        mApfFilter.setDataSnapshot((byte[]) msg.obj);
+                    }
+                    mApfDataSnapshotComplete.open();
+                    break;
+                }
+
+                case EVENT_DHCPACTION_TIMEOUT:
+                    stopDhcpAction();
+                    break;
+
+                case DhcpClient.CMD_PRE_DHCP_ACTION:
+                    if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
+                        ensureDhcpAction();
+                    } else {
+                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+                    }
+                    break;
+
+                case DhcpClient.CMD_CLEAR_LINKADDRESS:
+                    mInterfaceCtrl.clearIPv4Address();
+                    break;
+
+                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
+                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
+                    if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
+                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
+                    } else {
+                        logError("Failed to set IPv4 address.");
+                        dispatchCallback(PROV_CHANGE_LOST_PROVISIONING,
+                                new LinkProperties(mLinkProperties));
+                        transitionTo(mStoppingState);
+                    }
+                    break;
+                }
+
+                // This message is only received when:
+                //
+                //     a) initial address acquisition succeeds,
+                //     b) renew succeeds or is NAK'd,
+                //     c) rebind succeeds or is NAK'd, or
+                //     c) the lease expires,
+                //
+                // but never when initial address acquisition fails. The latter
+                // condition is now governed by the provisioning timeout.
+                case DhcpClient.CMD_POST_DHCP_ACTION:
+                    stopDhcpAction();
+
+                    switch (msg.arg1) {
+                        case DhcpClient.DHCP_SUCCESS:
+                            handleIPv4Success((DhcpResults) msg.obj);
+                            break;
+                        case DhcpClient.DHCP_FAILURE:
+                            handleIPv4Failure();
+                            break;
+                        default:
+                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
+                    }
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    // DHCPv4 quit early for some reason.
+                    logError("Unexpected CMD_ON_QUIT.");
+                    mDhcpClient = null;
+                    break;
+
+                default:
+                    return NOT_HANDLED;
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    private static class MessageHandlingLogger {
+        public String processedInState;
+        public String receivedInState;
+
+        public void reset() {
+            processedInState = null;
+            receivedInState = null;
+        }
+
+        public void handled(State processedIn, IState receivedIn) {
+            processedInState = processedIn.getClass().getSimpleName();
+            receivedInState = receivedIn.getName();
+        }
+
+        public String toString() {
+            return String.format("rcvd_in=%s, proc_in=%s",
+                                 receivedInState, processedInState);
+        }
+    }
+
+    private static void setNeighborParameters(
+            INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs)
+            throws RemoteException, IllegalArgumentException {
+        Preconditions.checkNotNull(netd);
+        Preconditions.checkArgument(!TextUtils.isEmpty(ifName));
+        Preconditions.checkArgument(numSolicits > 0);
+        Preconditions.checkArgument(interSolicitIntervalMs > 0);
+
+        for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) {
+            netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms",
+                    Integer.toString(interSolicitIntervalMs));
+            netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit",
+                    Integer.toString(numSolicits));
+        }
+    }
+
+    // TODO: extract out into CollectionUtils.
+    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+        for (T t : coll) {
+            if (fn.test(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+        return !any(coll, not(fn));
+    }
+
+    static <T> Predicate<T> not(Predicate<T> fn) {
+        return (t) -> !fn.test(t);
+    }
+
+    static <T> String join(String delimiter, Collection<T> coll) {
+        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+    }
+
+    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+        for (T t: coll) {
+            if (fn.test(t)) {
+                return t;
+            }
+        }
+        return null;
+    }
+
+    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
+        return coll.stream().filter(fn).collect(Collectors.toList());
+    }
+}
diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
similarity index 100%
rename from services/net/java/android/net/ip/IpNeighborMonitor.java
rename to packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
similarity index 100%
rename from services/net/java/android/net/ip/IpReachabilityMonitor.java
rename to packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
similarity index 79%
rename from services/net/java/android/net/util/ConnectivityPacketSummary.java
rename to packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
index ec833b0..08c3f60 100644
--- a/services/net/java/android/net/util/ConnectivityPacketSummary.java
+++ b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java
@@ -16,47 +16,46 @@
 
 package android.net.util;
 
-import static android.net.util.NetworkConstants.ARP_HWTYPE_ETHER;
-import static android.net.util.NetworkConstants.ARP_PAYLOAD_LEN;
-import static android.net.util.NetworkConstants.ARP_REPLY;
-import static android.net.util.NetworkConstants.ARP_REQUEST;
-import static android.net.util.NetworkConstants.DHCP4_CLIENT_PORT;
-import static android.net.util.NetworkConstants.ETHER_ADDR_LEN;
-import static android.net.util.NetworkConstants.ETHER_DST_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.ETHER_HEADER_LEN;
-import static android.net.util.NetworkConstants.ETHER_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.ETHER_TYPE_ARP;
-import static android.net.util.NetworkConstants.ETHER_TYPE_IPV4;
-import static android.net.util.NetworkConstants.ETHER_TYPE_IPV6;
-import static android.net.util.NetworkConstants.ETHER_TYPE_OFFSET;
-import static android.net.util.NetworkConstants.ICMPV6_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MTU;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_SLLA;
-import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_TLLA;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_SOLICITATION;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION;
-import static android.net.util.NetworkConstants.IPV4_ADDR_LEN;
-import static android.net.util.NetworkConstants.IPV4_DST_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_FLAGS_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_FRAGMENT_MASK;
-import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.IPV4_IHL_MASK;
-import static android.net.util.NetworkConstants.IPV4_PROTOCOL_OFFSET;
-import static android.net.util.NetworkConstants.IPV4_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.IPV6_ADDR_LEN;
-import static android.net.util.NetworkConstants.IPV6_HEADER_LEN;
-import static android.net.util.NetworkConstants.IPV6_PROTOCOL_OFFSET;
-import static android.net.util.NetworkConstants.IPV6_SRC_ADDR_OFFSET;
-import static android.net.util.NetworkConstants.UDP_HEADER_LEN;
-import static android.net.util.NetworkConstants.asString;
-import static android.net.util.NetworkConstants.asUint;
 import static android.system.OsConstants.IPPROTO_ICMPV6;
 import static android.system.OsConstants.IPPROTO_UDP;
 
+import static com.android.server.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
+import static com.android.server.util.NetworkStackConstants.ARP_PAYLOAD_LEN;
+import static com.android.server.util.NetworkStackConstants.ARP_REPLY;
+import static com.android.server.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.server.util.NetworkStackConstants.DHCP4_CLIENT_PORT;
+import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_ARP;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV4_DST_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_FLAGS_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_FRAGMENT_MASK;
+import static com.android.server.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV4_IHL_MASK;
+import static com.android.server.util.NetworkStackConstants.IPV4_PROTOCOL_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV4_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
+import static com.android.server.util.NetworkStackConstants.IPV6_SRC_ADDR_OFFSET;
+import static com.android.server.util.NetworkStackConstants.UDP_HEADER_LEN;
+
 import android.net.MacAddress;
 import android.net.dhcp.DhcpPacket;
 
@@ -412,4 +411,25 @@
         final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
         return String.format(MAC48_FORMAT, printableBytes);
     }
+
+    /**
+     * Convenience method to convert an int to a String.
+     */
+    public static String asString(int i) {
+        return Integer.toString(i);
+    }
+
+    /**
+     * Convenience method to read a byte as an unsigned int.
+     */
+    public static int asUint(byte b) {
+        return (b & 0xff);
+    }
+
+    /**
+     * Convenience method to read a short as an unsigned int.
+     */
+    public static int asUint(short s) {
+        return (s & 0xffff);
+    }
 }
diff --git a/services/net/java/android/net/util/FdEventsReader.java b/packages/NetworkStack/src/android/net/util/FdEventsReader.java
similarity index 100%
rename from services/net/java/android/net/util/FdEventsReader.java
rename to packages/NetworkStack/src/android/net/util/FdEventsReader.java
diff --git a/services/net/java/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java
similarity index 100%
rename from services/net/java/android/net/util/PacketReader.java
rename to packages/NetworkStack/src/android/net/util/PacketReader.java
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index cca71e7..4080ddf 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -39,6 +39,8 @@
 import android.net.dhcp.DhcpServingParams;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.SharedLog;
 import android.os.IBinder;
@@ -50,7 +52,11 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
 
 /**
  * Android service used to start the network stack when bound to via an intent.
@@ -80,6 +86,8 @@
         private static final int NUM_VALIDATION_LOG_LINES = 20;
         private final Context mContext;
         private final ConnectivityManager mCm;
+        @GuardedBy("mIpClients")
+        private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>();
 
         private static final int MAX_VALIDATION_LOGS = 10;
         @GuardedBy("mValidationLogs")
@@ -138,6 +146,24 @@
         }
 
         @Override
+        public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException {
+            final IpClient ipClient = new IpClient(mContext, ifName, cb);
+
+            synchronized (mIpClients) {
+                final Iterator<WeakReference<IpClient>> it = mIpClients.iterator();
+                while (it.hasNext()) {
+                    final IpClient ipc = it.next().get();
+                    if (ipc == null) {
+                        it.remove();
+                    }
+                }
+                mIpClients.add(new WeakReference<>(ipClient));
+            }
+
+            cb.onIpClientCreated(ipClient.makeConnector());
+        }
+
+        @Override
         protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
                 @Nullable String[] args) {
             checkDumpPermission();
@@ -145,6 +171,33 @@
             pw.println("NetworkStack logs:");
             mLog.dump(fd, pw, args);
 
+            // Dump full IpClient logs for non-GCed clients
+            pw.println();
+            pw.println("Recently active IpClient logs:");
+            final ArrayList<IpClient> ipClients = new ArrayList<>();
+            final HashSet<String> dumpedIpClientIfaces = new HashSet<>();
+            synchronized (mIpClients) {
+                for (WeakReference<IpClient> ipcRef : mIpClients) {
+                    final IpClient ipc = ipcRef.get();
+                    if (ipc != null) {
+                        ipClients.add(ipc);
+                    }
+                }
+            }
+
+            for (IpClient ipc : ipClients) {
+                pw.println(ipc.getName());
+                pw.increaseIndent();
+                ipc.dump(fd, pw, args);
+                pw.decreaseIndent();
+                dumpedIpClientIfaces.add(ipc.getInterfaceName());
+            }
+
+            // State machine and connectivity metrics logs are kept for GCed IpClients
+            pw.println();
+            pw.println("Other IpClient logs:");
+            IpClient.dumpAllLogs(fout, dumpedIpClientIfaces);
+
             pw.println();
             pw.println("Validation logs (most recent first):");
             synchronized (mValidationLogs) {
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index a3d7852..6b31b82 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -247,7 +247,6 @@
     private final TelephonyManager mTelephonyManager;
     private final WifiManager mWifiManager;
     private final ConnectivityManager mCm;
-    private final NetworkRequest mDefaultRequest;
     private final IpConnectivityLog mMetricsLog;
     private final Dependencies mDependencies;
 
@@ -336,7 +335,6 @@
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mDefaultRequest = defaultRequest;
 
         // CHECKSTYLE:OFF IndentationCheck
         addState(mDefaultState);
@@ -486,8 +484,7 @@
     }
 
     private boolean isValidationRequired() {
-        return NetworkMonitorUtils.isValidationRequired(
-                mDefaultRequest.networkCapabilities, mNetworkCapabilities);
+        return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities);
     }
 
 
diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
index bb5900c..eedaf30 100644
--- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
+++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
@@ -32,12 +32,103 @@
     public static final int IPV4_MAX_MTU = 65_535;
 
     /**
+     * Ethernet constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc894
+     *     - https://tools.ietf.org/html/rfc2464
+     *     - https://tools.ietf.org/html/rfc7042
+     *     - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
+     *     - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
+     */
+    public static final int ETHER_DST_ADDR_OFFSET = 0;
+    public static final int ETHER_SRC_ADDR_OFFSET = 6;
+    public static final int ETHER_ADDR_LEN = 6;
+    public static final int ETHER_TYPE_OFFSET = 12;
+    public static final int ETHER_TYPE_LENGTH = 2;
+    public static final int ETHER_TYPE_ARP  = 0x0806;
+    public static final int ETHER_TYPE_IPV4 = 0x0800;
+    public static final int ETHER_TYPE_IPV6 = 0x86dd;
+    public static final int ETHER_HEADER_LEN = 14;
+
+    /**
+     * ARP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc826
+     *     - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
+     */
+    public static final int ARP_PAYLOAD_LEN = 28;  // For Ethernet+IPv4.
+    public static final int ARP_REQUEST = 1;
+    public static final int ARP_REPLY   = 2;
+    public static final int ARP_HWTYPE_RESERVED_LO = 0;
+    public static final int ARP_HWTYPE_ETHER       = 1;
+    public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
+
+    /**
+     * IPv4 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc791
+     */
+    public static final int IPV4_HEADER_MIN_LEN = 20;
+    public static final int IPV4_IHL_MASK = 0xf;
+    public static final int IPV4_FLAGS_OFFSET = 6;
+    public static final int IPV4_FRAGMENT_MASK = 0x1fff;
+    public static final int IPV4_PROTOCOL_OFFSET = 9;
+    public static final int IPV4_SRC_ADDR_OFFSET = 12;
+    public static final int IPV4_DST_ADDR_OFFSET = 16;
+    public static final int IPV4_ADDR_LEN = 4;
+
+    /**
+     * IPv6 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc2460
+     */
+    public static final int IPV6_ADDR_LEN = 16;
+    public static final int IPV6_HEADER_LEN = 40;
+    public static final int IPV6_PROTOCOL_OFFSET = 6;
+    public static final int IPV6_SRC_ADDR_OFFSET = 8;
+    public static final int IPV6_DST_ADDR_OFFSET = 24;
+
+    /**
+     * ICMPv6 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc4443
+     *     - https://tools.ietf.org/html/rfc4861
+     */
+    public static final int ICMPV6_HEADER_MIN_LEN = 4;
+    public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
+    public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
+    public static final int ICMPV6_ROUTER_SOLICITATION    = 133;
+    public static final int ICMPV6_ROUTER_ADVERTISEMENT   = 134;
+    public static final int ICMPV6_NEIGHBOR_SOLICITATION  = 135;
+    public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
+    public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
+    public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
+    public static final int ICMPV6_ND_OPTION_SLLA = 1;
+    public static final int ICMPV6_ND_OPTION_TLLA = 2;
+    public static final int ICMPV6_ND_OPTION_MTU  = 5;
+
+    /**
+     * UDP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc768
+     */
+    public static final int UDP_HEADER_LEN = 8;
+
+
+    /**
      * DHCP constants.
      *
      * See also:
      *     - https://tools.ietf.org/html/rfc2131
      */
     public static final int INFINITE_LEASE = 0xffffffff;
+    public static final int DHCP4_CLIENT_PORT = 68;
 
     private NetworkStackConstants() {
         throw new UnsupportedOperationException("This class is not to be instantiated");
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
index bd7ff2a..45fa2dc 100644
--- a/packages/NetworkStack/tests/Android.bp
+++ b/packages/NetworkStack/tests/Android.bp
@@ -16,9 +16,12 @@
 
 android_test {
     name: "NetworkStackTests",
+    certificate: "platform",
     srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
     static_libs: [
         "android-support-test",
+        "frameworks-base-testutils",
         "mockito-target-extended-minus-junit4",
         "NetworkStackLib",
         "testables",
@@ -26,10 +29,70 @@
     libs: [
         "android.test.runner",
         "android.test.base",
+        "android.test.mock",
     ],
     jni_libs: [
         // For mockito extended
         "libdexmakerjvmtiagent",
         "libstaticjvmtiagent",
-    ]
-}
\ No newline at end of file
+        // For ApfTest
+        "libartbase",
+        "libbacktrace",
+        "libbase",
+        "libbinder",
+        "libbinderthreadstate",
+        "libc++",
+        "libcrypto",
+        "libcutils",
+        "libdexfile",
+        "libhidl-gen-utils",
+        "libhidlbase",
+        "libhidltransport",
+        "libhwbinder",
+        "liblog",
+        "liblzma",
+        "libnativehelper",
+        "libnetworkstacktestsjni",
+        "libpackagelistparser",
+        "libpcre2",
+        "libprocessgroup",
+        "libselinux",
+        "libui",
+        "libutils",
+        "libvintf",
+        "libvndksupport",
+        "libtinyxml2",
+        "libunwindstack",
+        "libutilscallstack",
+        "libziparchive",
+        "libz",
+        "netd_aidl_interface-cpp",
+    ],
+}
+
+cc_library_shared {
+    name: "libnetworkstacktestsjni",
+    srcs: [
+        "jni/**/*.cpp"
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    include_dirs: [
+        "hardware/google/apf",
+    ],
+    shared_libs: [
+        "libbinder",
+        "liblog",
+        "libcutils",
+        "libnativehelper",
+        "netd_aidl_interface-cpp",
+    ],
+    static_libs: [
+        "libapf",
+        "libpcap",
+    ],
+
+}
diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml
index 8b8474f..9cb2c21 100644
--- a/packages/NetworkStack/tests/AndroidManifest.xml
+++ b/packages/NetworkStack/tests/AndroidManifest.xml
@@ -15,6 +15,35 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.server.networkstack.tests">
+
+    <uses-permission android:name="android.permission.READ_LOGS" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+    <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+    <uses-permission android:name="android.permission.GET_DETAILED_TASKS" />
+    <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
+    <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
+    <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.NETWORK_STACK" />
+
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/net/jni/apf_jni.cpp b/packages/NetworkStack/tests/jni/apf_jni.cpp
similarity index 100%
rename from tests/net/jni/apf_jni.cpp
rename to packages/NetworkStack/tests/jni/apf_jni.cpp
diff --git a/tests/net/res/raw/apf.pcap b/packages/NetworkStack/tests/res/raw/apf.pcap
similarity index 100%
rename from tests/net/res/raw/apf.pcap
rename to packages/NetworkStack/tests/res/raw/apf.pcap
Binary files differ
diff --git a/tests/net/res/raw/apfPcap.pcap b/packages/NetworkStack/tests/res/raw/apfPcap.pcap
similarity index 100%
rename from tests/net/res/raw/apfPcap.pcap
rename to packages/NetworkStack/tests/res/raw/apfPcap.pcap
Binary files differ
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
similarity index 98%
rename from tests/net/java/android/net/apf/ApfTest.java
rename to packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
index 3c3e7ce..f76e412 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
@@ -16,8 +16,6 @@
 
 package android.net.apf;
 
-import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
 import static android.system.OsConstants.AF_UNIX;
 import static android.system.OsConstants.ARPHRD_ETHER;
 import static android.system.OsConstants.ETH_P_ARP;
@@ -28,12 +26,14 @@
 import static android.system.OsConstants.SOCK_STREAM;
 
 import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
@@ -42,10 +42,14 @@
 import android.net.apf.ApfFilter.ApfConfiguration;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
+import android.net.ip.IpClient.IpClientCallbacksWrapper;
 import android.net.ip.IpClientCallbacks;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.RaEvent;
 import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
 import android.os.ConditionVariable;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -57,8 +61,9 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 
-import com.android.frameworks.tests.net.R;
 import com.android.internal.util.HexDump;
+import com.android.server.networkstack.tests.R;
+import com.android.server.util.NetworkStackConstants;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -100,7 +105,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         // Load up native shared library containing APF interpreter exposed via JNI.
-        System.loadLibrary("frameworksnettestsjni");
+        System.loadLibrary("networkstacktestsjni");
     }
 
     private static final String TAG = "ApfTest";
@@ -915,10 +920,14 @@
             HexDump.toHexString(data, false), result);
     }
 
-    private class MockIpClientCallback extends IpClientCallbacks {
+    private class MockIpClientCallback extends IpClientCallbacksWrapper {
         private final ConditionVariable mGotApfProgram = new ConditionVariable();
         private byte[] mLastApfProgram;
 
+        MockIpClientCallback() {
+            super(mock(IIpClientCallbacks.class), mock(SharedLog.class));
+        }
+
         @Override
         public void installPacketFilter(byte[] filter) {
             mLastApfProgram = filter;
@@ -946,7 +955,7 @@
         private final long mFixedTimeMs = SystemClock.elapsedRealtime();
 
         public TestApfFilter(Context context, ApfConfiguration config,
-                IpClientCallbacks ipClientCallback, IpConnectivityLog log) throws Exception {
+                IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception {
             super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
         }
 
@@ -1075,8 +1084,8 @@
     private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
 
     // Helper to initialize a default apfFilter.
-    private ApfFilter setupApfFilter(IpClientCallbacks ipClientCallback, ApfConfiguration config)
-            throws Exception {
+    private ApfFilter setupApfFilter(
+            IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception {
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
@@ -1294,7 +1303,7 @@
 
         // However, we should still let through all other ICMPv6 types.
         ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
-        raPacket.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ROUTER_ADVERTISEMENT);
+        raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
         assertPass(ipClientCallback.getApfProgram(), raPacket.array());
 
         // Now wake up from doze mode to ensure that we no longer drop the packets.
diff --git a/tests/net/java/android/net/apf/Bpf2Apf.java b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
similarity index 100%
rename from tests/net/java/android/net/apf/Bpf2Apf.java
rename to packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java
diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
similarity index 100%
rename from tests/net/java/android/net/dhcp/DhcpPacketTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
similarity index 93%
rename from tests/net/java/android/net/ip/IpClientTest.java
rename to packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
index 5110ce1..f21809f 100644
--- a/tests/net/java/android/net/ip/IpClientTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
@@ -16,10 +16,13 @@
 
 package android.net.ip;
 
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.eq;
@@ -87,7 +90,7 @@
     @Mock private INetworkManagementService mNMService;
     @Mock private INetd mNetd;
     @Mock private Resources mResources;
-    @Mock private IpClientCallbacks mCb;
+    @Mock private IIpClientCallbacks mCb;
     @Mock private AlarmManager mAlarm;
     @Mock private IpClient.Dependencies mDependecies;
     private MockContentResolver mContentResolver;
@@ -209,7 +212,8 @@
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
-                .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface)));
+                .onLinkPropertiesChange(argThat(
+                        lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
     }
 
     @Test
@@ -254,13 +258,15 @@
         mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr]));
         LinkProperties want = linkproperties(links(addresses), routes(prefixes));
         want.setInterfaceName(iface);
-        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(eq(want));
+        verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(argThat(
+                lp -> fromStableParcelable(lp).equals(want)));
 
         ipc.shutdown();
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
-                .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface)));
+                .onLinkPropertiesChange(argThat(
+                        lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface))));
     }
 
     @Test
@@ -492,11 +498,11 @@
         List<String> list3 = Arrays.asList("bar", "baz");
         List<String> list4 = Arrays.asList("foo", "bar", "baz");
 
-        assertTrue(IpClient.all(list1, (x) -> false));
-        assertFalse(IpClient.all(list2, (x) -> false));
-        assertTrue(IpClient.all(list3, (x) -> true));
-        assertTrue(IpClient.all(list2, (x) -> x.charAt(0) == 'f'));
-        assertFalse(IpClient.all(list4, (x) -> x.charAt(0) == 'f'));
+        assertTrue(InitialConfiguration.all(list1, (x) -> false));
+        assertFalse(InitialConfiguration.all(list2, (x) -> false));
+        assertTrue(InitialConfiguration.all(list3, (x) -> true));
+        assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f'));
+        assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f'));
     }
 
     @Test
@@ -506,11 +512,11 @@
         List<String> list3 = Arrays.asList("bar", "baz");
         List<String> list4 = Arrays.asList("foo", "bar", "baz");
 
-        assertFalse(IpClient.any(list1, (x) -> true));
-        assertTrue(IpClient.any(list2, (x) -> true));
-        assertTrue(IpClient.any(list2, (x) -> x.charAt(0) == 'f'));
-        assertFalse(IpClient.any(list3, (x) -> x.charAt(0) == 'f'));
-        assertTrue(IpClient.any(list4, (x) -> x.charAt(0) == 'f'));
+        assertFalse(InitialConfiguration.any(list1, (x) -> true));
+        assertTrue(InitialConfiguration.any(list2, (x) -> true));
+        assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f'));
+        assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f'));
+        assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f'));
     }
 
     @Test
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
similarity index 100%
rename from tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
rename to packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java
diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
similarity index 100%
rename from tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
rename to packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java
diff --git a/tests/net/java/android/net/util/PacketReaderTest.java b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
similarity index 100%
rename from tests/net/java/android/net/util/PacketReaderTest.java
rename to packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index de86789..dbeee1c 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -40,11 +40,8 @@
     <bool name="def_wifi_display_on">false</bool>
     <bool name="def_install_non_market_apps">false</bool>
     <bool name="def_package_verifier_enable">true</bool>
-    <!-- Comma-separated list of location providers.
-         Network location is off by default because it requires
-         user opt-in via Setup Wizard or Settings.
-    -->
-    <string name="def_location_providers_allowed" translatable="false">gps</string>
+    <!-- Comma-separated list of location providers -->
+    <string name="def_location_providers_allowed" translatable="false">gps,network</string>
     <bool name="assisted_gps_enabled">true</bool>
     <bool name="def_netstats_enabled">true</bool>
     <bool name="def_usb_mass_storage_enabled">true</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5105ff4..4453121 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3232,7 +3232,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 172;
+            private static final int SETTINGS_VERSION = 173;
 
             private final int mUserId;
 
@@ -4217,6 +4217,41 @@
                     currentVersion = 172;
                 }
 
+                if (currentVersion == 172) {
+                    // Version 172: Set the default value for Secure Settings: LOCATION_MODE
+
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+
+                    final Setting locationMode = secureSettings.getSettingLocked(
+                            Secure.LOCATION_MODE);
+
+                    if (locationMode.isNull()) {
+                        final Setting locationProvidersAllowed = secureSettings.getSettingLocked(
+                                Secure.LOCATION_PROVIDERS_ALLOWED);
+
+                        String defLocationMode = Integer.toString(
+                                !TextUtils.isEmpty(locationProvidersAllowed.getValue())
+                                        ? Secure.LOCATION_MODE_HIGH_ACCURACY
+                                        : Secure.LOCATION_MODE_OFF);
+                        secureSettings.insertSettingLocked(
+                                Secure.LOCATION_MODE, defLocationMode,
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                        // also reset LOCATION_PROVIDERS_ALLOWED back to the default value - this
+                        // setting is now only for debug/test purposes, and will likely be removed
+                        // in a later release. LocationManagerService is responsible for adjusting
+                        // these settings to the proper state.
+
+                        String defLocationProvidersAllowed = getContext().getResources().getString(
+                                R.string.def_location_providers_allowed);
+                        secureSettings.insertSettingLocked(
+                                Secure.LOCATION_PROVIDERS_ALLOWED, defLocationProvidersAllowed,
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 173;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 2d7471d..a9ff21f 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1962,8 +1962,7 @@
         }
 
         @Override
-        public void onFinished(long durationMs, String title, String description)
-                throws RemoteException {
+        public void onFinished() throws RemoteException {
             // TODO(b/111441001): implement
         }
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d64e2f9..13d27bb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2279,7 +2279,7 @@
          app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
     <string name="heap_dump_tile_name">Dump SysUI Heap</string>
 
-    <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=10] -->
+    <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=12] -->
     <plurals name="ongoing_privacy_chip_multiple_apps">
         <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> app</item>
         <item quantity="few"><xliff:g id="num_apps" example="3">%d</xliff:g> apps</item>
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 77e25e3..26c6d50 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.Dependency
 import com.android.systemui.R
 import com.android.systemui.plugins.ActivityStarter
+import java.util.concurrent.TimeUnit
 
 class OngoingPrivacyDialog constructor(
     val context: Context,
@@ -60,7 +61,8 @@
             setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
             setPositiveButton(R.string.ongoing_privacy_dialog_open_settings,
                     object : DialogInterface.OnClickListener {
-                        val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
+                        val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).putExtra(
+                                Intent.EXTRA_DURATION_MILLIS, TimeUnit.MINUTES.toMillis(1))
 
                         @Suppress("DEPRECATION")
                         override fun onClick(dialog: DialogInterface?, which: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 1155a41..e1becdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -188,7 +188,7 @@
             state.secondaryLabel = r.getString(R.string.status_bar_airplane);
         } else if (mobileDataEnabled) {
             state.state = Tile.STATE_ACTIVE;
-            state.secondaryLabel = getMobileDataDescription(cb);
+            state.secondaryLabel = getMobileDataSubscriptionName(cb);
         } else {
             state.state = Tile.STATE_INACTIVE;
             state.secondaryLabel = r.getString(R.string.cell_data_off);
@@ -207,16 +207,16 @@
         state.contentDescription = state.label + ", " + contentDescriptionSuffix;
     }
 
-    private CharSequence getMobileDataDescription(CallbackInfo cb) {
-        if (cb.roaming && !TextUtils.isEmpty(cb.dataContentDescription)) {
+    private CharSequence getMobileDataSubscriptionName(CallbackInfo cb) {
+        if (cb.roaming && !TextUtils.isEmpty(cb.dataSubscriptionName)) {
             String roaming = mContext.getString(R.string.data_connection_roaming);
-            String dataDescription = cb.dataContentDescription;
+            String dataDescription = cb.dataSubscriptionName.toString();
             return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription);
         }
         if (cb.roaming) {
             return mContext.getString(R.string.data_connection_roaming);
         }
-        return cb.dataContentDescription;
+        return cb.dataSubscriptionName;
     }
 
     @Override
@@ -231,7 +231,7 @@
 
     private static final class CallbackInfo {
         boolean airplaneModeEnabled;
-        String dataContentDescription;
+        CharSequence dataSubscriptionName;
         boolean activityIn;
         boolean activityOut;
         boolean noSim;
@@ -249,7 +249,7 @@
                 // Not data sim, don't display.
                 return;
             }
-            mInfo.dataContentDescription = typeContentDescription;
+            mInfo.dataSubscriptionName = mController.getMobileDataNetworkName();
             mInfo.activityIn = activityIn;
             mInfo.activityOut = activityOut;
             mInfo.roaming = roaming;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index d5f4d04..d2ce31d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -49,6 +49,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 
@@ -130,8 +131,11 @@
                     final int count =
                             getEntryManager().getNotificationData().getActiveNotifications().size();
                     final int rank = getEntryManager().getNotificationData().getRank(notificationKey);
+                    NotificationVisibility.NotificationLocation location =
+                            NotificationLogger.getNotificationLocation(
+                                    getEntryManager().getNotificationData().get(notificationKey));
                     final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
-                            rank, count, true);
+                            rank, count, true, location);
                     try {
                         mBarService.onNotificationClick(notificationKey, nv);
                     } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 7d6231f..31d1621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,6 +54,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -181,7 +182,11 @@
             final int rank = mEntryManager.getNotificationData().getRank(key);
             final Notification.Action action =
                     statusBarNotification.getNotification().actions[actionIndex];
-            final NotificationVisibility nv = NotificationVisibility.obtain(key, rank, count, true);
+            NotificationVisibility.NotificationLocation location =
+                    NotificationLogger.getNotificationLocation(
+                            mEntryManager.getNotificationData().get(key));
+            final NotificationVisibility nv =
+                    NotificationVisibility.obtain(key, rank, count, true, location);
             try {
                 mBarService.onNotificationActionClick(key, buttonIndex, action, nv, false);
             } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 9ef9c94..546b2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -538,6 +538,7 @@
                     - getIntrinsicHeight());
         }
         float viewEnd = viewStart + fullHeight;
+        // TODO: fix this check for anchor scrolling.
         if (expandingAnimated && mAmbientState.getScrollY() == 0
                 && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
             // We are expanding animated. Because we switch to a linear interpolation in this case,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 573c1f8..a2abcd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -23,6 +23,7 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 
 import java.util.Set;
 
@@ -74,8 +75,10 @@
             boolean generatedByAssistant) {
         final int count = mEntryManager.getNotificationData().getActiveNotifications().size();
         final int rank = mEntryManager.getNotificationData().getRank(entry.key);
-        final NotificationVisibility nv =
-                NotificationVisibility.obtain(entry.key, rank, count, true);
+        NotificationVisibility.NotificationLocation location =
+                NotificationLogger.getNotificationLocation(entry);
+        final NotificationVisibility nv = NotificationVisibility.obtain(
+                entry.key, rank, count, true, location);
         try {
             mBarService.onNotificationActionClick(
                     entry.key, actionIndex, action, nv, generatedByAssistant);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 989e781..ef5e936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -36,6 +36,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationInflater;
 import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -160,8 +161,10 @@
     public void performRemoveNotification(StatusBarNotification n) {
         final int rank = mNotificationData.getRank(n.getKey());
         final int count = mNotificationData.getActiveNotifications().size();
+        NotificationVisibility.NotificationLocation location =
+                NotificationLogger.getNotificationLocation(getNotificationData().get(n.getKey()));
         final NotificationVisibility nv = NotificationVisibility.obtain(n.getKey(), rank, count,
-                true);
+                true, location);
         removeNotificationInternal(
                 n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 76d394d..9b1a2aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -32,7 +32,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarStateController;
@@ -40,6 +39,8 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -128,7 +129,8 @@
                 NotificationEntry entry = activeNotifications.get(i);
                 String key = entry.notification.getKey();
                 boolean isVisible = mListContainer.isInVisibleLocation(entry);
-                NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible);
+                NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible,
+                        getNotificationLocation(entry));
                 boolean previouslyVisible = mCurrentlyVisibleNotifications.contains(visObj);
                 if (isVisible) {
                     // Build new set of visible notifications.
@@ -160,6 +162,40 @@
         }
     };
 
+    /**
+     * Returns the location of the notification referenced by the given {@link NotificationEntry}.
+     */
+    public static NotificationVisibility.NotificationLocation getNotificationLocation(
+            NotificationEntry entry) {
+        ExpandableNotificationRow row = entry.getRow();
+        ExpandableViewState childViewState = row.getViewState();
+
+        if (childViewState == null) {
+            return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+        }
+        return convertNotificationLocation(childViewState.location);
+    }
+
+    private static NotificationVisibility.NotificationLocation convertNotificationLocation(
+            int location) {
+        switch (location) {
+            case ExpandableViewState.LOCATION_FIRST_HUN:
+                return NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP;
+            case ExpandableViewState.LOCATION_HIDDEN_TOP:
+                return NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP;
+            case ExpandableViewState.LOCATION_MAIN_AREA:
+                return NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA;
+            case ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING:
+                return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
+            case ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN:
+                return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
+            case ExpandableViewState.LOCATION_GONE:
+                return NotificationVisibility.NotificationLocation.LOCATION_GONE;
+            default:
+                return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+        }
+    }
+
     @Inject
     public NotificationLogger(NotificationListener notificationListener,
             UiOffloadThread uiOffloadThread,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 1b5013d..c246af5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -42,6 +42,8 @@
 
     private ArrayList<ExpandableView> mDraggedViews = new ArrayList<>();
     private int mScrollY;
+    private int mAnchorViewIndex;
+    private int mAnchorViewY;
     private boolean mDimmed;
     private ActivatableNotificationView mActivatedChild;
     private float mOverScrollTopAmount;
@@ -130,6 +132,27 @@
         this.mScrollY = scrollY;
     }
 
+    /**
+     * Index of the child view whose Y position on screen is returned by {@link #getAnchorViewY()}.
+     * Other views are laid out outwards from this view in both directions.
+     */
+    public int getAnchorViewIndex() {
+        return mAnchorViewIndex;
+    }
+
+    public void setAnchorViewIndex(int anchorViewIndex) {
+        mAnchorViewIndex = anchorViewIndex;
+    }
+
+    /** Current Y position of the view at {@link #getAnchorViewIndex()}. */
+    public int getAnchorViewY() {
+        return mAnchorViewY;
+    }
+
+    public void setAnchorViewY(int anchorViewY) {
+        mAnchorViewY = anchorViewY;
+    }
+
     /** Call when dragging begins. */
     public void onBeginDrag(ExpandableView view) {
         mDraggedViews.add(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 0c37666..2129b81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
 import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
@@ -176,10 +177,18 @@
 
     private float mExpandedHeight;
     private int mOwnScrollY;
+    private View mScrollAnchorView;
+    private int mScrollAnchorViewY;
     private int mMaxLayoutHeight;
 
     private VelocityTracker mVelocityTracker;
     private OverScroller mScroller;
+    /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
+    private int mLastScrollerY;
+    /**
+     * True if the max position was set to a known position on the last call to {@link #mScroller}.
+     */
+    private boolean mIsScrollerBoundSet;
     private Runnable mFinishScrollingCallback;
     private int mTouchSlop;
     private int mMinimumVelocity;
@@ -417,7 +426,12 @@
     private int mStatusBarState;
     private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
-    private Runnable mAnimateScroll = this::animateScroll;
+    private Runnable mReflingAndAnimateScroll = () -> {
+        if (ANCHOR_SCROLLING) {
+            maybeReflingScroller();
+        }
+        animateScroll();
+    };
     private int mCornerRadius;
     private int mSidePaddings;
     private final Rect mBackgroundAnimationRect = new Rect();
@@ -511,6 +525,7 @@
             mDebugPaint.setColor(0xffff0000);
             mDebugPaint.setStrokeWidth(2);
             mDebugPaint.setStyle(Paint.Style.STROKE);
+            mDebugPaint.setTextSize(25f);
         }
         mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
 
@@ -674,6 +689,30 @@
         }
     }
 
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        if (DEBUG && ANCHOR_SCROLLING) {
+            if (mScrollAnchorView instanceof ExpandableNotificationRow) {
+                canvas.drawRect(0,
+                        mScrollAnchorView.getTranslationY(),
+                        getWidth(),
+                        mScrollAnchorView.getTranslationY()
+                                + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(),
+                        mDebugPaint);
+                canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200,
+                        mScrollAnchorView.getTranslationY() + 30, mDebugPaint);
+                int y = (int) mShelf.getTranslationY();
+                canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+            }
+            canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100,
+                    getIntrinsicPadding() + 30, mDebugPaint);
+            canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100,
+                    getHeight() - 30, mDebugPaint);
+        }
+    }
+
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
     private void drawBackground(Canvas canvas) {
         int lockScreenLeft = mSidePaddings;
@@ -970,7 +1009,12 @@
         mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
                 ? 0
                 : mScroller.getCurrVelocity());
-        mAmbientState.setScrollY(mOwnScrollY);
+        if (ANCHOR_SCROLLING) {
+            mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView));
+            mAmbientState.setAnchorViewY(mScrollAnchorViewY);
+        } else {
+            mAmbientState.setScrollY(mOwnScrollY);
+        }
         mStackScrollAlgorithm.resetViewStates(mAmbientState);
         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
             applyCurrentState();
@@ -1004,7 +1048,7 @@
             float end = start + child.getActualHeight();
             boolean clip = clipStart > start && clipStart < end
                     || clipEnd >= start && clipEnd <= end;
-            clip &= !(first && mOwnScrollY == 0);
+            clip &= !(first && isScrolledToTop());
             child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
                     : ExpandableView.NO_ROUNDNESS);
             first = false;
@@ -1016,19 +1060,21 @@
         if (mChildrenToAddAnimated.isEmpty()) {
             return;
         }
-        for (int i = 0; i < getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
-            if (mChildrenToAddAnimated.contains(child)) {
-                int startingPosition = getPositionInLinearLayout(child);
-                float increasedPaddingAmount = child.getIncreasedPaddingAmount();
-                int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
-                        : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
-                int childHeight = getIntrinsicHeight(child) + padding;
-                if (startingPosition < mOwnScrollY) {
-                    // This child starts off screen, so let's keep it offscreen to keep the
-                    // others visible
+        if (!ANCHOR_SCROLLING) {
+            for (int i = 0; i < getChildCount(); i++) {
+                ExpandableView child = (ExpandableView) getChildAt(i);
+                if (mChildrenToAddAnimated.contains(child)) {
+                    int startingPosition = getPositionInLinearLayout(child);
+                    float increasedPaddingAmount = child.getIncreasedPaddingAmount();
+                    int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
+                            : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
+                    int childHeight = getIntrinsicHeight(child) + padding;
+                    if (startingPosition < mOwnScrollY) {
+                        // This child starts off screen, so let's keep it offscreen to keep the
+                        // others visible
 
-                    setOwnScrollY(mOwnScrollY + childHeight);
+                        setOwnScrollY(mOwnScrollY + childHeight);
+                    }
                 }
             }
         }
@@ -1047,12 +1093,16 @@
             int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
             int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
 
-            targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
+            if (ANCHOR_SCROLLING) {
+                // TODO
+            } else {
+                targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
 
-            // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
-            // that it is not visible anymore.
-            if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
-                setOwnScrollY(targetScroll);
+                // Only apply the scroll if we're scrolling the view upwards, or the view is so
+                // far up that it is not visible anymore.
+                if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+                    setOwnScrollY(targetScroll);
+                }
             }
         }
     }
@@ -1073,9 +1123,13 @@
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void clampScrollPosition() {
-        int scrollRange = getScrollRange();
-        if (scrollRange < mOwnScrollY) {
-            setOwnScrollY(scrollRange);
+        if (ANCHOR_SCROLLING) {
+            // TODO
+        } else {
+            int scrollRange = getScrollRange();
+            if (scrollRange < mOwnScrollY) {
+                setOwnScrollY(scrollRange);
+            }
         }
     }
 
@@ -1453,17 +1507,21 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean scrollTo(View v) {
         ExpandableView expandableView = (ExpandableView) v;
-        int positionInLinearLayout = getPositionInLinearLayout(v);
-        int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
-        int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
+        if (ANCHOR_SCROLLING) {
+            // TODO
+        } else {
+            int positionInLinearLayout = getPositionInLinearLayout(v);
+            int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
+            int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
 
-        // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
-        // that it is not visible anymore.
-        if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
-            mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
-            mDontReportNextOverScroll = true;
-            animateScroll();
-            return true;
+            // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
+            // that it is not visible anymore.
+            if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
+                mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
+                mDontReportNextOverScroll = true;
+                animateScroll();
+                return true;
+            }
         }
         return false;
     }
@@ -1484,16 +1542,20 @@
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mBottomInset = insets.getSystemWindowInsetBottom();
 
-        int range = getScrollRange();
-        if (mOwnScrollY > range) {
-            // HACK: We're repeatedly getting staggered insets here while the IME is
-            // animating away. To work around that we'll wait until things have settled.
-            removeCallbacks(mReclamp);
-            postDelayed(mReclamp, 50);
-        } else if (mForcedScroll != null) {
-            // The scroll was requested before we got the actual inset - in case we need
-            // to scroll up some more do so now.
-            scrollTo(mForcedScroll);
+        if (ANCHOR_SCROLLING) {
+            // TODO
+        } else {
+            int range = getScrollRange();
+            if (mOwnScrollY > range) {
+                // HACK: We're repeatedly getting staggered insets here while the IME is
+                // animating away. To work around that we'll wait until things have settled.
+                removeCallbacks(mReclamp);
+                postDelayed(mReclamp, 50);
+            } else if (mForcedScroll != null) {
+                // The scroll was requested before we got the actual inset - in case we need
+                // to scroll up some more do so now.
+                scrollTo(mForcedScroll);
+            }
         }
         return insets;
     }
@@ -1502,8 +1564,12 @@
     private Runnable mReclamp = new Runnable() {
         @Override
         public void run() {
-            int range = getScrollRange();
-            mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
+            if (ANCHOR_SCROLLING) {
+                // TODO
+            } else {
+                int range = getScrollRange();
+                mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
+            }
             mDontReportNextOverScroll = true;
             mDontClampNextScroll = true;
             animateScroll();
@@ -1581,20 +1647,39 @@
         }
         // Top overScroll might not grab all scrolling motion,
         // we have to scroll as well.
-        float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
-        float newScrollY = mOwnScrollY + scrollAmount;
-        if (newScrollY > range) {
-            if (!mExpandedInThisMotion) {
-                float currentBottomPixels = getCurrentOverScrolledPixels(false);
-                // We overScroll on the top
-                setOverScrolledPixels(currentBottomPixels + newScrollY - range,
-                        false /* onTop */,
-                        false /* animate */);
+        if (ANCHOR_SCROLLING) {
+            float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
+            // TODO: once we're recycling this will need to check the adapter position of the child
+            ExpandableView lastRow = getLastRowNotGone();
+            if (lastRow != null && !lastRow.isInShelf()) {
+                float distanceToMax = Math.max(0, getMaxPositiveScrollAmount());
+                if (scrollAmount > distanceToMax) {
+                    float currentBottomPixels = getCurrentOverScrolledPixels(false);
+                    // We overScroll on the bottom
+                    setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax),
+                            false /* onTop */,
+                            false /* animate */);
+                    mScrollAnchorViewY -= distanceToMax;
+                    scrollAmount = 0f;
+                }
             }
-            setOwnScrollY(range);
-            scrollAmount = 0.0f;
+            return scrollAmount;
+        } else {
+            float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
+            float newScrollY = mOwnScrollY + scrollAmount;
+            if (newScrollY > range) {
+                if (!mExpandedInThisMotion) {
+                    float currentBottomPixels = getCurrentOverScrolledPixels(false);
+                    // We overScroll on the bottom
+                    setOverScrolledPixels(currentBottomPixels + newScrollY - range,
+                            false /* onTop */,
+                            false /* animate */);
+                }
+                setOwnScrollY(range);
+                scrollAmount = 0.0f;
+            }
+            return scrollAmount;
         }
-        return scrollAmount;
     }
 
     /**
@@ -1615,18 +1700,37 @@
         }
         // Bottom overScroll might not grab all scrolling motion,
         // we have to scroll as well.
-        float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
-        float newScrollY = mOwnScrollY + scrollAmount;
-        if (newScrollY < 0) {
-            float currentTopPixels = getCurrentOverScrolledPixels(true);
-            // We overScroll on the top
-            setOverScrolledPixels(currentTopPixels - newScrollY,
-                    true /* onTop */,
-                    false /* animate */);
-            setOwnScrollY(0);
-            scrollAmount = 0.0f;
+        if (ANCHOR_SCROLLING) {
+            float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
+            // TODO: once we're recycling this will need to check the adapter position of the child
+            ExpandableView firstChild = getFirstChildNotGone();
+            float top = firstChild.getTranslationY();
+            float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY;
+            if (distanceToTop < -scrollAmount) {
+                float currentTopPixels = getCurrentOverScrolledPixels(true);
+                // We overScroll on the top
+                setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop),
+                        true /* onTop */,
+                        false /* animate */);
+                mScrollAnchorView = firstChild;
+                mScrollAnchorViewY = 0;
+                scrollAmount = 0f;
+            }
+            return scrollAmount;
+        } else {
+            float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
+            float newScrollY = mOwnScrollY + scrollAmount;
+            if (newScrollY < 0) {
+                float currentTopPixels = getCurrentOverScrolledPixels(true);
+                // We overScroll on the top
+                setOverScrolledPixels(currentTopPixels - newScrollY,
+                        true /* onTop */,
+                        false /* animate */);
+                setOwnScrollY(0);
+                scrollAmount = 0.0f;
+            }
+            return scrollAmount;
         }
-        return scrollAmount;
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -1661,26 +1765,43 @@
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void animateScroll() {
         if (mScroller.computeScrollOffset()) {
-            int oldY = mOwnScrollY;
-            int y = mScroller.getCurrY();
-
-            if (oldY != y) {
-                int range = getScrollRange();
-                if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
-                    float currVelocity = mScroller.getCurrVelocity();
-                    if (currVelocity >= mMinimumVelocity) {
-                        mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
+            if (ANCHOR_SCROLLING) {
+                int oldY = mLastScrollerY;
+                int y = mScroller.getCurrY();
+                int deltaY = y - oldY;
+                if (deltaY != 0) {
+                    int maxNegativeScrollAmount = getMaxNegativeScrollAmount();
+                    int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
+                    if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount)
+                            || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) {
+                        // This frame takes us into overscroll, so set the max overscroll based on
+                        // the current velocity
+                        setMaxOverScrollFromCurrentVelocity();
                     }
+                    customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll);
+                    mLastScrollerY = y;
                 }
+            } else {
+                int oldY = mOwnScrollY;
+                int y = mScroller.getCurrY();
 
-                if (mDontClampNextScroll) {
-                    range = Math.max(range, oldY);
+                if (oldY != y) {
+                    int range = getScrollRange();
+                    if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
+                        // This frame takes us into overscroll, so set the max overscroll based on
+                        // the current velocity
+                        setMaxOverScrollFromCurrentVelocity();
+                    }
+
+                    if (mDontClampNextScroll) {
+                        range = Math.max(range, oldY);
+                    }
+                    customOverScrollBy(y - oldY, oldY, range,
+                            (int) (mMaxOverScroll));
                 }
-                customOverScrollBy(y - oldY, oldY, range,
-                        (int) (mMaxOverScroll));
             }
 
-            postOnAnimation(mAnimateScroll);
+            postOnAnimation(mReflingAndAnimateScroll);
         } else {
             mDontClampNextScroll = false;
             if (mFinishScrollingCallback != null) {
@@ -1689,26 +1810,67 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
-    private boolean customOverScrollBy(int deltaY, int scrollY, int scrollRangeY,
-            int maxOverScrollY) {
-
-        int newScrollY = scrollY + deltaY;
-        final int top = -maxOverScrollY;
-        final int bottom = maxOverScrollY + scrollRangeY;
-
-        boolean clampedY = false;
-        if (newScrollY > bottom) {
-            newScrollY = bottom;
-            clampedY = true;
-        } else if (newScrollY < top) {
-            newScrollY = top;
-            clampedY = true;
+    private void setMaxOverScrollFromCurrentVelocity() {
+        float currVelocity = mScroller.getCurrVelocity();
+        if (currVelocity >= mMinimumVelocity) {
+            mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
         }
+    }
 
-        onCustomOverScrolled(newScrollY, clampedY);
+    /**
+     * Scrolls by the given delta, overscrolling if needed.  If called during a fling and the delta
+     * would cause us to exceed the provided maximum overscroll, springs back instead.
+     *
+     * This method performs the determination of whether we're exceeding the overscroll and clamps
+     * the scroll amount if so.  The actual scrolling/overscrolling happens in
+     * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or
+     * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling).
+     *
+     * @param deltaY         The (signed) number of pixels to scroll.
+     * @param scrollY        The current scroll position (absolute scrolling only).
+     * @param scrollRangeY   The maximum allowable scroll position (absolute scrolling only).
+     * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by.
+     */
+    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
+    private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) {
+        if (ANCHOR_SCROLLING) {
+            boolean clampedY = false;
+            if (deltaY < 0) {
+                int maxScrollAmount = getMaxNegativeScrollAmount();
+                if (maxScrollAmount > Integer.MIN_VALUE) {
+                    maxScrollAmount -= maxOverScrollY;
+                    if (deltaY < maxScrollAmount) {
+                        deltaY = maxScrollAmount;
+                        clampedY = true;
+                    }
+                }
+            } else {
+                int maxScrollAmount = getMaxPositiveScrollAmount();
+                if (maxScrollAmount < Integer.MAX_VALUE) {
+                    maxScrollAmount += maxOverScrollY;
+                    if (deltaY > maxScrollAmount) {
+                        deltaY = maxScrollAmount;
+                        clampedY = true;
+                    }
+                }
+            }
+            onCustomOverScrolledBy(deltaY, clampedY);
+        } else {
+            int newScrollY = scrollY + deltaY;
+            final int top = -maxOverScrollY;
+            final int bottom = maxOverScrollY + scrollRangeY;
 
-        return clampedY;
+            boolean clampedY = false;
+            if (newScrollY > bottom) {
+                newScrollY = bottom;
+                clampedY = true;
+            } else if (newScrollY < top) {
+                newScrollY = top;
+                clampedY = true;
+            }
+
+            onCustomOverScrolled(newScrollY, clampedY);
+        }
     }
 
     /**
@@ -1826,8 +1988,46 @@
         }
     }
 
+    /**
+     * Scrolls by the given delta, overscrolling if needed.  If called during a fling and the delta
+     * would cause us to exceed the provided maximum overscroll, springs back instead.
+     *
+     * @param deltaY   The (signed) number of pixels to scroll.
+     * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
+     *                 the overscroll limit.
+     */
+    private void onCustomOverScrolledBy(int deltaY, boolean clampedY) {
+        assert ANCHOR_SCROLLING;
+        mScrollAnchorViewY -= deltaY;
+        // Treat animating scrolls differently; see #computeScroll() for why.
+        if (!mScroller.isFinished()) {
+            if (clampedY) {
+                springBack();
+            } else {
+                float overScrollTop = getCurrentOverScrollAmount(true /* top */);
+                if (isScrolledToTop() && mScrollAnchorViewY > 0) {
+                    notifyOverscrollTopListener(mScrollAnchorViewY,
+                            isRubberbanded(true /* onTop */));
+                } else {
+                    notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */));
+                }
+            }
+        }
+        updateScrollAnchor();
+        updateOnScrollChange();
+    }
+
+    /**
+     * Scrolls to the given position, overscrolling if needed.  If called during a fling and the
+     * position exceeds the provided maximum overscroll, springs back instead.
+     *
+     * @param scrollY The target scroll position.
+     * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
+     *                 the overscroll limit.
+     */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void onCustomOverScrolled(int scrollY, boolean clampedY) {
+        assert !ANCHOR_SCROLLING;
         // Treat animating scrolls differently; see #computeScroll() for why.
         if (!mScroller.isFinished()) {
             setOwnScrollY(scrollY);
@@ -1846,27 +2046,51 @@
         }
     }
 
+    /**
+     * Springs back from an overscroll by stopping the {@link #mScroller} and animating the
+     * overscroll amount back to zero.
+     */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void springBack() {
-        int scrollRange = getScrollRange();
-        boolean overScrolledTop = mOwnScrollY <= 0;
-        boolean overScrolledBottom = mOwnScrollY >= scrollRange;
-        if (overScrolledTop || overScrolledBottom) {
-            boolean onTop;
-            float newAmount;
-            if (overScrolledTop) {
-                onTop = true;
-                newAmount = -mOwnScrollY;
-                setOwnScrollY(0);
-                mDontReportNextOverScroll = true;
-            } else {
-                onTop = false;
-                newAmount = mOwnScrollY - scrollRange;
-                setOwnScrollY(scrollRange);
+        if (ANCHOR_SCROLLING) {
+            boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0;
+            int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
+            boolean overscrolledBottom = maxPositiveScrollAmount < 0;
+            if (overScrolledTop || overscrolledBottom) {
+                float newAmount;
+                if (overScrolledTop) {
+                    newAmount = mScrollAnchorViewY;
+                    mScrollAnchorViewY = 0;
+                    mDontReportNextOverScroll = true;
+                } else {
+                    newAmount = -maxPositiveScrollAmount;
+                    mScrollAnchorViewY -= maxPositiveScrollAmount;
+                }
+                setOverScrollAmount(newAmount, overScrolledTop, false);
+                setOverScrollAmount(0.0f, overScrolledTop, true);
+                mScroller.forceFinished(true);
             }
-            setOverScrollAmount(newAmount, onTop, false);
-            setOverScrollAmount(0.0f, onTop, true);
-            mScroller.forceFinished(true);
+        } else {
+            int scrollRange = getScrollRange();
+            boolean overScrolledTop = mOwnScrollY <= 0;
+            boolean overScrolledBottom = mOwnScrollY >= scrollRange;
+            if (overScrolledTop || overScrolledBottom) {
+                boolean onTop;
+                float newAmount;
+                if (overScrolledTop) {
+                    onTop = true;
+                    newAmount = -mOwnScrollY;
+                    setOwnScrollY(0);
+                    mDontReportNextOverScroll = true;
+                } else {
+                    onTop = false;
+                    newAmount = mOwnScrollY - scrollRange;
+                    setOwnScrollY(scrollRange);
+                }
+                setOverScrollAmount(newAmount, onTop, false);
+                setOverScrollAmount(0.0f, onTop, true);
+                mScroller.forceFinished(true);
+            }
         }
     }
 
@@ -1971,6 +2195,17 @@
         return null;
     }
 
+    private ExpandableNotificationRow getLastRowNotGone() {
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
+                return (ExpandableNotificationRow) child;
+            }
+        }
+        return null;
+    }
+
     /**
      * @return the number of children which have visibility unequal to GONE
      */
@@ -2081,8 +2316,8 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void updateForwardAndBackwardScrollability() {
-        boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange();
-        boolean backwardsScrollable = mScrollable && mOwnScrollY > 0;
+        boolean forwardScrollable = mScrollable && !isScrolledToBottom();
+        boolean backwardsScrollable = mScrollable && !isScrolledToTop();
         boolean changed = forwardScrollable != mForwardScrollable
                 || backwardsScrollable != mBackwardScrollable;
         mForwardScrollable = forwardScrollable;
@@ -2403,18 +2638,24 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void fling(int velocityY) {
         if (getChildCount() > 0) {
-            int scrollRange = getScrollRange();
-
             float topAmount = getCurrentOverScrollAmount(true);
             float bottomAmount = getCurrentOverScrollAmount(false);
             if (velocityY < 0 && topAmount > 0) {
-                setOwnScrollY(mOwnScrollY - (int) topAmount);
+                if (ANCHOR_SCROLLING) {
+                    mScrollAnchorViewY += topAmount;
+                } else {
+                    setOwnScrollY(mOwnScrollY - (int) topAmount);
+                }
                 mDontReportNextOverScroll = true;
                 setOverScrollAmount(0, true, false);
                 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
                         * mOverflingDistance + topAmount;
             } else if (velocityY > 0 && bottomAmount > 0) {
-                setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+                if (ANCHOR_SCROLLING) {
+                    mScrollAnchorViewY -= bottomAmount;
+                } else {
+                    setOwnScrollY((int) (mOwnScrollY + bottomAmount));
+                }
                 setOverScrollAmount(0, false, false);
                 mMaxOverScroll = Math.abs(velocityY) / 1000f
                         * getRubberBandFactor(false /* onTop */) * mOverflingDistance
@@ -2423,18 +2664,138 @@
                 // it will be set once we reach the boundary
                 mMaxOverScroll = 0.0f;
             }
-            int minScrollY = Math.max(0, scrollRange);
-            if (mExpandedInThisMotion) {
-                minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
+            if (ANCHOR_SCROLLING) {
+                flingScroller(velocityY);
+            } else {
+                int scrollRange = getScrollRange();
+                int minScrollY = Math.max(0, scrollRange);
+                if (mExpandedInThisMotion) {
+                    minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
+                }
+                mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
+                        mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
             }
-            mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
-                    mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
 
             animateScroll();
         }
     }
 
     /**
+     * Flings the overscroller with the given velocity (anchor-based scrolling).
+     *
+     * Because anchor-based scrolling can't track the current scroll position, the overscroller is
+     * always started at startY = 0, and we interpret the positions it computes as relative to the
+     * start of the scroll.
+     */
+    private void flingScroller(int velocityY) {
+        assert ANCHOR_SCROLLING;
+        mIsScrollerBoundSet = false;
+        maybeFlingScroller(velocityY, true /* always fling */);
+    }
+
+    private void maybeFlingScroller(int velocityY, boolean alwaysFling) {
+        assert ANCHOR_SCROLLING;
+        // Attempt to determine the maximum amount to scroll before we reach the end.
+        // If the first view is not materialized (for an upwards scroll) or the last view is either
+        // not materialized or is pinned to the shade (for a downwards scroll), we don't know this
+        // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update
+        // the scroller once we approach the start/end of the list.
+        int minY = Integer.MIN_VALUE;
+        int maxY = Integer.MAX_VALUE;
+        if (velocityY < 0) {
+            minY = getMaxNegativeScrollAmount();
+            if (minY > Integer.MIN_VALUE) {
+                mIsScrollerBoundSet = true;
+            }
+        } else {
+            maxY = getMaxPositiveScrollAmount();
+            if (maxY < Integer.MAX_VALUE) {
+                mIsScrollerBoundSet = true;
+            }
+        }
+        if (mIsScrollerBoundSet || alwaysFling) {
+            mLastScrollerY = 0;
+            // x velocity is set to 1 to avoid overscroller bug
+            mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0,
+                    mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
+        }
+    }
+
+    /**
+     * Returns the maximum number of pixels we can scroll in the positive direction (downwards)
+     * before reaching the bottom of the list (discounting overscroll).
+     *
+     * If the return value is negative then we have overscrolled; this is a transient state which
+     * should immediately be handled by adjusting the anchor position and adding the extra space to
+     * the bottom overscroll amount.
+     *
+     * If we don't know how many pixels we have left to scroll (because the last row has not been
+     * materialized, or it's in the shelf so it doesn't have its "natural" position), we return
+     * {@link Integer#MAX_VALUE}.
+     */
+    private int getMaxPositiveScrollAmount() {
+        assert ANCHOR_SCROLLING;
+        // TODO: once we're recycling we need to check the adapter position of the last child.
+        ExpandableNotificationRow lastRow = getLastRowNotGone();
+        if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) {
+            // distance from bottom of last child to bottom of notifications area is:
+            // distance from bottom of last child
+            return (int) (lastRow.getTranslationY() + lastRow.getActualHeight()
+                    // to top of anchor view
+                    - mScrollAnchorView.getTranslationY()
+                    // plus distance from anchor view to top of notifications area
+                    + mScrollAnchorViewY
+                    // minus height of notifications area.
+                    - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight()));
+        } else {
+            return Integer.MAX_VALUE;
+        }
+    }
+
+    /**
+     * Returns the maximum number of pixels (as a negative number) we can scroll in the negative
+     * direction (upwards) before reaching the top of the list (discounting overscroll).
+     *
+     * If the return value is positive then we have overscrolled; this is a transient state which
+     * should immediately be handled by adjusting the anchor position and adding the extra space to
+     * the top overscroll amount.
+     *
+     * If we don't know how many pixels we have left to scroll (because the first row has not been
+     * materialized), we return {@link Integer#MIN_VALUE}.
+     */
+    private int getMaxNegativeScrollAmount() {
+        assert ANCHOR_SCROLLING;
+        // TODO: once we're recycling we need to check the adapter position of the first child.
+        ExpandableView firstChild = getFirstChildNotGone();
+        if (mScrollAnchorView != null && firstChild != null) {
+            // distance from top of first child to top of notifications area is:
+            // distance from top of anchor view
+            return (int) -(mScrollAnchorView.getTranslationY()
+                    // to top of first child
+                    - firstChild.getTranslationY()
+                    // minus distance from top of anchor view to top of notifications area.
+                    - mScrollAnchorViewY);
+        } else {
+            return Integer.MIN_VALUE;
+        }
+    }
+
+    /**
+     * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view
+     * not being materialized or being pinned to the shelf, we need to check on every frame if we're
+     * able to set the bounds.  If we are, we fling the scroller again with the newly computed
+     * bounds.
+     */
+    private void maybeReflingScroller() {
+        if (!mIsScrollerBoundSet) {
+            // Because mScroller is a flywheel scroller, we fling with the minimum possible
+            // velocity to establish direction, so as not to perceptibly affect the velocity.
+            maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()),
+                    false /* alwaysFling */);
+        }
+    }
+
+    /**
      * @return Whether a fling performed on the top overscroll edge lead to the expanded
      * overScroll view (i.e QS).
      */
@@ -2485,25 +2846,10 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public int getFirstChildIntrinsicHeight() {
-        final ExpandableView firstChild = getFirstChildNotGone();
-        int firstChildMinHeight = firstChild != null
-                ? firstChild.getIntrinsicHeight()
-                : mEmptyShadeView != null
-                        ? mEmptyShadeView.getIntrinsicHeight()
-                        : mCollapsedSize;
-        if (mOwnScrollY > 0) {
-            firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
-        }
-        return firstChildMinHeight;
-    }
-
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getTopPaddingOverflow() {
         return mTopPaddingOverflow;
     }
 
-
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getPeekHeight() {
         final ExpandableView firstChild = getFirstChildNotGone();
@@ -2711,30 +3057,51 @@
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
-        int startingPosition = getPositionInLinearLayout(removedChild);
-        float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
-        int padding;
-        if (increasedPaddingAmount >= 0) {
-            padding = (int) NotificationUtils.interpolate(
-                    mPaddingBetweenElements,
-                    mIncreasedPaddingBetweenElements,
-                    increasedPaddingAmount);
+        if (ANCHOR_SCROLLING) {
+            if (removedChild == mScrollAnchorView) {
+                ExpandableView firstChild = getFirstChildNotGone();
+                if (firstChild != null) {
+                    mScrollAnchorView = firstChild;
+                } else {
+                    mScrollAnchorView = mShelf;
+                }
+                // Adjust anchor view Y by the distance between the old and new anchors
+                // so that there's no visible change.
+                mScrollAnchorViewY +=
+                        mScrollAnchorView.getTranslationY() - removedChild.getTranslationY();
+            }
+            updateScrollAnchor();
+            // TODO: once we're recycling this will need to check the adapter position of the child
+            if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) {
+                mScrollAnchorViewY = 0;
+            }
+            updateOnScrollChange();
         } else {
-            padding = (int) NotificationUtils.interpolate(
-                    0,
-                    mPaddingBetweenElements,
-                    1.0f + increasedPaddingAmount);
-        }
-        int childHeight = getIntrinsicHeight(removedChild) + padding;
-        int endPosition = startingPosition + childHeight;
-        if (endPosition <= mOwnScrollY) {
-            // This child is fully scrolled of the top, so we have to deduct its height from the
-            // scrollPosition
-            setOwnScrollY(mOwnScrollY - childHeight);
-        } else if (startingPosition < mOwnScrollY) {
-            // This child is currently being scrolled into, set the scroll position to the start of
-            // this child
-            setOwnScrollY(startingPosition);
+            int startingPosition = getPositionInLinearLayout(removedChild);
+            float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
+            int padding;
+            if (increasedPaddingAmount >= 0) {
+                padding = (int) NotificationUtils.interpolate(
+                        mPaddingBetweenElements,
+                        mIncreasedPaddingBetweenElements,
+                        increasedPaddingAmount);
+            } else {
+                padding = (int) NotificationUtils.interpolate(
+                        0,
+                        mPaddingBetweenElements,
+                        1.0f + increasedPaddingAmount);
+            }
+            int childHeight = getIntrinsicHeight(removedChild) + padding;
+            int endPosition = startingPosition + childHeight;
+            if (endPosition <= mOwnScrollY) {
+                // This child is fully scrolled of the top, so we have to deduct its height from the
+                // scrollPosition
+                setOwnScrollY(mOwnScrollY - childHeight);
+            } else if (startingPosition < mOwnScrollY) {
+                // This child is currently being scrolled into, set the scroll position to the
+                // start of this child
+                setOwnScrollY(startingPosition);
+            }
         }
     }
 
@@ -2888,6 +3255,14 @@
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
+        if (ANCHOR_SCROLLING) {
+            // TODO: once we're recycling this will need to check the adapter position of the child
+            if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
+                // New child was added at the top while we're scrolled to the top;
+                // make it the new anchor view so that we stay at the top.
+                mScrollAnchorView = child;
+            }
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -3381,17 +3756,24 @@
                         final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
                         if (vscroll != 0) {
                             final int delta = (int) (vscroll * getVerticalScrollFactor());
-                            final int range = getScrollRange();
-                            int oldScrollY = mOwnScrollY;
-                            int newScrollY = oldScrollY - delta;
-                            if (newScrollY < 0) {
-                                newScrollY = 0;
-                            } else if (newScrollY > range) {
-                                newScrollY = range;
-                            }
-                            if (newScrollY != oldScrollY) {
-                                setOwnScrollY(newScrollY);
-                                return true;
+                            if (ANCHOR_SCROLLING) {
+                                mScrollAnchorViewY -= delta;
+                                updateScrollAnchor();
+                                clampScrollPosition();
+                                updateOnScrollChange();
+                            } else {
+                                final int range = getScrollRange();
+                                int oldScrollY = mOwnScrollY;
+                                int newScrollY = oldScrollY - delta;
+                                if (newScrollY < 0) {
+                                    newScrollY = 0;
+                                } else if (newScrollY > range) {
+                                    newScrollY = range;
+                                }
+                                if (newScrollY != oldScrollY) {
+                                    setOwnScrollY(newScrollY);
+                                    return true;
+                                }
                             }
                         }
                     }
@@ -3459,12 +3841,16 @@
                 if (mIsBeingDragged) {
                     // Scroll to follow the motion event
                     mLastMotionY = y;
-                    int range = getScrollRange();
-                    if (mExpandedInThisMotion) {
-                        range = Math.min(range, mMaxScrollAfterExpand);
-                    }
-
                     float scrollAmount;
+                    int range;
+                    if (ANCHOR_SCROLLING) {
+                        range = 0;  // unused in the methods it's being passed to
+                    } else {
+                        range = getScrollRange();
+                        if (mExpandedInThisMotion) {
+                            range = Math.min(range, mMaxScrollAfterExpand);
+                        }
+                    }
                     if (deltaY < 0) {
                         scrollAmount = overScrollDown(deltaY);
                     } else {
@@ -3501,9 +3887,13 @@
                                     onOverScrollFling(false, initialVelocity);
                                 }
                             } else {
-                                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
-                                        getScrollRange())) {
-                                    animateScroll();
+                                if (ANCHOR_SCROLLING) {
+                                    // TODO
+                                } else {
+                                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+                                            getScrollRange())) {
+                                        animateScroll();
+                                    }
                                 }
                             }
                         }
@@ -3515,8 +3905,13 @@
                 break;
             case MotionEvent.ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
-                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
-                        animateScroll();
+                    if (ANCHOR_SCROLLING) {
+                        // TODO
+                    } else {
+                        if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
+                                getScrollRange())) {
+                            animateScroll();
+                        }
                     }
                     mActivePointerId = INVALID_POINTER;
                     endDrag();
@@ -3585,12 +3980,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
-    private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
-        ev.offsetLocation(sourceView.getX(), sourceView.getY());
-        ev.offsetLocation(-targetView.getX(), -targetView.getY());
-    }
-
     @Override
     @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -3763,8 +4152,12 @@
                 setIsBeingDragged(false);
                 mActivePointerId = INVALID_POINTER;
                 recycleVelocityTracker();
-                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
-                    animateScroll();
+                if (ANCHOR_SCROLLING) {
+                    // TODO
+                } else {
+                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
+                        animateScroll();
+                    }
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -3839,14 +4232,20 @@
             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
                 // fall through
             case android.R.id.accessibilityActionScrollUp:
-                final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
-                        - mShelf.getIntrinsicHeight();
-                final int targetScrollY = Math.max(0,
-                        Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
-                if (targetScrollY != mOwnScrollY) {
-                    mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY);
-                    animateScroll();
-                    return true;
+                if (ANCHOR_SCROLLING) {
+                    // TODO
+                } else {
+                    final int viewportHeight =
+                            getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
+                                    - mShelf.getIntrinsicHeight();
+                    final int targetScrollY = Math.max(0,
+                            Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
+                    if (targetScrollY != mOwnScrollY) {
+                        mScroller.startScroll(mScrollX, mOwnScrollY, 0,
+                                targetScrollY - mOwnScrollY);
+                        animateScroll();
+                        return true;
+                    }
                 }
                 break;
         }
@@ -3905,13 +4304,23 @@
     @Override
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public boolean isScrolledToTop() {
-        return mOwnScrollY == 0;
+        if (ANCHOR_SCROLLING) {
+            updateScrollAnchor();
+            // TODO: once we're recycling this will need to check the adapter position of the child
+            return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
+        } else {
+            return mOwnScrollY == 0;
+        }
     }
 
     @Override
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public boolean isScrolledToBottom() {
-        return mOwnScrollY >= getScrollRange();
+        if (ANCHOR_SCROLLING) {
+            return getMaxPositiveScrollAmount() <= 0;
+        } else {
+            return mOwnScrollY >= getScrollRange();
+        }
     }
 
     @Override
@@ -3953,7 +4362,7 @@
         resetCheckSnoozeLeavebehind();
         mAmbientState.setExpansionChanging(false);
         if (!mIsExpanded) {
-            setOwnScrollY(0);
+            resetScrollPosition();
             mStatusBar.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
@@ -4012,7 +4421,14 @@
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void resetScrollPosition() {
         mScroller.abortAnimation();
-        setOwnScrollY(0);
+        if (ANCHOR_SCROLLING) {
+            // TODO: once we're recycling this will need to modify the adapter position instead
+            mScrollAnchorView = getFirstChildNotGone();
+            mScrollAnchorViewY = 0;
+            updateOnScrollChange();
+        } else {
+            setOwnScrollY(0);
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -4081,6 +4497,7 @@
     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
         if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            // TODO: once we're recycling this will need to check the adapter position of the child
             if (row.isUserLocked() && row != getFirstChildNotGone()) {
                 if (row.isSummaryWithChildren()) {
                     return;
@@ -4098,7 +4515,13 @@
                     layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
                 }
                 if (endPosition > layoutEnd) {
-                    setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+                    if (ANCHOR_SCROLLING) {
+                        mScrollAnchorViewY -= (endPosition - layoutEnd);
+                        updateScrollAnchor();
+                        updateOnScrollChange();
+                    } else {
+                        setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
+                    }
                     mDisallowScrollingInThisMotion = true;
                 }
             }
@@ -4663,9 +5086,13 @@
         super.onInitializeAccessibilityEventInternal(event);
         event.setScrollable(mScrollable);
         event.setScrollX(mScrollX);
-        event.setScrollY(mOwnScrollY);
         event.setMaxScrollX(mScrollX);
-        event.setMaxScrollY(getScrollRange());
+        if (ANCHOR_SCROLLING) {
+            // TODO
+        } else {
+            event.setScrollY(mOwnScrollY);
+            event.setMaxScrollY(getScrollRange());
+        }
     }
 
     @Override
@@ -4850,13 +5277,63 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
-    public void setOwnScrollY(int ownScrollY) {
+    private void setOwnScrollY(int ownScrollY) {
+        assert !ANCHOR_SCROLLING;
         if (ownScrollY != mOwnScrollY) {
             // We still want to call the normal scrolled changed for accessibility reasons
             onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
             mOwnScrollY = ownScrollY;
-            updateForwardAndBackwardScrollability();
-            requestChildrenUpdate();
+            updateOnScrollChange();
+        }
+    }
+
+    private void updateOnScrollChange() {
+        updateForwardAndBackwardScrollability();
+        requestChildrenUpdate();
+    }
+
+    private void updateScrollAnchor() {
+        int anchorIndex = indexOfChild(mScrollAnchorView);
+        // If the anchor view has been scrolled off the top, move to the next view.
+        while (mScrollAnchorViewY < 0) {
+            View nextAnchor = null;
+            for (int i = anchorIndex + 1; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                if (child.getVisibility() != View.GONE
+                        && child instanceof ExpandableNotificationRow) {
+                    anchorIndex = i;
+                    nextAnchor = child;
+                    break;
+                }
+            }
+            if (nextAnchor == null) {
+                break;
+            }
+            mScrollAnchorViewY +=
+                    (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY());
+            mScrollAnchorView = nextAnchor;
+        }
+        // If the view above the anchor view is fully visible, make it the anchor view.
+        while (anchorIndex > 0 && mScrollAnchorViewY > 0) {
+            View prevAnchor = null;
+            for (int i = anchorIndex - 1; i >= 0; i--) {
+                View child = getChildAt(i);
+                if (child.getVisibility() != View.GONE
+                        && child instanceof ExpandableNotificationRow) {
+                    anchorIndex = i;
+                    prevAnchor = child;
+                    break;
+                }
+            }
+            if (prevAnchor == null) {
+                break;
+            }
+            float distanceToPreviousAnchor =
+                    mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY();
+            if (distanceToPreviousAnchor < mScrollAnchorViewY) {
+                mScrollAnchorViewY -= (int) distanceToPreviousAnchor;
+                mScrollAnchorView = prevAnchor;
+            }
         }
     }
 
@@ -4872,6 +5349,9 @@
         mAmbientState.setShelf(shelf);
         mStateAnimator.setShelf(shelf);
         shelf.bind(mAmbientState, this);
+        if (ANCHOR_SCROLLING) {
+            mScrollAnchorView = mShelf;
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6022,7 +6502,11 @@
         public void expansionStateChanged(boolean isExpanding) {
             mExpandingNotification = isExpanding;
             if (!mExpandedInThisMotion) {
-                mMaxScrollAfterExpand = mOwnScrollY;
+                if (ANCHOR_SCROLLING) {
+                    // TODO
+                } else {
+                    mMaxScrollAfterExpand = mOwnScrollY;
+                }
                 mExpandedInThisMotion = true;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 25fb7f9..2a88080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -41,6 +41,8 @@
  */
 public class StackScrollAlgorithm {
 
+    static final boolean ANCHOR_SCROLLING = false;
+
     private static final String LOG_TAG = "StackScrollAlgorithm";
     private final ViewGroup mHostView;
 
@@ -236,6 +238,10 @@
         scrollY = Math.max(0, scrollY);
         state.scrollY = (int) (scrollY + bottomOverScroll);
 
+        if (ANCHOR_SCROLLING) {
+            state.anchorViewY = (int) (ambientState.getAnchorViewY() - bottomOverScroll);
+        }
+
         //now init the visible children and update paddings
         int childCount = hostView.getChildCount();
         state.visibleChildren.clear();
@@ -252,6 +258,11 @@
         // iterating over it again, it's filled with the actual resolved value.
 
         for (int i = 0; i < childCount; i++) {
+            if (ANCHOR_SCROLLING) {
+                if (i == ambientState.getAnchorViewIndex()) {
+                    state.anchorViewIndex = state.visibleChildren.size();
+                }
+            }
             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
             if (v.getVisibility() != View.GONE) {
                 if (v == ambientState.getShelf()) {
@@ -350,28 +361,74 @@
     private void updatePositionsForState(StackScrollAlgorithmState algorithmState,
             AmbientState ambientState) {
 
-        // The y coordinate of the current child.
-        float currentYPosition = -algorithmState.scrollY;
-        int childCount = algorithmState.visibleChildren.size();
-        for (int i = 0; i < childCount; i++) {
-            currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition);
+        if (ANCHOR_SCROLLING) {
+            float currentYPosition = algorithmState.anchorViewY;
+            int childCount = algorithmState.visibleChildren.size();
+            for (int i = algorithmState.anchorViewIndex; i < childCount; i++) {
+                if (i > algorithmState.anchorViewIndex && ambientState.beginsNewSection(i)) {
+                    currentYPosition += mGapHeight;
+                }
+                currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+                        false /* reverse */);
+            }
+            currentYPosition = algorithmState.anchorViewY;
+            for (int i = algorithmState.anchorViewIndex - 1; i >= 0; i--) {
+                if (ambientState.beginsNewSection(i + 1)) {
+                    currentYPosition -= mGapHeight;
+                }
+                currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+                        true /* reverse */);
+            }
+        } else {
+            // The y coordinate of the current child.
+            float currentYPosition = -algorithmState.scrollY;
+            int childCount = algorithmState.visibleChildren.size();
+            for (int i = 0; i < childCount; i++) {
+                if (ambientState.beginsNewSection(i)) {
+                    currentYPosition += mGapHeight;
+                }
+                currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
+                        false /* reverse */);
+            }
         }
     }
 
+    /**
+     * Populates the {@link ExpandableViewState} for a single child.
+     *
+     * @param i                The index of the child in
+     * {@link StackScrollAlgorithmState#visibleChildren}.
+     * @param algorithmState   The overall output state of the algorithm.
+     * @param ambientState     The input state provided to the algorithm.
+     * @param currentYPosition The Y position of the current pass of the algorithm.  For a forward
+     *                         pass, this should be the top of the child; for a reverse pass, the
+     *                         bottom of the child.
+     * @param reverse          Whether we're laying out children in the reverse direction (Y
+     *                         positions
+     *                         decreasing) instead of the forward direction (Y positions
+     *                         increasing).
+     * @return The Y position after laying out the child.  This will be the {@code currentYPosition}
+     * for the next call to this method, after adjusting for any gaps between children.
+     */
     protected float updateChild(
             int i,
             StackScrollAlgorithmState algorithmState,
             AmbientState ambientState,
-            float currentYPosition) {
+            float currentYPosition,
+            boolean reverse) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
         childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
         int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
         int childHeight = getMaxAllowedChildHeight(child);
-        if (ambientState.beginsNewSection(i)) {
-            currentYPosition += mGapHeight;
+        if (reverse) {
+            childViewState.yTranslation = currentYPosition - (childHeight + paddingAfterChild);
+            if (currentYPosition <= 0) {
+                childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+            }
+        } else {
+            childViewState.yTranslation = currentYPosition;
         }
-        childViewState.yTranslation = currentYPosition;
         boolean isFooterView = child instanceof FooterView;
         boolean isEmptyShadeView = child instanceof EmptyShadeView;
 
@@ -396,9 +453,13 @@
             clampPositionToShelf(child, childViewState, ambientState);
         }
 
-        currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
-        if (currentYPosition <= 0) {
-            childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+        if (reverse) {
+            currentYPosition = childViewState.yTranslation;
+        } else {
+            currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
+            if (currentYPosition <= 0) {
+                childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+            }
         }
         if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
             Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
@@ -464,6 +525,7 @@
                 // To check if the row need to do translation according to scroll Y
                 // heads up show full of row's content and any scroll y indicate that the
                 // translationY need to move up the HUN.
+                // TODO: fix this check for anchor scrolling.
                 if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
                     childState.yTranslation -= ambientState.getScrollY();
                 }
@@ -607,10 +669,18 @@
     public class StackScrollAlgorithmState {
 
         /**
-         * The scroll position of the algorithm
+         * The scroll position of the algorithm (absolute scrolling).
          */
         public int scrollY;
 
+        /** The index of the anchor view (anchor scrolling). */
+        public int anchorViewIndex;
+
+        /**
+         * The Y position, relative to the top of the screen, of the anchor view (anchor scrolling).
+         */
+        public int anchorViewY;
+
         /**
          * The children from the host view which are not gone.
          */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4f61009..86326be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -64,6 +64,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -304,8 +305,11 @@
         final int count =
                 mEntryManager.getNotificationData().getActiveNotifications().size();
         final int rank = mEntryManager.getNotificationData().getRank(notificationKey);
+        NotificationVisibility.NotificationLocation location =
+                NotificationLogger.getNotificationLocation(
+                        mEntryManager.getNotificationData().get(notificationKey));
         final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
-                rank, count, true);
+                rank, count, true, location);
         try {
             mBarService.onNotificationClick(notificationKey, nv);
         } catch (RemoteException ex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 9c4db34..7881df9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -188,6 +188,7 @@
                 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
         v.mController = controller;
         v.mEntry = entry;
+        v.mEditText.setTextOperationUser(computeTextOperationUser(entry.notification.getUser()));
         v.setTag(VIEW_TAG);
 
         return v;
@@ -298,7 +299,6 @@
         if (mWrapper != null) {
             mWrapper.setRemoteInputVisible(true);
         }
-        mEditText.setTextOperationUser(computeTextOperationUser(mEntry.notification.getUser()));
         mEditText.setInnerFocusable(true);
         mEditText.mShowImeOnInputConnection = true;
         mEditText.setText(mEntry.remoteInputText);
@@ -328,7 +328,6 @@
         mResetting = true;
         mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
 
-        mEditText.setTextOperationUser(null);
         mEditText.getText().clear();
         mEditText.setEnabled(true);
         mSendButton.setVisibility(VISIBLE);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index c992da4..2e45fa7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -18,14 +18,13 @@
 
 import static android.Manifest.permission.MANAGE_AUTO_FILL;
 import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
-import static android.util.DebugUtils.flagsToString;
 import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
+import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sFullScreenMode;
 import static com.android.server.autofill.Helper.sVerbose;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -61,6 +60,7 @@
 import android.util.SparseArray;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
 import android.view.autofill.AutofillManagerInternal;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManager;
@@ -80,8 +80,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -102,26 +100,6 @@
 
     private static final Object sLock = AutofillManagerService.class;
 
-    /**
-     * IME supports Smart Suggestions.
-     */
-    // NOTE: must be public because of flagsToString()
-    public static final int FLAG_SMART_SUGGESTION_IME = 0x1;
-
-    /**
-     * System supports Smarts Suggestions (as a popup-window similar to standard Autofill).
-     */
-    // NOTE: must be public because of flagsToString()
-    public static final int FLAG_SMART_SUGGESTION_SYSTEM = 0x2;
-
-    /** @hide */
-    @IntDef(flag = true, prefix = { "FLAG_SMART_SUGGESTION_" }, value = {
-            FLAG_SMART_SUGGESTION_IME,
-            FLAG_SMART_SUGGESTION_SYSTEM
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface SmartSuggestionMode {}
-
     static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
 
     private static final char COMPAT_PACKAGE_DELIMITER = ':';
@@ -484,7 +462,7 @@
                 Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS, 0);
         if (sDebug) {
             Slog.d(TAG, "setSmartSuggestionEmulationFromSettings(): "
-                    + smartSuggestionFlagsToString(flags));
+                    + getSmartSuggestionModeToString(flags));
         }
 
         synchronized (mLock) {
@@ -698,10 +676,6 @@
         }
     }
 
-    static String smartSuggestionFlagsToString(int flags) {
-        return flagsToString(AutofillManagerService.class, "FLAG_SMART_SUGGESTION_", flags);
-    }
-
     private final class LocalService extends AutofillManagerInternal {
         @Override
         public void onBackKeyPressed() {
@@ -1251,7 +1225,7 @@
                     pw.println(getWhitelistedCompatModePackagesFromSettings());
                     if (mSupportedSmartSuggestionModes != 0) {
                         pw.print("Smart Suggestion modes: ");
-                        pw.println(smartSuggestionFlagsToString(mSupportedSmartSuggestionModes));
+                        pw.println(getSmartSuggestionModeToString(mSupportedSmartSuggestionModes));
                     }
                     if (showHistory) {
                         pw.println(); pw.println("Requests history:"); pw.println();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 954b67e..8886ee2 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -63,6 +63,7 @@
 import android.util.TimeUtils;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
 
@@ -72,7 +73,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.LocalServices;
 import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
-import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
 import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.infra.AbstractPerUserSystemService;
@@ -855,7 +855,6 @@
 
     @GuardedBy("mLock")
     @SmartSuggestionMode int getSupportedSmartSuggestionModesLocked() {
-        // TODO(b/111330312): once we support IME, we need to set it per-user (OR'ed with master)
         return mMaster.getSupportedSmartSuggestionModesLocked();
     }
 
@@ -1049,7 +1048,7 @@
                     componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() {
                         @Override
                         public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) {
-                            // TODO(b/111330312): properly implement
+                            // TODO(b/123100811): properly implement
                             Slog.w(TAG, "remote augmented autofill service died");
                         }
                     }, mMaster.isInstantServiceAllowed(), mMaster.verbose);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 5d8d8fa..9b863a9 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -113,7 +113,7 @@
         scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
     }
 
-    // TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass
+    // TODO(b/123100811): inline into PendingAutofillRequest if it doesn't have any other subclass
     private abstract static class MyPendingRequest
             extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> {
         protected final int mSessionId;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 9f7d33f..194332a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -23,11 +23,10 @@
 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
+import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
+import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_IME;
-import static com.android.server.autofill.AutofillManagerService.FLAG_SMART_SUGGESTION_SYSTEM;
-import static com.android.server.autofill.AutofillManagerService.smartSuggestionFlagsToString;
 import static com.android.server.autofill.Helper.getNumericValue;
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sVerbose;
@@ -88,6 +87,7 @@
 import android.view.KeyEvent;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.SmartSuggestionMode;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
 import android.view.autofill.IAutofillWindowPresenter;
@@ -97,7 +97,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
 
@@ -250,7 +249,7 @@
     /**
      * Destroys the augmented Autofill UI.
      */
-    // TODO(b/111330312): this runnable is called when the Autofill session is destroyed, the
+    // TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the
     // main reason being the cases where user tap HOME.
     // Right now it's completely destroying the UI, but we need to decide whether / how to
     // properly recover it later (for example, if the user switches back to the activity,
@@ -2559,7 +2558,7 @@
             notifyUnavailableToClient(AutofillManager.STATE_FINISHED);
             removeSelf();
         } else {
-            // TODO(b/111330312, b/119638958): must set internal state so when user focus other
+            // TODO(b/123099468, b/119638958): must set internal state so when user focus other
             // fields it does not generate a new call to the standard autofill service (right now
             // it does). Must also add CTS tests to exercise this scenario.
             if (sVerbose) {
@@ -2574,7 +2573,7 @@
      *
      * @return callback to destroy the autofill UI, or {@code null} if not supported.
      */
-    // TODO(b/111330312): might need to call it in other places, like when the service returns a
+    // TODO(b/123099468): might need to call it in other places, like when the service returns a
     // non-null response but without datasets (for example, just SaveInfo)
     @GuardedBy("mLock")
     private Runnable triggerAugmentedAutofillLocked() {
@@ -2594,14 +2593,12 @@
 
         // Define which mode will be used
         final int mode;
-        if ((supportedModes & FLAG_SMART_SUGGESTION_IME) != 0) {
-            // TODO(b/111330312): support it :-)
-            Slog.w(TAG, "Smart Suggestions on IME not supported yet");
-            return null;
-        } else if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) {
+        if ((supportedModes & FLAG_SMART_SUGGESTION_SYSTEM) != 0) {
             mode = FLAG_SMART_SUGGESTION_SYSTEM;
+        } else if ((supportedModes & AutofillManager.FLAG_SMART_SUGGESTION_LEGACY) != 0) {
+            mode = AutofillManager.FLAG_SMART_SUGGESTION_LEGACY;
         } else {
-            Slog.w(TAG, "Unsupported Smart Suggestion Mode: " + supportedModes);
+            Slog.w(TAG, "Unsupported Smart Suggestion mode: " + supportedModes);
             return null;
         }
 
@@ -2614,7 +2611,7 @@
             Slog.v(TAG, "calling Augmented Autofill Service ("
                     + remoteService.getComponentName().toShortString() + ") on view "
                     + mCurrentViewId + " using suggestion mode "
-                    + smartSuggestionFlagsToString(mode)
+                    + getSmartSuggestionModeToString(mode)
                     + " when server returned null for session " + this.id);
         }
 
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 4a1e5b9..2241569 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -164,7 +164,7 @@
         int N = pkgs.size();
         for (int a = N-1; a >= 0; a--) {
             PackageInfo pkg = pkgs.get(a);
-            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
+            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, userId)) {
                 pkgs.remove(a);
             }
         }
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index b9a6f3c..4f58d79 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -636,14 +636,18 @@
     }
 
     @Override
-    public void opComplete(int token, long result) throws RemoteException {
-        int userId = binderGetCallingUserId();
+    public void opCompleteForUser(int userId, int token, long result) throws RemoteException {
         if (isUserReadyForBackup(userId)) {
-            mService.opComplete(binderGetCallingUserId(), token, result);
+            mService.opComplete(userId, token, result);
         }
     }
 
     @Override
+    public void opComplete(int token, long result) throws RemoteException {
+        opCompleteForUser(binderGetCallingUserId(), token, result);
+    }
+
+    @Override
     public long getAvailableRestoreTokenForUser(int userId, String packageName) {
         return isUserReadyForBackup(userId) ? mService.getAvailableRestoreToken(userId,
                 packageName) : 0;
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 79f8a7e..115e924 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -804,7 +804,7 @@
     public BackupAgent makeMetadataAgent() {
         PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager, mUserId);
         pmAgent.attach(mContext);
-        pmAgent.onCreate();
+        pmAgent.onCreate(UserHandle.of(mUserId));
         return pmAgent;
     }
 
@@ -815,7 +815,7 @@
         PackageManagerBackupAgent pmAgent =
                 new PackageManagerBackupAgent(mPackageManager, packages, mUserId);
         pmAgent.attach(mContext);
-        pmAgent.onCreate();
+        pmAgent.onCreate(UserHandle.of(mUserId));
         return pmAgent;
     }
 
@@ -910,10 +910,10 @@
                     long lastBackup = in.readLong();
                     foundApps.add(pkgName); // all apps that we've addressed already
                     try {
-                        PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
+                        PackageInfo pkg = mPackageManager.getPackageInfoAsUser(pkgName, 0, mUserId);
                         if (AppBackupUtils.appGetsFullBackup(pkg)
-                                && AppBackupUtils.appIsEligibleForBackup(
-                                pkg.applicationInfo, mPackageManager)) {
+                                && AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo,
+                                mUserId)) {
                             schedule.add(new FullBackupEntry(pkgName, lastBackup));
                         } else {
                             if (DEBUG) {
@@ -933,8 +933,8 @@
                 // scan to make sure that we're tracking all full-backup candidates properly
                 for (PackageInfo app : apps) {
                     if (AppBackupUtils.appGetsFullBackup(app)
-                            && AppBackupUtils.appIsEligibleForBackup(
-                            app.applicationInfo, mPackageManager)) {
+                            && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo,
+                            mUserId)) {
                         if (!foundApps.contains(app.packageName)) {
                             if (MORE_DEBUG) {
                                 Slog.i(TAG, "New full backup app " + app.packageName + " found");
@@ -960,7 +960,7 @@
             schedule = new ArrayList<>(apps.size());
             for (PackageInfo info : apps) {
                 if (AppBackupUtils.appGetsFullBackup(info) && AppBackupUtils.appIsEligibleForBackup(
-                        info.applicationInfo, mPackageManager)) {
+                        info.applicationInfo, mUserId)) {
                     schedule.add(new FullBackupEntry(info.packageName, 0));
                 }
             }
@@ -1222,8 +1222,8 @@
                                 mPackageManager.getPackageInfoAsUser(
                                         packageName, /* flags */ 0, mUserId);
                         if (AppBackupUtils.appGetsFullBackup(app)
-                                && AppBackupUtils.appIsEligibleForBackup(
-                                app.applicationInfo, mPackageManager)) {
+                                && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo,
+                                mUserId)) {
                             enqueueFullBackup(packageName, now);
                             scheduleNextFullBackupJob(0);
                         } else {
@@ -1618,8 +1618,7 @@
             try {
                 PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName,
                         PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
-                if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
-                        mPackageManager)) {
+                if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo, mUserId)) {
                     BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
                             BackupManager.ERROR_BACKUP_NOT_ALLOWED);
                     continue;
@@ -2095,7 +2094,8 @@
                     }
 
                     try {
-                        PackageInfo appInfo = mPackageManager.getPackageInfo(entry.packageName, 0);
+                        PackageInfo appInfo = mPackageManager.getPackageInfoAsUser(
+                                entry.packageName, 0, mUserId);
                         if (!AppBackupUtils.appGetsFullBackup(appInfo)) {
                             // The head app isn't supposed to get full-data backups [any more];
                             // so we cull it and force a loop around to consider the new head
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 31786d7..0a7159b 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -286,7 +286,8 @@
         Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
         while (iter.hasNext()) {
             PackageInfo pkg = iter.next().getValue();
-            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)
+            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo,
+                    mUserBackupManagerService.getUserId())
                     || AppBackupUtils.appIsStopped(pkg.applicationInfo)) {
                 iter.remove();
                 if (DEBUG) {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 0fb4f93..86e679f 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -143,6 +143,7 @@
     private final int mBackupRunnerOpToken;
     private final OnTaskFinishedListener mListener;
     private final TransportClient mTransportClient;
+    private final int mUserId;
 
     // This is true when a backup operation for some package is in progress.
     private volatile boolean mIsDoingBackup;
@@ -173,6 +174,7 @@
         mAgentTimeoutParameters = Preconditions.checkNotNull(
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
+        mUserId = backupManagerService.getUserId();
 
         if (backupManagerService.isBackupOperationInProgress()) {
             if (DEBUG) {
@@ -187,9 +189,10 @@
         for (String pkg : whichPackages) {
             try {
                 PackageManager pm = backupManagerService.getPackageManager();
-                PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNING_CERTIFICATES);
+                PackageInfo info = pm.getPackageInfoAsUser(pkg,
+                        PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
                 mCurrentPackage = info;
-                if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
+                if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
                     // Cull any packages that have indicated that backups are not permitted,
                     // that run as system-domain uids but do not define their own backup agents,
                     // as well as any explicit mention of the 'special' shared-storage agent
@@ -633,7 +636,7 @@
             unregisterTask();
 
             if (mJob != null) {
-                mJob.finishBackupPass(backupManagerService.getUserId());
+                mJob.finishBackupPass(mUserId);
             }
 
             synchronized (backupManagerService.getQueueLock()) {
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index cfc129e..294eb01 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -489,7 +489,7 @@
             throw AgentException.permanent(e);
         }
         ApplicationInfo applicationInfo = packageInfo.applicationInfo;
-        if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mPackageManager)) {
+        if (!AppBackupUtils.appIsEligibleForBackup(applicationInfo, mUserId)) {
             mReporter.onPackageNotEligibleForBackup(packageName);
             throw AgentException.permanent();
         }
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index b3d9fbc..c5389fa 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -230,7 +230,7 @@
                             PackageManagerInternal.class);
                     RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
                             mBackupManagerService.getPackageManager(), allowApks, info, signatures,
-                            pmi);
+                            pmi, mUserId);
                     mManifestSignatures.put(info.packageName, signatures);
                     mPackagePolicies.put(pkg, restorePolicy);
                     mPackageInstallers.put(pkg, info.installerPackageName);
@@ -332,8 +332,9 @@
                         }
 
                         try {
-                            mTargetApp = mBackupManagerService.getPackageManager()
-                                    .getApplicationInfoAsUser(pkg, 0, mUserId);
+                            mTargetApp =
+                                    mBackupManagerService.getPackageManager()
+                                            .getApplicationInfoAsUser(pkg, 0, mUserId);
 
                             // If we haven't sent any data to this app yet, we probably
                             // need to clear it first. Check that.
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index d01f77b..7763d7b 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -232,7 +232,7 @@
                         continue;
                     }
 
-                    if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
+                    if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, mUserId)) {
                         mAcceptSet.add(info);
                     }
                 } catch (NameNotFoundException e) {
diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
index 054879b..2db8928 100644
--- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
@@ -18,19 +18,25 @@
 
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
+import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 
 import android.annotation.Nullable;
+import android.app.AppGlobals;
 import android.app.backup.BackupTransport;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.Signature;
 import android.content.pm.SigningInfo;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.backup.transport.TransportClient;
@@ -39,7 +45,6 @@
  * Utility methods wrapping operations on ApplicationInfo and PackageInfo.
  */
 public class AppBackupUtils {
-
     private static final boolean DEBUG = false;
 
     /**
@@ -54,15 +59,30 @@
      *     <li>it is the special shared-storage backup package used for 'adb backup'
      * </ol>
      */
-    public static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
+    public static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
+        return appIsEligibleForBackup(app, AppGlobals.getPackageManager(), userId);
+    }
+
+    @VisibleForTesting
+    static boolean appIsEligibleForBackup(ApplicationInfo app,
+        IPackageManager packageManager, int userId) {
         // 1. their manifest states android:allowBackup="false"
         if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
             return false;
         }
 
-        // 2. they run as a system-level uid but do not supply their own backup agent
-        if ((app.uid < Process.FIRST_APPLICATION_UID) && (app.backupAgentName == null)) {
-            return false;
+        // 2. they run as a system-level uid
+        if ((app.uid < Process.FIRST_APPLICATION_UID)) {
+            // and the backup is happening for non-system user
+            if (userId != UserHandle.USER_SYSTEM && !app.packageName.equals(
+                    PACKAGE_MANAGER_SENTINEL)) {
+                return false;
+            }
+
+            // or do not supply their own backup agent
+            if (app.backupAgentName == null) {
+                return false;
+            }
         }
 
         // 3. it is the special shared-storage backup package used for 'adb backup'
@@ -75,9 +95,7 @@
             return false;
         }
 
-        // Everything else checks out; the only remaining roadblock would be if the
-        // package were disabled
-        return !appIsDisabled(app, pm);
+        return !appIsDisabled(app, packageManager, userId);
     }
 
     /**
@@ -99,9 +117,9 @@
             PackageInfo packageInfo = pm.getPackageInfoAsUser(packageName,
                     PackageManager.GET_SIGNING_CERTIFICATES, userId);
             ApplicationInfo applicationInfo = packageInfo.applicationInfo;
-            if (!appIsEligibleForBackup(applicationInfo, pm)
+            if (!appIsEligibleForBackup(applicationInfo, userId)
                     || appIsStopped(applicationInfo)
-                    || appIsDisabled(applicationInfo, pm)) {
+                    || appIsDisabled(applicationInfo, userId)) {
                 return false;
             }
             if (transportClient != null) {
@@ -123,8 +141,22 @@
     }
 
     /** Avoid backups of 'disabled' apps. */
-    public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
-        switch (pm.getApplicationEnabledSetting(app.packageName)) {
+    static boolean appIsDisabled(ApplicationInfo app, int userId) {
+        return appIsDisabled(app, AppGlobals.getPackageManager(), userId);
+    }
+
+    @VisibleForTesting
+    static boolean appIsDisabled(ApplicationInfo app,
+        IPackageManager packageManager, int userId) {
+        int enabledSetting;
+        try {
+            enabledSetting = packageManager.getApplicationEnabledSetting(app.packageName, userId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get application enabled setting: " + e);
+            return false;
+        }
+
+        switch (enabledSetting) {
             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 0f4b681..f4b235a 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -383,11 +383,12 @@
      * @param allowApks - allow restore set to include apks.
      * @param info - file metadata.
      * @param signatures - array of signatures parsed from backup file.
+     * @param userId - ID of the user for which restore is performed.
      * @return a restore policy constant.
      */
     public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
             boolean allowApks, FileMetadata info, Signature[] signatures,
-            PackageManagerInternal pmi) {
+            PackageManagerInternal pmi, int userId) {
         if (signatures == null) {
             return RestorePolicy.IGNORE;
         }
@@ -396,8 +397,8 @@
 
         // Okay, got the manifest info we need...
         try {
-            PackageInfo pkgInfo = packageManager.getPackageInfo(
-                    info.packageName, PackageManager.GET_SIGNING_CERTIFICATES);
+            PackageInfo pkgInfo = packageManager.getPackageInfoAsUser(
+                    info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId);
             // Fall through to IGNORE if the app explicitly disallows backup
             final int flags = pkgInfo.applicationInfo.flags;
             if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fae7a8d..aa74600 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -100,7 +100,7 @@
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -2640,8 +2640,7 @@
     }
 
     private boolean networkRequiresValidation(NetworkAgentInfo nai) {
-        return isValidationRequired(
-                mDefaultRequest.networkCapabilities, nai.networkCapabilities);
+        return isValidationRequired(nai.networkCapabilities);
     }
 
     private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 126bf65..371276f 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -44,7 +44,7 @@
 import android.net.Network;
 import android.net.NetworkUtils;
 import android.net.TrafficStats;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index b3a0f64..8dcc1d5 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -849,6 +849,15 @@
             mAllowed = !mIsManagedBySettings;
             mEnabled = false;
             mProperties = null;
+
+            if (mIsManagedBySettings) {
+                // since we assume providers are disabled by default
+                Settings.Secure.putStringForUser(
+                        mContext.getContentResolver(),
+                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                        "-" + mName,
+                        mCurrentUserId);
+            }
         }
 
         @GuardedBy("mLock")
@@ -2900,33 +2909,11 @@
 
         long identity = Binder.clearCallingIdentity();
         try {
-            boolean enabled;
-            try {
-                enabled = Settings.Secure.getIntForUser(
+            return Settings.Secure.getIntForUser(
                         mContext.getContentResolver(),
                         Settings.Secure.LOCATION_MODE,
+                        Settings.Secure.LOCATION_MODE_OFF,
                         userId) != Settings.Secure.LOCATION_MODE_OFF;
-            } catch (Settings.SettingNotFoundException e) {
-                // OS upgrade case where mode isn't set yet
-                enabled = !TextUtils.isEmpty(Settings.Secure.getStringForUser(
-                        mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        userId));
-
-                try {
-                    Settings.Secure.putIntForUser(
-                            mContext.getContentResolver(),
-                            Settings.Secure.LOCATION_MODE,
-                            enabled
-                                    ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
-                                    : Settings.Secure.LOCATION_MODE_OFF,
-                            userId);
-                } catch (RuntimeException ex) {
-                    // any problem with writing should not be propagated
-                    Slog.e(TAG, "error updating location mode", ex);
-                }
-            }
-            return enabled;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index c84389b..89ff338 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -46,7 +46,9 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.InetAddresses;
 import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
 import android.net.INetworkManagementEventObserver;
 import android.net.ITetheringStatsProvider;
 import android.net.InterfaceConfiguration;
@@ -60,7 +62,7 @@
 import android.net.RouteInfo;
 import android.net.TetherStatsParcel;
 import android.net.UidRange;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
@@ -205,6 +207,8 @@
 
     private INetd mNetdService;
 
+    private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener;
+
     private IBatteryStats mBatteryStats;
 
     private final Thread mThread;
@@ -322,6 +326,8 @@
 
         mDaemonHandler = new Handler(FgThread.get().getLooper());
 
+        mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
+
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
 
@@ -340,6 +346,7 @@
         mFgHandler = null;
         mThread = null;
         mServices = null;
+        mNetdUnsolicitedEventListener = null;
     }
 
     static NetworkManagementService create(Context context, String socket, SystemServices services)
@@ -446,7 +453,6 @@
         // our sanity-checking state.
         mActiveAlerts.remove(iface);
         mActiveQuotas.remove(iface);
-
         invokeForAllObservers(o -> o.interfaceRemoved(iface));
     }
 
@@ -552,7 +558,7 @@
                 return;
             }
             // No current code examines the interface parameter in a global alert. Just pass null.
-            notifyLimitReached(LIMIT_GLOBAL_ALERT, null);
+            mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null));
         }
     }
 
@@ -583,6 +589,12 @@
 
     private void connectNativeNetdService() {
         mNetdService = mServices.getNetd();
+        try {
+            mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener);
+            if (DBG) Slog.d(TAG, "Register unsolicited event listener");
+        } catch (RemoteException | ServiceSpecificException e) {
+            Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e);
+        }
     }
 
     /**
@@ -709,14 +721,96 @@
     /**
      * Notify our observers of a route change.
      */
-    private void notifyRouteChange(String action, RouteInfo route) {
-        if (action.equals("updated")) {
+    private void notifyRouteChange(boolean updated, RouteInfo route) {
+        if (updated) {
             invokeForAllObservers(o -> o.routeUpdated(route));
         } else {
             invokeForAllObservers(o -> o.routeRemoved(route));
         }
     }
 
+    private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
+        @Override
+        public void onInterfaceClassActivityChanged(boolean isActive,
+                int label, long timestamp, int uid) throws RemoteException {
+            final long timestampNanos;
+            if (timestamp <= 0) {
+                timestampNanos = SystemClock.elapsedRealtimeNanos();
+            } else {
+                timestampNanos = timestamp;
+            }
+            mDaemonHandler.post(() -> notifyInterfaceClassActivity(label,
+                    isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+                    : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                    timestampNanos, uid, false));
+        }
+
+        @Override
+        public void onQuotaLimitReached(String alertName, String ifName)
+                throws RemoteException {
+            mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
+        }
+
+        @Override
+        public void onInterfaceDnsServerInfo(String ifName,
+                long lifetime, String[] servers) throws RemoteException {
+            mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
+        }
+
+        @Override
+        public void onInterfaceAddressUpdated(String addr,
+                String ifName, int flags, int scope) throws RemoteException {
+            final LinkAddress address = new LinkAddress(addr, flags, scope);
+            mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
+        }
+
+        @Override
+        public void onInterfaceAddressRemoved(String addr,
+                String ifName, int flags, int scope) throws RemoteException {
+            final LinkAddress address = new LinkAddress(addr, flags, scope);
+            mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
+        }
+
+        @Override
+        public void onInterfaceAdded(String ifName) throws RemoteException {
+            mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
+        }
+
+        @Override
+        public void onInterfaceRemoved(String ifName) throws RemoteException {
+            mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
+        }
+
+        @Override
+        public void onInterfaceChanged(String ifName, boolean up)
+                throws RemoteException {
+            mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
+        }
+
+        @Override
+        public void onInterfaceLinkStateChanged(String ifName, boolean up)
+                throws RemoteException {
+            mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
+        }
+
+        @Override
+        public void onRouteChanged(boolean updated,
+                String route, String gateway, String ifName) throws RemoteException {
+            final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
+                    ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
+                    ifName);
+            mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
+        }
+
+        @Override
+        public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
+            // Don't need to post to mDaemonHandler because the only thing
+            // that notifyCleartextNetwork does is post to a handler
+            ActivityManager.getService().notifyCleartextNetwork(uid,
+                    HexDump.hexStringToByteArray(hex));
+        }
+    }
+
     //
     // Netd Callback handling
     //
@@ -904,7 +998,7 @@
                             InetAddress gateway = null;
                             if (via != null) gateway = InetAddress.parseNumericAddress(via);
                             RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev);
-                            notifyRouteChange(cooked[2], route);
+                            notifyRouteChange(cooked[2].equals("updated"), route);
                             return true;
                         } catch (IllegalArgumentException e) {}
                     }
@@ -1367,13 +1461,9 @@
             if (ConnectivityManager.isNetworkTypeMobile(type)) {
                 mNetworkActive = false;
             }
-            mDaemonHandler.post(new Runnable() {
-                @Override public void run() {
-                    notifyInterfaceClassActivity(type,
-                            DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
-                            SystemClock.elapsedRealtimeNanos(), -1, false);
-                }
-            });
+            mDaemonHandler.post(() -> notifyInterfaceClassActivity(type,
+                    DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                    SystemClock.elapsedRealtimeNanos(), -1, false));
         }
     }
 
@@ -1396,13 +1486,9 @@
                 throw new IllegalStateException(e);
             }
             mActiveIdleTimers.remove(iface);
-            mDaemonHandler.post(new Runnable() {
-                @Override public void run() {
-                    notifyInterfaceClassActivity(params.type,
-                            DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
-                            SystemClock.elapsedRealtimeNanos(), -1, false);
-                }
-            });
+            mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type,
+                    DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                    SystemClock.elapsedRealtimeNanos(), -1, false));
         }
     }
 
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index ccead6c..c7b9a3c 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
 
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
@@ -26,6 +27,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.debug.AdbProtoEnums;
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
 import android.os.Environment;
@@ -37,21 +39,36 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.service.adb.AdbDebuggingManagerProto;
+import android.util.AtomicFile;
 import android.util.Base64;
 import android.util.Slog;
+import android.util.StatsLog;
+import android.util.Xml;
 
 import com.android.internal.R;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
 /**
  * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
@@ -63,6 +80,7 @@
 
     private static final String ADBD_SOCKET = "adbd";
     private static final String ADB_DIRECTORY = "misc/adb";
+    // This file contains keys that will always be allowed to connect to the device via adb.
     private static final String ADB_KEYS_FILE = "adb_keys";
     private static final int BUFFER_SIZE = 4096;
 
@@ -71,12 +89,25 @@
     private AdbDebuggingThread mThread;
     private boolean mAdbEnabled = false;
     private String mFingerprints;
+    private String mConnectedKey;
+    private String mConfirmComponent;
 
     public AdbDebuggingManager(Context context) {
         mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
         mContext = context;
     }
 
+    /**
+     * Constructor that accepts the component to be invoked to confirm if the user wants to allow
+     * an adb connection from the key.
+     */
+    @TestApi
+    protected AdbDebuggingManager(Context context, String confirmComponent) {
+        mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
+        mContext = context;
+        mConfirmComponent = confirmComponent;
+    }
+
     class AdbDebuggingThread extends Thread {
         private boolean mStopped;
         private LocalSocket mSocket;
@@ -135,7 +166,9 @@
                 byte[] buffer = new byte[BUFFER_SIZE];
                 while (true) {
                     int count = mInputStream.read(buffer);
-                    if (count < 0) {
+                    // if less than 2 bytes are read the if statements below will throw an
+                    // IndexOutOfBoundsException.
+                    if (count < 2) {
                         break;
                     }
 
@@ -146,6 +179,11 @@
                                 AdbDebuggingHandler.MESSAGE_ADB_CONFIRM);
                         msg.obj = key;
                         mHandler.sendMessage(msg);
+                    } else if (buffer[0] == 'D' && buffer[1] == 'C') {
+                        Slog.d(TAG, "Received disconnected message");
+                        Message msg = mHandler.obtainMessage(
+                                AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT);
+                        mHandler.sendMessage(msg);
                     } else {
                         Slog.e(TAG, "Wrong message: "
                                 + (new String(Arrays.copyOfRange(buffer, 0, 2))));
@@ -202,15 +240,41 @@
     }
 
     class AdbDebuggingHandler extends Handler {
-        private static final int MESSAGE_ADB_ENABLED = 1;
-        private static final int MESSAGE_ADB_DISABLED = 2;
-        private static final int MESSAGE_ADB_ALLOW = 3;
-        private static final int MESSAGE_ADB_DENY = 4;
-        private static final int MESSAGE_ADB_CONFIRM = 5;
-        private static final int MESSAGE_ADB_CLEAR = 6;
+        // The time to schedule the job to keep the key store updated with a currently connected
+        // key. This job is required because a deveoper could keep a device connected to their
+        // system beyond the time within which a subsequent connection is allowed. But since the
+        // last connection time is only written when a device is connected and disconnected then
+        // if the device is rebooted while connected to the development system it would appear as
+        // though the adb grant for the system is no longer authorized and the developer would need
+        // to manually allow the connection again.
+        private static final long UPDATE_KEY_STORE_JOB_INTERVAL = 86400000;
+
+        static final int MESSAGE_ADB_ENABLED = 1;
+        static final int MESSAGE_ADB_DISABLED = 2;
+        static final int MESSAGE_ADB_ALLOW = 3;
+        static final int MESSAGE_ADB_DENY = 4;
+        static final int MESSAGE_ADB_CONFIRM = 5;
+        static final int MESSAGE_ADB_CLEAR = 6;
+        static final int MESSAGE_ADB_DISCONNECT = 7;
+        static final int MESSAGE_ADB_PERSIST_KEY_STORE = 8;
+        static final int MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME = 9;
+
+        private AdbKeyStore mAdbKeyStore;
 
         AdbDebuggingHandler(Looper looper) {
             super(looper);
+            mAdbKeyStore = new AdbKeyStore();
+        }
+
+        /**
+         * Constructor that accepts the AdbDebuggingThread to which responses should be sent
+         * and the AdbKeyStore to be used to store the temporary grants.
+         */
+        @TestApi
+        AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) {
+            super(looper);
+            mThread = thread;
+            mAdbKeyStore = adbKeyStore;
         }
 
         public void handleMessage(Message msg) {
@@ -251,12 +315,15 @@
                         break;
                     }
 
-                    if (msg.arg1 == 1) {
-                        writeKey(key);
-                    }
-
+                    boolean alwaysAllow = msg.arg1 == 1;
                     if (mThread != null) {
                         mThread.sendResponse("OK");
+                        if (alwaysAllow) {
+                            mConnectedKey = key;
+                            mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+                            scheduleJobToUpdateAdbKeyStore();
+                        }
+                        logAdbConnectionChanged(key, AdbProtoEnums.USER_ALLOWED, alwaysAllow);
                     }
                     break;
                 }
@@ -264,36 +331,88 @@
                 case MESSAGE_ADB_DENY:
                     if (mThread != null) {
                         mThread.sendResponse("NO");
+                        logAdbConnectionChanged(null, AdbProtoEnums.USER_DENIED, false);
                     }
                     break;
 
                 case MESSAGE_ADB_CONFIRM: {
+                    String key = (String) msg.obj;
                     if ("trigger_restart_min_framework".equals(
                             SystemProperties.get("vold.decrypt"))) {
                         Slog.d(TAG, "Deferring adb confirmation until after vold decrypt");
                         if (mThread != null) {
                             mThread.sendResponse("NO");
+                            logAdbConnectionChanged(key, AdbProtoEnums.DENIED_VOLD_DECRYPT, false);
                         }
                         break;
                     }
-                    String key = (String) msg.obj;
                     String fingerprints = getFingerprints(key);
                     if ("".equals(fingerprints)) {
                         if (mThread != null) {
                             mThread.sendResponse("NO");
+                            logAdbConnectionChanged(key, AdbProtoEnums.DENIED_INVALID_KEY, false);
                         }
                         break;
                     }
-                    mFingerprints = fingerprints;
-                    startConfirmation(key, mFingerprints);
+                    // Check if the key should be allowed without user interaction.
+                    if (mAdbKeyStore.isKeyAuthorized(key)) {
+                        if (mThread != null) {
+                            mThread.sendResponse("OK");
+                            mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+                            logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true);
+                            mConnectedKey = key;
+                            scheduleJobToUpdateAdbKeyStore();
+                        }
+                    } else {
+                        logAdbConnectionChanged(key, AdbProtoEnums.AWAITING_USER_APPROVAL, false);
+                        mFingerprints = fingerprints;
+                        startConfirmation(key, mFingerprints);
+                    }
                     break;
                 }
 
-                case MESSAGE_ADB_CLEAR:
+                case MESSAGE_ADB_CLEAR: {
                     deleteKeyFile();
+                    mConnectedKey = null;
+                    mAdbKeyStore.deleteKeyStore();
+                    cancelJobToUpdateAdbKeyStore();
                     break;
+                }
+
+                case MESSAGE_ADB_DISCONNECT: {
+                    if (mConnectedKey != null) {
+                        mAdbKeyStore.setLastConnectionTime(mConnectedKey,
+                                System.currentTimeMillis());
+                        cancelJobToUpdateAdbKeyStore();
+                    }
+                    logAdbConnectionChanged(mConnectedKey, AdbProtoEnums.DISCONNECTED,
+                            (mConnectedKey != null));
+                    mConnectedKey = null;
+                    break;
+                }
+
+                case MESSAGE_ADB_PERSIST_KEY_STORE: {
+                    mAdbKeyStore.persistKeyStore();
+                    break;
+                }
+
+                case MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME: {
+                    if (mConnectedKey != null) {
+                        mAdbKeyStore.setLastConnectionTime(mConnectedKey,
+                                System.currentTimeMillis());
+                        scheduleJobToUpdateAdbKeyStore();
+                    }
+                    break;
+                }
             }
         }
+
+        private void logAdbConnectionChanged(String key, int state, boolean alwaysAllow) {
+            long lastConnectionTime = mAdbKeyStore.getLastConnectionTime(key);
+            long authWindow = mAdbKeyStore.getAllowedConnectionTime();
+            StatsLog.write(StatsLog.ADB_CONNECTION_CHANGED, lastConnectionTime, authWindow, state,
+                    alwaysAllow);
+        }
     }
 
     private String getFingerprints(String key) {
@@ -335,7 +454,8 @@
         UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
         String componentString;
         if (userInfo.isAdmin()) {
-            componentString = Resources.getSystem().getString(
+            componentString = mConfirmComponent != null
+                    ? mConfirmComponent : Resources.getSystem().getString(
                     com.android.internal.R.string.config_customAdbPublicKeyConfirmationComponent);
         } else {
             // If the current foreground user is not the admin user we send a different
@@ -397,7 +517,10 @@
         return intent;
     }
 
-    private File getUserKeyFile() {
+    /**
+     * Returns a new File with the specified name in the adb directory.
+     */
+    private File getAdbFile(String fileName) {
         File dataDir = Environment.getDataDirectory();
         File adbDir = new File(dataDir, ADB_DIRECTORY);
 
@@ -406,7 +529,11 @@
             return null;
         }
 
-        return new File(adbDir, ADB_KEYS_FILE);
+        return new File(adbDir, fileName);
+    }
+
+    private File getUserKeyFile() {
+        return getAdbFile(ADB_KEYS_FILE);
     }
 
     private void writeKey(String key) {
@@ -476,6 +603,36 @@
     }
 
     /**
+     * Sends a message to the handler to persist the key store.
+     */
+    private void sendPersistKeyStoreMessage() {
+        Message msg = mHandler.obtainMessage(AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Schedules a job to update the connection time of the currently connected key. This is
+     * intended for cases such as development devices that are left connected to a user's
+     * system beyond the window within which a connection is allowed without user interaction.
+     * A job should be rescheduled daily so that if the device is rebooted while connected to
+     * the user's system the last time in the key store will show within 24 hours which should
+     * be within the allowed window.
+     */
+    private void scheduleJobToUpdateAdbKeyStore() {
+        Message message = mHandler.obtainMessage(
+                AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME);
+        mHandler.sendMessageDelayed(message, AdbDebuggingHandler.UPDATE_KEY_STORE_JOB_INTERVAL);
+    }
+
+    /**
+     * Cancels the scheduled job to update the connection time of the currently connected key.
+     * This should be invoked once the adb session is disconnected.
+     */
+    private void cancelJobToUpdateAdbKeyStore() {
+        mHandler.removeMessages(AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME);
+    }
+
+    /**
      * Dump the USB debugging state.
      */
     public void dump(DualDumpOutputStream dump, String idName, long id) {
@@ -501,4 +658,231 @@
 
         dump.end(token);
     }
+
+    /**
+     * Handles adb keys for which the user has granted the 'always allow' option. This class ensures
+     * these grants are revoked after a period of inactivity as specified in the
+     * ADB_ALLOWED_CONNECTION_TIME setting.
+     */
+    class AdbKeyStore {
+        private Map<String, Long> mKeyMap;
+        private File mKeyFile;
+        private AtomicFile mAtomicKeyFile;
+        // This file contains keys that will be allowed to connect without user interaction as long
+        // as a subsequent connection occurs within the allowed duration.
+        private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml";
+        private static final String XML_TAG_ADB_KEY = "adbKey";
+        private static final String XML_ATTRIBUTE_KEY = "key";
+        private static final String XML_ATTRIBUTE_LAST_CONNECTION = "lastConnection";
+
+        /**
+         * Value returned by {@code getLastConnectionTime} when there is no previously saved
+         * connection time for the specified key.
+         */
+        public static final long NO_PREVIOUS_CONNECTION = 0;
+
+        /**
+         * Constructor that uses the default location for the persistent adb key store.
+         */
+        AdbKeyStore() {
+            initKeyFile();
+            mKeyMap = getKeyMapFromFile();
+        }
+
+        /**
+         * Constructor that uses the specified file as the location for the persistent adb key
+         * store.
+         */
+        AdbKeyStore(File keyFile) {
+            mKeyFile = keyFile;
+            initKeyFile();
+            mKeyMap = getKeyMapFromFile();
+        }
+
+        /**
+         * Initializes the key file that will be used to persist the adb grants.
+         */
+        private void initKeyFile() {
+            if (mKeyFile == null) {
+                mKeyFile = getAdbFile(ADB_TEMP_KEYS_FILE);
+            }
+            // getAdbFile can return null if the adb file cannot be obtained
+            if (mKeyFile != null) {
+                mAtomicKeyFile = new AtomicFile(mKeyFile);
+            }
+        }
+
+        /**
+         * Returns the key map with the keys and last connection times from the key file.
+         */
+        private Map<String, Long> getKeyMapFromFile() {
+            Map<String, Long> keyMap = new HashMap<String, Long>();
+            // if the AtomicFile could not be instantiated before attempt again; if it still fails
+            // return an empty key map.
+            if (mAtomicKeyFile == null) {
+                initKeyFile();
+                if (mAtomicKeyFile == null) {
+                    Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
+                    return keyMap;
+                }
+            }
+            if (!mAtomicKeyFile.exists()) {
+                return keyMap;
+            }
+            try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(keyStream, StandardCharsets.UTF_8.name());
+                XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
+                while (parser.next() != XmlPullParser.END_DOCUMENT) {
+                    String tagName = parser.getName();
+                    if (tagName == null) {
+                        break;
+                    } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
+                    long connectionTime;
+                    try {
+                        connectionTime = Long.valueOf(
+                                parser.getAttributeValue(null, XML_ATTRIBUTE_LAST_CONNECTION));
+                    } catch (NumberFormatException e) {
+                        Slog.e(TAG,
+                                "Caught a NumberFormatException parsing the last connection time: "
+                                        + e);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    keyMap.put(key, connectionTime);
+                }
+            } catch (IOException | XmlPullParserException e) {
+                Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
+            }
+            return keyMap;
+        }
+
+        /**
+         * Writes the key map to the key file.
+         */
+        public void persistKeyStore() {
+            // if there is nothing in the key map then ensure any keys left in the key store files
+            // are deleted as well.
+            if (mKeyMap.size() == 0) {
+                deleteKeyStore();
+                return;
+            }
+            if (mAtomicKeyFile == null) {
+                initKeyFile();
+                if (mAtomicKeyFile == null) {
+                    Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing");
+                    return;
+                }
+            }
+            FileOutputStream keyStream = null;
+            try {
+                XmlSerializer serializer = new FastXmlSerializer();
+                keyStream = mAtomicKeyFile.startWrite();
+                serializer.setOutput(keyStream, StandardCharsets.UTF_8.name());
+                serializer.startDocument(null, true);
+                long allowedTime = getAllowedConnectionTime();
+                long systemTime = System.currentTimeMillis();
+                Iterator keyMapIterator = mKeyMap.entrySet().iterator();
+                while (keyMapIterator.hasNext()) {
+                    Map.Entry<String, Long> keyEntry = (Map.Entry) keyMapIterator.next();
+                    long connectionTime = keyEntry.getValue();
+                    if (systemTime < (connectionTime + allowedTime)) {
+                        serializer.startTag(null, XML_TAG_ADB_KEY);
+                        serializer.attribute(null, XML_ATTRIBUTE_KEY, keyEntry.getKey());
+                        serializer.attribute(null, XML_ATTRIBUTE_LAST_CONNECTION,
+                                String.valueOf(keyEntry.getValue()));
+                        serializer.endTag(null, XML_TAG_ADB_KEY);
+                    } else {
+                        keyMapIterator.remove();
+                    }
+                }
+                serializer.endDocument();
+                mAtomicKeyFile.finishWrite(keyStream);
+            } catch (IOException e) {
+                Slog.e(TAG, "Caught an exception writing the key map: ", e);
+                mAtomicKeyFile.failWrite(keyStream);
+            }
+        }
+
+        /**
+         * Removes all of the entries in the key map and deletes the key file.
+         */
+        public void deleteKeyStore() {
+            mKeyMap.clear();
+            if (mAtomicKeyFile == null) {
+                return;
+            }
+            mAtomicKeyFile.delete();
+        }
+
+        /**
+         * Returns the time of the last connection from the specified key, or {@code
+         * NO_PREVIOUS_CONNECTION} if the specified key does not have an active adb grant.
+         */
+        public long getLastConnectionTime(String key) {
+            return mKeyMap.getOrDefault(key, NO_PREVIOUS_CONNECTION);
+        }
+
+        /**
+         * Sets the time of the last connection for the specified key to the provided time.
+         */
+        public void setLastConnectionTime(String key, long connectionTime) {
+            // Do not set the connection time to a value that is earlier than what was previously
+            // stored as the last connection time.
+            if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime) {
+                return;
+            }
+            mKeyMap.put(key, connectionTime);
+            sendPersistKeyStoreMessage();
+        }
+
+        /**
+         * Returns whether the specified key should be authroized to connect without user
+         * interaction. This requires that the user previously connected this device and selected
+         * the option to 'Always allow', and the time since the last connection is within the
+         * allowed window.
+         */
+        public boolean isKeyAuthorized(String key) {
+            long lastConnectionTime = getLastConnectionTime(key);
+            if (lastConnectionTime == NO_PREVIOUS_CONNECTION) {
+                return false;
+            }
+            long allowedConnectionTime = getAllowedConnectionTime();
+            // if the allowed connection time is 0 then revert to the previous behavior of always
+            // allowing previously granted adb grants.
+            if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime
+                    + allowedConnectionTime))) {
+                return true;
+            } else {
+                // since this key is no longer auhorized remove it from the Map
+                removeKey(key);
+                return false;
+            }
+        }
+
+        /**
+         * Returns the connection time within which a connection from an allowed key is
+         * automatically allowed without user interaction.
+         */
+        public long getAllowedConnectionTime() {
+            return Settings.Global.getLong(mContext.getContentResolver(),
+                    Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
+                    Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
+        }
+
+        /**
+         * Removes the specified key from the key store.
+         */
+        public void removeKey(String key) {
+            if (!mKeyMap.containsKey(key)) {
+                return;
+            }
+            mKeyMap.remove(key);
+            sendPersistKeyStoreMessage();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ac392a1..a96676e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -82,7 +82,6 @@
 import android.webkit.WebViewZygote;
 
 import com.android.internal.R;
-import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.app.procstats.ServiceState;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
@@ -169,8 +168,6 @@
     /** Temporary list for holding the results of calls to {@link #collectPackageServicesLocked} */
     private ArrayList<ServiceRecord> mTmpCollectionResults = null;
 
-    private int mNumServiceRestarts = 0;
-
     /**
      * For keeping ActiveForegroundApps retaining state while the screen is off.
      */
@@ -2330,50 +2327,19 @@
                 r.restartDelay = mAm.mConstants.BOUND_SERVICE_CRASH_RESTART_DURATION
                         * (r.crashCount - 1);
             } else {
-                mNumServiceRestarts++;
-                if (!mAm.mConstants.FLAG_USE_MEM_AWARE_SERVICE_RESTARTS) {
-                    // If it has been a "reasonably long time" since the service
-                    // was started, then reset our restart duration back to
-                    // the beginning, so we don't infinitely increase the duration
-                    // on a service that just occasionally gets killed (which is
-                    // a normal case, due to process being killed to reclaim memory).
-                    if (now > (r.restartTime + resetTime)) {
-                        r.restartCount = 1;
-                        r.restartDelay = minDuration;
-                    } else {
-                        r.restartDelay *= mAm.mConstants.SERVICE_RESTART_DURATION_FACTOR;
-                        if (r.restartDelay < minDuration) {
-                            r.restartDelay = minDuration;
-                        }
-                    }
+                // If it has been a "reasonably long time" since the service
+                // was started, then reset our restart duration back to
+                // the beginning, so we don't infinitely increase the duration
+                // on a service that just occasionally gets killed (which is
+                // a normal case, due to process being killed to reclaim memory).
+                if (now > (r.restartTime+resetTime)) {
+                    r.restartCount = 1;
+                    r.restartDelay = minDuration;
                 } else {
-                    // The service will be restarted based on the last known oom_adj value
-                    // for the process when it was destroyed. A higher oom_adj value
-                    // means that the service will be restarted quicker.
-                    // If the service seems to keep crashing, the service restart counter will be
-                    // incremented and the restart delay will have an exponential backoff.
-                    final int lastOomAdj = r.app == null ? r.lastKnownOomAdj : r.app.setAdj;
-                    if (r.restartCount > 1) {
-                        r.restartDelay *= mAm.mConstants.SERVICE_RESTART_DURATION_FACTOR;
-                        if (r.restartDelay < minDuration) {
-                            r.restartDelay = minDuration;
-                        }
-                    } else {
-                        if (lastOomAdj <= ProcessList.VISIBLE_APP_ADJ) {
-                            r.restartDelay = 1 * 1000; // 1 second
-                        } else if (lastOomAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
-                            r.restartDelay = 2 * 1000; // 2 seconds
-                        } else if (lastOomAdj <= ProcessList.SERVICE_ADJ) {
-                            r.restartDelay = 5 * 1000; // 5 seconds
-                        } else if (lastOomAdj <= ProcessList.PREVIOUS_APP_ADJ) {
-                            r.restartDelay = 10 * 1000; // 10 seconds
-                        } else {
-                            r.restartDelay = 20 * 1000; // 20 seconds
-                        }
+                    r.restartDelay *= mAm.mConstants.SERVICE_RESTART_DURATION_FACTOR;
+                    if (r.restartDelay < minDuration) {
+                        r.restartDelay = minDuration;
                     }
-                    // If the last time the service restarted was more than a minute ago,
-                    // reset the service restart count, otherwise increment by one.
-                    r.restartCount = (now > (r.restartTime + resetTime)) ? 1 : (r.restartCount + 1);
                 }
             }
 
@@ -2381,7 +2347,21 @@
 
             // Make sure that we don't end up restarting a bunch of services
             // all at the same time.
-            ensureSpacedServiceRestarts(r, now);
+            boolean repeat;
+            do {
+                repeat = false;
+                final long restartTimeBetween = mAm.mConstants.SERVICE_MIN_RESTART_TIME_BETWEEN;
+                for (int i=mRestartingServices.size()-1; i>=0; i--) {
+                    ServiceRecord r2 = mRestartingServices.get(i);
+                    if (r2 != r && r.nextRestartTime >= (r2.nextRestartTime-restartTimeBetween)
+                            && r.nextRestartTime < (r2.nextRestartTime+restartTimeBetween)) {
+                        r.nextRestartTime = r2.nextRestartTime + restartTimeBetween;
+                        r.restartDelay = r.nextRestartTime - now;
+                        repeat = true;
+                        break;
+                    }
+                }
+            } while (repeat);
 
         } else {
             // Persistent processes are immediately restarted, so there is no
@@ -2411,24 +2391,6 @@
         return canceled;
     }
 
-    private void ensureSpacedServiceRestarts(ServiceRecord r, long now) {
-        boolean repeat;
-        do {
-            repeat = false;
-            final long restartTimeBetween = mAm.mConstants.SERVICE_MIN_RESTART_TIME_BETWEEN;
-            for (int i = mRestartingServices.size() - 1; i >= 0; i--) {
-                ServiceRecord r2 = mRestartingServices.get(i);
-                if (r2 != r && r.nextRestartTime >= (r2.nextRestartTime - restartTimeBetween)
-                        && r.nextRestartTime < (r2.nextRestartTime + restartTimeBetween)) {
-                    r.nextRestartTime = r2.nextRestartTime + restartTimeBetween;
-                    r.restartDelay = r.nextRestartTime - now;
-                    repeat = true;
-                    break;
-                }
-            }
-        } while (repeat);
-    }
-
     final void performServiceRestartLocked(ServiceRecord r) {
         if (!mRestartingServices.contains(r)) {
             return;
@@ -2503,26 +2465,6 @@
             return null;
         }
 
-        // If the service is restarting, check the memory pressure - if it's low or critical, then
-        // further delay the restart because it will most likely get killed again; if the pressure
-        // is not low or critical, continue restarting.
-        if (mAm.mConstants.FLAG_USE_MEM_AWARE_SERVICE_RESTARTS && whileRestarting) {
-            final int memLevel = mAm.getMemoryTrimLevel();
-            if (memLevel >= ProcessStats.ADJ_MEM_FACTOR_LOW) {
-                final long now = SystemClock.uptimeMillis();
-                // Delay the restart based on the current memory pressure
-                // Default delay duration is 5 seconds
-                r.restartDelay = memLevel * mAm.mConstants.SERVICE_RESTART_DELAY_DURATION;
-                r.nextRestartTime = now + r.restartDelay;
-                ensureSpacedServiceRestarts(r, now);
-                mAm.mHandler.removeCallbacks(r.restarter);
-                mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
-                Slog.w(TAG, "Delaying restart of crashed service " + r.shortInstanceName
-                        + " in " + r.restartDelay + "ms due to continued memory pressure");
-                return null;
-            }
-        }
-
         if (DEBUG_SERVICE) {
             Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent + " fg=" + r.fgRequired);
         }
@@ -3637,7 +3579,6 @@
                     || !mAm.mUserController.isUserRunning(sr.userId, 0)) {
                 bringDownServiceLocked(sr);
             } else {
-                sr.lastKnownOomAdj = app.setAdj;
                 boolean canceled = scheduleServiceRestartLocked(sr, true);
 
                 // Should the service remain running?  Note that in the
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 546975a..dd2b33a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -76,8 +76,6 @@
     static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
     static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
     static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
-    static final String KEY_USE_MEM_AWARE_SERVICE_RESTARTS = "use_mem_aware_service_restarts";
-    static final String KEY_SERVICE_RESTART_DELAY_DURATION = "service_restart_delay_duration";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -115,8 +113,6 @@
     public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000;
     public static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
     public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000;
-    private static final boolean DEFAULT_USE_MEM_AWARE_SERVICE_RESTARTS = true;
-    private static final long DEFAULT_SERVICE_RESTART_DELAY_DURATION = 5 * 1000;
 
     // Maximum number of cached processes we will allow.
     public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -253,12 +249,6 @@
     // How long we'll skip second compactAppFull after first compactAppFull
     public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4;
 
-    // Use memory aware optimized logic to restart services
-    public boolean FLAG_USE_MEM_AWARE_SERVICE_RESTARTS = DEFAULT_USE_MEM_AWARE_SERVICE_RESTARTS;
-
-    // How long a service restart will be delayed by if there is memory pressure
-    public long SERVICE_RESTART_DELAY_DURATION = DEFAULT_SERVICE_RESTART_DELAY_DURATION;
-
     // Indicates whether the activity starts logging is enabled.
     // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
     volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -423,10 +413,6 @@
             COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2);
             COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3);
             COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4);
-            FLAG_USE_MEM_AWARE_SERVICE_RESTARTS = mParser.getBoolean(
-                    KEY_USE_MEM_AWARE_SERVICE_RESTARTS, DEFAULT_USE_MEM_AWARE_SERVICE_RESTARTS);
-            SERVICE_RESTART_DELAY_DURATION = mParser.getLong(
-                    KEY_SERVICE_RESTART_DELAY_DURATION, DEFAULT_SERVICE_RESTART_DELAY_DURATION);
 
             updateMaxCachedProcesses();
         }
@@ -519,10 +505,6 @@
         pw.println(TOP_TO_FGS_GRACE_DURATION);
         pw.print("  "); pw.print(KEY_USE_COMPACTION); pw.print("=");
         pw.println(USE_COMPACTION);
-        pw.print("  "); pw.print(KEY_USE_MEM_AWARE_SERVICE_RESTARTS); pw.print("=");
-        pw.println(FLAG_USE_MEM_AWARE_SERVICE_RESTARTS);
-        pw.print("  "); pw.print(KEY_SERVICE_RESTART_DELAY_DURATION); pw.print("=");
-        pw.println(SERVICE_RESTART_DELAY_DURATION);
 
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 089847d..b24290f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -64,6 +64,7 @@
 import static android.os.Process.SIGNAL_USR1;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+import static android.os.Process.ZYGOTE_PROCESS;
 import static android.os.Process.getTotalMemory;
 import static android.os.Process.isThreadInProcess;
 import static android.os.Process.killProcess;
@@ -75,7 +76,6 @@
 import static android.os.Process.sendSignal;
 import static android.os.Process.setThreadPriority;
 import static android.os.Process.setThreadScheduler;
-import static android.os.Process.zygoteProcess;
 import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
 import static android.provider.Settings.Global.DEBUG_APP;
 import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
@@ -2162,7 +2162,7 @@
                             ? Collections.emptyList()
                             : Arrays.asList(exemptions.split(","));
                 }
-                if (!zygoteProcess.setApiBlacklistExemptions(mExemptions)) {
+                if (!ZYGOTE_PROCESS.setApiBlacklistExemptions(mExemptions)) {
                   Slog.e(TAG, "Failed to set API blacklist exemptions!");
                   // leave mExemptionsStr as is, so we don't try to send the same list again.
                   mExemptions = Collections.emptyList();
@@ -2175,7 +2175,7 @@
             }
             if (logSampleRate != -1 && logSampleRate != mLogSampleRate) {
                 mLogSampleRate = logSampleRate;
-                zygoteProcess.setHiddenApiAccessLogSampleRate(mLogSampleRate);
+                ZYGOTE_PROCESS.setHiddenApiAccessLogSampleRate(mLogSampleRate);
             }
             mPolicy = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY);
         }
@@ -2864,16 +2864,18 @@
      * @param userId
      * @param event
      * @param appToken ActivityRecord's appToken.
+     * @param taskRoot TaskRecord's root
      */
     public void updateActivityUsageStats(ComponentName activity, int userId, int event,
-            IBinder appToken) {
+            IBinder appToken, ComponentName taskRoot) {
         if (DEBUG_SWITCH) {
             Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp="
                     + activity + " hash=" + appToken.hashCode() + " event=" + event);
         }
         synchronized (this) {
             if (mUsageStatsService != null) {
-                mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode());
+                mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(),
+                        taskRoot);
             }
         }
     }
@@ -2911,7 +2913,7 @@
             if (mUsageStatsService != null) {
                 mUsageStatsService.reportEvent(service, userId,
                         started ? UsageEvents.Event.FOREGROUND_SERVICE_START
-                                : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0);
+                                : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0, null);
             }
         }
     }
@@ -4880,7 +4882,7 @@
 
         ArraySet<String> completedIsas = new ArraySet<String>();
         for (String abi : Build.SUPPORTED_ABIS) {
-            zygoteProcess.establishZygoteConnectionForAbi(abi);
+            ZYGOTE_PROCESS.establishZygoteConnectionForAbi(abi);
             final String instructionSet = VMRuntime.getInstructionSet(abi);
             if (!completedIsas.contains(instructionSet)) {
                 try {
@@ -17521,10 +17523,10 @@
 
         @Override
         public void updateActivityUsageStats(ComponentName activity, int userId, int event,
-                IBinder appToken) {
+                IBinder appToken, ComponentName taskRoot) {
             synchronized (ActivityManagerService.this) {
                 ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
-                        appToken);
+                        appToken, taskRoot);
             }
         }
 
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index fc2a444..da5ce1c 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -16,8 +16,10 @@
 
 package com.android.server.am;
 
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import com.android.internal.app.procstats.ServiceState;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
 
 import android.app.INotificationManager;
 import android.app.Notification;
@@ -42,11 +44,6 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.util.proto.ProtoUtils;
-
-import com.android.internal.app.procstats.ServiceState;
-import com.android.internal.os.BatteryStatsImpl;
-import com.android.server.LocalServices;
-import com.android.server.notification.NotificationManagerInternal;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriPermissionOwner;
 
@@ -55,6 +52,9 @@
 import java.util.List;
 import java.util.Objects;
 
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
 /**
  * A running application service.
  */
@@ -114,7 +114,6 @@
     long executingStart;    // start time of last execute request.
     boolean createdFromFg;  // was this service last created due to a foreground process call?
     int crashCount;         // number of times proc has crashed with service running
-    int lastKnownOomAdj;    // last known OOM adjustment for process (used if ProcessRecord is null)
     int totalRestartCount;  // number of times we have had to restart.
     int restartCount;       // number of restarts performed in a row.
     long restartDelay;      // delay until next restart attempt.
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index ac74598..79b56c6 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -20,7 +20,6 @@
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
 import android.net.INetdEventCallback;
-import android.net.ip.IpClient;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.os.Binder;
@@ -270,8 +269,6 @@
         // Dump the rolling buffer of metrics event and pretty print events using a human readable
         // format. Also print network dns/connect statistics and default network event time series.
         static final String CMD_LIST = "list";
-        // Dump all IpClient logs ("ipclient").
-        static final String CMD_IPCLIENT = IpClient.DUMP_ARG;
         // By default any other argument will fall into the default case which is the equivalent
         // of calling both the "list" and "ipclient" commands. This includes most notably bug
         // reports collected by dumpsys.cpp with the "-a" argument.
@@ -295,20 +292,9 @@
                 case CMD_PROTO:
                     cmdListAsProto(pw);
                     return;
-                case CMD_IPCLIENT: {
-                    final String[] ipclientArgs = ((args != null) && (args.length > 1))
-                            ? Arrays.copyOfRange(args, 1, args.length)
-                            : null;
-                    IpClient.dumpAllLogs(pw, ipclientArgs);
-                    return;
-                }
                 case CMD_LIST:
-                    cmdList(pw);
-                    return;
                 default:
                     cmdList(pw);
-                    pw.println("");
-                    IpClient.dumpAllLogs(pw, null);
                     return;
             }
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index eca371a..3fd3945 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4106,25 +4106,6 @@
         return mCurrentSubtype;
     }
 
-    @Override
-    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
-        synchronized (mMethodMap) {
-            // TODO: Make this work even for non-current users?
-            if (!calledFromValidUserLocked()) {
-                return false;
-            }
-            if (subtype != null && mCurMethodId != null) {
-                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
-                int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
-                if (subtypeId != NOT_A_SUBTYPE_ID) {
-                    setInputMethodLocked(mCurMethodId, subtypeId);
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
     private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
         synchronized (mMethodMap) {
             return getInputMethodListLocked(userId);
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index a31b3b4..3222143 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1519,13 +1519,6 @@
 
         @BinderThread
         @Override
-        public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
-            reportNotSupported();
-            return false;
-        }
-
-        @BinderThread
-        @Override
         public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
             reportNotSupported();
         }
diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java
index e241591..bee7a8b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerService.java
+++ b/services/core/java/com/android/server/os/BugreportManagerService.java
@@ -37,7 +37,6 @@
     @Override
     public void onStart() {
         mService = new BugreportManagerServiceImpl(getContext());
-        // TODO(b/111441001): Needs sepolicy to be submitted first.
-        // publishBinderService(Context.BUGREPORT_SERVICE, mService);
+        publishBinderService(Context.BUGREPORT_SERVICE, mService);
     }
 }
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1178cc1..f736056 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -44,6 +44,7 @@
  */
 class BugreportManagerServiceImpl extends IDumpstate.Stub {
     private static final String TAG = "BugreportManagerService";
+    private static final String BUGREPORT_SERVICE = "bugreportd";
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
 
     private IDumpstate mDs = null;
@@ -64,6 +65,8 @@
         throw new UnsupportedOperationException("setListener is not allowed on this service");
     }
 
+    // TODO(b/111441001): Intercept onFinished here in system server and shutdown
+    // the bugreportd service.
     @Override
     @RequiresPermission(android.Manifest.permission.DUMP)
     public void startBugreport(int callingUidUnused, String callingPackage,
@@ -84,6 +87,14 @@
                 bugreportFd, screenshotFd, bugreportMode, listener);
     }
 
+    @Override
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void cancelBugreport() throws RemoteException {
+        // This tells init to cancel bugreportd service.
+        SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+        mDs = null;
+    }
+
     private boolean validate(@BugreportParams.BugreportMode int mode) {
         if (mode != BugreportParams.BUGREPORT_MODE_FULL
                 && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -107,7 +118,7 @@
      */
     private IDumpstate getDumpstateService() {
         // Start bugreport service.
-        SystemProperties.set("ctl.start", "bugreport");
+        SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
 
         IDumpstate ds = null;
         boolean timedOut = false;
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
new file mode 100644
index 0000000..60d7925
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -0,0 +1,76 @@
+hackbod@android.com
+hackbod@google.com
+jsharkey@android.com
+jsharkey@google.com
+narayan@google.com
+patb@google.com
+svetoslavganov@android.com
+svetoslavganov@google.com
+toddke@android.com
+toddke@google.com
+
+# dex
+per-file AbstractStatsBase.java = agampe@google.com
+per-file AbstractStatsBase.java = calin@google.com
+per-file AbstractStatsBase.java = ngeoffray@google.com
+per-file BackgroundDexOptService.java = agampe@google.com
+per-file BackgroundDexOptService.java = calin@google.com
+per-file BackgroundDexOptService.java = ngeoffray@google.com
+per-file CompilerStats.java = agampe@google.com
+per-file CompilerStats.java = calin@google.com
+per-file CompilerStats.java = ngeoffray@google.com
+per-file InstructionSets.java = agampe@google.com
+per-file InstructionSets.java = calin@google.com
+per-file InstructionSets.java = ngeoffray@google.com
+per-file OtaDexoptService.java = agampe@google.com
+per-file OtaDexoptService.java = calin@google.com
+per-file OtaDexoptService.java = ngeoffray@google.com
+per-file OtaDexoptShellCommand.java = agampe@google.com
+per-file OtaDexoptShellCommand.java = calin@google.com
+per-file OtaDexoptShellCommand.java = ngeoffray@google.com
+per-file PackageDexOptimizer.java = agampe@google.com
+per-file PackageDexOptimizer.java = calin@google.com
+per-file PackageDexOptimizer.java = ngeoffray@google.com
+per-file PackageManagerServiceCompilerMapping.java = agampe@google.com
+per-file PackageManagerServiceCompilerMapping.java = calin@google.com
+per-file PackageManagerServiceCompilerMapping.java = ngeoffray@google.com
+per-file PackageUsage.java = agampe@google.com
+per-file PackageUsage.java = calin@google.com
+per-file PackageUsage.java = ngeoffray@google.com
+
+# multi user / cross profile
+per-file CrossProfileAppsServiceImpl.java = omakoto@google.com
+per-file CrossProfileAppsServiceImpl.java = yamasani@google.com
+per-file CrossProfileAppsService.java = omakoto@google.com
+per-file CrossProfileAppsService.java = yamasani@google.com
+per-file CrossProfileIntentFilter.java = omakoto@google.com
+per-file CrossProfileIntentFilter.java = yamasani@google.com
+per-file CrossProfileIntentResolver.java = omakoto@google.com
+per-file CrossProfileIntentResolver.java = yamasani@google.com
+per-file UserManagerService.java = omakoto@google.com
+per-file UserManagerService.java = yamasani@google.com
+per-file UserRestrictionsUtils.java = omakoto@google.com
+per-file UserRestrictionsUtils.java = yamasani@google.com
+
+# security
+per-file KeySetHandle.java = cbrubaker@google.com
+per-file KeySetManagerService.java = cbrubaker@google.com
+per-file PackageKeySetData.java = cbrubaker@google.com
+per-file PackageSignatures.java = cbrubaker@google.com
+per-file SELinuxMMAC.java = cbrubaker@google.com
+
+# shortcuts
+per-file LauncherAppsService.java = omakoto@google.com
+per-file ShareTargetInfo.java = omakoto@google.com
+per-file ShortcutBitmapSaver.java = omakoto@google.com
+per-file ShortcutDumpFiles.java = omakoto@google.com
+per-file ShortcutLauncher.java = omakoto@google.com
+per-file ShortcutNonPersistentUser.java = omakoto@google.com
+per-file ShortcutPackage.java = omakoto@google.com
+per-file ShortcutPackageInfo.java = omakoto@google.com
+per-file ShortcutPackageItem.java = omakoto@google.com
+per-file ShortcutParser.java = omakoto@google.com
+per-file ShortcutRequestPinProcessor.java = omakoto@google.com
+per-file ShortcutService.java = omakoto@google.com
+per-file ShortcutUser.java = omakoto@google.com
+
diff --git a/services/core/java/com/android/server/pm/dex/OWNERS b/services/core/java/com/android/server/pm/dex/OWNERS
new file mode 100644
index 0000000..fcc1f6c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/OWNERS
@@ -0,0 +1,4 @@
+agampe@google.com
+calin@google.com
+ngeoffray@google.com
+sehr@google.com
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 88b97ea..01dc01e 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,4 +1,4 @@
-per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com
+moltmann@google.com
 per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
 per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
 per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index f0589aa..4015c4f 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -29,6 +29,11 @@
  */
 class RollbackData {
     /**
+     * A unique identifier for this rollback.
+     */
+    public final int rollbackId;
+
+    /**
      * The per-package rollback information.
      */
     public final List<PackageRollbackInfo> packages = new ArrayList<>();
@@ -44,7 +49,8 @@
      */
     public Instant timestamp;
 
-    RollbackData(File backupDir) {
+    RollbackData(int rollbackId, File backupDir) {
+        this.rollbackId = rollbackId;
         this.backupDir = backupDir;
     }
 }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 085b4af..7f515bf 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.server.rollback;
 
-import android.Manifest;
 import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -32,10 +31,10 @@
 import android.content.pm.PackageParser;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.StringParceledListSlice;
+import android.content.pm.VersionedPackage;
 import android.content.rollback.IRollbackManager;
 import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
-import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
@@ -46,6 +45,7 @@
 import android.os.Process;
 import android.os.storage.StorageManager;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
@@ -55,14 +55,16 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.security.SecureRandom;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Random;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -82,6 +84,13 @@
     // mLock is held when they are called.
     private final Object mLock = new Object();
 
+    // Used for generating rollback IDs.
+    private final Random mRandom = new SecureRandom();
+
+    // Set of allocated rollback ids
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mAllocatedRollbackIds = new SparseBooleanArray();
+
     // Package rollback data for rollback-enabled installs that have not yet
     // been committed. Maps from sessionId to rollback data.
     @GuardedBy("mLock")
@@ -209,10 +218,10 @@
         // it's out of date or not, so no need to check package versions here.
 
         for (PackageRollbackInfo info : data.packages) {
-            if (info.packageName.equals(packageName)) {
+            if (info.getPackageName().equals(packageName)) {
                 // TODO: Once the RollbackInfo API supports info about
                 // dependant packages, add that info here.
-                return new RollbackInfo(info);
+                return new RollbackInfo(data.rollbackId, info);
             }
         }
         return null;
@@ -230,7 +239,7 @@
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
                 RollbackData data = mAvailableRollbacks.get(i);
                 for (PackageRollbackInfo info : data.packages) {
-                    packageNames.add(info.packageName);
+                    packageNames.add(info.getPackageName());
                 }
             }
         }
@@ -272,7 +281,7 @@
      */
     private void executeRollbackInternal(RollbackInfo rollback,
             String callerPackageName, IntentSender statusReceiver) {
-        String targetPackageName = rollback.targetPackage.packageName;
+        String targetPackageName = rollback.targetPackage.getPackageName();
         Log.i(TAG, "Initiating rollback of " + targetPackageName);
 
         // Get the latest RollbackData for the target package.
@@ -282,15 +291,9 @@
             return;
         }
 
-        // Verify the latest rollback matches the version requested.
-        // TODO: Check dependant packages too once RollbackInfo includes that
-        // information.
-        for (PackageRollbackInfo info : data.packages) {
-            if (info.packageName.equals(targetPackageName)
-                    && !rollback.targetPackage.higherVersion.equals(info.higherVersion)) {
-                sendFailure(statusReceiver, "Rollback is out of date.");
-                return;
-            }
+        if (data.rollbackId != rollback.getRollbackId()) {
+            sendFailure(statusReceiver, "Rollback for package is out of date");
+            return;
         }
 
         // Verify the RollbackData is up to date with what's installed on
@@ -302,15 +305,14 @@
         // Figure out how to ensure we don't commit the rollback if
         // roll forward happens at the same time.
         for (PackageRollbackInfo info : data.packages) {
-            PackageRollbackInfo.PackageVersion installedVersion =
-                    getInstalledPackageVersion(info.packageName);
+            VersionedPackage installedVersion = getInstalledPackageVersion(info.getPackageName());
             if (installedVersion == null) {
                 // TODO: Test this case
                 sendFailure(statusReceiver, "Package to roll back is not installed");
                 return;
             }
 
-            if (!info.higherVersion.equals(installedVersion)) {
+            if (!packageVersionsEqual(info.getVersionRolledBackFrom(), installedVersion)) {
                 // TODO: Test this case
                 sendFailure(statusReceiver, "Package version to roll back not installed.");
                 return;
@@ -353,7 +355,7 @@
 
                 // TODO: Will it always be called "base.apk"? What about splits?
                 // What about apex?
-                File packageDir = new File(data.backupDir, info.packageName);
+                File packageDir = new File(data.backupDir, info.getPackageName());
                 File baseApk = new File(packageDir, "base.apk");
                 try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(baseApk,
                         ParcelFileDescriptor.MODE_READ_ONLY)) {
@@ -380,12 +382,12 @@
                         addRecentlyExecutedRollback(rollback);
                         sendSuccess(statusReceiver);
 
-                        Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
-                                Uri.fromParts("package", targetPackageName,
-                                        Manifest.permission.MANAGE_ROLLBACKS));
+                        Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
 
                         // TODO: This call emits the warning "Calling a method in the
                         // system process without a qualified user". Fix that.
+                        // TODO: Limit this to receivers holding the
+                        // MANAGE_ROLLBACKS permission?
                         mContext.sendBroadcast(broadcast);
                     }
             );
@@ -427,7 +429,7 @@
             while (iter.hasNext()) {
                 RollbackData data = iter.next();
                 for (PackageRollbackInfo info : data.packages) {
-                    if (info.packageName.equals(packageName)) {
+                    if (info.getPackageName().equals(packageName)) {
                         iter.remove();
                         mRollbackStore.deleteAvailableRollback(data);
                         break;
@@ -469,7 +471,15 @@
     @GuardedBy("mLock")
     private void loadAllRollbackDataLocked() {
         mAvailableRollbacks = mRollbackStore.loadAvailableRollbacks();
+        for (RollbackData data : mAvailableRollbacks) {
+            mAllocatedRollbackIds.put(data.rollbackId, true);
+        }
+
         mRecentlyExecutedRollbacks = mRollbackStore.loadRecentlyExecutedRollbacks();
+        for (RollbackInfo info : mRecentlyExecutedRollbacks) {
+            mAllocatedRollbackIds.put(info.getRollbackId(), true);
+        }
+
         scheduleExpiration(0);
     }
 
@@ -481,8 +491,7 @@
     private void onPackageReplaced(String packageName) {
         // TODO: Could this end up incorrectly deleting a rollback for a
         // package that is about to be installed?
-        PackageRollbackInfo.PackageVersion installedVersion =
-                getInstalledPackageVersion(packageName);
+        VersionedPackage installedVersion = getInstalledPackageVersion(packageName);
 
         synchronized (mLock) {
             ensureRollbackDataLoadedLocked();
@@ -490,8 +499,10 @@
             while (iter.hasNext()) {
                 RollbackData data = iter.next();
                 for (PackageRollbackInfo info : data.packages) {
-                    if (info.packageName.equals(packageName)
-                            && !info.higherVersion.equals(installedVersion)) {
+                    if (info.getPackageName().equals(packageName)
+                            && !packageVersionsEqual(
+                                        info.getVersionRolledBackFrom(),
+                                        installedVersion)) {
                         iter.remove();
                         mRollbackStore.deleteAvailableRollback(data);
                         break;
@@ -514,7 +525,7 @@
             boolean changed = false;
             while (iter.hasNext()) {
                 RollbackInfo rollback = iter.next();
-                if (packageName.equals(rollback.targetPackage.packageName)) {
+                if (packageName.equals(rollback.targetPackage.getPackageName())) {
                     iter.remove();
                     changed = true;
                 }
@@ -689,8 +700,7 @@
             return false;
         }
 
-        PackageRollbackInfo.PackageVersion newVersion =
-                new PackageRollbackInfo.PackageVersion(newPackage.versionCode);
+        VersionedPackage newVersion = new VersionedPackage(packageName, newPackage.versionCode);
 
         // Get information about the currently installed package.
         PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
@@ -701,8 +711,8 @@
             Log.e(TAG, packageName + " is not installed");
             return false;
         }
-        PackageRollbackInfo.PackageVersion installedVersion =
-                new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());
+        VersionedPackage installedVersion = new VersionedPackage(packageName,
+                installedPackage.getLongVersionCode());
 
         for (int user : installedUsers) {
             final int storageFlags;
@@ -723,8 +733,7 @@
             }
         }
 
-        PackageRollbackInfo info = new PackageRollbackInfo(
-                packageName, newVersion, installedVersion);
+        PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion);
 
         RollbackData data;
         try {
@@ -732,7 +741,8 @@
                 mChildSessions.put(childSessionId, parentSessionId);
                 data = mPendingRollbacks.get(parentSessionId);
                 if (data == null) {
-                    data = mRollbackStore.createAvailableRollback();
+                    int rollbackId = allocateRollbackIdLocked();
+                    data = mRollbackStore.createAvailableRollback(rollbackId);
                     mPendingRollbacks.put(parentSessionId, data);
                 }
                 data.packages.add(info);
@@ -819,7 +829,7 @@
      * Gets the version of the package currently installed.
      * Returns null if the package is not currently installed.
      */
-    private PackageRollbackInfo.PackageVersion getInstalledPackageVersion(String packageName) {
+    private VersionedPackage getInstalledPackageVersion(String packageName) {
         PackageManager pm = mContext.getPackageManager();
         PackageInfo pkgInfo = null;
         try {
@@ -828,7 +838,12 @@
             return null;
         }
 
-        return new PackageRollbackInfo.PackageVersion(pkgInfo.getLongVersionCode());
+        return new VersionedPackage(packageName, pkgInfo.getLongVersionCode());
+    }
+
+    private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) {
+        return a.getPackageName().equals(b.getPackageName())
+            && a.getLongVersionCode() == b.getLongVersionCode();
     }
 
     private class SessionCallback extends PackageInstaller.SessionCallback {
@@ -904,7 +919,7 @@
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
                 RollbackData data = mAvailableRollbacks.get(i);
                 for (PackageRollbackInfo info : data.packages) {
-                    if (info.packageName.equals(packageName)) {
+                    if (info.getPackageName().equals(packageName)) {
                         return data;
                     }
                 }
@@ -912,4 +927,19 @@
         }
         return null;
     }
+
+    @GuardedBy("mLock")
+    private int allocateRollbackIdLocked() throws IOException {
+        int n = 0;
+        int rollbackId;
+        do {
+            rollbackId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+            if (!mAllocatedRollbackIds.get(rollbackId, false)) {
+                mAllocatedRollbackIds.put(rollbackId, true);
+                return rollbackId;
+            }
+        } while (n++ < 32);
+
+        throw new IOException("Failed to allocate rollback ID");
+    }
 }
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index eb06bb2..7738be9 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -16,6 +16,7 @@
 
 package com.android.server.rollback;
 
+import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 import android.util.Log;
@@ -29,7 +30,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.nio.file.Files;
 import java.time.Instant;
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
@@ -58,7 +58,7 @@
     //                  base.apk
     //      recently_executed.json
     //
-    // * XXX, YYY are random strings from Files.createTempDirectory
+    // * XXX, YYY are the rollbackIds for the corresponding rollbacks.
     // * rollback.json contains all relevant metadata for the rollback. This
     //   file is not written until the rollback is made available.
     //
@@ -113,13 +113,14 @@
                 JSONArray array = object.getJSONArray("recentlyExecuted");
                 for (int i = 0; i < array.length(); ++i) {
                     JSONObject element = array.getJSONObject(i);
+                    int rollbackId = element.getInt("rollbackId");
                     String packageName = element.getString("packageName");
                     long higherVersionCode = element.getLong("higherVersionCode");
                     long lowerVersionCode = element.getLong("lowerVersionCode");
-                    PackageRollbackInfo target = new PackageRollbackInfo(packageName,
-                            new PackageRollbackInfo.PackageVersion(higherVersionCode),
-                            new PackageRollbackInfo.PackageVersion(lowerVersionCode));
-                    RollbackInfo rollback = new RollbackInfo(target);
+                    PackageRollbackInfo target = new PackageRollbackInfo(
+                            new VersionedPackage(packageName, higherVersionCode),
+                            new VersionedPackage(packageName, lowerVersionCode));
+                    RollbackInfo rollback = new RollbackInfo(rollbackId, target);
                     recentlyExecutedRollbacks.add(rollback);
                 }
             } catch (IOException | JSONException e) {
@@ -135,9 +136,9 @@
     /**
      * Creates a new RollbackData instance with backupDir assigned.
      */
-    RollbackData createAvailableRollback() throws IOException {
-        File backupDir = Files.createTempDirectory(mAvailableRollbacksDir.toPath(), null).toFile();
-        return new RollbackData(backupDir);
+    RollbackData createAvailableRollback(int rollbackId) throws IOException {
+        File backupDir = new File(mAvailableRollbacksDir, Integer.toString(rollbackId));
+        return new RollbackData(rollbackId, backupDir);
     }
 
     /**
@@ -157,11 +158,14 @@
             JSONArray packagesJson = new JSONArray();
             for (PackageRollbackInfo info : data.packages) {
                 JSONObject infoJson = new JSONObject();
-                infoJson.put("packageName", info.packageName);
-                infoJson.put("higherVersionCode", info.higherVersion.versionCode);
-                infoJson.put("lowerVersionCode", info.lowerVersion.versionCode);
+                infoJson.put("packageName", info.getPackageName());
+                infoJson.put("higherVersionCode",
+                        info.getVersionRolledBackFrom().getLongVersionCode());
+                infoJson.put("lowerVersionCode",
+                        info.getVersionRolledBackTo().getVersionCode());
                 packagesJson.put(infoJson);
             }
+            dataJson.put("rollbackId", data.rollbackId);
             dataJson.put("packages", packagesJson);
             dataJson.put("timestamp", data.timestamp.toString());
 
@@ -195,9 +199,12 @@
             for (int i = 0; i < recentlyExecutedRollbacks.size(); ++i) {
                 RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
                 JSONObject element = new JSONObject();
-                element.put("packageName", rollback.targetPackage.packageName);
-                element.put("higherVersionCode", rollback.targetPackage.higherVersion.versionCode);
-                element.put("lowerVersionCode", rollback.targetPackage.lowerVersion.versionCode);
+                element.put("rollbackId", rollback.getRollbackId());
+                element.put("packageName", rollback.targetPackage.getPackageName());
+                element.put("higherVersionCode",
+                        rollback.targetPackage.getVersionRolledBackFrom().getLongVersionCode());
+                element.put("lowerVersionCode",
+                        rollback.targetPackage.getVersionRolledBackTo().getLongVersionCode());
                 array.put(element);
             }
 
@@ -216,19 +223,22 @@
      */
     private RollbackData loadRollbackData(File backupDir) throws IOException {
         try {
-            RollbackData data = new RollbackData(backupDir);
             File rollbackJsonFile = new File(backupDir, "rollback.json");
             JSONObject dataJson = new JSONObject(
                     IoUtils.readFileAsString(rollbackJsonFile.getAbsolutePath()));
+
+            int rollbackId = dataJson.getInt("rollbackId");
+            RollbackData data = new RollbackData(rollbackId, backupDir);
+
             JSONArray packagesJson = dataJson.getJSONArray("packages");
             for (int i = 0; i < packagesJson.length(); ++i) {
                 JSONObject infoJson = packagesJson.getJSONObject(i);
                 String packageName = infoJson.getString("packageName");
                 long higherVersionCode = infoJson.getLong("higherVersionCode");
                 long lowerVersionCode = infoJson.getLong("lowerVersionCode");
-                data.packages.add(new PackageRollbackInfo(packageName,
-                        new PackageRollbackInfo.PackageVersion(higherVersionCode),
-                        new PackageRollbackInfo.PackageVersion(lowerVersionCode)));
+                data.packages.add(new PackageRollbackInfo(
+                        new VersionedPackage(packageName, higherVersionCode),
+                        new VersionedPackage(packageName, lowerVersionCode)));
             }
 
             data.timestamp = Instant.parse(dataJson.getString("timestamp"));
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 9a7e75e..744efab 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -1129,7 +1129,8 @@
          * In this case, we grant a uri permission, even if the ContentProvider does not normally
          * grant uri permissions.
          */
-        boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId
+        boolean specialCrossUserGrant = targetUid >= 0
+                && UserHandle.getUserId(targetUid) != grantUri.sourceUserId
                 && checkHoldingPermissionsInternal(pm, pi, grantUri, callingUid,
                 modeFlags, false /*without considering the uid permissions*/);
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 61c4863..2affa97 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5313,9 +5313,18 @@
     }
 
     void updateActivityUsageStats(ActivityRecord activity, int event) {
+        ComponentName taskRoot = null;
+        final TaskRecord task = activity.getTaskRecord();
+        if (task != null) {
+            final ActivityRecord rootActivity = task.getRootActivity();
+            if (rootActivity != null) {
+                taskRoot = rootActivity.mActivityComponent;
+            }
+        }
+
         final Message m = PooledLambda.obtainMessage(
                 ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
-                activity.mActivityComponent, activity.mUserId, event, activity.appToken);
+                activity.mActivityComponent, activity.mUserId, event, activity.appToken, taskRoot);
         mH.sendMessage(m);
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fb7e47d..54053a8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7667,7 +7667,18 @@
             }
 
             // Shutting down backup manager service permanently.
-            toggleBackupServiceActive(UserHandle.USER_SYSTEM, /* makeActive= */ false);
+            long ident = mInjector.binderClearCallingIdentity();
+            try {
+                if (mInjector.getIBackupManager() != null) {
+                    mInjector.getIBackupManager()
+                            .setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+                }
+            } catch (RemoteException e) {
+                throw new IllegalStateException("Failed deactivating backup service.", e);
+            } finally {
+                mInjector.binderRestoreCallingIdentity(ident);
+            }
+
             if (isAdb()) {
                 // Log device owner provisioning was started using adb.
                 MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_DEVICE_OWNER);
@@ -7695,7 +7706,7 @@
                 saveUserRestrictionsLocked(userId);
             }
 
-            long ident = mInjector.binderClearCallingIdentity();
+            ident = mInjector.binderClearCallingIdentity();
             try {
                 // TODO Send to system too?
                 sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
@@ -7952,9 +7963,6 @@
                         .write();
             }
 
-            // Shutting down backup manager service permanently.
-            toggleBackupServiceActive(userHandle, /* makeActive= */ false);
-
             mOwners.setProfileOwner(who, ownerName, userHandle);
             mOwners.writeProfileOwner(userHandle);
             Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle);
@@ -7978,24 +7986,6 @@
         }
     }
 
-
-    private void toggleBackupServiceActive(int userId, boolean makeActive) {
-        // Shutting down backup manager service permanently.
-        enforceUserUnlocked(userId);
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            if (mInjector.getIBackupManager() != null) {
-                mInjector.getIBackupManager()
-                        .setBackupServiceActive(userId, makeActive);
-            }
-        } catch (RemoteException e) {
-            throw new IllegalStateException("Failed deactivating backup service.", e);
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
-
-    }
-
     @Override
     public void clearProfileOwner(ComponentName who) {
         if (!mHasFeature) {
@@ -12748,9 +12738,22 @@
             return;
         }
         Preconditions.checkNotNull(admin);
-        enforceProfileOrDeviceOwner(admin);
-        int userId = mInjector.userHandleGetCallingUserId();
-        toggleBackupServiceActive(userId, enabled);
+        synchronized (getLockObject()) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        }
+
+        final long ident = mInjector.binderClearCallingIdentity();
+        try {
+            IBackupManager ibm = mInjector.getIBackupManager();
+            if (ibm != null) {
+                ibm.setBackupServiceActive(UserHandle.USER_SYSTEM, enabled);
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException(
+                "Failed " + (enabled ? "" : "de") + "activating backup service.", e);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
     }
 
     @Override
@@ -12759,13 +12762,11 @@
         if (!mHasFeature) {
             return true;
         }
-
-        enforceProfileOrDeviceOwner(admin);
         synchronized (getLockObject()) {
             try {
+                getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
                 IBackupManager ibm = mInjector.getIBackupManager();
-                return ibm != null && ibm.isBackupServiceActive(
-                    mInjector.userHandleGetCallingUserId());
+                return ibm != null && ibm.isBackupServiceActive(UserHandle.USER_SYSTEM);
             } catch (RemoteException e) {
                 throw new IllegalStateException("Failed requesting backup service state.", e);
             }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 623990b..65eaf554 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -873,7 +873,7 @@
                     TimingsTraceLog traceLog = new TimingsTraceLog(
                             SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
                     traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD);
-                    if (!Process.zygoteProcess.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) {
+                    if (!Process.ZYGOTE_PROCESS.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) {
                         Slog.e(TAG, "Unable to preload default resources");
                     }
                     traceLog.traceEnd();
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 3b4d6a7..30c7de5 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -3,18 +3,18 @@
     srcs: ["java/**/*.java"],
 }
 
-// TODO: move to networking module with DhcpClient and remove lib
-java_library {
-    name: "dhcp-packet-lib",
-    srcs: [
-        "java/android/net/dhcp/*Packet.java",
-    ]
-}
-
 filegroup {
     name: "services-networkstack-shared-srcs",
     srcs: [
-        "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient
+        "java/android/net/ip/InterfaceController.java", // TODO: move to NetworkStack with tethering
+        "java/android/net/util/InterfaceParams.java", // TODO: move to NetworkStack with IpServer
         "java/android/net/shared/*.java",
+    ],
+}
+
+java_library {
+    name: "services-netlink-lib",
+    srcs: [
+        "java/android/net/netlink/*.java",
     ]
 }
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 04ac9a3..cddb91f 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -16,1037 +16,14 @@
 
 package android.net.dhcp;
 
-import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS;
-import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
-import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
-import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_MTU;
-import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME;
-import static android.net.dhcp.DhcpPacket.DHCP_ROUTER;
-import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK;
-import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO;
-import static android.net.dhcp.DhcpPacket.INADDR_ANY;
-import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
-import static android.net.util.SocketUtils.makePacketSocketAddress;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_PACKET;
-import static android.system.OsConstants.ETH_P_IP;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOCK_RAW;
-import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_BROADCAST;
-import static android.system.OsConstants.SO_RCVBUF;
-import static android.system.OsConstants.SO_REUSEADDR;
-
-import android.content.Context;
-import android.net.DhcpResults;
-import android.net.NetworkUtils;
-import android.net.TrafficStats;
-import android.net.ip.IpClient;
-import android.net.metrics.DhcpClientEvent;
-import android.net.metrics.DhcpErrorEvent;
-import android.net.metrics.IpConnectivityLog;
-import android.net.util.InterfaceParams;
-import android.net.util.SocketUtils;
-import android.os.Message;
-import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.util.HexDump;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.WakeupMessage;
-
-import libcore.io.IoBridge;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Random;
-
 /**
- * A DHCPv4 client.
- *
- * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
- * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
- *
- * TODO:
- *
- * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
- * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
- *   do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
- *   given SSID), it requests the last-leased IP address on the same interface, causing a delay if
- *   the server NAKs or a timeout if it doesn't.
- *
- * Known differences from current behaviour:
- *
- * - Does not request the "static routes" option.
- * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
- * - Requests the "broadcast" option, but does nothing with it.
- * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
- *
- * @hide
+ * TODO: remove this class after migrating clients.
  */
-public class DhcpClient extends StateMachine {
+public class DhcpClient {
+    public static final int CMD_PRE_DHCP_ACTION = 1003;
+    public static final int CMD_POST_DHCP_ACTION = 1004;
+    public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006;
 
-    private static final String TAG = "DhcpClient";
-    private static final boolean DBG = true;
-    private static final boolean STATE_DBG = false;
-    private static final boolean MSG_DBG = false;
-    private static final boolean PACKET_DBG = false;
-
-    // Timers and timeouts.
-    private static final int SECONDS = 1000;
-    private static final int FIRST_TIMEOUT_MS   =   2 * SECONDS;
-    private static final int MAX_TIMEOUT_MS     = 128 * SECONDS;
-
-    // This is not strictly needed, since the client is asynchronous and implements exponential
-    // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
-    // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
-    // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
-    private static final int DHCP_TIMEOUT_MS    =  36 * SECONDS;
-
-    // DhcpClient uses IpClient's handler.
-    private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE;
-
-    /* Commands from controller to start/stop DHCP */
-    public static final int CMD_START_DHCP                  = PUBLIC_BASE + 1;
-    public static final int CMD_STOP_DHCP                   = PUBLIC_BASE + 2;
-
-    /* Notification from DHCP state machine prior to DHCP discovery/renewal */
-    public static final int CMD_PRE_DHCP_ACTION             = PUBLIC_BASE + 3;
-    /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
-     * success/failure */
-    public static final int CMD_POST_DHCP_ACTION            = PUBLIC_BASE + 4;
-    /* Notification from DHCP state machine before quitting */
-    public static final int CMD_ON_QUIT                     = PUBLIC_BASE + 5;
-
-    /* Command from controller to indicate DHCP discovery/renewal can continue
-     * after pre DHCP action is complete */
-    public static final int CMD_PRE_DHCP_ACTION_COMPLETE    = PUBLIC_BASE + 6;
-
-    /* Command and event notification to/from IpManager requesting the setting
-     * (or clearing) of an IPv4 LinkAddress.
-     */
-    public static final int CMD_CLEAR_LINKADDRESS           = PUBLIC_BASE + 7;
-    public static final int CMD_CONFIGURE_LINKADDRESS       = PUBLIC_BASE + 8;
-    public static final int EVENT_LINKADDRESS_CONFIGURED    = PUBLIC_BASE + 9;
-
-    /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
     public static final int DHCP_SUCCESS = 1;
     public static final int DHCP_FAILURE = 2;
-
-    // Internal messages.
-    private static final int PRIVATE_BASE         = IpClient.DHCPCLIENT_CMD_BASE + 100;
-    private static final int CMD_KICK             = PRIVATE_BASE + 1;
-    private static final int CMD_RECEIVED_PACKET  = PRIVATE_BASE + 2;
-    private static final int CMD_TIMEOUT          = PRIVATE_BASE + 3;
-    private static final int CMD_RENEW_DHCP       = PRIVATE_BASE + 4;
-    private static final int CMD_REBIND_DHCP      = PRIVATE_BASE + 5;
-    private static final int CMD_EXPIRE_DHCP      = PRIVATE_BASE + 6;
-
-    // For message logging.
-    private static final Class[] sMessageClasses = { DhcpClient.class };
-    private static final SparseArray<String> sMessageNames =
-            MessageUtils.findMessageNames(sMessageClasses);
-
-    // DHCP parameters that we request.
-    /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
-        DHCP_SUBNET_MASK,
-        DHCP_ROUTER,
-        DHCP_DNS_SERVER,
-        DHCP_DOMAIN_NAME,
-        DHCP_MTU,
-        DHCP_BROADCAST_ADDRESS,  // TODO: currently ignored.
-        DHCP_LEASE_TIME,
-        DHCP_RENEWAL_TIME,
-        DHCP_REBINDING_TIME,
-        DHCP_VENDOR_INFO,
-    };
-
-    // DHCP flag that means "yes, we support unicast."
-    private static final boolean DO_UNICAST   = false;
-
-    // System services / libraries we use.
-    private final Context mContext;
-    private final Random mRandom;
-    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-
-    // Sockets.
-    // - We use a packet socket to receive, because servers send us packets bound for IP addresses
-    //   which we have not yet configured, and the kernel protocol stack drops these.
-    // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
-    //   be off-link as well as on-link).
-    private FileDescriptor mPacketSock;
-    private FileDescriptor mUdpSock;
-    private ReceiveThread mReceiveThread;
-
-    // State variables.
-    private final StateMachine mController;
-    private final WakeupMessage mKickAlarm;
-    private final WakeupMessage mTimeoutAlarm;
-    private final WakeupMessage mRenewAlarm;
-    private final WakeupMessage mRebindAlarm;
-    private final WakeupMessage mExpiryAlarm;
-    private final String mIfaceName;
-
-    private boolean mRegisteredForPreDhcpNotification;
-    private InterfaceParams mIface;
-    // TODO: MacAddress-ify more of this class hierarchy.
-    private byte[] mHwAddr;
-    private SocketAddress mInterfaceBroadcastAddr;
-    private int mTransactionId;
-    private long mTransactionStartMillis;
-    private DhcpResults mDhcpLease;
-    private long mDhcpLeaseExpiry;
-    private DhcpResults mOffer;
-
-    // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState.
-    private long mLastInitEnterTime;
-    private long mLastBoundExitTime;
-
-    // States.
-    private State mStoppedState = new StoppedState();
-    private State mDhcpState = new DhcpState();
-    private State mDhcpInitState = new DhcpInitState();
-    private State mDhcpSelectingState = new DhcpSelectingState();
-    private State mDhcpRequestingState = new DhcpRequestingState();
-    private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
-    private State mConfiguringInterfaceState = new ConfiguringInterfaceState();
-    private State mDhcpBoundState = new DhcpBoundState();
-    private State mDhcpRenewingState = new DhcpRenewingState();
-    private State mDhcpRebindingState = new DhcpRebindingState();
-    private State mDhcpInitRebootState = new DhcpInitRebootState();
-    private State mDhcpRebootingState = new DhcpRebootingState();
-    private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
-    private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
-
-    private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
-        cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
-        return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
-    }
-
-    // TODO: Take an InterfaceParams instance instead of an interface name String.
-    private DhcpClient(Context context, StateMachine controller, String iface) {
-        super(TAG, controller.getHandler());
-
-        mContext = context;
-        mController = controller;
-        mIfaceName = iface;
-
-        addState(mStoppedState);
-        addState(mDhcpState);
-            addState(mDhcpInitState, mDhcpState);
-            addState(mWaitBeforeStartState, mDhcpState);
-            addState(mDhcpSelectingState, mDhcpState);
-            addState(mDhcpRequestingState, mDhcpState);
-            addState(mDhcpHaveLeaseState, mDhcpState);
-                addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
-                addState(mDhcpBoundState, mDhcpHaveLeaseState);
-                addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
-                addState(mDhcpRenewingState, mDhcpHaveLeaseState);
-                addState(mDhcpRebindingState, mDhcpHaveLeaseState);
-            addState(mDhcpInitRebootState, mDhcpState);
-            addState(mDhcpRebootingState, mDhcpState);
-
-        setInitialState(mStoppedState);
-
-        mRandom = new Random();
-
-        // Used to schedule packet retransmissions.
-        mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
-        // Used to time out PacketRetransmittingStates.
-        mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
-        // Used to schedule DHCP reacquisition.
-        mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
-        mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP);
-        mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP);
-    }
-
-    public void registerForPreDhcpNotification() {
-        mRegisteredForPreDhcpNotification = true;
-    }
-
-    public static DhcpClient makeDhcpClient(
-            Context context, StateMachine controller, InterfaceParams ifParams) {
-        DhcpClient client = new DhcpClient(context, controller, ifParams.name);
-        client.mIface = ifParams;
-        client.start();
-        return client;
-    }
-
-    private boolean initInterface() {
-        if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName);
-        if (mIface == null) {
-            Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName);
-            return false;
-        }
-
-        mHwAddr = mIface.macAddr.toByteArray();
-        mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST);
-        return true;
-    }
-
-    private void startNewTransaction() {
-        mTransactionId = mRandom.nextInt();
-        mTransactionStartMillis = SystemClock.elapsedRealtime();
-    }
-
-    private boolean initSockets() {
-        return initPacketSocket() && initUdpSocket();
-    }
-
-    private boolean initPacketSocket() {
-        try {
-            mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
-            SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index);
-            Os.bind(mPacketSock, addr);
-            NetworkUtils.attachDhcpFilter(mPacketSock);
-        } catch(SocketException|ErrnoException e) {
-            Log.e(TAG, "Error creating packet socket", e);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean initUdpSocket() {
-        final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP);
-        try {
-            mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-            SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName);
-            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
-            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
-            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
-            Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
-        } catch(SocketException|ErrnoException e) {
-            Log.e(TAG, "Error creating UDP socket", e);
-            return false;
-        } finally {
-            TrafficStats.setThreadStatsTag(oldTag);
-        }
-        return true;
-    }
-
-    private boolean connectUdpSock(Inet4Address to) {
-        try {
-            Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
-            return true;
-        } catch (SocketException|ErrnoException e) {
-            Log.e(TAG, "Error connecting UDP socket", e);
-            return false;
-        }
-    }
-
-    private static void closeQuietly(FileDescriptor fd) {
-        try {
-            IoBridge.closeAndSignalBlockedThreads(fd);
-        } catch (IOException ignored) {}
-    }
-
-    private void closeSockets() {
-        closeQuietly(mUdpSock);
-        closeQuietly(mPacketSock);
-    }
-
-    class ReceiveThread extends Thread {
-
-        private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
-        private volatile boolean mStopped = false;
-
-        public void halt() {
-            mStopped = true;
-            closeSockets();  // Interrupts the read() call the thread is blocked in.
-        }
-
-        @Override
-        public void run() {
-            if (DBG) Log.d(TAG, "Receive thread started");
-            while (!mStopped) {
-                int length = 0;  // Or compiler can't tell it's initialized if a parse error occurs.
-                try {
-                    length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
-                    DhcpPacket packet = null;
-                    packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
-                    if (DBG) Log.d(TAG, "Received packet: " + packet);
-                    sendMessage(CMD_RECEIVED_PACKET, packet);
-                } catch (IOException|ErrnoException e) {
-                    if (!mStopped) {
-                        Log.e(TAG, "Read error", e);
-                        logError(DhcpErrorEvent.RECEIVE_ERROR);
-                    }
-                } catch (DhcpPacket.ParseException e) {
-                    Log.e(TAG, "Can't parse packet: " + e.getMessage());
-                    if (PACKET_DBG) {
-                        Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
-                    }
-                    if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
-                        int snetTagId = 0x534e4554;
-                        String bugId = "31850211";
-                        int uid = -1;
-                        String data = DhcpPacket.ParseException.class.getName();
-                        EventLog.writeEvent(snetTagId, bugId, uid, data);
-                    }
-                    logError(e.errorCode);
-                }
-            }
-            if (DBG) Log.d(TAG, "Receive thread stopped");
-        }
-    }
-
-    private short getSecs() {
-        return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
-    }
-
-    private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) {
-        try {
-            if (encap == DhcpPacket.ENCAP_L2) {
-                if (DBG) Log.d(TAG, "Broadcasting " + description);
-                Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
-            } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) {
-                if (DBG) Log.d(TAG, "Broadcasting " + description);
-                // We only send L3-encapped broadcasts in DhcpRebindingState,
-                // where we have an IP address and an unconnected UDP socket.
-                //
-                // N.B.: We only need this codepath because DhcpRequestPacket
-                // hardcodes the source IP address to 0.0.0.0. We could reuse
-                // the packet socket if this ever changes.
-                Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
-            } else {
-                // It's safe to call getpeername here, because we only send unicast packets if we
-                // have an IP address, and we connect the UDP socket in DhcpBoundState#enter.
-                if (DBG) Log.d(TAG, String.format("Unicasting %s to %s",
-                        description, Os.getpeername(mUdpSock)));
-                Os.write(mUdpSock, buf);
-            }
-        } catch(ErrnoException|IOException e) {
-            Log.e(TAG, "Can't send packet: ", e);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean sendDiscoverPacket() {
-        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
-                DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
-                DO_UNICAST, REQUESTED_PARAMS);
-        return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
-    }
-
-    private boolean sendRequestPacket(
-            Inet4Address clientAddress, Inet4Address requestedAddress,
-            Inet4Address serverAddress, Inet4Address to) {
-        // TODO: should we use the transaction ID from the server?
-        final int encap = INADDR_ANY.equals(clientAddress)
-                ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
-
-        ByteBuffer packet = DhcpPacket.buildRequestPacket(
-                encap, mTransactionId, getSecs(), clientAddress,
-                DO_UNICAST, mHwAddr, requestedAddress,
-                serverAddress, REQUESTED_PARAMS, null);
-        String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
-        String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
-                             " request=" + requestedAddress.getHostAddress() +
-                             " serverid=" + serverStr;
-        return transmitPacket(packet, description, encap, to);
-    }
-
-    private void scheduleLeaseTimers() {
-        if (mDhcpLeaseExpiry == 0) {
-            Log.d(TAG, "Infinite lease, no timer scheduling needed");
-            return;
-        }
-
-        final long now = SystemClock.elapsedRealtime();
-
-        // TODO: consider getting the renew and rebind timers from T1 and T2.
-        // See also:
-        //     https://tools.ietf.org/html/rfc2131#section-4.4.5
-        //     https://tools.ietf.org/html/rfc1533#section-9.9
-        //     https://tools.ietf.org/html/rfc1533#section-9.10
-        final long remainingDelay = mDhcpLeaseExpiry - now;
-        final long renewDelay = remainingDelay / 2;
-        final long rebindDelay = remainingDelay * 7 / 8;
-        mRenewAlarm.schedule(now + renewDelay);
-        mRebindAlarm.schedule(now + rebindDelay);
-        mExpiryAlarm.schedule(now + remainingDelay);
-        Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s");
-        Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s");
-        Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s");
-    }
-
-    private void notifySuccess() {
-        mController.sendMessage(
-                CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
-    }
-
-    private void notifyFailure() {
-        mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null);
-    }
-
-    private void acceptDhcpResults(DhcpResults results, String msg) {
-        mDhcpLease = results;
-        mOffer = null;
-        Log.d(TAG, msg + " lease: " + mDhcpLease);
-        notifySuccess();
-    }
-
-    private void clearDhcpState() {
-        mDhcpLease = null;
-        mDhcpLeaseExpiry = 0;
-        mOffer = null;
-    }
-
-    /**
-     * Quit the DhcpStateMachine.
-     *
-     * @hide
-     */
-    public void doQuit() {
-        Log.d(TAG, "doQuit");
-        quit();
-    }
-
-    @Override
-    protected void onQuitting() {
-        Log.d(TAG, "onQuitting");
-        mController.sendMessage(CMD_ON_QUIT);
-    }
-
-    abstract class LoggingState extends State {
-        private long mEnterTimeMs;
-
-        @Override
-        public void enter() {
-            if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
-            mEnterTimeMs = SystemClock.elapsedRealtime();
-        }
-
-        @Override
-        public void exit() {
-            long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs;
-            logState(getName(), (int) durationMs);
-        }
-
-        private String messageName(int what) {
-            return sMessageNames.get(what, Integer.toString(what));
-        }
-
-        private String messageToString(Message message) {
-            long now = SystemClock.uptimeMillis();
-            return new StringBuilder(" ")
-                    .append(message.getWhen() - now)
-                    .append(messageName(message.what))
-                    .append(" ").append(message.arg1)
-                    .append(" ").append(message.arg2)
-                    .append(" ").append(message.obj)
-                    .toString();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (MSG_DBG) {
-                Log.d(TAG, getName() + messageToString(message));
-            }
-            return NOT_HANDLED;
-        }
-
-        @Override
-        public String getName() {
-            // All DhcpClient's states are inner classes with a well defined name.
-            // Use getSimpleName() and avoid super's getName() creating new String instances.
-            return getClass().getSimpleName();
-        }
-    }
-
-    // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
-    // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
-    abstract class WaitBeforeOtherState extends LoggingState {
-        protected State mOtherState;
-
-        @Override
-        public void enter() {
-            super.enter();
-            mController.sendMessage(CMD_PRE_DHCP_ACTION);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case CMD_PRE_DHCP_ACTION_COMPLETE:
-                    transitionTo(mOtherState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-    }
-
-    class StoppedState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_START_DHCP:
-                    if (mRegisteredForPreDhcpNotification) {
-                        transitionTo(mWaitBeforeStartState);
-                    } else {
-                        transitionTo(mDhcpInitState);
-                    }
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-    }
-
-    class WaitBeforeStartState extends WaitBeforeOtherState {
-        public WaitBeforeStartState(State otherState) {
-            super();
-            mOtherState = otherState;
-        }
-    }
-
-    class WaitBeforeRenewalState extends WaitBeforeOtherState {
-        public WaitBeforeRenewalState(State otherState) {
-            super();
-            mOtherState = otherState;
-        }
-    }
-
-    class DhcpState extends State {
-        @Override
-        public void enter() {
-            clearDhcpState();
-            if (initInterface() && initSockets()) {
-                mReceiveThread = new ReceiveThread();
-                mReceiveThread.start();
-            } else {
-                notifyFailure();
-                transitionTo(mStoppedState);
-            }
-        }
-
-        @Override
-        public void exit() {
-            if (mReceiveThread != null) {
-                mReceiveThread.halt();  // Also closes sockets.
-                mReceiveThread = null;
-            }
-            clearDhcpState();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case CMD_STOP_DHCP:
-                    transitionTo(mStoppedState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-    }
-
-    public boolean isValidPacket(DhcpPacket packet) {
-        // TODO: check checksum.
-        int xid = packet.getTransactionId();
-        if (xid != mTransactionId) {
-            Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
-            return false;
-        }
-        if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
-            Log.d(TAG, "MAC addr mismatch: got " +
-                    HexDump.toHexString(packet.getClientMac()) + ", expected " +
-                    HexDump.toHexString(packet.getClientMac()));
-            return false;
-        }
-        return true;
-    }
-
-    public void setDhcpLeaseExpiry(DhcpPacket packet) {
-        long leaseTimeMillis = packet.getLeaseTimeMillis();
-        mDhcpLeaseExpiry =
-                (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
-    }
-
-    /**
-     * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
-     * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
-     * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
-     * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
-     * state.
-     *
-     * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
-     * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
-     * sent by the receive thread. They may also set mTimeout and implement timeout.
-     */
-    abstract class PacketRetransmittingState extends LoggingState {
-
-        private int mTimer;
-        protected int mTimeout = 0;
-
-        @Override
-        public void enter() {
-            super.enter();
-            initTimer();
-            maybeInitTimeout();
-            sendMessage(CMD_KICK);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case CMD_KICK:
-                    sendPacket();
-                    scheduleKick();
-                    return HANDLED;
-                case CMD_RECEIVED_PACKET:
-                    receivePacket((DhcpPacket) message.obj);
-                    return HANDLED;
-                case CMD_TIMEOUT:
-                    timeout();
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        @Override
-        public void exit() {
-            super.exit();
-            mKickAlarm.cancel();
-            mTimeoutAlarm.cancel();
-        }
-
-        abstract protected boolean sendPacket();
-        abstract protected void receivePacket(DhcpPacket packet);
-        protected void timeout() {}
-
-        protected void initTimer() {
-            mTimer = FIRST_TIMEOUT_MS;
-        }
-
-        protected int jitterTimer(int baseTimer) {
-            int maxJitter = baseTimer / 10;
-            int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
-            return baseTimer + jitter;
-        }
-
-        protected void scheduleKick() {
-            long now = SystemClock.elapsedRealtime();
-            long timeout = jitterTimer(mTimer);
-            long alarmTime = now + timeout;
-            mKickAlarm.schedule(alarmTime);
-            mTimer *= 2;
-            if (mTimer > MAX_TIMEOUT_MS) {
-                mTimer = MAX_TIMEOUT_MS;
-            }
-        }
-
-        protected void maybeInitTimeout() {
-            if (mTimeout > 0) {
-                long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
-                mTimeoutAlarm.schedule(alarmTime);
-            }
-        }
-    }
-
-    class DhcpInitState extends PacketRetransmittingState {
-        public DhcpInitState() {
-            super();
-        }
-
-        @Override
-        public void enter() {
-            super.enter();
-            startNewTransaction();
-            mLastInitEnterTime = SystemClock.elapsedRealtime();
-        }
-
-        protected boolean sendPacket() {
-            return sendDiscoverPacket();
-        }
-
-        protected void receivePacket(DhcpPacket packet) {
-            if (!isValidPacket(packet)) return;
-            if (!(packet instanceof DhcpOfferPacket)) return;
-            mOffer = packet.toDhcpResults();
-            if (mOffer != null) {
-                Log.d(TAG, "Got pending lease: " + mOffer);
-                transitionTo(mDhcpRequestingState);
-            }
-        }
-    }
-
-    // Not implemented. We request the first offer we receive.
-    class DhcpSelectingState extends LoggingState {
-    }
-
-    class DhcpRequestingState extends PacketRetransmittingState {
-        public DhcpRequestingState() {
-            mTimeout = DHCP_TIMEOUT_MS / 2;
-        }
-
-        protected boolean sendPacket() {
-            return sendRequestPacket(
-                    INADDR_ANY,                                    // ciaddr
-                    (Inet4Address) mOffer.ipAddress.getAddress(),  // DHCP_REQUESTED_IP
-                    (Inet4Address) mOffer.serverAddress,           // DHCP_SERVER_IDENTIFIER
-                    INADDR_BROADCAST);                             // packet destination address
-        }
-
-        protected void receivePacket(DhcpPacket packet) {
-            if (!isValidPacket(packet)) return;
-            if ((packet instanceof DhcpAckPacket)) {
-                DhcpResults results = packet.toDhcpResults();
-                if (results != null) {
-                    setDhcpLeaseExpiry(packet);
-                    acceptDhcpResults(results, "Confirmed");
-                    transitionTo(mConfiguringInterfaceState);
-                }
-            } else if (packet instanceof DhcpNakPacket) {
-                // TODO: Wait a while before returning into INIT state.
-                Log.d(TAG, "Received NAK, returning to INIT");
-                mOffer = null;
-                transitionTo(mDhcpInitState);
-            }
-        }
-
-        @Override
-        protected void timeout() {
-            // After sending REQUESTs unsuccessfully for a while, go back to init.
-            transitionTo(mDhcpInitState);
-        }
-    }
-
-    class DhcpHaveLeaseState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_EXPIRE_DHCP:
-                    Log.d(TAG, "Lease expired!");
-                    notifyFailure();
-                    transitionTo(mDhcpInitState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        @Override
-        public void exit() {
-            // Clear any extant alarms.
-            mRenewAlarm.cancel();
-            mRebindAlarm.cancel();
-            mExpiryAlarm.cancel();
-            clearDhcpState();
-            // Tell IpManager to clear the IPv4 address. There is no need to
-            // wait for confirmation since any subsequent packets are sent from
-            // INADDR_ANY anyway (DISCOVER, REQUEST).
-            mController.sendMessage(CMD_CLEAR_LINKADDRESS);
-        }
-    }
-
-    class ConfiguringInterfaceState extends LoggingState {
-        @Override
-        public void enter() {
-            super.enter();
-            mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case EVENT_LINKADDRESS_CONFIGURED:
-                    transitionTo(mDhcpBoundState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-    }
-
-    class DhcpBoundState extends LoggingState {
-        @Override
-        public void enter() {
-            super.enter();
-            if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) {
-                // There's likely no point in going into DhcpInitState here, we'll probably
-                // just repeat the transaction, get the same IP address as before, and fail.
-                //
-                // NOTE: It is observed that connectUdpSock() basically never fails, due to
-                // SO_BINDTODEVICE. Examining the local socket address shows it will happily
-                // return an IPv4 address from another interface, or even return "0.0.0.0".
-                //
-                // TODO: Consider deleting this check, following testing on several kernels.
-                notifyFailure();
-                transitionTo(mStoppedState);
-            }
-
-            scheduleLeaseTimers();
-            logTimeToBoundState();
-        }
-
-        @Override
-        public void exit() {
-            super.exit();
-            mLastBoundExitTime = SystemClock.elapsedRealtime();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            super.processMessage(message);
-            switch (message.what) {
-                case CMD_RENEW_DHCP:
-                    if (mRegisteredForPreDhcpNotification) {
-                        transitionTo(mWaitBeforeRenewalState);
-                    } else {
-                        transitionTo(mDhcpRenewingState);
-                    }
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        private void logTimeToBoundState() {
-            long now = SystemClock.elapsedRealtime();
-            if (mLastBoundExitTime > mLastInitEnterTime) {
-                logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime));
-            } else {
-                logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime));
-            }
-        }
-    }
-
-    abstract class DhcpReacquiringState extends PacketRetransmittingState {
-        protected String mLeaseMsg;
-
-        @Override
-        public void enter() {
-            super.enter();
-            startNewTransaction();
-        }
-
-        abstract protected Inet4Address packetDestination();
-
-        protected boolean sendPacket() {
-            return sendRequestPacket(
-                    (Inet4Address) mDhcpLease.ipAddress.getAddress(),  // ciaddr
-                    INADDR_ANY,                                        // DHCP_REQUESTED_IP
-                    null,                                              // DHCP_SERVER_IDENTIFIER
-                    packetDestination());                              // packet destination address
-        }
-
-        protected void receivePacket(DhcpPacket packet) {
-            if (!isValidPacket(packet)) return;
-            if ((packet instanceof DhcpAckPacket)) {
-                final DhcpResults results = packet.toDhcpResults();
-                if (results != null) {
-                    if (!mDhcpLease.ipAddress.equals(results.ipAddress)) {
-                        Log.d(TAG, "Renewed lease not for our current IP address!");
-                        notifyFailure();
-                        transitionTo(mDhcpInitState);
-                    }
-                    setDhcpLeaseExpiry(packet);
-                    // Updating our notion of DhcpResults here only causes the
-                    // DNS servers and routes to be updated in LinkProperties
-                    // in IpManager and by any overridden relevant handlers of
-                    // the registered IpManager.Callback.  IP address changes
-                    // are not supported here.
-                    acceptDhcpResults(results, mLeaseMsg);
-                    transitionTo(mDhcpBoundState);
-                }
-            } else if (packet instanceof DhcpNakPacket) {
-                Log.d(TAG, "Received NAK, returning to INIT");
-                notifyFailure();
-                transitionTo(mDhcpInitState);
-            }
-        }
-    }
-
-    class DhcpRenewingState extends DhcpReacquiringState {
-        public DhcpRenewingState() {
-            mLeaseMsg = "Renewed";
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (super.processMessage(message) == HANDLED) {
-                return HANDLED;
-            }
-
-            switch (message.what) {
-                case CMD_REBIND_DHCP:
-                    transitionTo(mDhcpRebindingState);
-                    return HANDLED;
-                default:
-                    return NOT_HANDLED;
-            }
-        }
-
-        @Override
-        protected Inet4Address packetDestination() {
-            // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
-            // http://b/25343517 . Try to make things work anyway by using broadcast renews.
-            return (mDhcpLease.serverAddress != null) ?
-                    mDhcpLease.serverAddress : INADDR_BROADCAST;
-        }
-    }
-
-    class DhcpRebindingState extends DhcpReacquiringState {
-        public DhcpRebindingState() {
-            mLeaseMsg = "Rebound";
-        }
-
-        @Override
-        public void enter() {
-            super.enter();
-
-            // We need to broadcast and possibly reconnect the socket to a
-            // completely different server.
-            closeQuietly(mUdpSock);
-            if (!initUdpSocket()) {
-                Log.e(TAG, "Failed to recreate UDP socket");
-                transitionTo(mDhcpInitState);
-            }
-        }
-
-        @Override
-        protected Inet4Address packetDestination() {
-            return INADDR_BROADCAST;
-        }
-    }
-
-    class DhcpInitRebootState extends LoggingState {
-    }
-
-    class DhcpRebootingState extends LoggingState {
-    }
-
-    private void logError(int errorCode) {
-        mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
-    }
-
-    private void logState(String name, int durationMs) {
-        final DhcpClientEvent event = new DhcpClientEvent.Builder()
-                .setMsg(name)
-                .setDurationMs(durationMs)
-                .build();
-        mMetricsLog.log(mIfaceName, event);
-    }
 }
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 233b86f..a61c2ef 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -16,222 +16,206 @@
 
 package android.net.ip;
 
-import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
 
 import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.DhcpResults;
-import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
-import android.net.ProvisioningConfigurationParcelable;
 import android.net.ProxyInfo;
-import android.net.ProxyInfoParcelable;
-import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
 import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.dhcp.DhcpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.shared.InitialConfiguration;
-import android.net.util.InterfaceParams;
-import android.net.util.NetdService;
-import android.net.util.SharedLog;
 import android.os.ConditionVariable;
 import android.os.INetworkManagementService;
-import android.os.Message;
 import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.LocalLog;
 import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IState;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.WakeupMessage;
-import com.android.server.net.NetlinkTracker;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.net.InetAddress;
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
 
 /**
- * IpClient
- *
- * This class provides the interface to IP-layer provisioning and maintenance
- * functionality that can be used by transport layers like Wi-Fi, Ethernet,
- * et cetera.
- *
- * [ Lifetime ]
- * IpClient is designed to be instantiated as soon as the interface name is
- * known and can be as long-lived as the class containing it (i.e. declaring
- * it "private final" is okay).
- *
+ * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated.
  * @hide
  */
-public class IpClient extends StateMachine {
-    private static final boolean DBG = false;
+public class IpClient {
+    private static final String TAG = IpClient.class.getSimpleName();
+    private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000;
 
-    // For message logging.
-    private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
-    private static final SparseArray<String> sWhatToString =
-            MessageUtils.findMessageNames(sMessageClasses);
-    // Two static concurrent hashmaps of interface name to logging classes.
-    // One holds StateMachine logs and the other connectivity packet logs.
-    private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>();
-    private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
+    public static final String DUMP_ARG = "ipclient";
 
-    // If |args| is non-empty, assume it's a list of interface names for which
-    // we should print IpClient logs (filter out all others).
-    public static void dumpAllLogs(PrintWriter writer, String[] args) {
-        for (String ifname : sSmLogs.keySet()) {
-            if (!ArrayUtils.isEmpty(args) && !ArrayUtils.contains(args, ifname)) continue;
+    private final ConditionVariable mIpClientCv;
+    private final ConditionVariable mShutdownCv;
 
-            writer.println(String.format("--- BEGIN %s ---", ifname));
-
-            final SharedLog smLog = sSmLogs.get(ifname);
-            if (smLog != null) {
-                writer.println("State machine log:");
-                smLog.dump(null, writer, null);
-            }
-
-            writer.println("");
-
-            final LocalLog pktLog = sPktLogs.get(ifname);
-            if (pktLog != null) {
-                writer.println("Connectivity packet log:");
-                pktLog.readOnlyLocalLog().dump(null, writer, null);
-            }
-
-            writer.println(String.format("--- END %s ---", ifname));
-        }
-    }
+    private volatile IIpClient mIpClient;
 
     /**
-     * TODO: remove after migrating clients to use IpClientCallbacks directly
      * @see IpClientCallbacks
      */
     public static class Callback extends IpClientCallbacks {}
 
     /**
-     * TODO: remove once clients are migrated to IpClientUtil.WaitForProvisioningCallback
-     * @see IpClientUtil.WaitForProvisioningCallbacks
+     * IpClient callback that allows clients to block until provisioning is complete.
      */
-    public static class WaitForProvisioningCallback
-            extends IpClientUtil.WaitForProvisioningCallbacks {}
+    public static class WaitForProvisioningCallback extends Callback {
+        private final ConditionVariable mCV = new ConditionVariable();
+        private LinkProperties mCallbackLinkProperties;
 
-    // Use a wrapper class to log in order to ensure complete and detailed
-    // logging. This method is lighter weight than annotations/reflection
-    // and has the following benefits:
-    //
-    //     - No invoked method can be forgotten.
-    //       Any new method added to IpClient.Callback must be overridden
-    //       here or it will never be called.
-    //
-    //     - No invoking call site can be forgotten.
-    //       Centralized logging in this way means call sites don't need to
-    //       remember to log, and therefore no call site can be forgotten.
-    //
-    //     - No variation in log format among call sites.
-    //       Encourages logging of any available arguments, and all call sites
-    //       are necessarily logged identically.
-    //
-    // NOTE: Log first because passed objects may or may not be thread-safe and
-    // once passed on to the callback they may be modified by another thread.
-    //
-    // TODO: Find an lighter weight approach.
-    private class LoggingCallbackWrapper extends IpClientCallbacks {
-        private static final String PREFIX = "INVOKE ";
-        private final IpClientCallbacks mCallback;
-
-        LoggingCallbackWrapper(IpClientCallbacks callback) {
-            mCallback = (callback != null) ? callback : new IpClientCallbacks();
+        /**
+         * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
+         * {@link #onProvisioningFailure(LinkProperties)} is called.
+         */
+        public LinkProperties waitForProvisioning() {
+            mCV.block();
+            return mCallbackLinkProperties;
         }
 
-        private void log(String msg) {
-            mLog.log(PREFIX + msg);
-        }
-
-        @Override
-        public void onPreDhcpAction() {
-            log("onPreDhcpAction()");
-            mCallback.onPreDhcpAction();
-        }
-        @Override
-        public void onPostDhcpAction() {
-            log("onPostDhcpAction()");
-            mCallback.onPostDhcpAction();
-        }
-        @Override
-        public void onNewDhcpResults(DhcpResults dhcpResults) {
-            log("onNewDhcpResults({" + dhcpResults + "})");
-            mCallback.onNewDhcpResults(dhcpResults);
-        }
         @Override
         public void onProvisioningSuccess(LinkProperties newLp) {
-            log("onProvisioningSuccess({" + newLp + "})");
-            mCallback.onProvisioningSuccess(newLp);
+            mCallbackLinkProperties = newLp;
+            mCV.open();
         }
+
         @Override
         public void onProvisioningFailure(LinkProperties newLp) {
-            log("onProvisioningFailure({" + newLp + "})");
-            mCallback.onProvisioningFailure(newLp);
+            mCallbackLinkProperties = null;
+            mCV.open();
         }
+    }
+
+    private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy {
+        /**
+         * Create a new IpClientCallbacksProxy.
+         */
+        CallbackImpl(IpClientCallbacks cb) {
+            super(cb);
+        }
+
         @Override
-        public void onLinkPropertiesChange(LinkProperties newLp) {
-            log("onLinkPropertiesChange({" + newLp + "})");
-            mCallback.onLinkPropertiesChange(newLp);
+        public void onIpClientCreated(IIpClient ipClient) {
+            mIpClient = ipClient;
+            mIpClientCv.open();
+            super.onIpClientCreated(ipClient);
         }
-        @Override
-        public void onReachabilityLost(String logMsg) {
-            log("onReachabilityLost(" + logMsg + ")");
-            mCallback.onReachabilityLost(logMsg);
-        }
+
         @Override
         public void onQuit() {
-            log("onQuit()");
-            mCallback.onQuit();
+            mShutdownCv.open();
+            super.onQuit();
         }
-        @Override
-        public void installPacketFilter(byte[] filter) {
-            log("installPacketFilter(byte[" + filter.length + "])");
-            mCallback.installPacketFilter(filter);
+    }
+
+    /**
+     * Create a new IpClient.
+     */
+    public IpClient(Context context, String iface, Callback callback) {
+        mIpClientCv = new ConditionVariable(false);
+        mShutdownCv = new ConditionVariable(false);
+
+        IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback));
+    }
+
+    /**
+     * @see IpClient#IpClient(Context, String, IpClient.Callback)
+     */
+    public IpClient(Context context, String iface, Callback callback,
+            INetworkManagementService nms) {
+        this(context, iface, callback);
+    }
+
+    private interface IpClientAction {
+        void useIpClient(IIpClient ipClient) throws RemoteException;
+    }
+
+    private void doWithIpClient(IpClientAction action) {
+        mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
+        try {
+            action.useIpClient(mIpClient);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error communicating with IpClient", e);
         }
-        @Override
-        public void startReadPacketFilter() {
-            log("startReadPacketFilter()");
-            mCallback.startReadPacketFilter();
-        }
-        @Override
-        public void setFallbackMulticastFilter(boolean enabled) {
-            log("setFallbackMulticastFilter(" + enabled + ")");
-            mCallback.setFallbackMulticastFilter(enabled);
-        }
-        @Override
-        public void setNeighborDiscoveryOffload(boolean enable) {
-            log("setNeighborDiscoveryOffload(" + enable + ")");
-            mCallback.setNeighborDiscoveryOffload(enable);
-        }
+    }
+
+    /**
+     * Notify IpClient that PreDhcpAction is completed.
+     */
+    public void completedPreDhcpAction() {
+        doWithIpClient(c -> c.completedPreDhcpAction());
+    }
+
+    /**
+     * Confirm the provisioning configuration.
+     */
+    public void confirmConfiguration() {
+        doWithIpClient(c -> c.confirmConfiguration());
+    }
+
+    /**
+     * Notify IpClient that packet filter read is complete.
+     */
+    public void readPacketFilterComplete(byte[] data) {
+        doWithIpClient(c -> c.readPacketFilterComplete(data));
+    }
+
+    /**
+     * Shutdown the IpClient altogether.
+     */
+    public void shutdown() {
+        doWithIpClient(c -> c.shutdown());
+    }
+
+    /**
+     * Start the IpClient provisioning.
+     */
+    public void startProvisioning(ProvisioningConfiguration config) {
+        doWithIpClient(c -> c.startProvisioning(config.toStableParcelable()));
+    }
+
+    /**
+     * Stop the IpClient.
+     */
+    public void stop() {
+        doWithIpClient(c -> c.stop());
+    }
+
+    /**
+     * Set the IpClient TCP buffer sizes.
+     */
+    public void setTcpBufferSizes(String tcpBufferSizes) {
+        doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes));
+    }
+
+    /**
+     * Set the IpClient HTTP proxy.
+     */
+    public void setHttpProxy(ProxyInfo proxyInfo) {
+        doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo)));
+    }
+
+    /**
+     * Set the IpClient multicast filter.
+     */
+    public void setMulticastFilter(boolean enabled) {
+        doWithIpClient(c -> c.setMulticastFilter(enabled));
+    }
+
+    /**
+     * Dump IpClient logs.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args));
+    }
+
+    /**
+     * Block until IpClient shutdown.
+     */
+    public void awaitShutdown() {
+        mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
+    }
+
+    /**
+     * Create a new ProvisioningConfiguration.
+     */
+    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+        return new ProvisioningConfiguration.Builder();
     }
 
     /**
@@ -333,1430 +317,4 @@
             }
         }
     }
-
-    public static final String DUMP_ARG = "ipclient";
-    public static final String DUMP_ARG_CONFIRM = "confirm";
-
-    private static final int CMD_TERMINATE_AFTER_STOP             = 1;
-    private static final int CMD_STOP                             = 2;
-    private static final int CMD_START                            = 3;
-    private static final int CMD_CONFIRM                          = 4;
-    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE       = 5;
-    // Triggered by NetlinkTracker to communicate netlink events.
-    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
-    private static final int CMD_UPDATE_TCP_BUFFER_SIZES          = 7;
-    private static final int CMD_UPDATE_HTTP_PROXY                = 8;
-    private static final int CMD_SET_MULTICAST_FILTER             = 9;
-    private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
-    private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
-    private static final int EVENT_READ_PACKET_FILTER_COMPLETE    = 12;
-
-    // Internal commands to use instead of trying to call transitionTo() inside
-    // a given State's enter() method. Calling transitionTo() from enter/exit
-    // encounters a Log.wtf() that can cause trouble on eng builds.
-    private static final int CMD_JUMP_STARTED_TO_RUNNING          = 100;
-    private static final int CMD_JUMP_RUNNING_TO_STOPPING         = 101;
-    private static final int CMD_JUMP_STOPPING_TO_STOPPED         = 102;
-
-    // IpClient shares a handler with DhcpClient: commands must not overlap
-    public static final int DHCPCLIENT_CMD_BASE = 1000;
-
-    private static final int MAX_LOG_RECORDS = 500;
-    private static final int MAX_PACKET_RECORDS = 100;
-
-    private static final boolean NO_CALLBACKS = false;
-    private static final boolean SEND_CALLBACKS = true;
-
-    // This must match the interface prefix in clatd.c.
-    // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
-    private static final String CLAT_PREFIX = "v4-";
-
-    private static final int IMMEDIATE_FAILURE_DURATION = 0;
-
-    private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1;
-    private static final int PROV_CHANGE_LOST_PROVISIONING = 2;
-    private static final int PROV_CHANGE_GAINED_PROVISIONING = 3;
-    private static final int PROV_CHANGE_STILL_PROVISIONED = 4;
-
-    private final State mStoppedState = new StoppedState();
-    private final State mStoppingState = new StoppingState();
-    private final State mStartedState = new StartedState();
-    private final State mRunningState = new RunningState();
-
-    private final String mTag;
-    private final Context mContext;
-    private final String mInterfaceName;
-    private final String mClatInterfaceName;
-    @VisibleForTesting
-    protected final IpClientCallbacks mCallback;
-    private final Dependencies mDependencies;
-    private final CountDownLatch mShutdownLatch;
-    private final ConnectivityManager mCm;
-    private final INetworkManagementService mNwService;
-    private final NetlinkTracker mNetlinkTracker;
-    private final WakeupMessage mProvisioningTimeoutAlarm;
-    private final WakeupMessage mDhcpActionTimeoutAlarm;
-    private final SharedLog mLog;
-    private final LocalLog mConnectivityPacketLog;
-    private final MessageHandlingLogger mMsgStateLogger;
-    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-    private final InterfaceController mInterfaceCtrl;
-
-    private InterfaceParams mInterfaceParams;
-
-    /**
-     * Non-final member variables accessed only from within our StateMachine.
-     */
-    private LinkProperties mLinkProperties;
-    private android.net.shared.ProvisioningConfiguration mConfiguration;
-    private IpReachabilityMonitor mIpReachabilityMonitor;
-    private DhcpClient mDhcpClient;
-    private DhcpResults mDhcpResults;
-    private String mTcpBufferSizes;
-    private ProxyInfo mHttpProxy;
-    private ApfFilter mApfFilter;
-    private boolean mMulticastFiltering;
-    private long mStartTimeMillis;
-
-    /**
-     * Reading the snapshot is an asynchronous operation initiated by invoking
-     * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
-     * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable
-     * signals when a new snapshot is ready.
-     */
-    private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable();
-
-    public static class Dependencies {
-        public INetworkManagementService getNMS() {
-            return INetworkManagementService.Stub.asInterface(
-                    ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
-        }
-
-        public INetd getNetd() {
-            return NetdService.getInstance();
-        }
-
-        /**
-         * Get interface parameters for the specified interface.
-         */
-        public InterfaceParams getInterfaceParams(String ifname) {
-            return InterfaceParams.getByName(ifname);
-        }
-    }
-
-    public IpClient(Context context, String ifName, IpClientCallbacks callback) {
-        this(context, ifName, callback, new Dependencies());
-    }
-
-    /**
-     * An expanded constructor, useful for dependency injection.
-     * TODO: migrate all test users to mock IpClient directly and remove this ctor.
-     */
-    public IpClient(Context context, String ifName, IpClientCallbacks callback,
-            INetworkManagementService nwService) {
-        this(context, ifName, callback, new Dependencies() {
-            @Override
-            public INetworkManagementService getNMS() {
-                return nwService;
-            }
-        });
-    }
-
-    @VisibleForTesting
-    IpClient(Context context, String ifName, IpClientCallbacks callback, Dependencies deps) {
-        super(IpClient.class.getSimpleName() + "." + ifName);
-        Preconditions.checkNotNull(ifName);
-        Preconditions.checkNotNull(callback);
-
-        mTag = getName();
-
-        mContext = context;
-        mInterfaceName = ifName;
-        mClatInterfaceName = CLAT_PREFIX + ifName;
-        mCallback = new LoggingCallbackWrapper(callback);
-        mDependencies = deps;
-        mShutdownLatch = new CountDownLatch(1);
-        mCm = mContext.getSystemService(ConnectivityManager.class);
-        mNwService = deps.getNMS();
-
-        sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
-        mLog = sSmLogs.get(mInterfaceName);
-        sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
-        mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
-        mMsgStateLogger = new MessageHandlingLogger();
-
-        // TODO: Consider creating, constructing, and passing in some kind of
-        // InterfaceController.Dependencies class.
-        mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog);
-
-        mNetlinkTracker = new NetlinkTracker(
-                mInterfaceName,
-                new NetlinkTracker.Callback() {
-                    @Override
-                    public void update() {
-                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-                    }
-                }) {
-            @Override
-            public void interfaceAdded(String iface) {
-                super.interfaceAdded(iface);
-                if (mClatInterfaceName.equals(iface)) {
-                    mCallback.setNeighborDiscoveryOffload(false);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceAdded(" + iface + ")";
-                logMsg(msg);
-            }
-
-            @Override
-            public void interfaceRemoved(String iface) {
-                super.interfaceRemoved(iface);
-                // TODO: Also observe mInterfaceName going down and take some
-                // kind of appropriate action.
-                if (mClatInterfaceName.equals(iface)) {
-                    // TODO: consider sending a message to the IpClient main
-                    // StateMachine thread, in case "NDO enabled" state becomes
-                    // tied to more things that 464xlat operation.
-                    mCallback.setNeighborDiscoveryOffload(true);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceRemoved(" + iface + ")";
-                logMsg(msg);
-            }
-
-            private void logMsg(String msg) {
-                Log.d(mTag, msg);
-                getHandler().post(() -> mLog.log("OBSERVED " + msg));
-            }
-        };
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-
-        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
-        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
-
-        // Anything the StateMachine may access must have been instantiated
-        // before this point.
-        configureAndStartStateMachine();
-
-        // Anything that may send messages to the StateMachine must only be
-        // configured to do so after the StateMachine has started (above).
-        startStateMachineUpdaters();
-    }
-
-    /**
-     * Make a IIpClient connector to communicate with this IpClient.
-     */
-    public IIpClient makeConnector() {
-        return new IpClientConnector();
-    }
-
-    class IpClientConnector extends IIpClient.Stub {
-        @Override
-        public void completedPreDhcpAction() {
-            IpClient.this.completedPreDhcpAction();
-        }
-        @Override
-        public void confirmConfiguration() {
-            IpClient.this.confirmConfiguration();
-        }
-        @Override
-        public void readPacketFilterComplete(byte[] data) {
-            IpClient.this.readPacketFilterComplete(data);
-        }
-        @Override
-        public void shutdown() {
-            IpClient.this.shutdown();
-        }
-        @Override
-        public void startProvisioning(ProvisioningConfigurationParcelable req) {
-            IpClient.this.startProvisioning(
-                    android.net.shared.ProvisioningConfiguration.fromStableParcelable(req));
-        }
-        @Override
-        public void stop() {
-            IpClient.this.stop();
-        }
-        @Override
-        public void setTcpBufferSizes(String tcpBufferSizes) {
-            IpClient.this.setTcpBufferSizes(tcpBufferSizes);
-        }
-        @Override
-        public void setHttpProxy(ProxyInfoParcelable proxyInfo) {
-            IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo));
-        }
-        @Override
-        public void setMulticastFilter(boolean enabled) {
-            IpClient.this.setMulticastFilter(enabled);
-        }
-        // TODO: remove and have IpClient logs dumped in NetworkStack dumpsys
-        public void dumpIpClientLogs(FileDescriptor fd, PrintWriter pw, String[] args) {
-            IpClient.this.dump(fd, pw, args);
-        }
-    }
-
-    private void configureAndStartStateMachine() {
-        // CHECKSTYLE:OFF IndentationCheck
-        addState(mStoppedState);
-        addState(mStartedState);
-            addState(mRunningState, mStartedState);
-        addState(mStoppingState);
-        // CHECKSTYLE:ON IndentationCheck
-
-        setInitialState(mStoppedState);
-
-        super.start();
-    }
-
-    private void startStateMachineUpdaters() {
-        try {
-            mNwService.registerObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't register NetlinkTracker: %s", e);
-        }
-    }
-
-    private void stopStateMachineUpdaters() {
-        try {
-            mNwService.unregisterObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't unregister NetlinkTracker: %s", e);
-        }
-    }
-
-    @Override
-    protected void onQuitting() {
-        mCallback.onQuit();
-        mShutdownLatch.countDown();
-    }
-
-    /**
-     * Shut down this IpClient instance altogether.
-     */
-    public void shutdown() {
-        stop();
-        sendMessage(CMD_TERMINATE_AFTER_STOP);
-    }
-
-    // In order to avoid deadlock, this method MUST NOT be called on the
-    // IpClient instance's thread. This prohibition includes code executed by
-    // when methods on the passed-in IpClient.Callback instance are called.
-    public void awaitShutdown() {
-        try {
-            mShutdownLatch.await();
-        } catch (InterruptedException e) {
-            mLog.e("Interrupted while awaiting shutdown: " + e);
-        }
-    }
-
-    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
-        return new ProvisioningConfiguration.Builder();
-    }
-
-    /**
-     * Start provisioning with the provided parameters.
-     */
-    public void startProvisioning(android.net.shared.ProvisioningConfiguration req) {
-        if (!req.isValid()) {
-            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-            return;
-        }
-
-        mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName);
-        if (mInterfaceParams == null) {
-            logError("Failed to find InterfaceParams for " + mInterfaceName);
-            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
-            return;
-        }
-
-        mCallback.setNeighborDiscoveryOffload(true);
-        sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
-    }
-
-    // TODO: Delete this.
-    public void startProvisioning(StaticIpConfiguration staticIpConfig) {
-        startProvisioning(buildProvisioningConfiguration()
-                .withStaticConfiguration(staticIpConfig)
-                .build());
-    }
-
-    public void startProvisioning() {
-        startProvisioning(new android.net.shared.ProvisioningConfiguration());
-    }
-
-    /**
-     * Stop this IpClient.
-     *
-     * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
-     */
-    public void stop() {
-        sendMessage(CMD_STOP);
-    }
-
-    /**
-     * Confirm the provisioning configuration.
-     */
-    public void confirmConfiguration() {
-        sendMessage(CMD_CONFIRM);
-    }
-
-    /**
-     * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
-     * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
-     * proceed.
-     */
-    public void completedPreDhcpAction() {
-        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-    }
-
-    /**
-     * Indicate that packet filter read is complete.
-     */
-    public void readPacketFilterComplete(byte[] data) {
-        sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data);
-    }
-
-    /**
-     * Set the TCP buffer sizes to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setTcpBufferSizes(String tcpBufferSizes) {
-        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
-    }
-
-    /**
-     * Set the HTTP Proxy configuration to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setHttpProxy(ProxyInfo proxyInfo) {
-        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
-    }
-
-    /**
-     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
-     * if not, Callback.setFallbackMulticastFilter() is called.
-     */
-    public void setMulticastFilter(boolean enabled) {
-        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
-    }
-
-    /**
-     * Dump logs of this IpClient.
-     */
-    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
-            // Execute confirmConfiguration() and take no further action.
-            confirmConfiguration();
-            return;
-        }
-
-        // Thread-unsafe access to mApfFilter but just used for debugging.
-        final ApfFilter apfFilter = mApfFilter;
-        final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration;
-        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
-                ? provisioningConfig.mApfCapabilities : null;
-
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println(mTag + " APF dump:");
-        pw.increaseIndent();
-        if (apfFilter != null) {
-            if (apfCapabilities.hasDataAccess()) {
-                // Request a new snapshot, then wait for it.
-                mApfDataSnapshotComplete.close();
-                mCallback.startReadPacketFilter();
-                if (!mApfDataSnapshotComplete.block(1000)) {
-                    pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT");
-                }
-            }
-            apfFilter.dump(pw);
-
-        } else {
-            pw.print("No active ApfFilter; ");
-            if (provisioningConfig == null) {
-                pw.println("IpClient not yet started.");
-            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
-                pw.println("Hardware does not support APF.");
-            } else {
-                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
-            }
-        }
-        pw.decreaseIndent();
-        pw.println();
-        pw.println(mTag + " current ProvisioningConfiguration:");
-        pw.increaseIndent();
-        pw.println(Objects.toString(provisioningConfig, "N/A"));
-        pw.decreaseIndent();
-
-        final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
-        if (iprm != null) {
-            pw.println();
-            pw.println(mTag + " current IpReachabilityMonitor state:");
-            pw.increaseIndent();
-            iprm.dump(pw);
-            pw.decreaseIndent();
-        }
-
-        pw.println();
-        pw.println(mTag + " StateMachine dump:");
-        pw.increaseIndent();
-        mLog.dump(fd, pw, args);
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " connectivity packet log:");
-        pw.println();
-        pw.println("Debug with python and scapy via:");
-        pw.println("shell$ python");
-        pw.println(">>> from scapy import all as scapy");
-        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
-        pw.println();
-
-        pw.increaseIndent();
-        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
-        pw.decreaseIndent();
-    }
-
-
-    /**
-     * Internals.
-     */
-
-    @Override
-    protected String getWhatToString(int what) {
-        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
-    }
-
-    @Override
-    protected String getLogRecString(Message msg) {
-        final String logLine = String.format(
-                "%s/%d %d %d %s [%s]",
-                mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index,
-                msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
-
-        final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
-        mLog.log(richerLogLine);
-        if (DBG) {
-            Log.d(mTag, richerLogLine);
-        }
-
-        mMsgStateLogger.reset();
-        return logLine;
-    }
-
-    @Override
-    protected boolean recordLogRec(Message msg) {
-        // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
-        // and we already log any LinkProperties change that results in an
-        // invocation of IpClient.Callback#onLinkPropertiesChange().
-        final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-        if (!shouldLog) {
-            mMsgStateLogger.reset();
-        }
-        return shouldLog;
-    }
-
-    private void logError(String fmt, Object... args) {
-        final String msg = "ERROR " + String.format(fmt, args);
-        Log.e(mTag, msg);
-        mLog.log(msg);
-    }
-
-    // This needs to be called with care to ensure that our LinkProperties
-    // are in sync with the actual LinkProperties of the interface. For example,
-    // we should only call this if we know for sure that there are no IP addresses
-    // assigned to the interface, etc.
-    private void resetLinkProperties() {
-        mNetlinkTracker.clearLinkProperties();
-        mConfiguration = null;
-        mDhcpResults = null;
-        mTcpBufferSizes = "";
-        mHttpProxy = null;
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-    }
-
-    private void recordMetric(final int type) {
-        // We may record error metrics prior to starting.
-        // Map this to IMMEDIATE_FAILURE_DURATION.
-        final long duration = (mStartTimeMillis > 0)
-                ? (SystemClock.elapsedRealtime() - mStartTimeMillis)
-                : IMMEDIATE_FAILURE_DURATION;
-        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
-    }
-
-    // For now: use WifiStateMachine's historical notion of provisioned.
-    @VisibleForTesting
-    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
-        // For historical reasons, we should connect even if all we have is
-        // an IPv4 address and nothing else.
-        if (lp.hasIPv4Address() || lp.isProvisioned()) {
-            return true;
-        }
-        if (config == null) {
-            return false;
-        }
-
-        // When an InitialConfiguration is specified, ignore any difference with previous
-        // properties and instead check if properties observed match the desired properties.
-        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
-    }
-
-    // TODO: Investigate folding all this into the existing static function
-    // LinkProperties.compareProvisioning() or some other single function that
-    // takes two LinkProperties objects and returns a ProvisioningChange
-    // object that is a correct and complete assessment of what changed, taking
-    // account of the asymmetries described in the comments in this function.
-    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
-    private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
-        int delta;
-        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
-        final boolean wasProvisioned = isProvisioned(oldLp, config);
-        final boolean isProvisioned = isProvisioned(newLp, config);
-
-        if (!wasProvisioned && isProvisioned) {
-            delta = PROV_CHANGE_GAINED_PROVISIONING;
-        } else if (wasProvisioned && isProvisioned) {
-            delta = PROV_CHANGE_STILL_PROVISIONED;
-        } else if (!wasProvisioned && !isProvisioned) {
-            delta = PROV_CHANGE_STILL_NOT_PROVISIONED;
-        } else {
-            // (wasProvisioned && !isProvisioned)
-            //
-            // Note that this is true even if we lose a configuration element
-            // (e.g., a default gateway) that would not be required to advance
-            // into provisioned state. This is intended: if we have a default
-            // router and we lose it, that's a sure sign of a problem, but if
-            // we connect to a network with no IPv4 DNS servers, we consider
-            // that to be a network without DNS servers and connect anyway.
-            //
-            // See the comment below.
-            delta = PROV_CHANGE_LOST_PROVISIONING;
-        }
-
-        final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
-        final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
-        final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
-
-        // If bad wifi avoidance is disabled, then ignore IPv6 loss of
-        // provisioning. Otherwise, when a hotspot that loses Internet
-        // access sends out a 0-lifetime RA to its clients, the clients
-        // will disconnect and then reconnect, avoiding the bad hotspot,
-        // instead of getting stuck on the bad hotspot. http://b/31827713 .
-        //
-        // This is incorrect because if the hotspot then regains Internet
-        // access with a different prefix, TCP connections on the
-        // deprecated addresses will remain stuck.
-        //
-        // Note that we can still be disconnected by IpReachabilityMonitor
-        // if the IPv6 default gateway (but not the IPv6 DNS servers; see
-        // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss =
-                mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
-                && mCm.getAvoidBadWifi();
-
-        // Additionally:
-        //
-        // Partial configurations (e.g., only an IPv4 address with no DNS
-        // servers and no default route) are accepted as long as DHCPv4
-        // succeeds. On such a network, isProvisioned() will always return
-        // false, because the configuration is not complete, but we want to
-        // connect anyway. It might be a disconnected network such as a
-        // Chromecast or a wireless printer, for example.
-        //
-        // Because on such a network isProvisioned() will always return false,
-        // delta will never be LOST_PROVISIONING. So check for loss of
-        // provisioning here too.
-        if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
-            delta = PROV_CHANGE_LOST_PROVISIONING;
-        }
-
-        // Additionally:
-        //
-        // If the previous link properties had a global IPv6 address and an
-        // IPv6 default route then also consider the loss of that default route
-        // to be a loss of provisioning. See b/27962810.
-        if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
-            delta = PROV_CHANGE_LOST_PROVISIONING;
-        }
-
-        return delta;
-    }
-
-    private void dispatchCallback(int delta, LinkProperties newLp) {
-        switch (delta) {
-            case PROV_CHANGE_GAINED_PROVISIONING:
-                if (DBG) {
-                    Log.d(mTag, "onProvisioningSuccess()");
-                }
-                recordMetric(IpManagerEvent.PROVISIONING_OK);
-                mCallback.onProvisioningSuccess(newLp);
-                break;
-
-            case PROV_CHANGE_LOST_PROVISIONING:
-                if (DBG) {
-                    Log.d(mTag, "onProvisioningFailure()");
-                }
-                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
-                mCallback.onProvisioningFailure(newLp);
-                break;
-
-            default:
-                if (DBG) {
-                    Log.d(mTag, "onLinkPropertiesChange()");
-                }
-                mCallback.onLinkPropertiesChange(newLp);
-                break;
-        }
-    }
-
-    // Updates all IpClient-related state concerned with LinkProperties.
-    // Returns a ProvisioningChange for possibly notifying other interested
-    // parties that are not fronted by IpClient.
-    private int setLinkProperties(LinkProperties newLp) {
-        if (mApfFilter != null) {
-            mApfFilter.setLinkProperties(newLp);
-        }
-        if (mIpReachabilityMonitor != null) {
-            mIpReachabilityMonitor.updateLinkProperties(newLp);
-        }
-
-        int delta = compareProvisioning(mLinkProperties, newLp);
-        mLinkProperties = new LinkProperties(newLp);
-
-        if (delta == PROV_CHANGE_GAINED_PROVISIONING) {
-            // TODO: Add a proper ProvisionedState and cancel the alarm in
-            // its enter() method.
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        return delta;
-    }
-
-    private LinkProperties assembleLinkProperties() {
-        // [1] Create a new LinkProperties object to populate.
-        LinkProperties newLp = new LinkProperties();
-        newLp.setInterfaceName(mInterfaceName);
-
-        // [2] Pull in data from netlink:
-        //         - IPv4 addresses
-        //         - IPv6 addresses
-        //         - IPv6 routes
-        //         - IPv6 DNS servers
-        //
-        // N.B.: this is fundamentally race-prone and should be fixed by
-        // changing NetlinkTracker from a hybrid edge/level model to an
-        // edge-only model, or by giving IpClient its own netlink socket(s)
-        // so as to track all required information directly.
-        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
-        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
-        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
-            newLp.addRoute(route);
-        }
-        addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
-
-        // [3] Add in data from DHCPv4, if available.
-        //
-        // mDhcpResults is never shared with any other owner so we don't have
-        // to worry about concurrent modification.
-        if (mDhcpResults != null) {
-            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
-                newLp.addRoute(route);
-            }
-            addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
-            newLp.setDomains(mDhcpResults.domains);
-
-            if (mDhcpResults.mtu != 0) {
-                newLp.setMtu(mDhcpResults.mtu);
-            }
-        }
-
-        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
-        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
-            newLp.setTcpBufferSizes(mTcpBufferSizes);
-        }
-        if (mHttpProxy != null) {
-            newLp.setHttpProxy(mHttpProxy);
-        }
-
-        // [5] Add data from InitialConfiguration
-        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
-            InitialConfiguration config = mConfiguration.mInitialConfig;
-            // Add InitialConfiguration routes and dns server addresses once all addresses
-            // specified in the InitialConfiguration have been observed with Netlink.
-            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
-                for (IpPrefix prefix : config.directlyConnectedRoutes) {
-                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
-                }
-            }
-            addAllReachableDnsServers(newLp, config.dnsServers);
-        }
-        final LinkProperties oldLp = mLinkProperties;
-        if (DBG) {
-            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
-                    netlinkLinkProperties, newLp, oldLp));
-        }
-
-        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
-        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
-        return newLp;
-    }
-
-    private static void addAllReachableDnsServers(
-            LinkProperties lp, Iterable<InetAddress> dnses) {
-        // TODO: Investigate deleting this reachability check.  We should be
-        // able to pass everything down to netd and let netd do evaluation
-        // and RFC6724-style sorting.
-        for (InetAddress dns : dnses) {
-            if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
-                lp.addDnsServer(dns);
-            }
-        }
-    }
-
-    // Returns false if we have lost provisioning, true otherwise.
-    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
-        final LinkProperties newLp = assembleLinkProperties();
-        if (Objects.equals(newLp, mLinkProperties)) {
-            return true;
-        }
-        final int delta = setLinkProperties(newLp);
-        if (sendCallbacks) {
-            dispatchCallback(delta, newLp);
-        }
-        return (delta != PROV_CHANGE_LOST_PROVISIONING);
-    }
-
-    private void handleIPv4Success(DhcpResults dhcpResults) {
-        mDhcpResults = new DhcpResults(dhcpResults);
-        final LinkProperties newLp = assembleLinkProperties();
-        final int delta = setLinkProperties(newLp);
-
-        if (DBG) {
-            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
-        }
-        mCallback.onNewDhcpResults(dhcpResults);
-        dispatchCallback(delta, newLp);
-    }
-
-    private void handleIPv4Failure() {
-        // TODO: Investigate deleting this clearIPv4Address() call.
-        //
-        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
-        // that could trigger a call to this function. If we missed handling
-        // that message in StartedState for some reason we would still clear
-        // any addresses upon entry to StoppedState.
-        mInterfaceCtrl.clearIPv4Address();
-        mDhcpResults = null;
-        if (DBG) {
-            Log.d(mTag, "onNewDhcpResults(null)");
-        }
-        mCallback.onNewDhcpResults(null);
-
-        handleProvisioningFailure();
-    }
-
-    private void handleProvisioningFailure() {
-        final LinkProperties newLp = assembleLinkProperties();
-        int delta = setLinkProperties(newLp);
-        // If we've gotten here and we're still not provisioned treat that as
-        // a total loss of provisioning.
-        //
-        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
-        // there was no usable IPv6 obtained before a non-zero provisioning
-        // timeout expired.
-        //
-        // Regardless: GAME OVER.
-        if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) {
-            delta = PROV_CHANGE_LOST_PROVISIONING;
-        }
-
-        dispatchCallback(delta, newLp);
-        if (delta == PROV_CHANGE_LOST_PROVISIONING) {
-            transitionTo(mStoppingState);
-        }
-    }
-
-    private void doImmediateProvisioningFailure(int failureType) {
-        logError("onProvisioningFailure(): %s", failureType);
-        recordMetric(failureType);
-        mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
-    }
-
-    private boolean startIPv4() {
-        // If we have a StaticIpConfiguration attempt to apply it and
-        // handle the result accordingly.
-        if (mConfiguration.mStaticIpConfig != null) {
-            if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
-                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
-            } else {
-                return false;
-            }
-        } else {
-            // Start DHCPv4.
-            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams);
-            mDhcpClient.registerForPreDhcpNotification();
-            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
-        }
-
-        return true;
-    }
-
-    private boolean startIPv6() {
-        return mInterfaceCtrl.setIPv6PrivacyExtensions(true)
-                && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode)
-                && mInterfaceCtrl.enableIPv6();
-    }
-
-    private boolean applyInitialConfig(InitialConfiguration config) {
-        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
-        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
-            if (!mInterfaceCtrl.addAddress(addr)) return false;
-        }
-
-        return true;
-    }
-
-    private boolean startIpReachabilityMonitor() {
-        try {
-            // TODO: Fetch these parameters from settings, and install a
-            // settings observer to watch for update and re-program these
-            // parameters (Q: is this level of dynamic updatability really
-            // necessary or does reading from settings at startup suffice?).
-            final int numSolicits = 5;
-            final int interSolicitIntervalMs = 750;
-            setNeighborParameters(mDependencies.getNetd(), mInterfaceName,
-                    numSolicits, interSolicitIntervalMs);
-        } catch (Exception e) {
-            mLog.e("Failed to adjust neighbor parameters", e);
-            // Carry on using the system defaults (currently: 3, 1000);
-        }
-
-        try {
-            mIpReachabilityMonitor = new IpReachabilityMonitor(
-                    mContext,
-                    mInterfaceParams,
-                    getHandler(),
-                    mLog,
-                    new IpReachabilityMonitor.Callback() {
-                        @Override
-                        public void notifyLost(InetAddress ip, String logMsg) {
-                            mCallback.onReachabilityLost(logMsg);
-                        }
-                    },
-                    mConfiguration.mUsingMultinetworkPolicyTracker);
-        } catch (IllegalArgumentException iae) {
-            // Failed to start IpReachabilityMonitor. Log it and call
-            // onProvisioningFailure() immediately.
-            //
-            // See http://b/31038971.
-            logError("IpReachabilityMonitor failure: %s", iae);
-            mIpReachabilityMonitor = null;
-        }
-
-        return (mIpReachabilityMonitor != null);
-    }
-
-    private void stopAllIP() {
-        // We don't need to worry about routes, just addresses, because:
-        //     - disableIpv6() will clear autoconf IPv6 routes as well, and
-        //     - we don't get IPv4 routes from netlink
-        // so we neither react to nor need to wait for changes in either.
-
-        mInterfaceCtrl.disableIPv6();
-        mInterfaceCtrl.clearAllAddresses();
-    }
-
-    class StoppedState extends State {
-        @Override
-        public void enter() {
-            stopAllIP();
-
-            resetLinkProperties();
-            if (mStartTimeMillis > 0) {
-                // Completed a life-cycle; send a final empty LinkProperties
-                // (cleared in resetLinkProperties() above) and record an event.
-                mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties));
-                recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
-                mStartTimeMillis = 0;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_TERMINATE_AFTER_STOP:
-                    stopStateMachineUpdaters();
-                    quit();
-                    break;
-
-                case CMD_STOP:
-                    break;
-
-                case CMD_START:
-                    mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj;
-                    transitionTo(mStartedState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_SET_MULTICAST_FILTER:
-                    mMulticastFiltering = (boolean) msg.obj;
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // Everything is already stopped.
-                    logError("Unexpected CMD_ON_QUIT (already stopped).");
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StoppingState extends State {
-        @Override
-        public void enter() {
-            if (mDhcpClient == null) {
-                // There's no DHCPv4 for which to wait; proceed to stopped.
-                deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED));
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_JUMP_STOPPING_TO_STOPPED:
-                    transitionTo(mStoppedState);
-                    break;
-
-                case CMD_STOP:
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    mDhcpClient = null;
-                    transitionTo(mStoppedState);
-                    break;
-
-                default:
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StartedState extends State {
-        @Override
-        public void enter() {
-            mStartTimeMillis = SystemClock.elapsedRealtime();
-
-            if (mConfiguration.mProvisioningTimeoutMs > 0) {
-                final long alarmTime = SystemClock.elapsedRealtime()
-                        + mConfiguration.mProvisioningTimeoutMs;
-                mProvisioningTimeoutAlarm.schedule(alarmTime);
-            }
-
-            if (readyToProceed()) {
-                deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING));
-            } else {
-                // Clear all IPv4 and IPv6 before proceeding to RunningState.
-                // Clean up any leftover state from an abnormal exit from
-                // tethering or during an IpClient restart.
-                stopAllIP();
-            }
-        }
-
-        @Override
-        public void exit() {
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_JUMP_STARTED_TO_RUNNING:
-                    transitionTo(mRunningState);
-                    break;
-
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    if (readyToProceed()) {
-                        transitionTo(mRunningState);
-                    }
-                    break;
-
-                case EVENT_PROVISIONING_TIMEOUT:
-                    handleProvisioningFailure();
-                    break;
-
-                default:
-                    // It's safe to process messages out of order because the
-                    // only message that can both
-                    //     a) be received at this time and
-                    //     b) affect provisioning state
-                    // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-
-        private boolean readyToProceed() {
-            return (!mLinkProperties.hasIPv4Address() && !mLinkProperties.hasGlobalIPv6Address());
-        }
-    }
-
-    class RunningState extends State {
-        private ConnectivityPacketTracker mPacketTracker;
-        private boolean mDhcpActionInFlight;
-
-        @Override
-        public void enter() {
-            ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
-            apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
-            apfConfig.multicastFilter = mMulticastFiltering;
-            // Get the Configuration for ApfFilter from Context
-            apfConfig.ieee802_3Filter =
-                    mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-            apfConfig.ethTypeBlackList =
-                    mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
-            mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
-            // TODO: investigate the effects of any multicast filtering racing/interfering with the
-            // rest of this IP configuration startup.
-            if (mApfFilter == null) {
-                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-            }
-
-            mPacketTracker = createPacketTracker();
-            if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
-
-            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
-                enqueueJumpToStoppingState();
-                return;
-            }
-
-            if (mConfiguration.mEnableIPv4 && !startIPv4()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
-                enqueueJumpToStoppingState();
-                return;
-            }
-
-            final InitialConfiguration config = mConfiguration.mInitialConfig;
-            if ((config != null) && !applyInitialConfig(config)) {
-                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-                enqueueJumpToStoppingState();
-                return;
-            }
-
-            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
-                doImmediateProvisioningFailure(
-                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
-                enqueueJumpToStoppingState();
-                return;
-            }
-        }
-
-        @Override
-        public void exit() {
-            stopDhcpAction();
-
-            if (mIpReachabilityMonitor != null) {
-                mIpReachabilityMonitor.stop();
-                mIpReachabilityMonitor = null;
-            }
-
-            if (mDhcpClient != null) {
-                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
-                mDhcpClient.doQuit();
-            }
-
-            if (mPacketTracker != null) {
-                mPacketTracker.stop();
-                mPacketTracker = null;
-            }
-
-            if (mApfFilter != null) {
-                mApfFilter.shutdown();
-                mApfFilter = null;
-            }
-
-            resetLinkProperties();
-        }
-
-        private void enqueueJumpToStoppingState() {
-            deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
-        }
-
-        private ConnectivityPacketTracker createPacketTracker() {
-            try {
-                return new ConnectivityPacketTracker(
-                        getHandler(), mInterfaceParams, mConnectivityPacketLog);
-            } catch (IllegalArgumentException e) {
-                return null;
-            }
-        }
-
-        private void ensureDhcpAction() {
-            if (!mDhcpActionInFlight) {
-                mCallback.onPreDhcpAction();
-                mDhcpActionInFlight = true;
-                final long alarmTime = SystemClock.elapsedRealtime()
-                        + mConfiguration.mRequestedPreDhcpActionMs;
-                mDhcpActionTimeoutAlarm.schedule(alarmTime);
-            }
-        }
-
-        private void stopDhcpAction() {
-            mDhcpActionTimeoutAlarm.cancel();
-            if (mDhcpActionInFlight) {
-                mCallback.onPostDhcpAction();
-                mDhcpActionInFlight = false;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_JUMP_RUNNING_TO_STOPPING:
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case CMD_START:
-                    logError("ALERT: START received in StartedState. Please fix caller.");
-                    break;
-
-                case CMD_CONFIRM:
-                    // TODO: Possibly introduce a second type of confirmation
-                    // that both probes (a) on-link neighbors and (b) does
-                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
-                    // roams.
-                    if (mIpReachabilityMonitor != null) {
-                        mIpReachabilityMonitor.probeAll();
-                    }
-                    break;
-
-                case EVENT_PRE_DHCP_ACTION_COMPLETE:
-                    // It's possible to reach here if, for example, someone
-                    // calls completedPreDhcpAction() after provisioning with
-                    // a static IP configuration.
-                    if (mDhcpClient != null) {
-                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_SET_MULTICAST_FILTER: {
-                    mMulticastFiltering = (boolean) msg.obj;
-                    if (mApfFilter != null) {
-                        mApfFilter.setMulticastFilter(mMulticastFiltering);
-                    } else {
-                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-                    }
-                    break;
-                }
-
-                case EVENT_READ_PACKET_FILTER_COMPLETE: {
-                    if (mApfFilter != null) {
-                        mApfFilter.setDataSnapshot((byte[]) msg.obj);
-                    }
-                    mApfDataSnapshotComplete.open();
-                    break;
-                }
-
-                case EVENT_DHCPACTION_TIMEOUT:
-                    stopDhcpAction();
-                    break;
-
-                case DhcpClient.CMD_PRE_DHCP_ACTION:
-                    if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
-                        ensureDhcpAction();
-                    } else {
-                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
-                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
-                    if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
-                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
-                    } else {
-                        logError("Failed to set IPv4 address.");
-                        dispatchCallback(PROV_CHANGE_LOST_PROVISIONING,
-                                new LinkProperties(mLinkProperties));
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-                }
-
-                // This message is only received when:
-                //
-                //     a) initial address acquisition succeeds,
-                //     b) renew succeeds or is NAK'd,
-                //     c) rebind succeeds or is NAK'd, or
-                //     c) the lease expires,
-                //
-                // but never when initial address acquisition fails. The latter
-                // condition is now governed by the provisioning timeout.
-                case DhcpClient.CMD_POST_DHCP_ACTION:
-                    stopDhcpAction();
-
-                    switch (msg.arg1) {
-                        case DhcpClient.DHCP_SUCCESS:
-                            handleIPv4Success((DhcpResults) msg.obj);
-                            break;
-                        case DhcpClient.DHCP_FAILURE:
-                            handleIPv4Failure();
-                            break;
-                        default:
-                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
-                    }
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // DHCPv4 quit early for some reason.
-                    logError("Unexpected CMD_ON_QUIT.");
-                    mDhcpClient = null;
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    private static class MessageHandlingLogger {
-        public String processedInState;
-        public String receivedInState;
-
-        public void reset() {
-            processedInState = null;
-            receivedInState = null;
-        }
-
-        public void handled(State processedIn, IState receivedIn) {
-            processedInState = processedIn.getClass().getSimpleName();
-            receivedInState = receivedIn.getName();
-        }
-
-        public String toString() {
-            return String.format("rcvd_in=%s, proc_in=%s",
-                                 receivedInState, processedInState);
-        }
-    }
-
-    private static void setNeighborParameters(
-            INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs)
-            throws RemoteException, IllegalArgumentException {
-        Preconditions.checkNotNull(netd);
-        Preconditions.checkArgument(!TextUtils.isEmpty(ifName));
-        Preconditions.checkArgument(numSolicits > 0);
-        Preconditions.checkArgument(interSolicitIntervalMs > 0);
-
-        for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) {
-            netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms",
-                    Integer.toString(interSolicitIntervalMs));
-            netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit",
-                    Integer.toString(numSolicits));
-        }
-    }
-
-    // TODO: extract out into CollectionUtils.
-    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
-        for (T t : coll) {
-            if (fn.test(t)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
-        return !any(coll, not(fn));
-    }
-
-    static <T> Predicate<T> not(Predicate<T> fn) {
-        return (t) -> !fn.test(t);
-    }
-
-    static <T> String join(String delimiter, Collection<T> coll) {
-        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
-    }
-
-    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
-        for (T t: coll) {
-            if (fn.test(t)) {
-                return t;
-            }
-        }
-        return null;
-    }
-
-    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
-        return coll.stream().filter(fn).collect(Collectors.toList());
-    }
 }
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 0aec101..2a2a67a 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -16,8 +16,15 @@
 
 package android.net.ip;
 
+import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+
 import android.content.Context;
+import android.net.DhcpResultsParcelable;
 import android.net.LinkProperties;
+import android.net.LinkPropertiesParcelable;
+import android.net.NetworkStack;
+import android.net.ip.IIpClientCallbacks;
 import android.os.ConditionVariable;
 
 import java.io.FileDescriptor;
@@ -31,8 +38,8 @@
  * @hide
  */
 public class IpClientUtil {
-    // TODO: remove once IpClient dumps are moved to NetworkStack and callers don't need this arg
-    public static final String DUMP_ARG = IpClient.DUMP_ARG;
+    // TODO: remove with its callers
+    public static final String DUMP_ARG = "ipclient";
 
     /**
      * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is
@@ -69,24 +76,129 @@
      *
      * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of
      * {@link IIpClientCallbacks}.
+     * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)}
      */
     public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) {
-        // TODO: request IpClient asynchronously from NetworkStack.
-        final IpClient ipClient = new IpClient(context, ifName, callback);
-        callback.onIpClientCreated(ipClient.makeConnector());
+        context.getSystemService(NetworkStack.class)
+                .makeIpClient(ifName, new IpClientCallbacksProxy(callback));
+    }
+
+    /**
+     * Create a new IpClient.
+     *
+     * <p>This is a convenience method to allow clients to use {@link IpClientCallbacksProxy}
+     * instead of {@link IIpClientCallbacks}.
+     * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)}
+     */
+    public static void makeIpClient(
+            Context context, String ifName, IpClientCallbacksProxy callback) {
+        context.getSystemService(NetworkStack.class)
+                .makeIpClient(ifName, callback);
+    }
+
+    /**
+     * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}.
+     */
+    public static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub {
+        protected final IpClientCallbacks mCb;
+
+        /**
+         * Create a new IpClientCallbacksProxy.
+         */
+        public IpClientCallbacksProxy(IpClientCallbacks cb) {
+            mCb = cb;
+        }
+
+        @Override
+        public void onIpClientCreated(IIpClient ipClient) {
+            mCb.onIpClientCreated(ipClient);
+        }
+
+        @Override
+        public void onPreDhcpAction() {
+            mCb.onPreDhcpAction();
+        }
+
+        @Override
+        public void onPostDhcpAction() {
+            mCb.onPostDhcpAction();
+        }
+
+        // This is purely advisory and not an indication of provisioning
+        // success or failure.  This is only here for callers that want to
+        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+        // DHCPv4 or static IPv4 configuration failure or success can be
+        // determined by whether or not the passed-in DhcpResults object is
+        // null or not.
+        @Override
+        public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
+            mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+        }
+
+        @Override
+        public void onProvisioningSuccess(LinkPropertiesParcelable newLp) {
+            mCb.onProvisioningSuccess(fromStableParcelable(newLp));
+        }
+        @Override
+        public void onProvisioningFailure(LinkPropertiesParcelable newLp) {
+            mCb.onProvisioningFailure(fromStableParcelable(newLp));
+        }
+
+        // Invoked on LinkProperties changes.
+        @Override
+        public void onLinkPropertiesChange(LinkPropertiesParcelable newLp) {
+            mCb.onLinkPropertiesChange(fromStableParcelable(newLp));
+        }
+
+        // Called when the internal IpReachabilityMonitor (if enabled) has
+        // detected the loss of a critical number of required neighbors.
+        @Override
+        public void onReachabilityLost(String logMsg) {
+            mCb.onReachabilityLost(logMsg);
+        }
+
+        // Called when the IpClient state machine terminates.
+        @Override
+        public void onQuit() {
+            mCb.onQuit();
+        }
+
+        // Install an APF program to filter incoming packets.
+        @Override
+        public void installPacketFilter(byte[] filter) {
+            mCb.installPacketFilter(filter);
+        }
+
+        // Asynchronously read back the APF program & data buffer from the wifi driver.
+        // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+        // buffer. In response to this request, the driver returns the data buffer asynchronously
+        // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+        @Override
+        public void startReadPacketFilter() {
+            mCb.startReadPacketFilter();
+        }
+
+        // If multicast filtering cannot be accomplished with APF, this function will be called to
+        // actuate multicast filtering using another means.
+        @Override
+        public void setFallbackMulticastFilter(boolean enabled) {
+            mCb.setFallbackMulticastFilter(enabled);
+        }
+
+        // Enabled/disable Neighbor Discover offload functionality. This is
+        // called, for example, whenever 464xlat is being started or stopped.
+        @Override
+        public void setNeighborDiscoveryOffload(boolean enable) {
+            mCb.setNeighborDiscoveryOffload(enable);
+        }
     }
 
     /**
      * Dump logs for the specified IpClient.
-     * TODO: remove logging from this method once IpClient logs are dumped in NetworkStack dumpsys,
-     * then remove callers and delete.
+     * TODO: remove callers and delete
      */
     public static void dumpIpClient(
             IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!(connector instanceof IpClient.IpClientConnector)) {
-            pw.println("Invalid connector");
-            return;
-        }
-        ((IpClient.IpClientConnector) connector).dumpIpClientLogs(fd, pw, args);
+        pw.println("IpClient logs have moved to dumpsys network_stack");
     }
 }
diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java
index 7910c9a..f7360f5 100644
--- a/services/net/java/android/net/ip/IpServer.java
+++ b/services/net/java/android/net/ip/IpServer.java
@@ -40,7 +40,7 @@
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
-import android.net.util.NetdService;
+import android.net.shared.NetdService;
 import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
 import android.os.Looper;
diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/shared/NetdService.java
similarity index 99%
rename from services/net/java/android/net/util/NetdService.java
rename to services/net/java/android/net/shared/NetdService.java
index 80b2c27..be0f5f2 100644
--- a/services/net/java/android/net/util/NetdService.java
+++ b/services/net/java/android/net/shared/NetdService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.net.util;
+package android.net.shared;
 
 import android.net.INetd;
 import android.os.RemoteException;
diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java
index 463cf2a..3d2a2de 100644
--- a/services/net/java/android/net/shared/NetworkMonitorUtils.java
+++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java
@@ -16,6 +16,11 @@
 
 package android.net.shared;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+
 import android.content.Context;
 import android.net.NetworkCapabilities;
 import android.provider.Settings;
@@ -58,9 +63,12 @@
      * @param dfltNetCap Default requested network capabilities.
      * @param nc Network capabilities of the network to test.
      */
-    public static boolean isValidationRequired(
-            NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
+    public static boolean isValidationRequired(NetworkCapabilities nc) {
         // TODO: Consider requiring validation for DUN networks.
-        return dfltNetCap.satisfiedByNetworkCapabilities(nc);
+        return nc != null
+                && nc.hasCapability(NET_CAPABILITY_INTERNET)
+                && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                && nc.hasCapability(NET_CAPABILITY_TRUSTED)
+                && nc.hasCapability(NET_CAPABILITY_NOT_VPN);
     }
 }
diff --git a/services/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java
index 7b060da..f6bb873 100644
--- a/services/net/java/android/net/util/InterfaceParams.java
+++ b/services/net/java/android/net/util/InterfaceParams.java
@@ -16,9 +16,6 @@
 
 package android.net.util;
 
-import static android.net.util.NetworkConstants.ETHER_MTU;
-import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
-
 import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.net.MacAddress;
@@ -44,6 +41,11 @@
     public final MacAddress macAddr;
     public final int defaultMtu;
 
+    // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack.
+    private static final int ETHER_MTU = 1500;
+    private static final int IPV6_MIN_MTU = 1280;
+
+
     public static InterfaceParams getByName(String name) {
         final NetworkInterface netif = getNetworkInterfaceByName(name);
         if (netif == null) return null;
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index c183b81..ea5ce65 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -28,28 +28,6 @@
 public final class NetworkConstants {
     private NetworkConstants() { throw new RuntimeException("no instance permitted"); }
 
-    /**
-     * Ethernet constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc894
-     *     - https://tools.ietf.org/html/rfc2464
-     *     - https://tools.ietf.org/html/rfc7042
-     *     - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
-     *     - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
-     */
-    public static final int ETHER_DST_ADDR_OFFSET = 0;
-    public static final int ETHER_SRC_ADDR_OFFSET = 6;
-    public static final int ETHER_ADDR_LEN = 6;
-
-    public static final int ETHER_TYPE_OFFSET = 12;
-    public static final int ETHER_TYPE_LENGTH = 2;
-    public static final int ETHER_TYPE_ARP  = 0x0806;
-    public static final int ETHER_TYPE_IPV4 = 0x0800;
-    public static final int ETHER_TYPE_IPV6 = 0x86dd;
-
-    public static final int ETHER_HEADER_LEN = 14;
-
     public static final byte FF = asByte(0xff);
     public static final byte[] ETHER_ADDR_BROADCAST = {
         FF, FF, FF, FF, FF, FF
@@ -58,34 +36,12 @@
     public static final int ETHER_MTU = 1500;
 
     /**
-     * ARP constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc826
-     *     - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
-     */
-    public static final int ARP_PAYLOAD_LEN = 28;  // For Ethernet+IPv4.
-    public static final int ARP_REQUEST = 1;
-    public static final int ARP_REPLY   = 2;
-    public static final int ARP_HWTYPE_RESERVED_LO = 0;
-    public static final int ARP_HWTYPE_ETHER       = 1;
-    public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
-
-    /**
      * IPv4 constants.
      *
      * See also:
      *     - https://tools.ietf.org/html/rfc791
      */
-    public static final int IPV4_HEADER_MIN_LEN = 20;
-    public static final int IPV4_IHL_MASK = 0xf;
-    public static final int IPV4_FLAGS_OFFSET = 6;
-    public static final int IPV4_FRAGMENT_MASK = 0x1fff;
-    public static final int IPV4_PROTOCOL_OFFSET = 9;
-    public static final int IPV4_SRC_ADDR_OFFSET = 12;
-    public static final int IPV4_DST_ADDR_OFFSET = 16;
     public static final int IPV4_ADDR_BITS = 32;
-    public static final int IPV4_ADDR_LEN = 4;
 
     /**
      * IPv6 constants.
@@ -93,15 +49,10 @@
      * See also:
      *     - https://tools.ietf.org/html/rfc2460
      */
-    public static final int IPV6_HEADER_LEN = 40;
-    public static final int IPV6_PROTOCOL_OFFSET = 6;
-    public static final int IPV6_SRC_ADDR_OFFSET = 8;
-    public static final int IPV6_DST_ADDR_OFFSET = 24;
     public static final int IPV6_ADDR_BITS = 128;
     public static final int IPV6_ADDR_LEN = 16;
     public static final int IPV6_MIN_MTU = 1280;
     public static final int RFC7421_PREFIX_LENGTH = 64;
-    public static final int RFC6177_MIN_PREFIX_LENGTH = 48;
 
     /**
      * ICMP common (v4/v6) constants.
@@ -124,45 +75,7 @@
      *     - https://tools.ietf.org/html/rfc792
      */
     public static final int ICMPV4_ECHO_REQUEST_TYPE = 8;
-
-    /**
-     * ICMPv6 constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc4443
-     *     - https://tools.ietf.org/html/rfc4861
-     */
-    public static final int ICMPV6_HEADER_MIN_LEN = 4;
     public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
-    public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
-    public static final int ICMPV6_ROUTER_SOLICITATION    = 133;
-    public static final int ICMPV6_ROUTER_ADVERTISEMENT   = 134;
-    public static final int ICMPV6_NEIGHBOR_SOLICITATION  = 135;
-    public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
-
-    public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
-    public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
-    public static final int ICMPV6_ND_OPTION_SLLA = 1;
-    public static final int ICMPV6_ND_OPTION_TLLA = 2;
-    public static final int ICMPV6_ND_OPTION_MTU  = 5;
-
-
-    /**
-     * UDP constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc768
-     */
-    public static final int UDP_HEADER_LEN = 8;
-
-    /**
-     * DHCP(v4) constants.
-     *
-     * See also:
-     *     - https://tools.ietf.org/html/rfc2131
-     */
-    public static final int DHCP4_SERVER_PORT = 67;
-    public static final int DHCP4_CLIENT_PORT = 68;
 
     /**
      * DNS constants.
@@ -176,9 +89,4 @@
      * Utility functions.
      */
     public static byte asByte(int i) { return (byte) i; }
-
-    public static String asString(int i) { return Integer.toString(i); }
-
-    public static int asUint(byte b) { return (b & 0xff); }
-    public static int asUint(short s) { return (s & 0xffff); }
 }
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 4811523..164570a 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -116,6 +116,7 @@
 import com.android.server.backup.testing.TransportTestUtils.TransportMock;
 import com.android.server.testing.shadows.FrameworkShadowLooper;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
+import com.android.server.testing.shadows.ShadowBackupActivityThread;
 import com.android.server.testing.shadows.ShadowBackupDataInput;
 import com.android.server.testing.shadows.ShadowBackupDataOutput;
 import com.android.server.testing.shadows.ShadowEventLog;
@@ -163,6 +164,7 @@
             ShadowBackupDataOutput.class,
             ShadowEventLog.class,
             ShadowQueuedWork.class,
+            ShadowBackupActivityThread.class,
         })
 @Presubmit
 public class KeyValueBackupTaskTest {
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
index aefc871..33b8aa7 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
@@ -62,7 +62,7 @@
     }
 
     @Implementation
-    protected static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
+    protected static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
         return sAppsEligibleForBackup.contains(app.packageName);
     }
 
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java
new file mode 100644
index 0000000..ca2e3b6
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupActivityThread.java
@@ -0,0 +1,79 @@
+/*
+ * 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.testing.shadows;
+
+import android.app.ActivityThread;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowActivityThread;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Extends the existing {@link ShadowActivityThread} to add support for
+ * {@link PackageManager#getApplicationEnabledSetting(String)} in the shadow {@link PackageManager}
+ * returned  by {@link ShadowBackupActivityThread#getPackageManager()}.
+ */
+@Implements(value = ActivityThread.class, isInAndroidSdk = false, looseSignatures = true)
+public class ShadowBackupActivityThread extends ShadowActivityThread {
+    @Implementation
+    public static Object getPackageManager() {
+        ClassLoader classLoader = ShadowActivityThread.class.getClassLoader();
+        Class<?> iPackageManagerClass;
+        try {
+            iPackageManagerClass = classLoader.loadClass("android.content.pm.IPackageManager");
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        return Proxy.newProxyInstance(
+                classLoader,
+                new Class[] {iPackageManagerClass},
+                new InvocationHandler() {
+                    @Override
+                    public Object invoke(Object proxy, @Nonnull Method method, Object[] args)
+                            throws Exception {
+                        if (method.getName().equals("getApplicationInfo")) {
+                            String packageName = (String) args[0];
+                            int flags = (Integer) args[1];
+
+                            try {
+                                return RuntimeEnvironment.application
+                                        .getPackageManager()
+                                        .getApplicationInfo(packageName, flags);
+                            } catch (PackageManager.NameNotFoundException e) {
+                                throw new RemoteException(e.getMessage());
+                            }
+                        } else if (method.getName().equals("getApplicationEnabledSetting")) {
+                            return 0;
+                        } else {
+                            return null;
+                        }
+                    }
+                });
+    }
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 1b5ba26..dc31c0f 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -156,6 +156,7 @@
         </activity>
 
         <activity android:name="com.android.server.accounts.AccountAuthenticatorDummyActivity" />
+        <activity android:name="com.android.server.adb.AdbDebuggingManagerTestActivity" />
 
         <activity-alias android:name="a.ShortcutEnabled"
             android:targetActivity="com.android.server.pm.ShortcutTestActivity"
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
new file mode 100644
index 0000000..65af677
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2018 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.adb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.FgThread;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public final class AdbDebuggingManagerTest {
+
+    private static final String TAG = "AdbDebuggingManagerTest";
+
+    // This component is passed to the AdbDebuggingManager to act as the activity that can confirm
+    // unknown adb keys.  An overlay package was first attempted to override the
+    // config_customAdbPublicKeyConfirmationComponent config, but the value from that package was
+    // not being read.
+    private static final String ADB_CONFIRM_COMPONENT =
+            "com.android.frameworks.servicestests/"
+                    + "com.android.server.adb.AdbDebuggingManagerTestActivity";
+
+    // The base64 encoding of the values 'test key 1' and 'test key 2'.
+    private static final String TEST_KEY_1 = "dGVzdCBrZXkgMQo=";
+    private static final String TEST_KEY_2 = "dGVzdCBrZXkgMgo=";
+
+    // This response is received from the AdbDebuggingHandler when the key is allowed to connect
+    private static final String RESPONSE_KEY_ALLOWED = "OK";
+    // This response is received from the AdbDebuggingHandler when the key is not allowed to connect
+    private static final String RESPONSE_KEY_DENIED = "NO";
+
+    // wait up to 5 seconds for any blocking queries
+    private static final long TIMEOUT = 5000;
+    private static final TimeUnit TIMEOUT_TIME_UNIT = TimeUnit.MILLISECONDS;
+
+    private Context mContext;
+    private AdbDebuggingManager mManager;
+    private AdbDebuggingManager.AdbDebuggingThread mThread;
+    private AdbDebuggingManager.AdbDebuggingHandler mHandler;
+    private AdbDebuggingManager.AdbKeyStore mKeyStore;
+    private BlockingQueue<TestResult> mBlockingQueue;
+    private long mOriginalAllowedConnectionTime;
+    private File mKeyFile;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT);
+        mKeyFile = new File(mContext.getFilesDir(), "test_adb_keys.xml");
+        if (mKeyFile.exists()) {
+            mKeyFile.delete();
+        }
+        mThread = new AdbDebuggingThreadTest();
+        mKeyStore = mManager.new AdbKeyStore(mKeyFile);
+        mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore);
+        mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime();
+        mBlockingQueue = new ArrayBlockingQueue<>(1);
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mKeyStore.deleteKeyStore();
+        setAllowedConnectionTime(mOriginalAllowedConnectionTime);
+    }
+
+    /**
+     * Sets the allowed connection time within which a subsequent connection from a key for which
+     * the user selected the 'Always allow' option will be allowed without user interaction.
+     */
+    private void setAllowedConnectionTime(long connectionTime) {
+        Settings.Global.putLong(mContext.getContentResolver(),
+                Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime);
+    };
+
+    @Test
+    public void testAllowNewKeyOnce() throws Exception {
+        // Verifies the behavior when a new key first attempts to connect to a device. During the
+        // first connection the ADB confirmation activity should be launched to prompt the user to
+        // allow the connection with an option to always allow connections from this key.
+
+        // Verify if the user allows the key but does not select the option to 'always
+        // allow' that the connection is allowed but the key is not stored.
+        runAdbTest(TEST_KEY_1, true, false, false);
+    }
+
+    @Test
+    public void testDenyNewKey() throws Exception {
+        // Verifies if the user does not allow the key then the connection is not allowed and the
+        // key is not stored.
+        runAdbTest(TEST_KEY_1, false, false, false);
+    }
+
+    @Test
+    public void testDisconnectAlwaysAllowKey() throws Exception {
+        // When a key is disconnected from a device ADB should send a disconnect message; this
+        // message should trigger an update of the last connection time for the currently connected
+        // key.
+
+        // Allow a connection from a new key with the 'Always allow' option selected.
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Get the last connection time for the currently connected key to verify that it is updated
+        // after the disconnect.
+        long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+        // Sleep for a small amount of time to ensure a difference can be observed in the last
+        // connection time after a disconnect.
+        Thread.sleep(10);
+
+        // Send the disconnect message for the currently connected key to trigger an update of the
+        // last connection time.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+        // Use a latch to ensure the test does not exit untill the Runnable has been processed.
+        CountDownLatch latch = new CountDownLatch(1);
+
+        // Post a new Runnable to the handler to ensure it runs after the disconnect message is
+        // processed.
+        mHandler.post(() -> {
+            assertNotEquals(
+                    "The last connection time was not updated after the disconnect",
+                    lastConnectionTime,
+                    mKeyStore.getLastConnectionTime(TEST_KEY_1));
+            latch.countDown();
+        });
+        if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+            fail("The Runnable to verify the last connection time was updated did not complete "
+                    + "within the timeout period");
+        }
+    }
+
+    @Test
+    public void testDisconnectAllowedOnceKey() throws Exception {
+        // When a key is disconnected ADB should send a disconnect message; this message should
+        // essentially result in a noop for keys that the user only allows once since the last
+        // connection time is not maintained for these keys.
+
+        // Allow a connection from a new key with the 'Always allow' option set to false
+        runAdbTest(TEST_KEY_1, true, false, false);
+
+        // Send the disconnect message for the currently connected key.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+        // Verify that the disconnected key is not automatically allowed on a subsequent connection.
+        runAdbTest(TEST_KEY_1, true, false, false);
+    }
+
+    @Test
+    public void testAlwaysAllowConnectionFromKey() throws Exception {
+        // Verifies when the user selects the 'Always allow' option for the current key that
+        // subsequent connection attempts from that key are allowed.
+
+        // Allow a connection from a new key with the 'Always allow' option selected.
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Next attempt another connection with the same key and verify that the activity to prompt
+        // the user to accept the key is not launched.
+        runAdbTest(TEST_KEY_1, true, true, true);
+
+        // Verify that a different key is not automatically allowed.
+        runAdbTest(TEST_KEY_2, false, false, false);
+    }
+
+    @Test
+    public void testOriginalAlwaysAllowBehavior() throws Exception {
+        // If the Settings.Global.ADB_ALLOWED_CONNECTION_TIME setting is set to 0 then the original
+        // behavior of 'Always allow' should be restored.
+
+        // Accept the test key with the 'Always allow' option selected.
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Set the connection time to 0 to restore the original behavior.
+        setAllowedConnectionTime(0);
+
+        // Set the last connection time to the test key to a very small value to ensure it would
+        // fail the new test but would be allowed with the original behavior.
+        mKeyStore.setLastConnectionTime(TEST_KEY_1, 1);
+
+        // Run the test with the key and verify that the connection is automatically allowed.
+        runAdbTest(TEST_KEY_1, true, true, true);
+    }
+
+    @Test
+    public void testLastConnectionTimeUpdatedByScheduledJob() throws Exception {
+        // If a development device is left connected to a system beyond the allowed connection time
+        // a reboot of the device while connected could make it appear as though the last connection
+        // time is beyond the allowed window. A scheduled job runs daily while a key is connected
+        // to update the last connection time to the current time; this ensures if the device is
+        // rebooted while connected to a system the last connection time should be within 24 hours.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Get the current last connection time for comparison after the scheduled job is run
+        long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+        // Sleep a small amount of time to ensure that the updated connection time changes
+        Thread.sleep(10);
+
+        // Send a message to the handler to update the last connection time for the active key
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME)
+                .sendToTarget();
+
+        // Post a Runnable to the handler to ensure it runs after the update key connection time
+        // message is processed.
+        CountDownLatch latch = new CountDownLatch(1);
+        mHandler.post(() -> {
+            assertNotEquals(
+                    "The last connection time of the key was not updated after the update key "
+                            + "connection time message",
+                    lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+            latch.countDown();
+        });
+        if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+            fail("The Runnable to verify the last connection time was updated did not complete "
+                    + "within the timeout period");
+        }
+    }
+
+    @Test
+    public void testKeystorePersisted() throws Exception {
+        // After any updates are made to the key store a message should be sent to persist the
+        // key store. This test verifies that a key that is always allowed is persisted in the key
+        // store along with its last connection time.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Send a message to the handler to persist the updated keystore.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE)
+                .sendToTarget();
+
+        // Post a Runnable to the handler to ensure the persist key store message has been processed
+        // using a new AdbKeyStore backed by the key file.
+        mHandler.post(() -> assertTrue(
+                "The key with the 'Always allow' option selected was not persisted in the keystore",
+                mManager.new AdbKeyStore(mKeyFile).isKeyAuthorized(TEST_KEY_1)));
+
+        // Get the current last connection time to ensure it is updated in the persisted keystore.
+        long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+        // Sleep a small amount of time to ensure the last connection time is updated.
+        Thread.sleep(10);
+
+        // Send a message to the handler to update the last connection time for the active key.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEY_CONNECTION_TIME)
+                .sendToTarget();
+
+        // Persist the updated last connection time.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEY_STORE)
+                .sendToTarget();
+
+        // Post a Runnable with a new key store backed by the key file to verify that the last
+        // connection time obtained above is different from the persisted updated value.
+        CountDownLatch latch = new CountDownLatch(1);
+        mHandler.post(() -> {
+            assertNotEquals(
+                    "The last connection time in the key file was not updated after the update "
+                            + "connection time message", lastConnectionTime,
+                    mManager.new AdbKeyStore(mKeyFile).getLastConnectionTime(TEST_KEY_1));
+            latch.countDown();
+        });
+        if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+            fail("The Runnable to verify the last connection time was updated did not complete "
+                    + "within the timeout period");
+        }
+    }
+
+    @Test
+    public void testAdbClearRemovesActiveKey() throws Exception {
+        // If the user selects the option to 'Revoke USB debugging authorizations' while an 'Always
+        // allow' key is connected that key should be deleted as well.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Send a message to the handler to clear the adb authorizations.
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CLEAR).sendToTarget();
+
+        // Send a message to disconnect the currently connected key
+        mHandler.obtainMessage(
+                AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT).sendToTarget();
+
+        // Post a Runnable to ensure the disconnect has completed to verify the 'Always allow' key
+        // that was connected when the clear was sent requires authorization.
+        CountDownLatch latch = new CountDownLatch(1);
+        mHandler.post(() -> {
+            assertFalse(
+                    "The currently connected 'always allow' key should not be authorized after an"
+                            + " adb"
+                            + " clear message.",
+                    mKeyStore.isKeyAuthorized(TEST_KEY_1));
+            latch.countDown();
+        });
+        if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+            fail("The Runnable to verify the key is not authorized did not complete within the "
+                    + "timeout period");
+        }
+    }
+
+    @Test
+    public void testAdbGrantRevokedIfLastConnectionBeyondAllowedTime() throws Exception {
+        // If the user selects the 'Always allow' option then subsequent connections from the key
+        // will be allowed as long as the connection is within the allowed window. Once the last
+        // connection time is beyond this window the user should be prompted to allow the key again.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Set the allowed window to a small value to ensure the time is beyond the allowed window.
+        setAllowedConnectionTime(1);
+
+        // Sleep for a small amount of time to exceed the allowed window.
+        Thread.sleep(10);
+
+        // A new connection from this key should prompt the user again.
+        runAdbTest(TEST_KEY_1, true, true, false);
+    }
+
+    @Test
+    public void testLastConnectionTimeCannotBeSetBack() throws Exception {
+        // When a device is first booted there is a possibility that the system time will be set to
+        // the build time of the system image. If a device is connected to a system during a reboot
+        // this could cause the connection time to be set in the past; if the device time is not
+        // corrected before the device is disconnected then a subsequent connection with the time
+        // corrected would appear as though the last connection time was beyond the allowed window,
+        // and the user would be required to authorize the connection again. This test verifies that
+        // the AdbKeyStore does not update the last connection time if it is less than the
+        // previously written connection time.
+
+        // Allow the key to connect with the 'Always allow' option selected
+        runAdbTest(TEST_KEY_1, true, true, false);
+
+        // Get the last connection time that was written to the key store.
+        long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
+
+        // Attempt to set the last connection time to 1970
+        mKeyStore.setLastConnectionTime(TEST_KEY_1, 0);
+        assertEquals(
+                "The last connection time in the adb key store should not be set to a value less "
+                        + "than the previous connection time",
+                lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+
+        // Attempt to set the last connection time just beyond the allowed window.
+        mKeyStore.setLastConnectionTime(TEST_KEY_1,
+                Math.max(0, lastConnectionTime - (mKeyStore.getAllowedConnectionTime() + 1)));
+        assertEquals(
+                "The last connection time in the adb key store should not be set to a value less "
+                        + "than the previous connection time",
+                lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
+    }
+
+    /**
+     * Runs an adb test with the provided configuration.
+     *
+     * @param key The base64 encoding of the key to be used during the test.
+     * @param allowKey boolean indicating whether the key should be allowed to connect.
+     * @param alwaysAllow boolean indicating whether the 'Always allow' option should be selected.
+     * @param autoAllowExpected boolean indicating whether the key is expected to be automatically
+     *                          allowed without user interaction.
+     */
+    private void runAdbTest(String key, boolean allowKey, boolean alwaysAllow,
+            boolean autoAllowExpected) throws Exception {
+        // if the key should not be automatically allowed then set up the activity
+        if (!autoAllowExpected) {
+            new AdbDebuggingManagerTestActivity.Configurator()
+                    .setExpectedKey(key)
+                    .setAllowKey(allowKey)
+                    .setAlwaysAllow(alwaysAllow)
+                    .setHandler(mHandler)
+                    .setBlockingQueue(mBlockingQueue);
+        }
+        // send the message indicating a new key is attempting to connect
+        mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONFIRM,
+                key).sendToTarget();
+        // if the key should not be automatically allowed then the ADB public key confirmation
+        // activity should be launched
+        if (!autoAllowExpected) {
+            TestResult activityResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
+            assertNotNull(
+                    "The ADB public key confirmation activity did not complete within the timeout"
+                            + " period", activityResult);
+            assertEquals("The ADB public key activity failed with result: " + activityResult,
+                    TestResult.RESULT_ACTIVITY_LAUNCHED, activityResult.mReturnCode);
+        }
+        // If the activity was launched it should send a response back to the manager that would
+        // trigger a response to the thread, or if the key is a known valid key then a response
+        // should be sent back without requiring interaction with the activity.
+        TestResult threadResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
+        assertNotNull("A response was not sent to the thread within the timeout period",
+                threadResult);
+        // verify that the result is an expected message from the thread
+        assertEquals("An unexpected result was received: " + threadResult,
+                TestResult.RESULT_RESPONSE_RECEIVED, threadResult.mReturnCode);
+        assertEquals("The manager did not send the proper response for allowKey = " + allowKey,
+                allowKey ? RESPONSE_KEY_ALLOWED : RESPONSE_KEY_DENIED, threadResult.mMessage);
+        // if the key is not allowed or not always allowed verify it is not in the key store
+        if (!allowKey || !alwaysAllow) {
+            assertFalse(
+                    "The key should not be allowed automatically on subsequent connection attempts",
+                    mKeyStore.isKeyAuthorized(key));
+        }
+    }
+
+    /**
+     * Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager
+     * indicating whether the key should be allowed to  connect.
+     */
+    class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
+        AdbDebuggingThreadTest() {
+            mManager.super();
+        }
+
+        @Override
+        public void sendResponse(String msg) {
+            TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg);
+            try {
+                mBlockingQueue.put(result);
+            } catch (InterruptedException e) {
+                Log.e(TAG,
+                        "Caught an InterruptedException putting the result in the queue: " + result,
+                        e);
+            }
+        }
+    }
+
+    /**
+     * Contains the result for the current portion of the test along with any corresponding
+     * messages.
+     */
+    public static class TestResult {
+        public int mReturnCode;
+        public String mMessage;
+
+        public static final int RESULT_ACTIVITY_LAUNCHED = 1;
+        public static final int RESULT_UNEXPECTED_KEY = 2;
+        public static final int RESULT_RESPONSE_RECEIVED = 3;
+
+        public TestResult(int returnCode) {
+            this(returnCode, null);
+        }
+
+        public TestResult(int returnCode, String message) {
+            mReturnCode = returnCode;
+            mMessage = message;
+        }
+
+        @Override
+        public String toString() {
+            return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}";
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java
new file mode 100644
index 0000000..1a9c180
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTestActivity.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 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.adb;
+
+import static com.android.server.adb.AdbDebuggingManagerTest.TestResult;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Helper Activity used to test the AdbDebuggingManager's prompt to allow an adb key.
+ */
+public class AdbDebuggingManagerTestActivity extends Activity {
+
+    private static final String TAG = "AdbDebuggingManagerTestActivity";
+
+    /*
+     * Static values that must be set before each test to modify the behavior of the Activity.
+     */
+    private static AdbDebuggingManager.AdbDebuggingHandler sHandler;
+    private static boolean sAllowKey;
+    private static boolean sAlwaysAllow;
+    private static String sExpectedKey;
+    private static BlockingQueue sBlockingQueue;
+
+    /**
+     * Receives the Intent sent from the AdbDebuggingManager and sends the preconfigured response.
+     */
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        Intent intent = getIntent();
+        String key = intent.getStringExtra("key");
+        if (!key.equals(sExpectedKey)) {
+            TestResult result = new TestResult(TestResult.RESULT_UNEXPECTED_KEY, key);
+            postResult(result);
+            finish();
+            return;
+        }
+        // Post the result that the activity was successfully launched as expected and a response
+        // is being sent to let the test method know that it should move on to waiting for the next
+        // expected response from the AdbDebuggingManager.
+        TestResult result = new TestResult(
+                AdbDebuggingManagerTest.TestResult.RESULT_ACTIVITY_LAUNCHED);
+        postResult(result);
+
+        // Initialize the message based on the preconfigured values. If the key is accepted the
+        // AdbDebuggingManager expects the key to be in the obj field of the message, and if the
+        // user selects the 'Always allow' option the manager expects the arg1 field to be set to 1.
+        int messageType;
+        if (sAllowKey) {
+            messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_ALLOW;
+        } else {
+            messageType = AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DENY;
+        }
+        Message message = sHandler.obtainMessage(messageType);
+        message.obj = key;
+        if (sAlwaysAllow) {
+            message.arg1 = 1;
+        }
+        finish();
+        sHandler.sendMessage(message);
+    }
+
+    /**
+     * Posts the result of the activity to the test method.
+     */
+    private void postResult(TestResult result) {
+        try {
+            sBlockingQueue.put(result);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Caught an InterruptedException posting the result " + result, e);
+        }
+    }
+
+    /**
+     * Allows test methods to specify the behavior of the Activity before it is invoked by the
+     * AdbDebuggingManager.
+     */
+    public static class Configurator {
+
+        /**
+         * Sets the test handler to be used by this activity to send the configured response.
+         */
+        public Configurator setHandler(AdbDebuggingManager.AdbDebuggingHandler handler) {
+            sHandler = handler;
+            return this;
+        }
+
+        /**
+         * Sets whether the key should be allowed for this test.
+         */
+        public Configurator setAllowKey(boolean allow) {
+            sAllowKey = allow;
+            return this;
+        }
+
+        /**
+         * Sets whether the 'Always allow' option should be selected for this test.
+         */
+        public Configurator setAlwaysAllow(boolean alwaysAllow) {
+            sAlwaysAllow = alwaysAllow;
+            return this;
+        }
+
+        /**
+         * Sets the key that should be expected from the AdbDebuggingManager for this test.
+         */
+        public Configurator setExpectedKey(String expectedKey) {
+            sExpectedKey = expectedKey;
+            return this;
+        }
+
+        /**
+         * Sets the BlockingQueue that should be used to post the result of the Activity back to the
+         * test method.
+         */
+        public Configurator setBlockingQueue(BlockingQueue blockingQueue) {
+            sBlockingQueue = blockingQueue;
+            return this;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
new file mode 100644
index 0000000..095a1de
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
@@ -0,0 +1,1172 @@
+/*
+ * 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.backup.testutils;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
+import android.content.pm.IDexModuleRegisterCallback;
+import android.content.pm.IOnPermissionsChangeListener;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageDeleteObserver2;
+import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageManager;
+import android.content.pm.IPackageMoveObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.SuspendDialogInfo;
+import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
+import android.content.pm.dex.IArtManager;
+import android.graphics.Bitmap;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import java.util.List;
+
+/**
+ * Stub for IPackageManager to use in tests.
+ */
+public class IPackageManagerStub implements IPackageManager {
+    public static PackageInfo sPackageInfo;
+    public static int sApplicationEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+
+    @Override
+    public PackageInfo getPackageInfo(String packageName, int flags, int userId)
+        throws RemoteException {
+        return sPackageInfo;
+    }
+
+    @Override
+    public int getApplicationEnabledSetting(String packageName, int userId) throws RemoteException {
+        return sApplicationEnabledSetting;
+    }
+
+    @Override
+    public void checkPackageStartable(String packageName, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public boolean isPackageAvailable(String packageName, int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public int getPackageUid(String packageName, int flags, int userId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int[] getPackageGids(String packageName, int flags, int userId) throws RemoteException {
+        return new int[0];
+    }
+
+    @Override
+    public String[] currentToCanonicalPackageNames(String[] names) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public String[] canonicalToCurrentPackageNames(String[] names) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public PermissionInfo getPermissionInfo(String name, String packageName, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryPermissionsByGroup(String group, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public PermissionGroupInfo getPermissionGroupInfo(String name, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getAllPermissionGroups(int flags) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ActivityInfo getActivityInfo(ComponentName className, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean activitySupportsIntent(ComponentName className, Intent intent,
+        String resolvedType)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public ActivityInfo getReceiverInfo(ComponentName className, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ServiceInfo getServiceInfo(ComponentName className, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ProviderInfo getProviderInfo(ComponentName className, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public int checkPermission(String permName, String pkgName, int userId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int checkUidPermission(String permName, int uid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public boolean addPermission(PermissionInfo info) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void removePermission(String name) throws RemoteException {
+
+    }
+
+    @Override
+    public void grantRuntimePermission(String packageName, String permissionName, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void revokeRuntimePermission(String packageName, String permissionName, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void resetRuntimePermissions() throws RemoteException {
+
+    }
+
+    @Override
+    public int getPermissionFlags(String permissionName, String packageName, int userId)
+        throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void updatePermissionFlags(String permissionName, String packageName, int flagMask,
+        int flagValues, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public boolean shouldShowRequestPermissionRationale(String permissionName, String packageName,
+        int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isProtectedBroadcast(String actionName) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public int checkSignatures(String pkg1, String pkg2) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int checkUidSignatures(int uid1, int uid2) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public List<String> getAllPackages() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String[] getPackagesForUid(int uid) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public String getNameForUid(int uid) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String[] getNamesForUids(int[] uids) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public int getUidForSharedUser(String sharedUserName) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int getFlagsForUid(int uid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int getPrivateFlagsForUid(int uid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public boolean isUidPrivileged(int uid) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public String[] getAppOpPermissionPackages(String permissionName) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean canForwardTo(Intent intent, String resolvedType, int sourceUserId,
+        int targetUserId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentActivities(Intent intent, String resolvedType, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentActivityOptions(ComponentName caller, Intent[] specifics,
+        String[] specificTypes, Intent intent, String resolvedType, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentReceivers(Intent intent, String resolvedType, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentServices(Intent intent, String resolvedType, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryIntentContentProviders(Intent intent, String resolvedType,
+        int flags, int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getInstalledPackages(int flags, int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getPackagesHoldingPermissions(String[] permissions, int flags,
+        int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getInstalledApplications(int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getPersistentApplications(int flags) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ProviderInfo resolveContentProvider(String name, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public ParceledListSlice queryContentProviders(String processName, int uid, int flags,
+        String metaDataKey) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice queryInstrumentation(String targetPackage, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void finishPackageInstall(int token, boolean didLaunch) throws RemoteException {
+
+    }
+
+    @Override
+    public void setInstallerPackageName(String targetPackage, String installerPackageName)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void setApplicationCategoryHint(String packageName, int categoryHint,
+        String callerPackageName) throws RemoteException {
+
+    }
+
+    @Override
+    public void deletePackageAsUser(String packageName, int versionCode,
+        IPackageDeleteObserver observer, int userId, int flags) throws RemoteException {
+
+    }
+
+    @Override
+    public void deletePackageVersioned(VersionedPackage versionedPackage,
+        IPackageDeleteObserver2 observer, int userId, int flags) throws RemoteException {
+
+    }
+
+    @Override
+    public String getInstallerPackageName(String packageName) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void resetApplicationPreferences(int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
+        IntentFilter filter, int match, ComponentName activity) throws RemoteException {
+
+    }
+
+    @Override
+    public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+        ComponentName activity, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+        ComponentName activity, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearPackagePreferredActivities(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public int getPreferredActivities(List<IntentFilter> outFilters,
+        List<ComponentName> outActivities, String packageName) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearPackagePersistentPreferredActivities(String packageName, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage,
+        int sourceUserId, int targetUserId, int flags) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames,
+        int restrictionFlags, int userId) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
+        PersistableBundle appExtras, PersistableBundle launcherExtras, SuspendDialogInfo dialogInfo,
+        String callingPackage, int userId) throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId)
+        throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public boolean isPackageSuspendedForUser(String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public byte[] getPreferredActivityBackup(int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public void restorePreferredActivities(byte[] backup, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public byte[] getDefaultAppsBackup(int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public void restoreDefaultApps(byte[] backup, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public byte[] getIntentFilterVerificationBackup(int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public void restoreIntentFilterVerification(byte[] backup, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public byte[] getPermissionGrantBackup(int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public void restorePermissionGrants(byte[] backup, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public ComponentName getHomeActivities(List<ResolveInfo> outHomeCandidates)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void setHomeActivity(ComponentName className, int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public int getComponentEnabledSetting(ComponentName componentName, int userId)
+        throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void setApplicationEnabledSetting(String packageName, int newState, int flags,
+        int userId,
+        String callingPackage) throws RemoteException {
+
+    }
+
+    @Override
+    public void logAppProcessStartIfNeeded(String processName, int uid, String seinfo,
+        String apkFile,
+        int pid) throws RemoteException {
+
+    }
+
+    @Override
+    public void flushPackageRestrictionsAsUser(int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void setPackageStoppedState(String packageName, boolean stopped, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void freeStorageAndNotify(String volumeUuid, long freeStorageSize, int storageFlags,
+        IPackageDataObserver observer) throws RemoteException {
+
+    }
+
+    @Override
+    public void freeStorage(String volumeUuid, long freeStorageSize, int storageFlags,
+        IntentSender pi) throws RemoteException {
+
+    }
+
+    @Override
+    public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void deleteApplicationCacheFilesAsUser(String packageName, int userId,
+        IPackageDataObserver observer) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearApplicationUserData(String packageName, IPackageDataObserver observer,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void clearApplicationProfileData(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public void getPackageSizeInfo(String packageName, int userHandle,
+        IPackageStatsObserver observer)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public String[] getSystemSharedLibraryNames() throws RemoteException {
+        return new String[0];
+    }
+
+    @Override
+    public ParceledListSlice getSystemAvailableFeatures() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean hasSystemFeature(String name, int version) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void enterSafeMode() throws RemoteException {
+
+    }
+
+    @Override
+    public boolean isSafeMode() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void systemReady() throws RemoteException {
+
+    }
+
+    @Override
+    public boolean hasSystemUidErrors() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void performFstrimIfNeeded() throws RemoteException {
+
+    }
+
+    @Override
+    public void updatePackagesIfNeeded() throws RemoteException {
+
+    }
+
+    @Override
+    public void notifyPackageUse(String packageName, int reason) throws RemoteException {
+
+    }
+
+    @Override
+    public void notifyDexLoad(String loadingPackageName, List<String> classLoadersNames,
+        List<String> classPaths, String loaderIsa) throws RemoteException {
+
+    }
+
+    @Override
+    public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule,
+        IDexModuleRegisterCallback callback) throws RemoteException {
+
+    }
+
+    @Override
+    public boolean performDexOptMode(String packageName, boolean checkProfiles,
+        String targetCompilerFilter, boolean force, boolean bootComplete, String splitName)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean performDexOptSecondary(String packageName, String targetCompilerFilter,
+        boolean force) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean compileLayouts(String packageName) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void dumpProfiles(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public void forceDexOpt(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public boolean runBackgroundDexoptJob(List<String> packageNames) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void reconcileSecondaryDexFiles(String packageName) throws RemoteException {
+
+    }
+
+    @Override
+    public int getMoveStatus(int moveId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void registerMoveCallback(IPackageMoveObserver callback) throws RemoteException {
+
+    }
+
+    @Override
+    public void unregisterMoveCallback(IPackageMoveObserver callback) throws RemoteException {
+
+    }
+
+    @Override
+    public int movePackage(String packageName, String volumeUuid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int movePrimaryStorage(String volumeUuid) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public boolean addPermissionAsync(PermissionInfo info) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean setInstallLocation(int loc) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public int getInstallLocation() throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
+        int installReason) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
+
+    }
+
+    @Override
+    public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+        long millisecondsToDelay) throws RemoteException {
+
+    }
+
+    @Override
+    public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public int getIntentVerificationStatus(String packageName, int userId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public boolean updateIntentVerificationStatus(String packageName, int status, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public ParceledListSlice getIntentFilterVerifications(String packageName)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getAllIntentFilters(String packageName) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean setDefaultBrowserPackageName(String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public String getDefaultBrowserPackageName(int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isFirstBoot() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isOnlyCoreApps() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isUpgrade() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void setPermissionEnforced(String permission, boolean enforced) throws RemoteException {
+
+    }
+
+    @Override
+    public boolean isPermissionEnforced(String permission) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isStorageLow() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean getApplicationHiddenSettingAsUser(String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void setSystemAppHiddenUntilInstalled(String packageName, boolean hidden)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public boolean setSystemAppInstallState(String packageName, boolean installed, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public IPackageInstaller getPackageInstaller() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean getBlockUninstallForUser(String packageName, int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public KeySet getKeySetByAlias(String packageName, String alias) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public KeySet getSigningKeySet(String packageName) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isPackageSignedByKeySet(String packageName, KeySet ks) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean isPackageSignedByKeySetExactly(String packageName, KeySet ks)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void grantDefaultPermissionsToEnabledCarrierApps(String[] packageNames, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void grantDefaultPermissionsToEnabledTelephonyDataServices(String[] packageNames,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(String[] packageNames,
+        int userId) throws RemoteException {
+
+    }
+
+    @Override
+    public void grantDefaultPermissionsToActiveLuiApp(String packageName, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public void revokeDefaultPermissionsFromLuiApps(String[] packageNames, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public boolean isPermissionRevokedByPolicy(String permission, String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public String getPermissionControllerPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ParceledListSlice getInstantApps(int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public byte[] getInstantAppCookie(String packageName, int userId) throws RemoteException {
+        return new byte[0];
+    }
+
+    @Override
+    public boolean setInstantAppCookie(String packageName, byte[] cookie, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public Bitmap getInstantAppIcon(String packageName, int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isInstantApp(String packageName, int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean setRequiredForSystemUser(String packageName, boolean systemUserApp)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void setUpdateAvailable(String packageName, boolean updateAvaialble)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public String getServicesSystemSharedLibraryPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String getSharedSystemSharedLibraryPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ChangedPackages getChangedPackages(int sequenceNumber, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isPackageDeviceAdminOnAnyUser(String packageName) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public int getInstallReason(String packageName, int userId) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public ParceledListSlice getSharedLibraries(String packageName, int flags, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean canRequestPackageInstalls(String packageName, int userId)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void deletePreloadsFileCache() throws RemoteException {
+
+    }
+
+    @Override
+    public ComponentName getInstantAppResolverComponent() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ComponentName getInstantAppResolverSettingsComponent() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ComponentName getInstantAppInstallerComponent() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String getInstantAppAndroidId(String packageName, int userId) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public IArtManager getArtManager() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void setHarmfulAppWarning(String packageName, CharSequence warning, int userId)
+        throws RemoteException {
+
+    }
+
+    @Override
+    public CharSequence getHarmfulAppWarning(String packageName, int userId)
+        throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean hasSigningCertificate(String packageName, byte[] signingCertificate, int flags)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean hasUidSigningCertificate(int uid, byte[] signingCertificate, int flags)
+        throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public String getSystemTextClassifierPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public String getWellbeingPackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public boolean isPackageStateProtected(String packageName, int userId) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void sendDeviceCustomizationReadyBroadcast() throws RemoteException {
+
+    }
+
+    @Override
+    public List<ModuleInfo> getInstalledModules(int flags) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public ModuleInfo getModuleInfo(String packageName, int flags) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return null;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
index 525135c..7172752 100644
--- a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
@@ -48,11 +48,7 @@
     @Override
     public PackageInfo getPackageInfo(String packageName, int flags)
             throws NameNotFoundException {
-        if (sPackageInfo == null) {
-            throw new NameNotFoundException();
-        }
-
-        return sPackageInfo;
+        return getPackageInfoAsUser(packageName, flags, UserHandle.USER_SYSTEM);
     }
 
     @Override
@@ -64,7 +60,11 @@
     @Override
     public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
-        return null;
+        if (sPackageInfo == null) {
+            throw new NameNotFoundException();
+        }
+
+        return sPackageInfo;
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
index 479a19b..a92b576 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
@@ -29,13 +29,14 @@
 import android.content.pm.Signature;
 import android.content.pm.SigningInfo;
 import android.os.Process;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.backup.UserBackupManagerService;
-import com.android.server.backup.testutils.PackageManagerStub;
+import com.android.server.backup.testutils.IPackageManagerStub;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -53,13 +54,17 @@
     private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
     private static final Signature SIGNATURE_4 = generateSignature((byte) 4);
 
-    private PackageManagerStub mPackageManagerStub;
+    private IPackageManagerStub mPackageManagerStub;
     private PackageManagerInternal mMockPackageManagerInternal;
 
+    private int mUserId;
+
     @Before
     public void setUp() throws Exception {
-        mPackageManagerStub = new PackageManagerStub();
+        mPackageManagerStub = new IPackageManagerStub();
         mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+
+        mUserId = UserHandle.USER_SYSTEM;
     }
 
     @Test
@@ -71,7 +76,7 @@
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -86,7 +91,7 @@
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -100,7 +105,7 @@
         applicationInfo.packageName = UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -114,11 +119,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isTrue();
     }
@@ -132,11 +137,11 @@
         applicationInfo.backupAgentName = null;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isTrue();
     }
@@ -150,11 +155,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isTrue();
     }
@@ -168,11 +173,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -186,11 +191,11 @@
         applicationInfo.backupAgentName = null;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -204,11 +209,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 
         boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
-                mPackageManagerStub);
+                mPackageManagerStub, mUserId);
 
         assertThat(isEligible).isFalse();
     }
@@ -222,10 +227,11 @@
         applicationInfo.packageName = TEST_PACKAGE_NAME;
         applicationInfo.enabled = true;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isFalse();
     }
@@ -239,10 +245,11 @@
         applicationInfo.packageName = TEST_PACKAGE_NAME;
         applicationInfo.enabled = false;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isTrue();
     }
@@ -255,10 +262,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isFalse();
     }
@@ -271,10 +279,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isTrue();
     }
@@ -287,10 +296,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isTrue();
     }
@@ -303,10 +313,11 @@
         applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
         applicationInfo.packageName = TEST_PACKAGE_NAME;
 
-        PackageManagerStub.sApplicationEnabledSetting =
+        IPackageManagerStub.sApplicationEnabledSetting =
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 
-        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub);
+        boolean isDisabled = AppBackupUtils.appIsDisabled(applicationInfo, mPackageManagerStub,
+            mUserId);
 
         assertThat(isDisabled).isTrue();
     }
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index d43b677..5fcce67 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -44,6 +44,7 @@
 import android.content.pm.SigningInfo;
 import android.os.Bundle;
 import android.os.Process;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -88,12 +89,14 @@
 
     private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
     private Context mContext;
+    private int mUserId;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         mContext = InstrumentationRegistry.getContext();
+        mUserId = UserHandle.USER_SYSTEM;
     }
 
     @Test
@@ -146,7 +149,7 @@
                 fileMetadata);
         RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
                 mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
         assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -160,7 +163,7 @@
                 fileMetadata);
         restorePolicy = tarBackupReader.chooseRestorePolicy(
                 mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
         assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -223,7 +226,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 true /* allowApks */, new FileMetadata(), null /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         verifyZeroInteractions(mBackupManagerMonitorMock);
@@ -244,7 +247,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 true /* allowApks */, info, new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -269,7 +272,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 true /* allowApks */, info, new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -295,7 +298,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -320,7 +323,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -347,7 +350,7 @@
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
                 false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
-                mMockPackageManagerInternal);
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -381,7 +384,8 @@
         PackageManagerStub.sPackageInfo = packageInfo;
 
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+                false /* allowApks */, new FileMetadata(), signatures,
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -419,7 +423,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+                false /* allowApks */, new FileMetadata(), signatures,
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -456,7 +461,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
+                false /* allowApks */, new FileMetadata(), signatures,
+                mMockPackageManagerInternal, mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -497,7 +503,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, info, signatures, mMockPackageManagerInternal);
+                false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+                mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -540,7 +547,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                true /* allowApks */, info, signatures, mMockPackageManagerInternal);
+                true /* allowApks */, info, signatures, mMockPackageManagerInternal,
+                mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
         verifyNoMoreInteractions(mBackupManagerMonitorMock);
@@ -579,7 +587,8 @@
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
                 packageInfo.packageName);
         RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
-                false /* allowApks */, info, signatures, mMockPackageManagerInternal);
+                false /* allowApks */, info, signatures, mMockPackageManagerInternal,
+                mUserId);
 
         assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 860656b..8d9b3cf 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -45,6 +45,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class UsageStatsDatabaseTest {
+
+    private static final int MAX_TESTED_VERSION = 4;
     protected Context mContext;
     private UsageStatsDatabase mUsageStatsDatabase;
     private File mTestDir;
@@ -131,8 +133,8 @@
 
         for (int i = 0; i < numberOfEvents; i++) {
             Event event = new Event();
-            final int packageInt = ((i / 3) % 7);
-            event.mPackage = "fake.package.name" + packageInt; //clusters of 3 events from 7 "apps"
+            final int packageInt = ((i / 3) % 7); //clusters of 3 events from 7 "apps"
+            event.mPackage = "fake.package.name" + packageInt;
             if (packageInt == 3) {
                 // Third app is an instant app
                 event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP;
@@ -144,6 +146,13 @@
             event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
             event.mInstanceId = instanceId;
 
+
+            final int rootPackageInt = (i % 5); // 5 "apps" start each task
+            event.mTaskRootPackage = "fake.package.name" + rootPackageInt;
+
+            final int rootClassInt = i % 6;
+            event.mTaskRootClass = ".fake.class.name" + rootClassInt;
+
             switch (event.mEventType) {
                 case Event.CONFIGURATION_CHANGE:
                     //empty config,
@@ -163,7 +172,7 @@
                     break;
             }
 
-            mIntervalStats.events.insert(event);
+            mIntervalStats.addEvent(event);
             mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
                     event.mInstanceId);
 
@@ -234,31 +243,40 @@
         assertEquals(us1.mChooserCounts, us2.mChooserCounts);
     }
 
-    void compareUsageEvent(Event e1, Event e2, int debugId) {
-        assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
-        assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
-        assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
-        assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
-        switch (e1.mEventType) {
-            case Event.CONFIGURATION_CHANGE:
-                assertEquals(e1.mConfiguration, e2.mConfiguration,
-                        "Usage event " + debugId + e2.mConfiguration.toString());
-                break;
-            case Event.SHORTCUT_INVOCATION:
-                assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
-                break;
-            case Event.STANDBY_BUCKET_CHANGED:
-                assertEquals(e1.mBucketAndReason, e2.mBucketAndReason, "Usage event " + debugId);
-                break;
-            case Event.NOTIFICATION_INTERRUPTION:
-                assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
-                        "Usage event " + debugId);
-                break;
+    void compareUsageEvent(Event e1, Event e2, int debugId, int minVersion) {
+        switch (minVersion) {
+            case 4: // test fields added in version 4
+                assertEquals(e1.mInstanceId, e2.mInstanceId, "Usage event " + debugId);
+                assertEquals(e1.mTaskRootPackage, e2.mTaskRootPackage, "Usage event " + debugId);
+                assertEquals(e1.mTaskRootClass, e2.mTaskRootClass, "Usage event " + debugId);
+                // fallthrough
+            default:
+                assertEquals(e1.mPackage, e2.mPackage, "Usage event " + debugId);
+                assertEquals(e1.mClass, e2.mClass, "Usage event " + debugId);
+                assertEquals(e1.mTimeStamp, e2.mTimeStamp, "Usage event " + debugId);
+                assertEquals(e1.mEventType, e2.mEventType, "Usage event " + debugId);
+                switch (e1.mEventType) {
+                    case Event.CONFIGURATION_CHANGE:
+                        assertEquals(e1.mConfiguration, e2.mConfiguration,
+                                "Usage event " + debugId + e2.mConfiguration.toString());
+                        break;
+                    case Event.SHORTCUT_INVOCATION:
+                        assertEquals(e1.mShortcutId, e2.mShortcutId, "Usage event " + debugId);
+                        break;
+                    case Event.STANDBY_BUCKET_CHANGED:
+                        assertEquals(e1.mBucketAndReason, e2.mBucketAndReason,
+                                "Usage event " + debugId);
+                        break;
+                    case Event.NOTIFICATION_INTERRUPTION:
+                        assertEquals(e1.mNotificationChannelId, e2.mNotificationChannelId,
+                                "Usage event " + debugId);
+                        break;
+                }
+                assertEquals(e1.mFlags, e2.mFlags);
         }
-        assertEquals(e1.mFlags, e2.mFlags);
     }
 
-    void compareIntervalStats(IntervalStats stats1, IntervalStats stats2) {
+    void compareIntervalStats(IntervalStats stats1, IntervalStats stats2, int minVersion) {
         assertEquals(stats1.majorVersion, stats2.majorVersion);
         assertEquals(stats1.minorVersion, stats2.minorVersion);
         assertEquals(stats1.beginTime, stats2.beginTime);
@@ -311,7 +329,7 @@
         } else {
             assertEquals(stats1.events.size(), stats2.events.size());
             for (int i = 0; i < stats1.events.size(); i++) {
-                compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i);
+                compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i, minVersion);
             }
         }
     }
@@ -326,7 +344,7 @@
                 mIntervalStatsVerifier);
 
         assertEquals(1, stats.size());
-        compareIntervalStats(mIntervalStats, stats.get(0));
+        compareIntervalStats(mIntervalStats, stats.get(0), MAX_TESTED_VERSION);
     }
 
     /**
@@ -359,8 +377,10 @@
                 mIntervalStatsVerifier);
 
         assertEquals(1, stats.size());
+
+        final int minVersion = oldVersion < newVersion ? oldVersion : newVersion;
         // The written and read IntervalStats should match
-        compareIntervalStats(mIntervalStats, stats.get(0));
+        compareIntervalStats(mIntervalStats, stats.get(0), minVersion);
     }
 
     /**
@@ -401,7 +421,7 @@
         if (mIntervalStats.events != null) mIntervalStats.events.clear();
 
         // The written and read IntervalStats should match
-        compareIntervalStats(mIntervalStats, stats.get(0));
+        compareIntervalStats(mIntervalStats, stats.get(0), version);
     }
 
     /**
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 9a5bd13..f1ddfe4 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -218,6 +218,14 @@
                 case (int) IntervalStatsProto.Event.INSTANCE_ID:
                     event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID);
                     break;
+                case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX:
+                    event.mTaskRootPackage = getCachedStringRef(stringPool.get(
+                            parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1));
+                    break;
+                case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX:
+                    event.mTaskRootClass = getCachedStringRef(stringPool.get(
+                            parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1));
+                    break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     // Handle default values for certain events types
                     switch (event.mEventType) {
@@ -332,6 +340,12 @@
         if (event.mClass != null) {
             event.mClass = getCachedStringRef(event.mClass);
         }
+        if (event.mTaskRootPackage != null) {
+            event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage);
+        }
+        if (event.mTaskRootClass != null) {
+            event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass);
+        }
         if (event.mEventType == NOTIFICATION_INTERRUPTION) {
             event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId);
         }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index d706537..11d49eb 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -442,6 +442,28 @@
         proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags);
         proto.write(IntervalStatsProto.Event.TYPE, event.mEventType);
         proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId);
+        if (event.mTaskRootPackage != null) {
+            final int taskRootPackageIndex = stats.mStringCache.indexOf(event.mTaskRootPackage);
+            if (taskRootPackageIndex >= 0) {
+                proto.write(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX,
+                        taskRootPackageIndex + 1);
+            } else {
+                // Task root package not in Stringpool for some reason.
+                Slog.w(TAG, "Usage event task root package name (" + event.mTaskRootPackage
+                        + ") not found in IntervalStats string cache");
+            }
+        }
+        if (event.mTaskRootClass != null) {
+            final int taskRootClassIndex = stats.mStringCache.indexOf(event.mTaskRootClass);
+            if (taskRootClassIndex >= 0) {
+                proto.write(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX,
+                        taskRootClassIndex + 1);
+            } else {
+                // Task root class not in Stringpool for some reason.
+                Slog.w(TAG, "Usage event task root class name (" + event.mTaskRootClass
+                        + ") not found in IntervalStats string cache");
+            }
+        }
         switch (event.mEventType) {
             case UsageEvents.Event.CONFIGURATION_CHANGE:
                 if (event.mConfiguration != null) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 76a3aa8..6ad698b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -22,6 +22,8 @@
 import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
 import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
 import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -39,6 +41,7 @@
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManager.StandbyBuckets;
+import android.app.usage.UsageStatsManager.UsageSource;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -65,6 +68,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -132,6 +136,7 @@
     private File mUsageStatsDir;
     long mRealTimeSnapshot;
     long mSystemTimeSnapshot;
+    int mUsageSource;
 
     /** Manages the standby state of apps. */
     AppStandbyController mAppStandby;
@@ -258,6 +263,7 @@
             } else {
                 Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
             }
+            readUsageSourceSetting();
         }
     }
 
@@ -268,6 +274,13 @@
         return mDpmInternal;
     }
 
+    private void readUsageSourceSetting() {
+        synchronized (mLock) {
+            mUsageSource = Settings.Global.getInt(getContext().getContentResolver(),
+                    Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+        }
+    }
+
     private class UserActionsReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -459,12 +472,28 @@
             service.reportEvent(event);
 
             mAppStandby.reportEvent(event, elapsedRealtime, userId);
+
+            String packageName;
+
+            switch(mUsageSource) {
+                case USAGE_SOURCE_CURRENT_ACTIVITY:
+                    packageName = event.getPackageName();
+                    break;
+                case USAGE_SOURCE_TASK_ROOT_ACTIVITY:
+                default:
+                    packageName = event.getTaskRootPackageName();
+                    if (packageName == null) {
+                        packageName = event.getPackageName();
+                    }
+                    break;
+            }
+
             switch (event.mEventType) {
                 case Event.ACTIVITY_RESUMED:
                     synchronized (mVisibleActivities) {
                         mVisibleActivities.put(event.mInstanceId, event.getClassName());
                         try {
-                            mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
+                            mAppTimeLimit.noteUsageStart(packageName, userId);
                         } catch (IllegalArgumentException iae) {
                             Slog.e(TAG, "Failed to note usage start", iae);
                         }
@@ -496,7 +525,7 @@
                     synchronized (mVisibleActivities) {
                         if (mVisibleActivities.removeReturnOld(event.mInstanceId) != null) {
                             try {
-                                mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
+                                mAppTimeLimit.noteUsageStop(packageName, userId);
                             } catch (IllegalArgumentException iae) {
                                 Slog.w(TAG, "Failed to note usage stop", iae);
                             }
@@ -638,7 +667,7 @@
      * Called by the Binder stub.
      */
     UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
-            String packageName) {
+            String packageName, boolean includeTaskRoot) {
         synchronized (mLock) {
             final long timeNow = checkAndGetTimeLocked();
             if (!validRange(timeNow, beginTime, endTime)) {
@@ -647,7 +676,7 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, timeNow);
-            return service.queryEventsForPackage(beginTime, endTime, packageName);
+            return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
         }
     }
 
@@ -738,6 +767,10 @@
                 mAppStandby.dumpState(args, pw);
             }
 
+            idpw.println();
+            idpw.printPair("Usage Source", UsageStatsManager.usageSourceToString(mUsageSource));
+            idpw.println();
+
             mAppTimeLimit.dump(null, pw);
         }
     }
@@ -808,7 +841,7 @@
             return mode == AppOpsManager.MODE_ALLOWED;
         }
 
-        private boolean hasObserverPermission(String callingPackage) {
+        private boolean hasObserverPermission() {
             final int callingUid = Binder.getCallingUid();
             DevicePolicyManagerInternal dpmInternal = getDpmInternal();
             if (callingUid == Process.SYSTEM_UID
@@ -939,10 +972,12 @@
             final int callingUserId = UserHandle.getUserId(callingUid);
 
             checkCallerIsSameApp(callingPackage);
+            final boolean includeTaskRoot = hasPermission(callingPackage);
+
             final long token = Binder.clearCallingIdentity();
             try {
                 return UsageStatsService.this.queryEventsForPackage(callingUserId, beginTime,
-                        endTime, callingPackage);
+                        endTime, callingPackage, includeTaskRoot);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -989,7 +1024,7 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 return UsageStatsService.this.queryEventsForPackage(userId, beginTime,
-                        endTime, pkg);
+                        endTime, pkg, true);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1229,7 +1264,7 @@
         public void registerAppUsageObserver(int observerId,
                 String[] packages, long timeLimitMs, PendingIntent
                 callbackIntent, String callingPackage) {
-            if (!hasObserverPermission(callingPackage)) {
+            if (!hasObserverPermission()) {
                 throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
             }
 
@@ -1252,7 +1287,7 @@
 
         @Override
         public void unregisterAppUsageObserver(int observerId, String callingPackage) {
-            if (!hasObserverPermission(callingPackage)) {
+            if (!hasObserverPermission()) {
                 throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
             }
 
@@ -1271,7 +1306,7 @@
                 long timeLimitMs, long sessionThresholdTimeMs,
                 PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
                 String callingPackage) {
-            if (!hasObserverPermission(callingPackage)) {
+            if (!hasObserverPermission()) {
                 throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
             }
 
@@ -1295,7 +1330,7 @@
 
         @Override
         public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) {
-            if (!hasObserverPermission(callingPackage)) {
+            if (!hasObserverPermission()) {
                 throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
             }
 
@@ -1373,6 +1408,21 @@
                 Binder.restoreCallingIdentity(binderToken);
             }
         }
+
+        @Override
+        public @UsageSource int getUsageSource() {
+            if (!hasObserverPermission()) {
+                throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+            }
+            synchronized (mLock) {
+                return mUsageSource;
+            }
+        }
+
+        @Override
+        public void forceUsageSourceSettingRead() {
+            readUsageSourceSetting();
+        }
     }
 
     void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
@@ -1406,7 +1456,7 @@
 
         @Override
         public void reportEvent(ComponentName component, int userId, int eventType,
-                int instanceId) {
+                int instanceId, ComponentName taskRoot) {
             if (component == null) {
                 Slog.w(TAG, "Event reported without a component name");
                 return;
@@ -1416,6 +1466,13 @@
             event.mPackage = component.getPackageName();
             event.mClass = component.getClassName();
             event.mInstanceId = instanceId;
+            if (taskRoot == null) {
+                event.mTaskRootPackage = null;
+                event.mTaskRootClass = null;
+            } else {
+                event.mTaskRootPackage = taskRoot.getPackageName();
+                event.mTaskRootClass = taskRoot.getClassName();
+            }
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
 
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 2d1098c7..d52d32f 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -401,7 +401,7 @@
     }
 
     UsageEvents queryEvents(final long beginTime, final long endTime,
-            boolean obfuscateInstantApps) {
+                            boolean obfuscateInstantApps) {
         final ArraySet<String> names = new ArraySet<>();
         List<Event> results = queryStats(INTERVAL_DAILY,
                 beginTime, endTime, new StatCombiner<Event>() {
@@ -425,6 +425,12 @@
                             if (event.mClass != null) {
                                 names.add(event.mClass);
                             }
+                            if (event.mTaskRootPackage != null) {
+                                names.add(event.mTaskRootPackage);
+                            }
+                            if (event.mTaskRootClass != null) {
+                                names.add(event.mTaskRootClass);
+                            }
                             accumulatedResult.add(event);
                         }
                     }
@@ -436,11 +442,11 @@
 
         String[] table = names.toArray(new String[names.size()]);
         Arrays.sort(table);
-        return new UsageEvents(results, table);
+        return new UsageEvents(results, table, true);
     }
 
     UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
-            final String packageName) {
+            final String packageName, boolean includeTaskRoot) {
         final ArraySet<String> names = new ArraySet<>();
         names.add(packageName);
         final List<Event> results = queryStats(INTERVAL_DAILY,
@@ -459,6 +465,12 @@
                         if (event.mClass != null) {
                             names.add(event.mClass);
                         }
+                        if (includeTaskRoot && event.mTaskRootPackage != null) {
+                            names.add(event.mTaskRootPackage);
+                        }
+                        if (includeTaskRoot && event.mTaskRootClass != null) {
+                            names.add(event.mTaskRootClass);
+                        }
                         accumulatedResult.add(event);
                     }
                 });
@@ -469,7 +481,7 @@
 
         final String[] table = names.toArray(new String[names.size()]);
         Arrays.sort(table);
-        return new UsageEvents(results, table);
+        return new UsageEvents(results, table, includeTaskRoot);
     }
 
     void persistActiveStats() {
@@ -684,6 +696,14 @@
             pw.printPair("instanceId", event.getInstanceId());
         }
 
+        if (event.getTaskRootPackageName() != null) {
+            pw.printPair("taskRootPackage", event.getTaskRootPackageName());
+        }
+
+        if (event.getTaskRootClassName() != null) {
+            pw.printPair("taskRootClass", event.getTaskRootClassName());
+        }
+
         if (event.mNotificationChannelId != null) {
             pw.printPair("channelId", event.mNotificationChannelId);
         }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 312b318..a33b44c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2508,10 +2508,18 @@
         public static final String KEY_GPS_LOCK_STRING = KEY_PREFIX + "gps_lock";
 
         /**
-         * SUPL NI emergency extension time in seconds. Default to "0".
+         * Control Plane / SUPL NI emergency extension time in seconds. Default to "0".
          */
         public static final String KEY_ES_EXTENSION_SEC = KEY_PREFIX + "es_extension_sec";
 
+        /**
+         * Space separated list of Android package names of proxy applications representing
+         * the non-framework entities requesting location directly from GNSS without involving
+         * the framework, as managed by IGnssVisibilityControl.hal. For example,
+         * "com.example.mdt com.example.ims".
+         */
+        public static final String KEY_NFW_PROXY_APPS = KEY_PREFIX + "nfw_proxy_apps";
+
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
             defaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, true);
@@ -2525,6 +2533,7 @@
             defaults.putString(KEY_A_GLONASS_POS_PROTOCOL_SELECT_STRING, "0");
             defaults.putString(KEY_GPS_LOCK_STRING, "3");
             defaults.putString(KEY_ES_EXTENSION_SEC, "0");
+            defaults.putString(KEY_NFW_PROXY_APPS, "");
             return defaults;
         }
     }
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 7b29f69..30e641d 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -148,8 +148,9 @@
 
     /**
      * Return the Bit Error Rate
-     * @returns the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or UNAVAILABLE.
-     * @hide
+     *
+     * @return the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+     *         {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}.
      */
     public int getBitErrorRate() {
         return mBitErrorRate;
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index ad3ca6d..91375bc 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -292,14 +292,27 @@
      * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
      *
      * @return RSSI in ASU 0..31, 99, or UNAVAILABLE
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getAsuLevel}.
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths
      */
+    @Deprecated
     public int getGsmSignalStrength() {
         return mGsm.getAsuLevel();
     }
 
     /**
      * Get the GSM bit error rate (0-7, 99) as defined in TS 27.007 8.5
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getBitErrorRate}.
+     *
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getGsmBitErrorRate() {
         return mGsm.getBitErrorRate();
     }
@@ -308,14 +321,28 @@
      * Get the CDMA RSSI value in dBm
      *
      * @return the CDMA RSSI value or {@link #INVALID} if invalid
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getCdmaDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getCdmaDbm() {
         return mCdma.getCdmaDbm();
     }
 
     /**
      * Get the CDMA Ec/Io value in dB*10
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getCdmaEcio}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getCdmaEcio() {
         return mCdma.getCdmaEcio();
     }
@@ -324,51 +351,112 @@
      * Get the EVDO RSSI value in dBm
      *
      * @return the EVDO RSSI value or {@link #INVALID} if invalid
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getEvdoDbm() {
         return mCdma.getEvdoDbm();
     }
 
     /**
      * Get the EVDO Ec/Io value in dB*10
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoEcio}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getEvdoEcio() {
         return mCdma.getEvdoEcio();
     }
 
     /**
      * Get the signal to noise ratio. Valid values are 0-8. 8 is the highest.
+     *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoSnr}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
+    @Deprecated
     public int getEvdoSnr() {
         return mCdma.getEvdoSnr();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getRssi}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteSignalStrength() {
         return mLte.getRssi();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getRsrp}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteRsrp() {
         return mLte.getRsrp();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getRsrq}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteRsrq() {
         return mLte.getRsrq();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getRssnr}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteRssnr() {
         return mLte.getRssnr();
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getCqi}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteCqi() {
         return mLte.getCqi();
     }
@@ -391,11 +479,17 @@
     }
 
     /**
-     * Get the signal level as an asu value between 0..31, 99 is unknown
+     * Get the signal level as an asu value with a range dependent on the underlying technology.
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrength#getAsuLevel}. Because the levels vary by technology,
+     *             this method is misleading and should not be used.
+     * @see android.telephony#CellSignalStrength
+     * @see android.telephony.SignalStrength#getCellSignalStrengths
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getAsuLevel() {
         return getPrimary().getAsuLevel();
     }
@@ -403,9 +497,15 @@
     /**
      * Get the signal strength as dBm
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrength#getDbm()}. Because the levels vary by technology,
+     *             this method is misleading and should not be used.
+     * @see android.telephony#CellSignalStrength
+     * @see android.telephony.SignalStrength#getCellSignalStrengths
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getDbm() {
         return getPrimary().getDbm();
     }
@@ -413,9 +513,15 @@
     /**
      * Get Gsm signal strength as dBm
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getGsmDbm() {
         return mGsm.getDbm();
     }
@@ -423,9 +529,15 @@
     /**
      * Get gsm as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getGsmLevel() {
         return mGsm.getLevel();
     }
@@ -433,9 +545,15 @@
     /**
      * Get the gsm signal level as an asu value between 0..31, 99 is unknown
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthGsm#getAsuLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getGsmAsuLevel() {
         return mGsm.getAsuLevel();
     }
@@ -443,9 +561,15 @@
     /**
      * Get cdma as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getCdmaLevel() {
         return mCdma.getLevel();
     }
@@ -453,9 +577,17 @@
     /**
      * Get the cdma signal level as an asu value between 0..31, 99 is unknown
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getAsuLevel}. Since there is no definition of
+     *             ASU for CDMA, the resultant value is Android-specific and is not recommended
+     *             for use.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getCdmaAsuLevel() {
         return mCdma.getAsuLevel();
     }
@@ -463,9 +595,15 @@
     /**
      * Get Evdo as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getEvdoLevel() {
         return mCdma.getEvdoLevel();
     }
@@ -473,9 +611,17 @@
     /**
      * Get the evdo signal level as an asu value between 0..31, 99 is unknown
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthCdma#getEvdoAsuLevel}. Since there is no definition of
+     *             ASU for EvDO, the resultant value is Android-specific and is not recommended
+     *             for use.
+     *
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getEvdoAsuLevel() {
         return mCdma.getEvdoAsuLevel();
     }
@@ -483,9 +629,15 @@
     /**
      * Get LTE as dBm
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteDbm() {
         return mLte.getRsrp();
     }
@@ -493,9 +645,15 @@
     /**
      * Get LTE as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteLevel() {
         return mLte.getLevel();
     }
@@ -504,26 +662,46 @@
      * Get the LTE signal level as an asu value between 0..97, 99 is unknown
      * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthLte#getAsuLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getLteAsuLevel() {
         return mLte.getAsuLevel();
     }
 
     /**
      * @return true if this is for GSM
+     *
+     * @deprecated This method returns true if there are any 3gpp type SignalStrength elements in
+     *             this SignalStrength report or if the report contains no valid SignalStrength
+     *             information. Instead callers should use
+     *             {@link android.telephony.SignalStrength#getCellSignalStrengths
+     *             getCellSignalStrengths()} to determine which types of information are contained
+     *             in the SignalStrength report.
      */
+    @Deprecated
     public boolean isGsm() {
         return !(getPrimary() instanceof CellSignalStrengthCdma);
     }
 
     /**
-     * @return get TD_SCDMA dbm
+     * @return get TD-SCDMA dBm
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthTdscdma#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getTdScdmaDbm() {
         return mTdscdma.getRscp();
     }
@@ -534,9 +712,15 @@
      * INT_MAX: 0x7FFFFFFF denotes invalid value
      * Reference: 3GPP TS 25.123, section 9.1.1.1
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthTdscdma#getLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getTdScdmaLevel() {
         return mTdscdma.getLevel();
      }
@@ -544,18 +728,30 @@
     /**
      * Get the TD-SCDMA signal level as an asu value.
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthTdscdma#getAsuLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getTdScdmaAsuLevel() {
         return mTdscdma.getAsuLevel();
     }
 
     /**
-     * Gets WCDMA RSCP as a dbm value between -120 and -24, as defined in TS 27.007 8.69.
+     * Gets WCDMA RSCP as a dBm value between -120 and -24, as defined in TS 27.007 8.69.
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthWcdma#getRscp}.
+     *
+     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
+    @Deprecated
     public int getWcdmaRscp() {
         return mWcdma.getRscp();
     }
@@ -563,8 +759,14 @@
     /**
      * Get the WCDMA signal level as an ASU value between 0-96, 255 is unknown
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthWcdma#getAsuLevel}.
+     *
+     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
+    @Deprecated
     public int getWcdmaAsuLevel() {
         /*
          * 3GPP 27.007 (Ver 10.3.0) Sec 8.69
@@ -578,10 +780,16 @@
     }
 
     /**
-     * Gets WCDMA signal strength as a dbm value between -120 and -24, as defined in TS 27.007 8.69.
+     * Gets WCDMA signal strength as a dBm value between -120 and -24, as defined in TS 27.007 8.69.
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthWcdma#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
+    @Deprecated
     public int getWcdmaDbm() {
         return mWcdma.getDbm();
     }
@@ -589,13 +797,19 @@
     /**
      * Get WCDMA as level 0..4
      *
+     * @deprecated this information should be retrieved from
+     *             {@link CellSignalStrengthWcdma#getDbm}.
+     *
+     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
+    @Deprecated
     public int getWcdmaLevel() {
         return mWcdma.getLevel();
     }
 
-   /**
+    /**
      * @return hash code
      */
     @Override
@@ -639,9 +853,13 @@
      * Set SignalStrength based on intent notifier map
      *
      * @param m intent notifier map
+     *
+     * @deprecated this method relies on non-stable implementation details, and full access to
+     *             internal storage is available via {@link getCellSignalStrengths()}.
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     private void setFromNotifierBundle(Bundle m) {
         mCdma = m.getParcelable("Cdma");
         mGsm = m.getParcelable("Gsm");
@@ -654,9 +872,13 @@
      * Set intent notifier Bundle based on SignalStrength
      *
      * @param m intent notifier Bundle
+     *
+     * @deprecated this method relies on non-stable implementation details, and full access to
+     *             internal storage is available via {@link getCellSignalStrengths()}.
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public void fillInNotifierBundle(Bundle m) {
         m.putParcelable("Cdma", mCdma);
         m.putParcelable("Gsm", mGsm);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8053353..e710e0e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -93,6 +93,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.regex.Matcher;
@@ -8585,12 +8586,25 @@
     }
 
 
-    /** @hide */
-    public String getLocaleFromDefaultSim() {
+    /**
+     * Returns a well-formed IETF BCP 47 language tag representing the locale from the SIM, e.g,
+     * en-US. Returns {@code null} if no locale could be derived from subscriptions.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     *
+     * @see Locale#toLanguageTag()
+     * @see Locale#forLanguageTag(String)
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @Nullable public String getSimLocale() {
         try {
             final ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getLocaleFromDefaultSim();
+                return telephony.getSimLocaleForSubscriber(getSubId());
             }
         } catch (RemoteException ex) {
         }
@@ -8598,6 +8612,22 @@
     }
 
     /**
+     * TODO delete after SuW migrates to new API.
+     * @hide
+     */
+    public String getLocaleFromDefaultSim() {
+        try {
+            final ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getSimLocaleForSubscriber(getSubId());
+            }
+        } catch (RemoteException ex) {
+        }
+        return null;
+    }
+
+
+    /**
      * Requests the modem activity info. The recipient will place the result
      * in `result`.
      * @param result The object on which the recipient will send the resulting
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 5736a46..9cc173c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1178,12 +1178,12 @@
     void factoryReset(int subId);
 
     /**
-     * An estimate of the users's current locale based on the default SIM.
+     * Returns users's current locale based on the SIM.
      *
      * The returned string will be a well formed BCP-47 language tag, or {@code null}
      * if no locale could be derived.
      */
-    String getLocaleFromDefaultSim();
+    String getSimLocaleForSubscriber(int subId);
 
     /**
      * Requests the modem activity info asynchronously.
diff --git a/tests/HwAccelerationTest/Android.mk b/tests/HwAccelerationTest/Android.mk
index 11ea954..79072fa 100644
--- a/tests/HwAccelerationTest/Android.mk
+++ b/tests/HwAccelerationTest/Android.mk
@@ -21,6 +21,7 @@
 
 LOCAL_PACKAGE_NAME := HwAccelerationTest
 LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
 
 LOCAL_MODULE_TAGS := tests
 
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c8f96c9..f330b83 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -310,6 +310,15 @@
                 <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:name="PictureCaptureDemo"
+            android:label="Debug/Picture Capture">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
         
         <activity
                 android:name="SmallCircleActivity"
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java
new file mode 100644
index 0000000..029e302
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PictureCaptureDemo.java
@@ -0,0 +1,153 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Picture;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewDebug;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ProgressBar;
+
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class PictureCaptureDemo extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final LinearLayout layout = new LinearLayout(this);
+        layout.setOrientation(LinearLayout.VERTICAL);
+
+        final LinearLayout inner = new LinearLayout(this);
+        inner.setOrientation(LinearLayout.HORIZONTAL);
+        ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
+        inner.addView(spinner,
+                new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+        inner.addView(new View(this), new LayoutParams(50, 1));
+
+        Picture picture = new Picture();
+        Canvas canvas = picture.beginRecording(100, 100);
+        canvas.drawColor(Color.RED);
+        Paint paint = new Paint();
+        paint.setTextSize(32);
+        paint.setColor(Color.BLACK);
+        canvas.drawText("Hello", 0, 50, paint);
+        picture.endRecording();
+
+        ImageView iv1 = new ImageView(this);
+        iv1.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.ARGB_8888));
+        inner.addView(iv1, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+        inner.addView(new View(this), new LayoutParams(50, 1));
+
+        ImageView iv2 = new ImageView(this);
+        iv2.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.HARDWARE));
+        inner.addView(iv2, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+
+        layout.addView(inner,
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        // For testing with a functor in the tree
+        WebView wv = new WebView(this);
+        wv.setWebViewClient(new WebViewClient());
+        wv.setWebChromeClient(new WebChromeClient());
+        wv.loadUrl("https://google.com");
+        layout.addView(wv, new LayoutParams(LayoutParams.MATCH_PARENT, 400));
+
+        SurfaceView mySurfaceView = new SurfaceView(this);
+        layout.addView(mySurfaceView,
+                new LayoutParams(LayoutParams.MATCH_PARENT, 600));
+
+        setContentView(layout);
+
+        mySurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            private AutoCloseable mStopCapture;
+
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                final Random rand = new Random();
+                mStopCapture = ViewDebug.startRenderingCommandsCapture(mySurfaceView,
+                        mCaptureThread, (picture) -> {
+                            if (rand.nextInt(20) == 0) {
+                                try {
+                                    Thread.sleep(100);
+                                } catch (InterruptedException e) {
+                                }
+                            }
+                            Canvas canvas = holder.lockCanvas();
+                            if (canvas == null) {
+                                return false;
+                            }
+                            canvas.drawPicture(picture);
+                            holder.unlockCanvasAndPost(canvas);
+                            picture.close();
+                            return true;
+                        });
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                if (mStopCapture != null) {
+                    try {
+                        mStopCapture.close();
+                    } catch (Exception e) {
+                    }
+                    mStopCapture = null;
+                }
+            }
+        });
+    }
+
+    ExecutorService mCaptureThread = Executors.newSingleThreadExecutor();
+    ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+    Picture deepCopy(Picture src) {
+        try {
+            PipedInputStream inputStream = new PipedInputStream();
+            PipedOutputStream outputStream = new PipedOutputStream(inputStream);
+            Future<Picture> future = mExecutor.submit(() -> Picture.createFromStream(inputStream));
+            src.writeToStream(outputStream);
+            outputStream.close();
+            return future.get();
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
index d3c39f0..030641b 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
@@ -44,7 +44,6 @@
     RollbackBroadcastReceiver() {
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
-        filter.addDataScheme("package");
         InstrumentationRegistry.getContext().registerReceiver(this, filter);
     }
 
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index ec6f4b5..9d67cea 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -22,9 +22,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
-import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.support.test.InstrumentationRegistry;
@@ -98,7 +98,7 @@
             // so that's not the case!
             for (int i = 0; i < 5; ++i) {
                 for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
-                    if (TEST_APP_A.equals(info.targetPackage.packageName)) {
+                    if (TEST_APP_A.equals(info.targetPackage.getPackageName())) {
                         Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
                         Thread.sleep(1000);
                         break;
@@ -116,7 +116,7 @@
 
             // There should be no recently executed rollbacks for this package.
             for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
-                assertNotEquals(TEST_APP_A, info.targetPackage.packageName);
+                assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName());
             }
 
             // Install v1 of the app (without rollbacks enabled).
@@ -135,9 +135,7 @@
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
 
             // We should not have received any rollback requests yet.
             // TODO: Possibly flaky if, by chance, some other app on device
@@ -153,21 +151,18 @@
             // received could lead to test flakiness.
             Intent broadcast = broadcastReceiver.poll(5, TimeUnit.SECONDS);
             assertNotNull(broadcast);
-            assertEquals(TEST_APP_A, broadcast.getData().getSchemeSpecificPart());
             assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
 
             // Verify the recent rollback has been recorded.
             rollback = null;
             for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
                     assertNull(rollback);
                     rollback = r;
                 }
             }
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
 
             broadcastReceiver.unregister();
             context.unregisterReceiver(enableRollbackReceiver);
@@ -208,16 +203,12 @@
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
-            assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
 
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
             RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
-            assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
@@ -225,16 +216,12 @@
             // The apps should still be available for rollback.
             rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
-            assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
 
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
             rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
-            assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
 
             // Rollback of B should not rollback A
             RollbackTestUtils.rollback(rollbackB);
@@ -278,16 +265,12 @@
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
-            assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
 
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
             RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
-            assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
@@ -295,16 +278,12 @@
             // The apps should still be available for rollback.
             rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
-            assertEquals(2, rollbackA.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackA.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
 
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
             rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
-            assertEquals(2, rollbackB.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollbackB.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
 
             // Rollback of B should rollback A as well
             RollbackTestUtils.rollback(rollbackB);
@@ -348,15 +327,13 @@
             // Verify the recent rollback has been recorded.
             rollback = null;
             for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
                     assertNull(rollback);
                     rollback = r;
                 }
             }
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
@@ -364,15 +341,13 @@
             // Verify the recent rollback is still recorded.
             rollback = null;
             for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.packageName)) {
+                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
                     assertNull(rollback);
                     rollback = r;
                 }
             }
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -404,9 +379,7 @@
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollback);
-            assertEquals(TEST_APP_A, rollback.targetPackage.packageName);
-            assertEquals(2, rollback.targetPackage.higherVersion.versionCode);
-            assertEquals(1, rollback.targetPackage.lowerVersion.versionCode);
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
 
             // Expire the rollback.
             rm.expireRollbackForPackage(TEST_APP_A);
@@ -499,8 +472,7 @@
     @Test
     public void testRollbackBroadcastRestrictions() throws Exception {
         RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
-        Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
-                Uri.fromParts("package", "com.android.tests.rollback.bogus", null));
+        Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
         try {
             InstrumentationRegistry.getContext().sendBroadcast(broadcast);
             fail("Succeeded in sending restricted broadcast from app context.");
@@ -549,11 +521,11 @@
             Thread.sleep(1000);
             RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
+            assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
 
             RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
             assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.packageName);
+            assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
 
             // Executing rollback should roll back the correct package.
             RollbackTestUtils.rollback(rollbackA);
@@ -670,7 +642,7 @@
 
             // We should not see a recent rollback listed for TEST_APP_B
             for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                assertNotEquals(TEST_APP_B, r.targetPackage.packageName);
+                assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName());
             }
 
             // TODO: Test the listed dependent apps for the recently executed
@@ -680,4 +652,15 @@
             RollbackTestUtils.dropShellPermissionIdentity();
         }
     }
+
+    // Helper function to test the value of a PackageRollbackInfo
+    private void assertPackageRollbackInfoEquals(String packageName,
+            long versionRolledBackFrom, long versionRolledBackTo,
+            PackageRollbackInfo info) {
+        assertEquals(packageName, info.getPackageName());
+        assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName());
+        assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode());
+        assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
+        assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
+    }
 }
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index 6850673..7e1b400 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -32,74 +32,6 @@
 
 LOCAL_CERTIFICATE := platform
 
-# These are not normally accessible from apps so they must be explicitly included.
-LOCAL_JNI_SHARED_LIBRARIES := \
-    android.hidl.token@1.0 \
-    libartbase \
-    libbacktrace \
-    libbase \
-    libbinder \
-    libbinderthreadstate \
-    libc++ \
-    libcrypto \
-    libcutils \
-    libdexfile \
-    libframeworksnettestsjni \
-    libhidl-gen-utils \
-    libhidlbase \
-    libhidltransport \
-    libhwbinder \
-    liblog \
-    liblzma \
-    libnativehelper \
-    libpackagelistparser \
-    libpcre2 \
-    libprocessgroup \
-    libselinux \
-    libui \
-    libutils \
-    libvintf \
-    libvndksupport \
-    libtinyxml2 \
-    libunwindstack \
-    libutilscallstack \
-    libziparchive \
-    libz \
-    netd_aidl_interface-cpp
-
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
 include $(BUILD_PACKAGE)
-
-#########################################################################
-# Build JNI Shared Library
-#########################################################################
-
-LOCAL_PATH:= $(LOCAL_PATH)/jni
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_CFLAGS := -Wall -Wextra -Werror
-
-LOCAL_C_INCLUDES := \
-  libpcap \
-  hardware/google/apf
-
-LOCAL_SRC_FILES := $(call all-cpp-files-under)
-
-LOCAL_SHARED_LIBRARIES := \
-  libbinder \
-  liblog \
-  libcutils \
-  libnativehelper \
-  netd_aidl_interface-cpp
-
-LOCAL_STATIC_LIBRARIES := \
-  libpcap \
-  libapf
-
-LOCAL_MODULE := libframeworksnettestsjni
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1c26418..dda4481 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1508,6 +1508,12 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
+    @Test
+    public void testRequiresValidation() {
+        assertTrue(NetworkMonitorUtils.isValidationRequired(
+                mCm.getDefaultRequest().networkCapabilities));
+    }
+
     enum CallbackState {
         NONE,
         AVAILABLE,
@@ -4404,8 +4410,7 @@
         mMockVpn.setUids(ranges);
         // VPN networks do not satisfy the default request and are automatically validated
         // by NetworkMonitor
-        assertFalse(NetworkMonitorUtils.isValidationRequired(
-                mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities));
+        assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
         vpnNetworkAgent.setNetworkValid();
 
         vpnNetworkAgent.connect(false);
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
index a6b0102..163b00a 100644
--- a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
@@ -190,21 +190,22 @@
 
     @Test
     public void testSelectClassAndSamePackage() {
-        final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, PACKAGE_A,
-                CLASS_C5, CLASS_C6, PACKAGE_C).build();
-        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
+        final Filter filter = mBuilder.withSelectTest(
+                CLASS_A1, PACKAGE_A, CLASS_B3, PACKAGE_C, CLASS_C5).build();
+        acceptTests(filter, TEST_PACKAGE_A, TEST_CLASS_B3, TEST_PACKAGE_C);
     }
 
     @Test
     public void testSelectMethodAndSameClass() {
-        final Filter filter = mBuilder.withSelectTest(METHOD_A1K, METHOD_A1L, METHOD_A2M, CLASS_A1,
-                CLASS_B3, METHOD_B3P, METHOD_B3Q, METHOD_B4R).build();
+        final Filter filter = mBuilder.withSelectTest(
+                METHOD_A1K, METHOD_A2M, CLASS_A1, CLASS_B3, METHOD_B3P, METHOD_B4R).build();
         acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R);
     }
 
     @Test
     public void testSelectMethodAndSamePackage() {
-        final Filter filter = mBuilder.withSelectTest(METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+        final Filter filter = mBuilder.withSelectTest(
+                METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A,
                 PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build();
         acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
     }
diff --git a/tools/processors/unsupportedappusage/Android.bp b/tools/processors/unsupportedappusage/Android.bp
index 1aca3ed..0e33fdd 100644
--- a/tools/processors/unsupportedappusage/Android.bp
+++ b/tools/processors/unsupportedappusage/Android.bp
@@ -1,6 +1,8 @@
 
-java_library_host {
+java_plugin {
     name: "unsupportedappusage-annotation-processor",
+    processor_class: "android.processor.unsupportedappusage.UnsupportedAppUsageProcessor",
+
     java_resources: [
         "META-INF/**/*",
     ],