Merge "Added argument checking on ContentCaptureContext."
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 1520a01..63657c5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3941,7 +3941,8 @@
     method public boolean isBackgroundRestricted();
     method @Deprecated public boolean isInLockTaskMode();
     method public boolean isLowRamDevice();
-    method public static boolean isRunningInTestHarness();
+    method @Deprecated public static boolean isRunningInTestHarness();
+    method public static boolean isRunningInUserTestHarness();
     method public static boolean isUserAMonkey();
     method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String);
     method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
@@ -6822,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";
@@ -10346,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";
@@ -11497,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;
@@ -11505,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);
@@ -11571,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);
@@ -11652,6 +11655,7 @@
     field public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
     field public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait";
     field public static final String FEATURE_SECURELY_REMOVES_USERS = "android.software.securely_removes_users";
+    field public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
     field public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
     field public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature";
     field public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
@@ -14615,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();
@@ -14868,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();
@@ -22253,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);
@@ -24635,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;
@@ -26591,12 +26596,9 @@
     field public static final int SUCCESS = 0; // 0x0
   }
 
-  public static final class AudioEffect.Descriptor implements android.os.Parcelable {
+  public static class AudioEffect.Descriptor {
     ctor public AudioEffect.Descriptor();
     ctor public AudioEffect.Descriptor(String, String, String, String, String);
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.media.audiofx.AudioEffect.Descriptor> CREATOR;
     field public String connectMode;
     field public String implementor;
     field public String name;
@@ -27460,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();
@@ -28578,6 +28579,28 @@
     field public int serverAddress;
   }
 
+  public final class DnsResolver {
+    method public static android.net.DnsResolver getInstance();
+    method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
+    method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
+    method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException;
+    field public static final int CLASS_IN = 1; // 0x1
+    field public static final int FLAG_EMPTY = 0; // 0x0
+    field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
+    field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
+    field public static final int FLAG_NO_RETRY = 1; // 0x1
+    field public static final int TYPE_A = 1; // 0x1
+    field public static final int TYPE_AAAA = 28; // 0x1c
+  }
+
+  public static interface DnsResolver.InetAddressAnswerListener {
+    method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>);
+  }
+
+  public static interface DnsResolver.RawAnswerListener {
+    method public void onAnswer(@Nullable byte[]);
+  }
+
   public class InetAddresses {
     method public static boolean isNumericAddress(String);
     method public static java.net.InetAddress parseNumericAddress(String);
@@ -38172,6 +38195,8 @@
     field public static final String MEDIA_SCANNER_VOLUME = "volume";
     field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service";
     field public static final String UNKNOWN_STRING = "<unknown>";
+    field public static final String VOLUME_EXTERNAL = "external";
+    field public static final String VOLUME_INTERNAL = "internal";
   }
 
   public static final class MediaStore.Audio {
@@ -38378,28 +38403,28 @@
     field public static final android.net.Uri INTERNAL_CONTENT_URI;
   }
 
-  public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
-    ctor public MediaStore.Images.Thumbnails();
+  @Deprecated public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
+    ctor @Deprecated public MediaStore.Images.Thumbnails();
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
-    method public static android.net.Uri getContentUri(String);
+    method @Deprecated public static android.net.Uri getContentUri(String);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
-    method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
-    method public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]);
-    method public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]);
+    method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
+    method @Deprecated public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]);
+    method @Deprecated public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]);
     field @Deprecated public static final String DATA = "_data";
-    field public static final String DEFAULT_SORT_ORDER = "image_id ASC";
-    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
-    field public static final int FULL_SCREEN_KIND = 2; // 0x2
-    field public static final String HEIGHT = "height";
-    field public static final String IMAGE_ID = "image_id";
-    field public static final android.net.Uri INTERNAL_CONTENT_URI;
-    field public static final String KIND = "kind";
-    field public static final int MICRO_KIND = 3; // 0x3
-    field public static final int MINI_KIND = 1; // 0x1
-    field public static final String THUMB_DATA = "thumb_data";
-    field public static final String WIDTH = "width";
+    field @Deprecated public static final String DEFAULT_SORT_ORDER = "image_id ASC";
+    field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2
+    field @Deprecated public static final String HEIGHT = "height";
+    field @Deprecated public static final String IMAGE_ID = "image_id";
+    field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI;
+    field @Deprecated public static final String KIND = "kind";
+    field @Deprecated public static final int MICRO_KIND = 3; // 0x3
+    field @Deprecated public static final int MINI_KIND = 1; // 0x1
+    field @Deprecated public static final String THUMB_DATA = "thumb_data";
+    field @Deprecated public static final String WIDTH = "width";
   }
 
   public static interface MediaStore.MediaColumns extends android.provider.BaseColumns {
@@ -38451,24 +38476,24 @@
     field public static final android.net.Uri INTERNAL_CONTENT_URI;
   }
 
-  public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns {
-    ctor public MediaStore.Video.Thumbnails();
+  @Deprecated public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns {
+    ctor @Deprecated public MediaStore.Video.Thumbnails();
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
-    method public static android.net.Uri getContentUri(String);
+    method @Deprecated public static android.net.Uri getContentUri(String);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
     field @Deprecated public static final String DATA = "_data";
-    field public static final String DEFAULT_SORT_ORDER = "video_id ASC";
-    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
-    field public static final int FULL_SCREEN_KIND = 2; // 0x2
-    field public static final String HEIGHT = "height";
-    field public static final android.net.Uri INTERNAL_CONTENT_URI;
-    field public static final String KIND = "kind";
-    field public static final int MICRO_KIND = 3; // 0x3
-    field public static final int MINI_KIND = 1; // 0x1
-    field public static final String VIDEO_ID = "video_id";
-    field public static final String WIDTH = "width";
+    field @Deprecated public static final String DEFAULT_SORT_ORDER = "video_id ASC";
+    field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2
+    field @Deprecated public static final String HEIGHT = "height";
+    field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI;
+    field @Deprecated public static final String KIND = "kind";
+    field @Deprecated public static final int MICRO_KIND = 3; // 0x3
+    field @Deprecated public static final int MINI_KIND = 1; // 0x1
+    field @Deprecated public static final String VIDEO_ID = "video_id";
+    field @Deprecated public static final String WIDTH = "width";
   }
 
   public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
@@ -43020,6 +43045,15 @@
     method public abstract void onScreenCall(@NonNull android.telecom.Call.Details);
     method public final void provideCallIdentification(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallIdentification);
     method public final void respondToCall(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallScreeningService.CallResponse);
+    field public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
+    field public static final int CALL_DURATION_LONG = 4; // 0x4
+    field public static final int CALL_DURATION_MEDIUM = 3; // 0x3
+    field public static final int CALL_DURATION_SHORT = 2; // 0x2
+    field public static final int CALL_DURATION_VERY_SHORT = 1; // 0x1
+    field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+    field public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
+    field public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
+    field public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
     field public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService";
   }
 
@@ -43625,6 +43659,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String);
     method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle);
     method public void registerPhoneAccount(android.telecom.PhoneAccount);
+    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void reportNuisanceCallStatus(@NonNull android.net.Uri, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger();
     method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
@@ -44173,6 +44208,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();
@@ -44474,16 +44510,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
   }
@@ -52613,12 +52649,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 {
@@ -52957,7 +52993,7 @@
     method @Deprecated public boolean isWatchingCursor(android.view.View);
     method public void restartInput(android.view.View);
     method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
-    method public void setAdditionalInputMethodSubtypes(String, android.view.inputmethod.InputMethodSubtype[]);
+    method @Deprecated public void setAdditionalInputMethodSubtypes(String, android.view.inputmethod.InputMethodSubtype[]);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
     method @Deprecated public void setInputMethod(android.os.IBinder, String);
     method @Deprecated public void setInputMethodAndSubtype(@NonNull android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype);
@@ -52976,7 +53012,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
@@ -56823,7 +56859,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 56033a9..d43353f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -244,6 +244,7 @@
   }
 
   public static final class R.style {
+    field public static final int Theme_DeviceDefault_DocumentsUI = 16974562; // 0x10302e2
     field public static final int Theme_Leanback_FormWizard = 16974544; // 0x10302d0
   }
 
@@ -1095,6 +1096,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 +1112,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 +1128,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
   }
 
 }
@@ -1136,19 +1142,46 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect();
     method public boolean isBleScanAlwaysAvailable();
     method public boolean isLeEnabled();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean registerMetadataListener(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothAdapter.MetadataListener, android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean unregisterMetadataListener(android.bluetooth.BluetoothDevice);
     field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
     field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
   }
 
+  public abstract class BluetoothAdapter.MetadataListener {
+    ctor public BluetoothAdapter.MetadataListener();
+    method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String);
+  }
+
   public final class BluetoothDevice implements android.os.Parcelable {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int);
     field public static final int ACCESS_ALLOWED = 1; // 0x1
     field public static final int ACCESS_REJECTED = 2; // 0x2
     field public static final int ACCESS_UNKNOWN = 0; // 0x0
+    field public static final int METADATA_COMPANION_APP = 4; // 0x4
+    field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10
+    field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3
+    field public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; // 0x6
+    field public static final int METADATA_MAIN_ICON = 5; // 0x5
+    field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0
+    field public static final int METADATA_MAX_LENGTH = 2048; // 0x800
+    field public static final int METADATA_MODEL_NAME = 1; // 0x1
+    field public static final int METADATA_SOFTWARE_VERSION = 2; // 0x2
+    field public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; // 0xc
+    field public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; // 0xf
+    field public static final int METADATA_UNTHETHERED_CASE_ICON = 9; // 0x9
+    field public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; // 0xa
+    field public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; // 0xd
+    field public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; // 0x7
+    field public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; // 0xb
+    field public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; // 0xe
+    field public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; // 0x8
   }
 
   public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
@@ -1645,22 +1678,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;
@@ -3367,6 +3395,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 {
@@ -3542,6 +3579,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();
@@ -5525,10 +5566,22 @@
     field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
   }
 
+  public static interface DeviceConfig.FsiBoot {
+    field public static final String NAMESPACE = "fsi_boot";
+    field public static final String OOB_ENABLED = "oob_enabled";
+    field public static final String OOB_WHITELIST = "oob_whitelist";
+  }
+
   public static interface DeviceConfig.OnPropertyChangedListener {
     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);
@@ -5710,6 +5763,7 @@
     field public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications";
     field public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications";
     field public static final String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count";
+    field public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES = "theme_customization_overlay_packages";
     field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
     field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa
     field public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; // 0x0
@@ -5985,10 +6039,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 {
@@ -6002,7 +6053,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 {
@@ -6164,12 +6214,15 @@
     method public abstract int onSwitchToSubscription(int, @Nullable String, boolean);
     method public abstract int onUpdateSubscriptionNickname(int, String, String);
     field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
+    field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
     field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
     field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+    field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
     field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
     field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
     field public static final String ACTION_RESOLVE_NO_PRIVILEGES = "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
     field public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS = "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS";
+    field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
     field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI";
     field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService";
     field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES";
@@ -7611,6 +7664,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();
@@ -7913,9 +7967,12 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
+    field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
     field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
     field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION";
     field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+    field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+    field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
     field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2
     field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1
     field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3
@@ -7926,7 +7983,10 @@
     field public static final int EUICC_OTA_SUCCEEDED = 3; // 0x3
     field public static final String EXTRA_ACTIVATION_TYPE = "android.telephony.euicc.extra.ACTIVATION_TYPE";
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS";
+    field public static final String EXTRA_ENABLE_SUBSCRIPTION = "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
     field public static final String EXTRA_FORCE_PROVISION = "android.telephony.euicc.extra.FORCE_PROVISION";
+    field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+    field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
   }
 
   @IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus {
@@ -9037,6 +9097,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 d089831..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 {
@@ -772,6 +776,11 @@
     field public static final java.util.UUID EFFECT_TYPE_NULL;
   }
 
+  public static class AudioEffect.Descriptor {
+    ctor public AudioEffect.Descriptor(android.os.Parcel);
+    method public void writeToParcel(android.os.Parcel);
+  }
+
   public static interface AudioEffect.OnParameterChangeListener {
     method public void onParameterChange(android.media.audiofx.AudioEffect, int, byte[], byte[]);
   }
@@ -1679,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 {
@@ -1696,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 {
@@ -2268,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);
@@ -2328,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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e0b8d78..1045b7a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3529,12 +3529,32 @@
 
     /**
      * Returns "true" if device is running in a test harness.
+     *
+     * @deprecated this method is false for all user builds. Users looking to check if their device
+     * is running in a device farm should see {@link #isRunningInUserTestHarness()}.
      */
+    @Deprecated
     public static boolean isRunningInTestHarness() {
         return SystemProperties.getBoolean("ro.test_harness", false);
     }
 
     /**
+     * Returns "true" if the device is running in Test Harness Mode.
+     *
+     * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a
+     * device farm/testing harness (such as Firebase Test Lab). You should check this method if you
+     * want your app to behave differently when running in a test harness to skip setup screens that
+     * would impede UI testing. e.g. a keyboard application that has a full screen setup page for
+     * the first time it is launched.
+     *
+     * <p>Note that you should <em>not</em> use this to determine whether or not your app is running
+     * an instrumentation test, as it is not set for a standard device running a test.
+     */
+    public static boolean isRunningInUserTestHarness() {
+        return SystemProperties.getBoolean("persist.sys.test_harness", false);
+    }
+
+    /**
      * Unsupported compiled sdk warning should always be shown for the intput activity
      * even in cases where the system would normally not show the warning. E.g. when running in a
      * test harness.
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/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 1ae0f52..75c9054 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -124,6 +125,7 @@
      *
      * @return the intent for launching the activity or null if no password is required.
      **/
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) {
         if (!isDeviceSecure()) return null;
         Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
@@ -176,6 +178,7 @@
      * @throws IllegalStateException if the device has already been provisioned
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @SystemApi
     public Intent createConfirmFactoryResetCredentialIntent(
             CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
@@ -231,6 +234,7 @@
      * secure notifications cannot be shown if {@code false}
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
     @SystemApi
     public void setPrivateNotificationsAllowed(boolean allow) {
@@ -249,6 +253,7 @@
      * By default, private notifications are allowed.
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)
     @SystemApi
     public boolean getPrivateNotificationsAllowed() {
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index 5b87de4..5f1a94c 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -215,19 +215,12 @@
     }
 
     /**
-     * Start VR Input method for the packageName in {@link ComponentName}.
-     * This method notifies InputMethodManagerService to use VR IME instead of
-     * regular phone IME.
-     * @param componentName ComponentName of a VR InputMethod that should be set as selected
-     * input by InputMethodManagerService.
+     * This method is not implemented.
+     *
+     * @param componentName not used
      */
     @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
     public void setVrInputMethod(ComponentName componentName) {
-        try {
-            mService.setVrInputMethod(componentName);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
     }
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 86db99bf..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.
@@ -2513,6 +2538,9 @@
      * requested quality constant (between the policy set here, the user's preference, and any other
      * considerations) is the one that is in effect.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2548,6 +2576,9 @@
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
      *
+     * <p>Note: on devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature,
+     * the password is always treated as empty.
+     *
      * @param admin The name of the admin component to check, or {@code null} to aggregate
      * all admins.
      */
@@ -2580,6 +2611,9 @@
      * {@link #PASSWORD_QUALITY_ALPHANUMERIC}, or {@link #PASSWORD_QUALITY_COMPLEX} with
      * {@link #setPasswordQuality}.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2609,11 +2643,13 @@
      * restrictions on this user and its participating profiles. Restrictions on profiles that have
      * a separate challenge are not taken into account.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
      *
-     * user and its profiles or a particular one.
      * @param admin The name of the admin component to check, or {@code null} to aggregate
      * all admins.
      */
@@ -2644,6 +2680,9 @@
      * setting this value. This constraint is only imposed if the administrator has also requested
      * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2678,6 +2717,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2714,6 +2756,9 @@
      * setting this value. This constraint is only imposed if the administrator has also requested
      * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2748,6 +2793,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2784,6 +2832,9 @@
      * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with
      * {@link #setPasswordQuality}. The default value is 1.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2818,6 +2869,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2853,6 +2907,9 @@
      * setting this value. This constraint is only imposed if the administrator has also requested
      * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 1.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2887,6 +2944,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2922,6 +2982,9 @@
      * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with
      * {@link #setPasswordQuality}. The default value is 1.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -2955,6 +3018,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -2990,6 +3056,9 @@
      * setting this value. This constraint is only imposed if the administrator has also requested
      * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -3024,6 +3093,9 @@
      * and only applies when the password quality is
      * {@link #PASSWORD_QUALITY_COMPLEX}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * <p>This method can be called on the {@link DevicePolicyManager} instance
      * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve
      * restrictions on the parent profile.
@@ -3060,6 +3132,9 @@
      * , {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX} {@link #PASSWORD_QUALITY_ALPHABETIC}, or
      * {@link #PASSWORD_QUALITY_ALPHANUMERIC} with {@link #setPasswordQuality}.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -3112,6 +3187,7 @@
      * @throws SecurityException if {@code admin} is not an active administrator or {@code admin}
      *             does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD}
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setPasswordExpirationTimeout(@NonNull ComponentName admin, long timeout) {
         if (mService != null) {
             try {
@@ -3136,6 +3212,7 @@
      * @param admin The name of the admin component to check, or {@code null} to aggregate all admins.
      * @return The timeout for the given admin or the minimum of all timeouts
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public long getPasswordExpirationTimeout(@Nullable ComponentName admin) {
         if (mService != null) {
             try {
@@ -3160,6 +3237,7 @@
      * @param admin The name of the admin component to check, or {@code null} to aggregate all admins.
      * @return The password expiration time, in milliseconds since epoch.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public long getPasswordExpiration(@Nullable ComponentName admin) {
         if (mService != null) {
             try {
@@ -3184,12 +3262,14 @@
      * all admins.
      * @return The length of the password history
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getPasswordHistoryLength(@Nullable ComponentName admin) {
         return getPasswordHistoryLength(admin, myUserId());
     }
 
     /** @hide per-user version */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getPasswordHistoryLength(@Nullable ComponentName admin, int userHandle) {
         if (mService != null) {
             try {
@@ -3204,10 +3284,16 @@
     /**
      * Return the maximum password length that the device supports for a
      * particular password quality.
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always empty.
      * @param quality The quality being interrogated.
      * @return Returns the maximum length that the user can enter.
      */
     public int getPasswordMaximumLength(int quality) {
+        PackageManager pm = mContext.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) {
+            return 0;
+        }
         // Kind-of arbitrary.
         return 16;
     }
@@ -3218,6 +3304,10 @@
      * user and its participating profiles. Restrictions on profiles that have a separate challenge
      * are not taken into account. The user must be unlocked in order to perform the check.
      * <p>
+     * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty - i.e. this method will always return false on such
+     * devices, provided any password requirements were set.
+     * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
      * not, a security exception will be thrown.
@@ -3250,6 +3340,9 @@
      * explicitly querying the parent profile screen lock complexity via {@link
      * #getParentProfileInstance}.
      *
+     * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
+     * password is always treated as empty.
+     *
      * @throws IllegalStateException if the user is not unlocked.
      * @throws SecurityException if the calling application does not have the permission
      *                           {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}
@@ -3329,6 +3422,7 @@
      * @throws SecurityException if the calling application does not own an active administrator
      *             that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN}
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getCurrentFailedPasswordAttempts() {
         return getCurrentFailedPasswordAttempts(myUserId());
     }
@@ -3396,6 +3490,7 @@
      *             both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
      *             {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setMaximumFailedPasswordsForWipe(@NonNull ComponentName admin, int num) {
         if (mService != null) {
             try {
@@ -3419,12 +3514,14 @@
      * @param admin The name of the admin component to check, or {@code null} to aggregate
      * all admins.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin) {
         return getMaximumFailedPasswordsForWipe(admin, myUserId());
     }
 
     /** @hide per-user version */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin, int userHandle) {
         if (mService != null) {
             try {
@@ -3444,6 +3541,7 @@
      * user passed in.
      * @hide Used only by Keyguard
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) {
         if (mService != null) {
             try {
@@ -3514,6 +3612,7 @@
      *             that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD}
      * @throws IllegalStateException if the calling user is locked or has a managed profile.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean resetPassword(String password, int flags) {
         throwIfParentInstance("resetPassword");
         if (mService != null) {
@@ -3557,6 +3656,7 @@
      * @throws SecurityException if admin is not a device or profile owner.
      * @throws IllegalArgumentException if the supplied token is invalid.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
         throwIfParentInstance("setResetPasswordToken");
         if (mService != null) {
@@ -3576,6 +3676,7 @@
      * @return true if the operation is successful, false otherwise.
      * @throws SecurityException if admin is not a device or profile owner.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean clearResetPasswordToken(ComponentName admin) {
         throwIfParentInstance("clearResetPasswordToken");
         if (mService != null) {
@@ -3596,6 +3697,7 @@
      * @throws SecurityException if admin is not a device or profile owner.
      * @throws IllegalStateException if no token has been set.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean isResetPasswordTokenActive(ComponentName admin) {
         throwIfParentInstance("isResetPasswordTokenActive");
         if (mService != null) {
@@ -3637,6 +3739,7 @@
      * @throws SecurityException if admin is not a device or profile owner.
      * @throws IllegalStateException if the provided token is not valid.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password,
             byte[] token, int flags) {
         throwIfParentInstance("resetPassword");
@@ -3742,6 +3845,7 @@
      *
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setRequiredStrongAuthTimeout(@NonNull ComponentName admin,
             long timeoutMs) {
         if (mService != null) {
@@ -3766,12 +3870,14 @@
      *         across all participating admins.
      * @return The timeout in milliseconds or 0 if not configured for the provided admin.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin) {
         return getRequiredStrongAuthTimeout(admin, myUserId());
     }
 
     /** @hide per-user version */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin, @UserIdInt int userId) {
         if (mService != null) {
             try {
@@ -5350,6 +5456,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
         if (mService != null) {
             try {
@@ -5363,6 +5470,7 @@
     /**
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportPasswordChanged(@UserIdInt int userId) {
         if (mService != null) {
             try {
@@ -5377,6 +5485,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportFailedPasswordAttempt(int userHandle) {
         if (mService != null) {
             try {
@@ -5391,6 +5500,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportSuccessfulPasswordAttempt(int userHandle) {
         if (mService != null) {
             try {
@@ -5404,6 +5514,7 @@
     /**
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportFailedBiometricAttempt(int userHandle) {
         if (mService != null) {
             try {
@@ -5417,6 +5528,7 @@
     /**
      * @hide
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportSuccessfulBiometricAttempt(int userHandle) {
         if (mService != null) {
             try {
@@ -6383,6 +6495,7 @@
      * @throws SecurityException if {@code admin} is not an active administrator or does not use
      *             {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES}
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void setTrustAgentConfiguration(@NonNull ComponentName admin,
             @NonNull ComponentName target, PersistableBundle configuration) {
         if (mService != null) {
@@ -6412,6 +6525,7 @@
      * @param agent Which component to get enabled features for.
      * @return configuration for the given trust agent.
      */
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public @Nullable List<PersistableBundle> getTrustAgentConfiguration(
             @Nullable ComponentName admin, @NonNull ComponentName agent) {
         return getTrustAgentConfiguration(admin, agent, myUserId());
@@ -6419,6 +6533,7 @@
 
     /** @hide per-user version */
     @UnsupportedAppUsage
+    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public @Nullable List<PersistableBundle> getTrustAgentConfiguration(
             @Nullable ComponentName admin, @NonNull ComponentName agent, int userHandle) {
         if (mService != null) {
@@ -9403,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.
@@ -9430,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/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 1945b2f..97bc079 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -36,6 +36,7 @@
 import android.content.Context;
 import android.os.BatteryStats;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
@@ -648,6 +649,32 @@
 
     private final Object mLock = new Object();
     private final Map<LeScanCallback, ScanCallback> mLeScanClients;
+    private static final Map<BluetoothDevice, List<Pair<MetadataListener, Handler>>>
+                sMetadataListeners = new HashMap<>();
+
+    /**
+     * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener
+     * implementation.
+     */
+    private static final IBluetoothMetadataListener sBluetoothMetadataListener =
+            new IBluetoothMetadataListener.Stub() {
+        @Override
+        public void onMetadataChanged(BluetoothDevice device, int key, String value) {
+            synchronized (sMetadataListeners) {
+                if (sMetadataListeners.containsKey(device)) {
+                    List<Pair<MetadataListener, Handler>> list = sMetadataListeners.get(device);
+                    for (Pair<MetadataListener, Handler> pair : list) {
+                        MetadataListener listener = pair.first;
+                        Handler handler = pair.second;
+                        handler.post(() -> {
+                            listener.onMetadataChanged(device, key, value);
+                        });
+                    }
+                }
+            }
+            return;
+        }
+    };
 
     /**
      * Get a handle to the default local Bluetooth adapter.
@@ -2607,6 +2634,16 @@
                             }
                         }
                     }
+                    synchronized (sMetadataListeners) {
+                        sMetadataListeners.forEach((device, pair) -> {
+                            try {
+                                mService.registerMetadataListener(sBluetoothMetadataListener,
+                                        device);
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Failed to register metadata listener", e);
+                            }
+                        });
+                    }
                 }
 
                 public void onBluetoothServiceDown() {
@@ -3090,4 +3127,142 @@
                     + "listenUsingInsecureL2capChannel");
         return listenUsingInsecureL2capChannel();
     }
+
+    /**
+     * Register a {@link #MetadataListener} to receive update about metadata
+     * changes for this {@link BluetoothDevice}.
+     * Registration must be done when Bluetooth is ON and will last until
+     * {@link #unregisterMetadataListener(BluetoothDevice)} is called, even when Bluetooth
+     * restarted in the middle.
+     * All input parameters should not be null or {@link NullPointerException} will be triggered.
+     * The same {@link BluetoothDevice} and {@link #MetadataListener} pair can only be registered
+     * once, double registration would cause {@link IllegalArgumentException}.
+     *
+     * @param device {@link BluetoothDevice} that will be registered
+     * @param listener {@link #MetadataListener} that will receive asynchronous callbacks
+     * @param handler the handler for listener callback
+     * @return true on success, false on error
+     * @throws NullPointerException If one of {@code listener}, {@code device} or {@code handler}
+     * is null.
+     * @throws IllegalArgumentException The same {@link #MetadataListener} and
+     * {@link BluetoothDevice} are registered twice.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean registerMetadataListener(BluetoothDevice device, MetadataListener listener,
+            Handler handler) {
+        if (DBG) Log.d(TAG, "registerMetdataListener()");
+
+        final IBluetooth service = mService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener");
+            return false;
+        }
+        if (listener == null) {
+            throw new NullPointerException("listener is null");
+        }
+        if (device == null) {
+            throw new NullPointerException("device is null");
+        }
+        if (handler == null) {
+            throw new NullPointerException("handler is null");
+        }
+
+        synchronized (sMetadataListeners) {
+            List<Pair<MetadataListener, Handler>> listenerList = sMetadataListeners.get(device);
+            if (listenerList == null) {
+                // Create new listener/handler list for registeration
+                listenerList = new ArrayList<>();
+                sMetadataListeners.put(device, listenerList);
+            } else {
+                // Check whether this device was already registed by the lisenter
+                if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) {
+                    throw new IllegalArgumentException("listener was already regestered"
+                            + " for the device");
+                }
+            }
+
+            Pair<MetadataListener, Handler> listenerPair = new Pair(listener, handler);
+            listenerList.add(listenerPair);
+
+            boolean ret = false;
+            try {
+                ret = service.registerMetadataListener(sBluetoothMetadataListener, device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "registerMetadataListener fail", e);
+            } finally {
+                if (!ret) {
+                    // Remove listener registered earlier when fail.
+                    listenerList.remove(listenerPair);
+                    if (listenerList.isEmpty()) {
+                        // Remove the device if its listener list is empty
+                        sMetadataListeners.remove(device);
+                    }
+                }
+            }
+            return ret;
+        }
+    }
+
+    /**
+     * Unregister all {@link MetadataListener} from this {@link BluetoothDevice}.
+     * Unregistration can be done when Bluetooth is either ON or OFF.
+     * {@link #registerMetadataListener(MetadataListener, BluetoothDevice, Handler)} must
+     * be called before unregisteration.
+     * Unregistering a device that is not regestered would cause {@link IllegalArgumentException}.
+     *
+     * @param device {@link BluetoothDevice} that will be unregistered. it
+     * should not be null or {@link NullPointerException} will be triggered.
+     * @return true on success, false on error
+     * @throws NullPointerException If {@code device} is null.
+     * @throws IllegalArgumentException If {@code device} has not been registered before.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean unregisterMetadataListener(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "unregisterMetdataListener()");
+        if (device == null) {
+            throw new NullPointerException("device is null");
+        }
+
+        synchronized (sMetadataListeners) {
+            if (sMetadataListeners.containsKey(device)) {
+                sMetadataListeners.remove(device);
+            } else {
+                throw new IllegalArgumentException("device was not registered");
+            }
+
+            final IBluetooth service = mService;
+            if (service == null) {
+                // Bluetooth is OFF, do nothing to Bluetooth service.
+                return true;
+            }
+            try {
+                return service.unregisterMetadataListener(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "unregisterMetadataListener fail", e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * This abstract class is used to implement {@link BluetoothAdapter} metadata listener.
+     * @hide
+     */
+    @SystemApi
+    public abstract class MetadataListener {
+        /**
+         * Callback triggered if the metadata of {@link BluetoothDevice} registered in
+         * {@link #registerMetadataListener}.
+         *
+         * @param device changed {@link BluetoothDevice}.
+         * @param key changed metadata key, one of BluetoothDevice.METADATA_*.
+         * @param value the new value of metadata.
+         */
+        public void onMetadataChanged(BluetoothDevice device, int key, String value) {
+        }
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 235dc5c..17cf702 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -341,6 +341,137 @@
             "android.bluetooth.device.action.SDP_RECORD";
 
     /**
+     * Maximum length of a metadata entry, this is to avoid exploding Bluetooth
+     * disk usage
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MAX_LENGTH = 2048;
+
+    /**
+     * Manufacturer name of this Bluetooth device
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MANUFACTURER_NAME = 0;
+
+    /**
+     * Model name of this Bluetooth device
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MODEL_NAME = 1;
+
+    /**
+     * Software version of this Bluetooth device
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_SOFTWARE_VERSION = 2;
+
+    /**
+     * Hardware version of this Bluetooth device
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_HARDWARE_VERSION = 3;
+
+    /**
+     * Package name of the companion app, if any
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_COMPANION_APP = 4;
+
+    /**
+     * URI to the main icon shown on the settings UI
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MAIN_ICON = 5;
+
+    /**
+     * Whether this device is an untethered headset with left, right and case
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_IS_UNTHETHERED_HEADSET = 6;
+
+    /**
+     * URI to icon of the left headset
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_LEFT_ICON = 7;
+
+    /**
+     * URI to icon of the right headset
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8;
+
+    /**
+     * URI to icon of the headset charging case
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_CASE_ICON = 9;
+
+    /**
+     * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+     * is invalid, of the left headset
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10;
+
+    /**
+     * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+     * is invalid, of the right headset
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11;
+
+    /**
+     * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+     * is invalid, of the headset charging case
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12;
+
+    /**
+     * Whether the left headset is charging
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13;
+
+    /**
+     * Whether the right headset is charging
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14;
+
+    /**
+     * Whether the headset charging case is charging
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15;
+
+    /**
+     * URI to the enhanced settings UI slice, null or empty String means
+     * the UI does not exist
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
+
+    /**
      * Broadcast Action: This intent is used to broadcast the {@link UUID}
      * wrapped as a {@link android.os.ParcelUuid} of the remote device after it
      * has been fetched. This intent is sent only when the UUIDs of the remote
@@ -2026,4 +2157,61 @@
         Log.e(TAG, "createL2capCocSocket: PLEASE USE THE OFFICIAL API, createInsecureL2capChannel");
         return createInsecureL2capChannel(psm);
     }
+
+    /**
+     * Set a keyed metadata of this {@link BluetoothDevice} to a
+     * {@link String} value.
+     * Only bonded devices's metadata will be persisted across Bluetooth
+     * restart.
+     * Metadata will be removed when the device's bond state is moved to
+     * {@link #BOND_NONE}.
+     *
+     * @param key must be within the list of BluetoothDevice.METADATA_*
+     * @param value the string data to set for key. Must be less than
+     * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length
+     * @return true on success, false on error
+     * @hide
+    */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setMetadata(int key, String value) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata");
+            return false;
+        }
+        if (value.length() > METADATA_MAX_LENGTH) {
+            throw new IllegalArgumentException("value length is " + value.length()
+                    + ", should not over " + METADATA_MAX_LENGTH);
+        }
+        try {
+            return service.setMetadata(this, key, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setMetadata fail", e);
+            return false;
+        }
+    }
+
+    /**
+     * Get a keyed metadata for this {@link BluetoothDevice} as {@link String}
+     *
+     * @param key must be within the list of BluetoothDevice.METADATA_*
+     * @return Metadata of the key as string, null on error or not found
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public String getMetadata(int key) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata");
+            return null;
+        }
+        try {
+            return service.getMetadata(this, key);
+        } catch (RemoteException e) {
+            Log.e(TAG, "getMetadata fail", e);
+            return null;
+        }
+    }
 }
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 b845673..9e2f316 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2060,6 +2060,14 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device has a secure implementation of keyguard, meaning the
+     * device supports PIN, pattern and password as defined in Android CDD
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device includes an accelerometer.
      */
     @SdkConstant(SdkConstantType.FEATURE)
@@ -5764,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);
 
     /**
@@ -5778,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
@@ -5793,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);
 
     /**
@@ -5807,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/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index c7320b0..c9a4c82 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -794,6 +794,12 @@
             "android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS";
 
     /**
+     * Extra field name for the set of installed users for a given rollback package.
+     */
+    public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS =
+            "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS";
+
+    /**
      * Used as the {@code enableRollbackCode} argument for
      * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that
      * enabling rollback succeeded.
@@ -827,4 +833,10 @@
      * Ask the package manager to compile layouts in the given package.
      */
     public abstract boolean compileLayouts(String packageName);
+
+    /*
+     * Inform the package manager that the pending package install identified by
+     * {@code token} can be completed.
+     */
+    public abstract void finishPackageInstall(int token, boolean didLaunch);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 88a240f..d5636d5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8513,6 +8513,7 @@
 
         pi.packageName = apk.packageName;
         pi.setLongVersionCode(apk.getLongVersionCode());
+        ai.setVersionCode(apk.getLongVersionCode());
 
         if (collectCerts) {
             if (apk.signingDetails.hasPastSigningCertificates()) {
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 7f557cd..420bcb6 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -33,6 +33,12 @@
     void executeRollback(in RollbackInfo rollback, String callerPackageName,
             in IntentSender statusReceiver);
 
+    // Exposed for use from the system server only. Callback from the package
+    // manager during the install flow when user data can be restored for a given
+    // package.
+    void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
+            String seInfo, int token);
+
     // Exposed for test purposes only.
     void reloadPersistedData();
 
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/DnsPacket.java b/core/java/android/net/DnsPacket.java
new file mode 100644
index 0000000..458fb34
--- /dev/null
+++ b/core/java/android/net/DnsPacket.java
@@ -0,0 +1,235 @@
+/*
+ * 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 android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.internal.util.BitUtils;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Defines basic data for DNS protocol based on RFC 1035.
+ * Subclasses create the specific format used in DNS packet.
+ *
+ * @hide
+ */
+public abstract class DnsPacket {
+    public class DnsHeader {
+        private static final String TAG = "DnsHeader";
+        public final int id;
+        public final int flags;
+        public final int rcode;
+        private final int[] mSectionCount;
+
+        /**
+         * Create a new DnsHeader from a positioned ByteBuffer.
+         *
+         * The ByteBuffer must be in network byte order (which is the default).
+         * Reads the passed ByteBuffer from its current position and decodes a DNS header.
+         * When this constructor returns, the reading position of the ByteBuffer has been
+         * advanced to the end of the DNS header record.
+         * This is meant to chain with other methods reading a DNS response in sequence.
+         *
+         */
+        DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
+            id = BitUtils.uint16(buf.getShort());
+            flags = BitUtils.uint16(buf.getShort());
+            rcode = flags & 0xF;
+            mSectionCount = new int[NUM_SECTIONS];
+            for (int i = 0; i < NUM_SECTIONS; ++i) {
+                mSectionCount[i] = BitUtils.uint16(buf.getShort());
+            }
+        }
+
+        /**
+         * Get section count by section type.
+         */
+        public int getSectionCount(int sectionType) {
+            return mSectionCount[sectionType];
+        }
+    }
+
+    public class DnsSection {
+        private static final int MAXNAMESIZE = 255;
+        private static final int MAXLABELSIZE = 63;
+        private static final int MAXLABELCOUNT = 128;
+        private static final int NAME_NORMAL = 0;
+        private static final int NAME_COMPRESSION = 0xC0;
+        private final DecimalFormat byteFormat = new DecimalFormat();
+        private final FieldPosition pos = new FieldPosition(0);
+
+        private static final String TAG = "DnsSection";
+
+        public final String dName;
+        public final int nsType;
+        public final int nsClass;
+        public final long ttl;
+        private final byte[] mRR;
+
+        /**
+         * Create a new DnsSection from a positioned ByteBuffer.
+         *
+         * The ByteBuffer must be in network byte order (which is the default).
+         * Reads the passed ByteBuffer from its current position and decodes a DNS section.
+         * When this constructor returns, the reading position of the ByteBuffer has been
+         * advanced to the end of the DNS header record.
+         * This is meant to chain with other methods reading a DNS response in sequence.
+         *
+         */
+        DnsSection(int sectionType, @NonNull ByteBuffer buf)
+                throws BufferUnderflowException, ParseException {
+            dName = parseName(buf, 0 /* Parse depth */);
+            if (dName.length() > MAXNAMESIZE) {
+                throw new ParseException("Parse name fail, name size is too long");
+            }
+            nsType = BitUtils.uint16(buf.getShort());
+            nsClass = BitUtils.uint16(buf.getShort());
+
+            if (sectionType != QDSECTION) {
+                ttl = BitUtils.uint32(buf.getInt());
+                final int length = BitUtils.uint16(buf.getShort());
+                mRR = new byte[length];
+                buf.get(mRR);
+            } else {
+                ttl = 0;
+                mRR = null;
+            }
+        }
+
+        /**
+         * Get a copy of rr.
+         */
+        @Nullable public byte[] getRR() {
+            return (mRR == null) ? null : mRR.clone();
+        }
+
+        /**
+         * Convert label from {@code byte[]} to {@code String}
+         *
+         * It follows the same converting rule as native layer.
+         * (See ns_name.c in libc)
+         *
+         */
+        private String labelToString(@NonNull byte[] label) {
+            final StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < label.length; ++i) {
+                int b = BitUtils.uint8(label[i]);
+                // Control characters and non-ASCII characters.
+                if (b <= 0x20 || b >= 0x7f) {
+                    sb.append('\\');
+                    byteFormat.format(b, sb, pos);
+                } else if (b == '"' || b == '.' || b == ';' || b == '\\'
+                        || b == '(' || b == ')' || b == '@' || b == '$') {
+                    sb.append('\\');
+                    sb.append((char) b);
+                } else {
+                    sb.append((char) b);
+                }
+            }
+            return sb.toString();
+        }
+
+        private String parseName(@NonNull ByteBuffer buf, int depth) throws
+                BufferUnderflowException, ParseException {
+            if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels");
+            final int len = BitUtils.uint8(buf.get());
+            final int mask = len & NAME_COMPRESSION;
+            if (0 == len) {
+                return "";
+            } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
+                throw new ParseException("Parse name fail, bad label type");
+            } else if (mask == NAME_COMPRESSION) {
+                // Name compression based on RFC 1035 - 4.1.4 Message compression
+                final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get());
+                final int oldPos = buf.position();
+                if (offset >= oldPos - 2) {
+                    throw new ParseException("Parse compression name fail, invalid compression");
+                }
+                buf.position(offset);
+                final String pointed = parseName(buf, depth + 1);
+                buf.position(oldPos);
+                return pointed;
+            } else {
+                final byte[] label = new byte[len];
+                buf.get(label);
+                final String head = labelToString(label);
+                if (head.length() > MAXLABELSIZE) {
+                    throw new ParseException("Parse name fail, invalid label length");
+                }
+                final String tail = parseName(buf, depth + 1);
+                return TextUtils.isEmpty(tail) ? head : head + "." + tail;
+            }
+        }
+    }
+
+    public static final int QDSECTION = 0;
+    public static final int ANSECTION = 1;
+    public static final int NSSECTION = 2;
+    public static final int ARSECTION = 3;
+    private static final int NUM_SECTIONS = ARSECTION + 1;
+
+    private static final String TAG = DnsPacket.class.getSimpleName();
+
+    protected final DnsHeader mHeader;
+    protected final List<DnsSection>[] mSections;
+
+    public static class ParseException extends Exception {
+        public ParseException(String msg) {
+            super(msg);
+        }
+
+        public ParseException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+    }
+
+    protected DnsPacket(@NonNull byte[] data) throws ParseException {
+        if (null == data) throw new ParseException("Parse header failed, null input data");
+        final ByteBuffer buffer;
+        try {
+            buffer = ByteBuffer.wrap(data);
+            mHeader = new DnsHeader(buffer);
+        } catch (BufferUnderflowException e) {
+            throw new ParseException("Parse Header fail, bad input data", e);
+        }
+
+        mSections = new ArrayList[NUM_SECTIONS];
+
+        for (int i = 0; i < NUM_SECTIONS; ++i) {
+            final int count = mHeader.getSectionCount(i);
+            if (count > 0) {
+                mSections[i] = new ArrayList(count);
+            }
+            for (int j = 0; j < count; ++j) {
+                try {
+                    mSections[i].add(new DnsSection(i, buffer));
+                } catch (BufferUnderflowException e) {
+                    throw new ParseException("Parse section fail", e);
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
new file mode 100644
index 0000000..6d54264
--- /dev/null
+++ b/core/java/android/net/DnsResolver.java
@@ -0,0 +1,289 @@
+/*
+ * 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 android.net;
+
+import static android.net.NetworkUtils.resNetworkQuery;
+import static android.net.NetworkUtils.resNetworkResult;
+import static android.net.NetworkUtils.resNetworkSend;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+
+/**
+ * Dns resolver class for asynchronous dns querying
+ *
+ */
+public final class DnsResolver {
+    private static final String TAG = "DnsResolver";
+    private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+    private static final int MAXPACKET = 8 * 1024;
+
+    @IntDef(prefix = { "CLASS_" }, value = {
+            CLASS_IN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface QueryClass {}
+    public static final int CLASS_IN = 1;
+
+    @IntDef(prefix = { "TYPE_" },  value = {
+            TYPE_A,
+            TYPE_AAAA
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface QueryType {}
+    public static final int TYPE_A = 1;
+    public static final int TYPE_AAAA = 28;
+
+    @IntDef(prefix = { "FLAG_" }, value = {
+            FLAG_EMPTY,
+            FLAG_NO_RETRY,
+            FLAG_NO_CACHE_STORE,
+            FLAG_NO_CACHE_LOOKUP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface QueryFlag {}
+    public static final int FLAG_EMPTY = 0;
+    public static final int FLAG_NO_RETRY = 1 << 0;
+    public static final int FLAG_NO_CACHE_STORE = 1 << 1;
+    public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
+
+    private static final int DNS_RAW_RESPONSE = 1;
+
+    private static final int NETID_UNSET = 0;
+
+    private static final DnsResolver sInstance = new DnsResolver();
+
+    /**
+     * listener for receiving raw answers
+     */
+    public interface RawAnswerListener {
+        /**
+         * {@code byte[]} is {@code null} if query timed out
+         */
+        void onAnswer(@Nullable byte[] answer);
+    }
+
+    /**
+     * listener for receiving parsed answers
+     */
+    public interface InetAddressAnswerListener {
+        /**
+         * Will be called exactly once with all the answers to the query.
+         * size of addresses will be zero if no available answer could be parsed.
+         */
+        void onAnswer(@NonNull List<InetAddress> addresses);
+    }
+
+    /**
+     * Get instance for DnsResolver
+     */
+    public static DnsResolver getInstance() {
+        return sInstance;
+    }
+
+    private DnsResolver() {}
+
+    /**
+     * Pass in a blob and corresponding setting,
+     * get a blob back asynchronously with the entire raw answer.
+     *
+     * @param network {@link Network} specifying which network for querying.
+     *         {@code null} for query on default network.
+     * @param query blob message
+     * @param flags flags as a combination of the FLAGS_* constants
+     * @param handler {@link Handler} to specify the thread
+     *         upon which the {@link RawAnswerListener} will be invoked.
+     * @param listener a {@link RawAnswerListener} which will be called to notify the caller
+     *         of the result of dns query.
+     */
+    public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
+            @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
+        final FileDescriptor queryfd = resNetworkSend((network != null
+                ? network.netId : NETID_UNSET), query, query.length, flags);
+        registerFDListener(handler.getLooper().getQueue(), queryfd,
+                answerbuf -> listener.onAnswer(answerbuf));
+    }
+
+    /**
+     * Pass in a domain name and corresponding setting,
+     * get a blob back asynchronously with the entire raw answer.
+     *
+     * @param network {@link Network} specifying which network for querying.
+     *         {@code null} for query on default network.
+     * @param domain domain name for querying
+     * @param nsClass dns class as one of the CLASS_* constants
+     * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+     * @param flags flags as a combination of the FLAGS_* constants
+     * @param handler {@link Handler} to specify the thread
+     *         upon which the {@link RawAnswerListener} will be invoked.
+     * @param listener a {@link RawAnswerListener} which will be called to notify the caller
+     *         of the result of dns query.
+     */
+    public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass,
+            @QueryType int nsType, @QueryFlag int flags,
+            @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
+        final FileDescriptor queryfd = resNetworkQuery((network != null
+                ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
+        registerFDListener(handler.getLooper().getQueue(), queryfd,
+                answerbuf -> listener.onAnswer(answerbuf));
+    }
+
+    /**
+     * Pass in a domain name and corresponding setting,
+     * get back a set of InetAddresses asynchronously.
+     *
+     * @param network {@link Network} specifying which network for querying.
+     *         {@code null} for query on default network.
+     * @param domain domain name for querying
+     * @param flags flags as a combination of the FLAGS_* constants
+     * @param handler {@link Handler} to specify the thread
+     *         upon which the {@link InetAddressAnswerListener} will be invoked.
+     * @param listener an {@link InetAddressAnswerListener} which will be called to
+     *         notify the caller of the result of dns query.
+     *
+     */
+    public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
+            @NonNull Handler handler, @NonNull InetAddressAnswerListener listener)
+            throws ErrnoException {
+        final FileDescriptor v4fd = resNetworkQuery((network != null
+                ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
+        final FileDescriptor v6fd = resNetworkQuery((network != null
+                ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
+
+        final InetAddressAnswerAccumulator accmulator =
+                new InetAddressAnswerAccumulator(2, listener);
+        final Consumer<byte[]> consumer = answerbuf ->
+                accmulator.accumulate(parseAnswers(answerbuf));
+
+        registerFDListener(handler.getLooper().getQueue(), v4fd, consumer);
+        registerFDListener(handler.getLooper().getQueue(), v6fd, consumer);
+    }
+
+    private void registerFDListener(@NonNull MessageQueue queue,
+            @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) {
+        queue.addOnFileDescriptorEventListener(
+                queryfd,
+                FD_EVENTS,
+                (fd, events) -> {
+                    byte[] answerbuf = null;
+                    try {
+                    // TODO: Implement result function in Java side instead of using JNI
+                    //       Because JNI method close fd prior than unregistering fd on
+                    //       event listener.
+                        answerbuf = resNetworkResult(fd);
+                    } catch (ErrnoException e) {
+                        Log.e(TAG, "resNetworkResult:" + e.toString());
+                    }
+                    answerConsumer.accept(answerbuf);
+
+                    // Unregister this fd listener
+                    return 0;
+                });
+    }
+
+    private class DnsAddressAnswer extends DnsPacket {
+        private static final String TAG = "DnsResolver.DnsAddressAnswer";
+        private static final boolean DBG = false;
+
+        private final int mQueryType;
+
+        DnsAddressAnswer(@NonNull byte[] data) throws ParseException {
+            super(data);
+            if ((mHeader.flags & (1 << 15)) == 0) {
+                throw new ParseException("Not an answer packet");
+            }
+            if (mHeader.rcode != 0) {
+                throw new ParseException("Response error, rcode:" + mHeader.rcode);
+            }
+            if (mHeader.getSectionCount(ANSECTION) == 0) {
+                throw new ParseException("No available answer");
+            }
+            if (mHeader.getSectionCount(QDSECTION) == 0) {
+                throw new ParseException("No question found");
+            }
+            // Assume only one question per answer packet. (RFC1035)
+            mQueryType = mSections[QDSECTION].get(0).nsType;
+        }
+
+        public @NonNull List<InetAddress> getAddresses() {
+            final List<InetAddress> results = new ArrayList<InetAddress>();
+            for (final DnsSection ansSec : mSections[ANSECTION]) {
+                // Only support A and AAAA, also ignore answers if query type != answer type.
+                int nsType = ansSec.nsType;
+                if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) {
+                    continue;
+                }
+                try {
+                    results.add(InetAddress.getByAddress(ansSec.getRR()));
+                } catch (UnknownHostException e) {
+                    if (DBG) {
+                        Log.w(TAG, "rr to address fail");
+                    }
+                }
+            }
+            return results;
+        }
+    }
+
+    private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) {
+        try {
+            return (data == null) ? null : new DnsAddressAnswer(data).getAddresses();
+        } catch (DnsPacket.ParseException e) {
+            Log.e(TAG, "Parse answer fail " + e.getMessage());
+            return null;
+        }
+    }
+
+    private class InetAddressAnswerAccumulator {
+        private final List<InetAddress> mAllAnswers;
+        private final InetAddressAnswerListener mAnswerListener;
+        private final int mTargetAnswerCount;
+        private int mReceivedAnswerCount = 0;
+
+        InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) {
+            mTargetAnswerCount = size;
+            mAllAnswers = new ArrayList<>();
+            mAnswerListener = listener;
+        }
+
+        public void accumulate(@Nullable List<InetAddress> answer) {
+            if (null != answer) {
+                mAllAnswers.addAll(answer);
+            }
+            if (++mReceivedAnswerCount == mTargetAnswerCount) {
+                mAnswerListener.onAnswer(mAllAnswers);
+            }
+        }
+    }
+}
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/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 4eab49c..c996d01 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -39,6 +39,8 @@
 import java.util.Locale;
 import java.util.TreeSet;
 
+import android.system.ErrnoException;
+
 /**
  * Native methods for managing network interfaces.
  *
@@ -138,6 +140,32 @@
     public native static boolean queryUserAccess(int uid, int netId);
 
     /**
+     * DNS resolver series jni method.
+     * Issue the query {@code msg} on the network designated by {@code netId}.
+     * {@code flags} is an additional config to control actual querying behavior.
+     * @return a file descriptor to watch for read events
+     */
+    public static native FileDescriptor resNetworkSend(
+            int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
+
+    /**
+     * DNS resolver series jni method.
+     * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated
+     * with Domain Name {@code dname} on the network designated by {@code netId}.
+     * {@code flags} is an additional config to control actual querying behavior.
+     * @return a file descriptor to watch for read events
+     */
+    public static native FileDescriptor resNetworkQuery(
+            int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+
+    /**
+     * DNS resolver series jni method.
+     * Read a result for the query associated with the {@code fd}.
+     * @return a byte array containing blob answer
+     */
+    public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException;
+
+    /**
      * Add an entry into the ARP cache.
      */
     public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname,
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
index 5397b57..6a9eae0 100644
--- a/core/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -252,6 +252,12 @@
         }
     }
 
+    /** @hide */
+    public boolean isEmpty() {
+        return (null == assignedV4Address) && (null == groupHint)
+                && (null == dnsAddresses) && (null == mtu);
+    }
+
     @Override
     public boolean equals(@Nullable final Object o) {
         if (!(o instanceof NetworkAttributes)) return false;
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/FileUtils.java b/core/java/android/os/FileUtils.java
index ca39051..51c3c4c 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -72,6 +72,7 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -831,6 +832,16 @@
         return false;
     }
 
+    /** {@hide} */
+    public static boolean contains(Collection<File> dirs, File file) {
+        for (File dir : dirs) {
+            if (contains(dir, file)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Test if a file lives under the given directory, either as a direct child
      * or a distant grandchild.
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/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index 8271701..fe17c6b 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -45,4 +45,6 @@
     void exitIdle(String reason);
     boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener);
     void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener);
+    int setPreIdleTimeoutMode(int Mode);
+    void resetPreIdleTimeoutMode();
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 0942d97..4ce760f 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1724,6 +1724,25 @@
             = "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED";
 
     /**
+     * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
+     * @hide
+     */
+    public static final int PRE_IDLE_TIMEOUT_MODE_NORMAL = 0;
+
+    /**
+     * Constant for PreIdleTimeout long mode (extend timeout to keep in inactive mode
+     * longer).
+     * @hide
+     */
+    public static final int PRE_IDLE_TIMEOUT_MODE_LONG = 1;
+
+    /**
+     * Constant for PreIdleTimeout short mode (short timeout to go to doze mode quickly)
+     * @hide
+     */
+    public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
+
+    /**
      * A wake lock is a mechanism to indicate that your application needs
      * to have the device stay on.
      * <p>
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/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index df1a713..714a061 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -35,6 +35,7 @@
 
 import java.io.CharArrayWriter;
 import java.io.File;
+import java.util.Locale;
 
 /**
  * Information about a shared/external storage volume for a specific user.
@@ -263,6 +264,11 @@
         return mFsUuid;
     }
 
+    /** {@hide} */
+    public @Nullable String getNormalizedUuid() {
+        return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null;
+    }
+
     /**
      * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
      * parse or UUID is unknown.
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 8c3aa17..5d310e1 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -42,6 +42,7 @@
 import java.io.CharArrayWriter;
 import java.io.File;
 import java.util.Comparator;
+import java.util.Locale;
 import java.util.Objects;
 
 /**
@@ -254,6 +255,10 @@
         return fsUuid;
     }
 
+    public @Nullable String getNormalizedFsUuid() {
+        return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
+    }
+
     @UnsupportedAppUsage
     public int getMountUserId() {
         return mountUserId;
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index cd3efb4..158d231 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -103,6 +103,37 @@
     @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";
+    }
+
+    /**
+     * Namespace for Full Stack Integrity to run privileged apps only in JIT mode. The flag applies
+     * at process start, so reboot is a way to bring the device to a clean state.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface FsiBoot {
+        String NAMESPACE = "fsi_boot";
+        String OOB_ENABLED = "oob_enabled";
+        String OOB_WHITELIST = "oob_whitelist";
+    }
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 3a49986..f5c442f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -70,6 +70,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -87,9 +89,19 @@
     /** A content:// style uri to the authority for the media provider */
     public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
 
-    /** {@hide} */
+    /**
+     * Volume name used for content on "internal" storage of device. This
+     * volume contains media distributed with the device, such as built-in
+     * ringtones and wallpapers.
+     */
     public static final String VOLUME_INTERNAL = "internal";
-    /** {@hide} */
+
+    /**
+     * Volume name used for content on "external" storage of device. This only
+     * includes media on the primary shared storage device; the contents of any
+     * secondary storage devices can be obtained using
+     * {@link #getAllVolumeNames(Context)}.
+     */
     public static final String VOLUME_EXTERNAL = "external";
 
     /**
@@ -1224,7 +1236,7 @@
                 if (sv.isPrimary()) {
                     return VOLUME_EXTERNAL;
                 } else {
-                    return checkArgumentVolumeName(sv.getUuid());
+                    return checkArgumentVolumeName(sv.getNormalizedUuid());
                 }
             }
             throw new IllegalStateException("Unknown volume at " + path);
@@ -1564,7 +1576,13 @@
         /**
          * This class provides utility methods to obtain thumbnails for various
          * {@link Images} items.
+         *
+         * @deprecated Callers should migrate to using
+         *             {@link ContentResolver#loadThumbnail}, since it offers
+         *             richer control over requested thumbnail sizes and
+         *             cancellation behavior.
          */
+        @Deprecated
         public static class Thumbnails implements BaseColumns {
             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
@@ -2741,7 +2759,13 @@
         /**
          * This class provides utility methods to obtain thumbnails for various
          * {@link Video} items.
+         *
+         * @deprecated Callers should migrate to using
+         *             {@link ContentResolver#loadThumbnail}, since it offers
+         *             richer control over requested thumbnail sizes and
+         *             cancellation behavior.
          */
+        @Deprecated
         public static class Thumbnails implements BaseColumns {
             /**
              * Cancel any outstanding {@link #getThumbnail} requests, causing
@@ -2919,7 +2943,7 @@
                 if (vi.isPrimary()) {
                     volumeNames.add(VOLUME_EXTERNAL);
                 } else {
-                    volumeNames.add(vi.getFsUuid());
+                    volumeNames.add(vi.getNormalizedFsUuid());
                 }
             }
         }
@@ -2953,8 +2977,7 @@
         // When not one of the well-known values above, it must be a hex UUID
         for (int i = 0; i < volumeName.length(); i++) {
             final char c = volumeName.charAt(i);
-            if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
-                    || ('0' <= c && c <= '9') || (c == '-')) {
+            if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) {
                 continue;
             } else {
                 throw new IllegalArgumentException("Invalid volume name: " + volumeName);
@@ -2963,23 +2986,26 @@
         return volumeName;
     }
 
-    /** {@hide} */
+    /**
+     * Return path where the given volume is mounted. Not valid for
+     * {@link #VOLUME_INTERNAL}.
+     *
+     * @hide
+     */
     public static @NonNull File getVolumePath(@NonNull String volumeName)
             throws FileNotFoundException {
         if (TextUtils.isEmpty(volumeName)) {
             throw new IllegalArgumentException();
         }
 
-        if (VOLUME_INTERNAL.equals(volumeName)) {
-            return Environment.getDataDirectory();
-        } else if (VOLUME_EXTERNAL.equals(volumeName)) {
+        if (VOLUME_EXTERNAL.equals(volumeName)) {
             return Environment.getExternalStorageDirectory();
         }
 
         final StorageManager sm = AppGlobals.getInitialApplication()
                 .getSystemService(StorageManager.class);
         for (VolumeInfo vi : sm.getVolumes()) {
-            if (Objects.equals(vi.getFsUuid(), volumeName)) {
+            if (Objects.equals(vi.getNormalizedFsUuid(), volumeName)) {
                 final File path = vi.getPathForUser(UserHandle.myUserId());
                 if (path != null) {
                     return path;
@@ -2992,6 +3018,33 @@
     }
 
     /**
+     * Return paths that should be scanned for the given volume.
+     *
+     * @hide
+     */
+    public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
+            throws FileNotFoundException {
+        if (TextUtils.isEmpty(volumeName)) {
+            throw new IllegalArgumentException();
+        }
+
+        final ArrayList<File> res = new ArrayList<>();
+        if (VOLUME_INTERNAL.equals(volumeName)) {
+            res.add(new File(Environment.getRootDirectory(), "media"));
+            res.add(new File(Environment.getOemDirectory(), "media"));
+            res.add(new File(Environment.getProductDirectory(), "media"));
+        } else {
+            res.add(getVolumePath(volumeName));
+            final UserManager um = AppGlobals.getInitialApplication()
+                    .getSystemService(UserManager.class);
+            if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) {
+                res.add(Environment.getDataPreloadsMediaDirectory());
+            }
+        }
+        return res;
+    }
+
+    /**
      * Uri for querying the state of the media scanner.
      */
     public static Uri getMediaScannerUri() {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 707778f..618fbd0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8421,6 +8421,20 @@
         public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS =
                 "location_access_check_delay_millis";
 
+
+        /**
+         * Comma separated list of enabled overlay packages for all android.theme.customization.*
+         * categories. If there is no corresponding package included for a category, then all
+         * overlay packages in that category must be disabled.
+         * @hide
+         */
+        @SystemApi
+        public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES =
+                "theme_customization_overlay_packages";
+
+        private static final Validator THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR =
+                new SettingsValidators.PackageNameListValidator(",");
+
         /**
          * This are the settings to be backed up.
          *
@@ -8544,6 +8558,7 @@
             LOCK_SCREEN_WHEN_TRUST_LOST,
             SKIP_GESTURE,
             SILENCE_GESTURE,
+            THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
         };
 
         /**
@@ -8714,6 +8729,8 @@
             VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR);
             VALIDATORS.put(SKIP_GESTURE, SKIP_GESTURE_VALIDATOR);
             VALIDATORS.put(SILENCE_GESTURE, SILENCE_GESTURE_VALIDATOR);
+            VALIDATORS.put(THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                    THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR);
         }
 
         /**
@@ -9452,23 +9469,6 @@
                 "hdmi_control_auto_device_off_enabled";
 
         /**
-         * If <b>true</b>, enables out-of-the-box execution for priv apps.
-         * Default: false
-         * Values: 0 = false, 1 = true
-         *
-         * @hide
-         */
-        public static final String PRIV_APP_OOB_ENABLED = "priv_app_oob_enabled";
-
-        /**
-         * Comma separated list of privileged package names, which will be running out-of-box APK.
-         * Default: "ALL"
-         *
-         * @hide
-         */
-        public static final String PRIV_APP_OOB_LIST = "priv_app_oob_list";
-
-        /**
          * The interval in milliseconds at which location requests will be throttled when they are
          * coming from the background.
          *
@@ -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/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 4dc10cd..ffb524d 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -103,10 +103,23 @@
      */
     public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
             "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+
     /** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */
     public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
             "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
 
+    /** @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED */
+    public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+            "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+    /** @see android.telephony.euicc.EuiccManager#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED */
+    public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+            "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+    /** @see android.telephony.euicc.EuiccManager#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED */
+    public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+            "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
     // LUI resolution actions. These are called by the platform to resolve errors in situations that
     // require user interaction.
     // TODO(b/33075886): Define extras for any input parameters to these dialogs once they are
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index f7acfc5..b0269e3 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -110,13 +110,5 @@
      * @param standy True if the device is entering standby, false if it's exiting standby.
      */
     void setStandbyEnabled(boolean standby);
-
-    /**
-     * Start VR Input method for the given packageName in {@param componentName}.
-     * This method notifies InputMethodManagerService to use VR IME instead of
-     * regular phone IME.
-     */
-    void setVrInputMethod(in ComponentName componentName);
-
 }
 
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 33b3ff4f..7d9ec70 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -237,6 +237,16 @@
     private static final int LONG_PRESS = 2;
     private static final int TAP = 3;
 
+    /**
+     * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain actions, such as
+     * scrolling, will be inhibited. However, to account for the possibility of incorrect
+     * classification, the default scrolling will only be inhibited if the gesture moves beyond
+     * (default touch slop * AMBIGUOUS_GESTURE_MULTIPLIER). Likewise, the default long press
+     * timeout will be increased for some situations where the default behaviour
+     * is to cancel it.
+     */
+    private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2;
+
     private final Handler mHandler;
     @UnsupportedAppUsage
     private final OnGestureListener mListener;
@@ -292,27 +302,27 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case SHOW_PRESS:
-                mListener.onShowPress(mCurrentDownEvent);
-                break;
-                
-            case LONG_PRESS:
-                dispatchLongPress();
-                break;
-                
-            case TAP:
-                // If the user's finger is still down, do not count it as a tap
-                if (mDoubleTapListener != null) {
-                    if (!mStillDown) {
-                        mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
-                    } else {
-                        mDeferConfirmSingleTap = true;
-                    }
-                }
-                break;
+                case SHOW_PRESS:
+                    mListener.onShowPress(mCurrentDownEvent);
+                    break;
 
-            default:
-                throw new RuntimeException("Unknown message " + msg); //never
+                case LONG_PRESS:
+                    dispatchLongPress();
+                    break;
+
+                case TAP:
+                    // If the user's finger is still down, do not count it as a tap
+                    if (mDoubleTapListener != null) {
+                        if (!mStillDown) {
+                            mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+                        } else {
+                            mDeferConfirmSingleTap = true;
+                        }
+                    }
+                    break;
+
+                default:
+                    throw new RuntimeException("Unknown message " + msg); //never
             }
         }
     }
@@ -427,7 +437,7 @@
         if (context == null) {
             //noinspection deprecation
             touchSlop = ViewConfiguration.getTouchSlop();
-            doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
+            doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this
             doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
             //noinspection deprecation
             mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
@@ -605,6 +615,10 @@
                 if (mInLongPress || mInContextClick) {
                     break;
                 }
+
+                final int motionClassification = ev.getClassification();
+                final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS);
+
                 final float scrollX = mLastFocusX - focusX;
                 final float scrollY = mLastFocusY - focusY;
                 if (mIsDoubleTapping) {
@@ -615,6 +629,31 @@
                     final int deltaY = (int) (focusY - mDownFocusY);
                     int distance = (deltaX * deltaX) + (deltaY * deltaY);
                     int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
+
+                    final boolean ambiguousGesture =
+                            motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
+                    final boolean shouldInhibitDefaultAction =
+                            hasPendingLongPress && ambiguousGesture;
+                    if (shouldInhibitDefaultAction) {
+                        // Inhibit default long press
+                        if (distance > slopSquare) {
+                            // The default action here is to remove long press. But if the touch
+                            // slop below gets increased, and we never exceed the modified touch
+                            // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*
+                            // will happen in response to user input. To prevent this,
+                            // reschedule long press with a modified timeout.
+                            mHandler.removeMessages(LONG_PRESS);
+                            final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
+                            mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime()
+                                    + longPressTimeout * AMBIGUOUS_GESTURE_MULTIPLIER);
+                        }
+                        // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
+                        // until the gesture is resolved.
+                        // However, for safety, simply increase the touch slop in case the
+                        // classification is erroneous. Since the value is squared, multiply twice.
+                        slopSquare *= AMBIGUOUS_GESTURE_MULTIPLIER * AMBIGUOUS_GESTURE_MULTIPLIER;
+                    }
+
                     if (distance > slopSquare) {
                         handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                         mLastFocusX = focusX;
@@ -633,6 +672,12 @@
                     mLastFocusX = focusX;
                     mLastFocusY = focusY;
                 }
+                final boolean deepPress =
+                        motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
+                if (deepPress && hasPendingLongPress) {
+                    mHandler.removeMessages(LONG_PRESS);
+                    mHandler.sendEmptyMessage(LONG_PRESS);
+                }
                 break;
 
             case MotionEvent.ACTION_UP:
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/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 43963c3..a6d4472 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -15,6 +15,8 @@
  */
 package android.view.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -267,8 +269,7 @@
             pw.print(", parentSessionId="); pw.print(mParentSessionId);
         }
         if (mText != null) {
-            // Cannot print content because could have PII
-            pw.print(", text="); pw.print(mText.length()); pw.print("_chars");
+            pw.print(", text="); pw.println(getSanitizedString(mText));
         }
     }
 
@@ -293,6 +294,9 @@
             }
             string.append(", id=").append(mNode.getAutofillId());
         }
+        if (mText != null) {
+            string.append(", text=").append(getSanitizedString(mText));
+        }
         return string.append(']').toString();
     }
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureHelper.java b/core/java/android/view/contentcapture/ContentCaptureHelper.java
new file mode 100644
index 0000000..508880f
--- /dev/null
+++ b/core/java/android/view/contentcapture/ContentCaptureHelper.java
@@ -0,0 +1,40 @@
+/*
+ * 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 android.view.contentcapture;
+
+import android.annotation.Nullable;
+
+/**
+ * Helpe class for this package.
+ */
+final class ContentCaptureHelper {
+
+    // TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
+    static final boolean VERBOSE = false;
+    static final boolean DEBUG = true; // STOPSHIP if not set to false
+
+    /**
+     * Used to log text that could contain PII.
+     */
+    @Nullable
+    public static String getSanitizedString(@Nullable CharSequence text) {
+        return text == null ? null : text.length() + "_chars";
+    }
+
+    private ContentCaptureHelper() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 413f1a5..3474e23 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -15,6 +15,8 @@
  */
 package android.view.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.NonNull;
@@ -57,10 +59,6 @@
      */
     private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
 
-    // TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
-    static final boolean VERBOSE = false;
-    static final boolean DEBUG = true; // STOPSHIP if not set to false
-
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index b620ab1..6ed2d80 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -15,8 +15,8 @@
  */
 package android.view.contentcapture;
 
-import static android.view.contentcapture.ContentCaptureManager.DEBUG;
-import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
+import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
+import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
 
 import android.annotation.CallSuper;
 import android.annotation.IntDef;
@@ -246,7 +246,7 @@
     public final void destroy() {
         synchronized (mLock) {
             if (mDestroyed) {
-                Log.e(TAG, "destroy(" + mId + "): already destroyed");
+                if (DEBUG) Log.d(TAG, "destroy(" + mId + "): already destroyed");
                 return;
             }
             mDestroyed = true;
@@ -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/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 103d7e6..9e99c88 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -20,8 +20,9 @@
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
-import static android.view.contentcapture.ContentCaptureManager.DEBUG;
-import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
+import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
+import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
+import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -269,6 +270,7 @@
 
     private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
         final int eventType = event.getType();
+        if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
         if (!handleHasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) {
             // TODO(b/120494182): comment when this could happen (dialogs?)
             Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
@@ -276,12 +278,16 @@
                     + "): session not started yet");
             return;
         }
-        if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
+        if (mDisabled.get()) {
+            // This happens when the event was queued in the handler before the sesison was ready,
+            // then handleSessionStarted() returned and set it as disabled - we need to drop it,
+            // otherwise it will keep triggering handleScheduleFlush()
+            if (VERBOSE) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
+            return;
+        }
         if (mEvents == null) {
             if (VERBOSE) {
-                Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
-                        + ContentCaptureEvent.getTypeAsString(eventType)
-                        + "): creating buffer for " + MAX_BUFFER_SIZE + " events");
+                Log.v(TAG, "handleSendEvent(): creating buffer for " + MAX_BUFFER_SIZE + " events");
             }
             mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
         }
@@ -296,8 +302,8 @@
             if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
                     && lastEvent.getId().equals(event.getId())) {
                 if (VERBOSE) {
-                    Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text = "
-                            + event.getText());
+                    Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
+                            + getSanitizedString(event.getText()));
                 }
                 lastEvent.setText(event.getText());
                 addEvent = false;
@@ -365,8 +371,20 @@
     }
 
     private void handleScheduleFlush(@FlushReason int reason, boolean checkExisting) {
+        if (VERBOSE) {
+            Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
+                    + ", checkExisting=" + checkExisting);
+        }
         if (!handleHasStarted()) {
-            Log.v(TAG, "handleScheduleFlush(" + getDebugState() + "): session not started yet");
+            if (VERBOSE) Log.v(TAG, "handleScheduleFlush(): session not started yet");
+            return;
+        }
+
+        if (mDisabled.get()) {
+            // Should not be called on this state, as handleSendEvent checks.
+            // But we rather add one if check and log than re-schedule and keep the session alive...
+            Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called "
+                    + "when disabled. events=" + (mEvents == null ? null : mEvents.size()));
             return;
         }
         if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
@@ -375,8 +393,7 @@
         }
         mNextFlush = System.currentTimeMillis() + FLUSHING_FREQUENCY_MS;
         if (VERBOSE) {
-            Log.v(TAG, "handleScheduleFlush(" + getDebugState()
-                    + ", reason=" + getflushReasonAsString(reason) + "): scheduled to flush in "
+            Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
                     + FLUSHING_FREQUENCY_MS + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
         }
         mHandler.sendMessageDelayed(
@@ -395,11 +412,16 @@
     private void handleForceFlush(@FlushReason int reason) {
         if (mEvents == null) return;
 
+        if (mDisabled.get()) {
+            Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
+                    + "disabled");
+            return;
+        }
+
         if (mDirectServiceInterface == null) {
             if (VERBOSE) {
-                Log.v(TAG, "handleForceFlush(" + getDebugState()
-                        + ", reason=" + getflushReasonAsString(reason)
-                        + "): hold your horses, client not ready: " + mEvents);
+                Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
+                        + "client not ready: " + mEvents);
             }
             if (!mHandler.hasMessages(MSG_FLUSH)) {
                 handleScheduleFlush(reason, /* checkExisting= */ false);
@@ -410,8 +432,7 @@
         final int numberEvents = mEvents.size();
         final String reasonString = getflushReasonAsString(reason);
         if (DEBUG) {
-            Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState()
-                    + ". Reason: " + reasonString);
+            Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason));
         }
         // Logs reason, size, max size, idle timeout
         final String logRecord = "r=" + reasonString + " s=" + numberEvents
@@ -592,7 +613,14 @@
                 : "act:" + mComponentName.flattenToShortString();
     }
 
+    @NonNull
     private String getDebugState() {
-        return getActivityName() + " (state=" + getStateAsString(mState) + ")";
+        return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled="
+                + mDisabled.get() + "]";
+    }
+
+    @NonNull
+    private String getDebugState(@FlushReason int reason) {
+        return getDebugState() + ", reason=" + getflushReasonAsString(reason);
     }
 }
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 86c5f18..57ed2f8 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;
@@ -922,19 +923,6 @@
         }
     }
 
-    /**
-     * Returns a list of VR InputMethod currently installed.
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
-    public List<InputMethodInfo> getVrInputMethodList() {
-        try {
-            return mService.getVrInputMethodList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     public List<InputMethodInfo> getEnabledInputMethodList() {
         try {
             return mService.getEnabledInputMethodList();
@@ -1894,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);
@@ -2497,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;
     }
 
     /**
@@ -2642,7 +2675,13 @@
      *
      * @param imiId Id of InputMethodInfo which additional input method subtypes will be added to.
      * @param subtypes subtypes will be added as additional subtypes of the current input method.
+     * @deprecated For IMEs that have already implemented features like customizable/downloadable
+     *             keyboard layouts/languages, please start migration to other approaches. One idea
+     *             would be exposing only one unified {@link InputMethodSubtype} then implement
+     *             IME's own language switching mechanism within that unified subtype. The support
+     *             of "Additional Subtype" may be completely dropped in a future version of Android.
      */
+    @Deprecated
     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
         try {
             mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
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 356d178..0752efe 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -35,7 +35,6 @@
 
     // TODO: Use ParceledListSlice instead
     List<InputMethodInfo> getInputMethodList();
-    List<InputMethodInfo> getVrInputMethodList();
     // TODO: Use ParceledListSlice instead
     List<InputMethodInfo> getEnabledInputMethodList();
     List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
@@ -65,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/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d5dc703..8d3c482 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -34,6 +34,7 @@
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -176,6 +177,7 @@
     private UserManager mUserManager;
     private final Handler mHandler;
     private final SparseLongArray mLockoutDeadlines = new SparseLongArray();
+    private Boolean mHasSecureLockScreen;
 
     /**
      * Use {@link TrustManager#isTrustUsuallyManaged(int)}.
@@ -706,6 +708,10 @@
      * @param userId the user whose pattern is to be saved.
      */
     public void saveLockPattern(List<LockPatternView.Cell> pattern, String savedPattern, int userId) {
+        if (!hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires the lock screen feature.");
+        }
         if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
             throw new IllegalArgumentException("pattern must not be null and at least "
                     + MIN_LOCK_PATTERN_SIZE + " dots long.");
@@ -801,6 +807,10 @@
 
     /** Update the encryption password if it is enabled **/
     private void updateEncryptionPassword(final int type, final String password) {
+        if (!hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires the lock screen feature.");
+        }
         if (!isDeviceEncryptionEnabled()) {
             return;
         }
@@ -835,6 +845,10 @@
      */
     public void saveLockPassword(String password, String savedPassword, int requestedQuality,
             int userHandle) {
+        if (!hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires the lock screen feature.");
+        }
         if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
             throw new IllegalArgumentException("password must not be null and at least "
                     + "of length " + MIN_LOCK_PASSWORD_SIZE);
@@ -1621,6 +1635,10 @@
      */
     public boolean setLockCredentialWithToken(String credential, int type, int requestedQuality,
             long tokenHandle, byte[] token, int userId) {
+        if (!hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires the lock screen feature.");
+        }
         LockSettingsInternal localService = getLockSettingsInternal();
         if (type != CREDENTIAL_TYPE_NONE) {
             if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) {
@@ -1854,6 +1872,17 @@
         return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
     }
 
+    /**
+     * Return true if the device supports the lock screen feature, false otherwise.
+     */
+    public boolean hasSecureLockScreen() {
+        if (mHasSecureLockScreen == null) {
+            mHasSecureLockScreen = Boolean.valueOf(mContext.getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN));
+        }
+        return mHasSecureLockScreen.booleanValue();
+    }
+
     public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
         return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
     }
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_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 9b138eb..7eddcfe 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -16,8 +16,11 @@
 
 #define LOG_TAG "NetUtils"
 
+#include <vector>
+
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
 #include "NetdClient.h"
 #include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
@@ -55,6 +58,31 @@
 static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
 static const uint16_t kDhcpClientPort = 68;
 
+constexpr int MAXPACKETSIZE = 8 * 1024;
+// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this.
+constexpr int MAXCMDSIZE = 1024;
+
+static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
+    ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
+    if (detailMessage.get() == NULL) {
+        // Not really much we can do here. We're probably dead in the water,
+        // but let's try to stumble on...
+        env->ExceptionClear();
+    }
+    static jclass errnoExceptionClass =
+            MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
+
+    static jmethodID errnoExceptionCtor =
+            GetMethodIDOrDie(env, errnoExceptionClass,
+            "<init>", "(Ljava/lang/String;I)V");
+
+    jobject exception = env->NewObject(errnoExceptionClass,
+                                       errnoExceptionCtor,
+                                       detailMessage.get(),
+                                       error);
+    env->Throw(reinterpret_cast<jthrowable>(exception));
+}
+
 static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
     struct sock_filter filter_code[] = {
@@ -372,6 +400,63 @@
     }
 }
 
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
+        jstring dname, jint ns_class, jint ns_type, jint flags) {
+    const jsize javaCharsCount = env->GetStringLength(dname);
+    const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
+
+    // Only allow dname which could be simply formatted to UTF8.
+    // In native layer, res_mkquery would re-format the input char array to packet.
+    std::vector<char> queryname(byteCountUTF8 + 1, 0);
+
+    env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
+    int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
+
+    if (fd < 0) {
+        throwErrnoException(env, "resNetworkQuery", -fd);
+        return nullptr;
+    }
+
+    return jniCreateFileDescriptor(env, fd);
+}
+
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
+        jbyteArray msg, jint msgLen, jint flags) {
+    uint8_t data[MAXCMDSIZE];
+
+    checkLenAndCopy(env, msg, msgLen, data);
+    int fd = resNetworkSend(netId, data, msgLen, flags);
+
+    if (fd < 0) {
+        throwErrnoException(env, "resNetworkSend", -fd);
+        return nullptr;
+    }
+
+    return jniCreateFileDescriptor(env, fd);
+}
+
+static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int rcode;
+    std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
+
+    int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+    if (res < 0) {
+        throwErrnoException(env, "resNetworkResult", -res);
+        return nullptr;
+    }
+
+    jbyteArray answer = env->NewByteArray(res);
+    if (answer == nullptr) {
+        throwErrnoException(env, "resNetworkResult", ENOMEM);
+        return nullptr;
+    } else {
+        env->SetByteArrayRegion(answer, 0, res,
+                reinterpret_cast<jbyte*>(buf.data()));
+    }
+
+    return answer;
+}
 
 // ----------------------------------------------------------------------------
 
@@ -391,6 +476,9 @@
     { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
     { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
     { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
+    { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
+    { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
+    { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult },
 };
 
 int register_android_net_NetworkUtils(JNIEnv* env)
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/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7028008..2e7184b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -493,10 +493,7 @@
   // Apply system or app filter based on uid.
   if (uid >= AID_APP_START) {
     if (is_child_zygote) {
-      // set_app_zygote_seccomp_filter();
-      // TODO(b/111434506) install the filter; for now, install the app filter
-      // which is more restrictive.
-      set_app_seccomp_filter();
+      set_app_zygote_seccomp_filter();
     } else {
       set_app_seccomp_filter();
     }
@@ -1645,14 +1642,10 @@
     return;
   }
 
-  // TODO(b/111434506) install the filter
-
-  /*
   bool installed = install_setuidgid_seccomp_filter(uidGidMin, uidGidMax);
   if (!installed) {
       RuntimeAbort(env, __LINE__, "Could not install setuid/setgid seccomp filter.");
   }
-  */
 }
 
 /**
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/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 4158577..712a8c0 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -731,8 +731,7 @@
     // Defines global runtime overrides to window policy.
     optional SettingProto policy_control = 92;
     optional SettingProto power_manager_constants = 93;
-    // If true, out-of-the-box execution for priv apps is enabled.
-    optional SettingProto priv_app_oob_enabled = 94 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    reserved 94; // Used to be priv_app_oob_enabled
 
     message PrepaidSetup {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4bfd4d2..c0d6139 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -528,8 +528,9 @@
 
     optional SettingProto skip_gesture_enabled = 74 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto silence_gesture_enabled = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto theme_customization_overlay_packages = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 76;
+    // Next tag = 77;
 }
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 7f3ea7a..e68f9db 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -221,6 +221,8 @@
     optional bool use_heartbeats = 23;
 
     message TimeController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
         // ready now.
         optional bool skip_not_ready_jobs = 1;
@@ -228,6 +230,8 @@
     optional TimeController time_controller = 25;
 
     message QuotaController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         // How much time each app will have to run jobs within their standby bucket window.
         optional int64 allowed_time_per_period_ms = 1;
         // How much time the package should have before transitioning from out-of-quota to in-quota.
@@ -251,6 +255,21 @@
         optional int64 rare_window_size_ms = 6;
         // The maximum amount of time an app can have its jobs running within a 24 hour window.
         optional int64 max_execution_time_ms = 7;
+        // The maximum number of jobs an app can run within this particular standby bucket's
+        // window size.
+        optional int32 max_job_count_active = 8;
+        // The maximum number of jobs an app can run within this particular standby bucket's
+        // window size.
+        optional int32 max_job_count_working = 9;
+        // The maximum number of jobs an app can run within this particular standby bucket's
+        // window size.
+        optional int32 max_job_count_frequent = 10;
+        // The maximum number of jobs an app can run within this particular standby bucket's
+        // window size.
+        optional int32 max_job_count_rare = 11;
+        // The maximum number of jobs that should be allowed to run in the past
+        // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
+        optional int32 max_job_count_per_allowed_time = 12;
     }
     optional QuotaController quota_controller = 24;
 
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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ea0c8e2..c7cba62 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -487,6 +487,7 @@
     <protected-broadcast android:name="android.security.action.TRUST_STORE_CHANGED" />
     <protected-broadcast android:name="android.security.action.KEYCHAIN_CHANGED" />
     <protected-broadcast android:name="android.security.action.KEY_ACCESS_CHANGED" />
+    <protected-broadcast android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" />
     <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" />
     <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" />
     <protected-broadcast android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
@@ -1748,6 +1749,10 @@
     <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows the device to be reset, clearing all data and enables Test Harness Mode. -->
+    <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
+        android:protectionLevel="signature" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
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/public.xml b/core/res/res/values/public.xml
index 777886a..4235341 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2944,6 +2944,8 @@
     </public-group>
 
     <public-group type="style" first-id="0x010302e2">
+        <!-- @hide @SystemApi -->
+        <public name="Theme.DeviceDefault.DocumentsUI" />
     </public-group>
 
     <public-group type="id" first-id="0x01020046">
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/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 75a727b..1603508 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1711,4 +1711,6 @@
         <item name="notificationHeaderTextAppearance">@style/TextAppearance.DeviceDefault.Notification.Info</item>
     </style>
 
+    <!-- @hide DeviceDefault theme for the DocumentsUI app.  -->
+    <style name="Theme.DeviceDefault.DocumentsUI" parent="Theme.DeviceDefault.DayNight" />
 </resources>
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index d2bd1e1..11eb158 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -18,13 +18,13 @@
 import static org.junit.Assert.*;
 import static org.junit.Assume.*;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.testng.Assert.assertThrows;
 
 import android.Manifest;
@@ -119,10 +119,9 @@
     }
 
     private void resetCallback() {
-        verify(mCallback, atLeast(0)).onMetadataChanged(any());
-        verify(mCallback, atLeast(0)).onProgramInfoChanged(any());
-        verify(mCallback, atLeast(0)).onProgramListChanged();
-        verifyNoMoreInteractions(mCallback);
+        verify(mCallback, never()).onError(anyInt());
+        verify(mCallback, never()).onTuneFailed(anyInt(), any());
+        verify(mCallback, never()).onControlChanged(anyBoolean());
         Mockito.reset(mCallback);
     }
 
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/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index c5454a6..7b92cf5 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -528,11 +528,13 @@
         PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexFile, false);
         assertEquals("com.google.android.tzdata", pi.packageName);
         assertEquals(1, pi.getLongVersionCode());
+        assertEquals(1, pi.applicationInfo.longVersionCode);
         assertNull(pi.signingInfo);
 
         pi = PackageParser.generatePackageInfoFromApex(apexFile, true);
         assertEquals("com.google.android.tzdata", pi.packageName);
         assertEquals(1, pi.getLongVersionCode());
+        assertEquals(1, pi.applicationInfo.longVersionCode);
         assertNotNull(pi.signingInfo);
         assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
     }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 87ad3d1..8e16ddf 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,
@@ -386,8 +388,6 @@
                     Settings.Global.POLICY_CONTROL,
                     Settings.Global.POWER_MANAGER_CONSTANTS,
                     Settings.Global.PREFERRED_NETWORK_MODE,
-                    Settings.Global.PRIV_APP_OOB_ENABLED,
-                    Settings.Global.PRIV_APP_OOB_LIST,
                     Settings.Global.PRIVATE_DNS_DEFAULT_MODE,
                     Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED,
                     Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED,
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 7cf5c7c..bfa6e06 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/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
new file mode 100644
index 0000000..1a81c2c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
@@ -0,0 +1,48 @@
+/*
+ * 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.wm.test.filters;
+
+import android.os.Bundle;
+
+import com.android.test.filters.SelectTest;
+
+/**
+ * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests.
+ *
+ * <p>Use this filter when running FrameworksCoreTests as
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.server.wm.test.filters.CoreTestsFilter  \
+ *     -e selectTest_verbose true \
+ *     com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+public final class CoreTestsFilter extends SelectTest {
+
+    private static final String[] SELECTED_CORE_TESTS = {
+            "android.app.servertransaction.", // all tests under the package.
+            "android.view.DisplayCutoutTest",
+            "android.view.InsetsControllerTest",
+            "android.view.InsetsSourceTest",
+            "android.view.InsetsSourceConsumerTest",
+            "android.view.InsetsStateTest",
+    };
+
+    public CoreTestsFilter(Bundle testArgs) {
+        super(addSelectTest(testArgs, SELECTED_CORE_TESTS));
+    }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 03cf3eb..9913531 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -67,6 +68,7 @@
         // TODO(b/63758238): stop spying the class under test
         mLockPatternUtils = spy(new LockPatternUtils(context));
         when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
+        doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
 
         final UserInfo userInfo = Mockito.mock(UserInfo.class);
         when(userInfo.isDemo()).thenReturn(isDemoUser);
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..720c603 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;
 }
 
@@ -244,8 +241,10 @@
 }
 
 void RenderProxy::dumpGraphicsMemory(int fd) {
-    auto& thread = RenderThread::getInstance();
-    thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); });
+    if (RenderThread::hasInstance()) {
+        auto& thread = RenderThread::getInstance();
+        thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); });
+    }
 }
 
 void RenderProxy::setProcessStatsBuffer(int fd) {
@@ -281,6 +280,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 +307,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 +351,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/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 52771e4..1d763ce 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -356,11 +356,11 @@
         dest.writeInt(mDeviceSource);
         dest.writeInt(mClientEffects.length);
         for (int i = 0; i < mClientEffects.length; i++) {
-            mClientEffects[i].writeToParcel(dest, 0);
+            mClientEffects[i].writeToParcel(dest);
         }
         dest.writeInt(mDeviceEffects.length);
         for (int i = 0; i < mDeviceEffects.length; i++) {
-            mDeviceEffects[i].writeToParcel(dest, 0);
+            mDeviceEffects[i].writeToParcel(dest);
         }
     }
 
@@ -375,13 +375,13 @@
         mClientPortId = in.readInt();
         mClientSilenced = in.readBoolean();
         mDeviceSource = in.readInt();
-        mClientEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt());
+        mClientEffects = new AudioEffect.Descriptor[in.readInt()];
         for (int i = 0; i < mClientEffects.length; i++) {
-            mClientEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in);
+            mClientEffects[i] = new AudioEffect.Descriptor(in);
         }
-        mDeviceEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt());
-        for (int i = 0; i < mClientEffects.length; i++) {
-            mDeviceEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in);
+        mDeviceEffects = new AudioEffect.Descriptor[in.readInt()];
+        for (int i = 0; i < mDeviceEffects.length; i++) {
+            mDeviceEffects[i] = new AudioEffect.Descriptor(in);
         }
     }
 
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index 814bc72..887b447 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -45,7 +45,7 @@
 
 /**
  * Allows an app to interact with an active {@link MediaSession2} or a
- * MediaSession2Service which would provide {@link MediaSession2}. Media buttons and other
+ * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other
  * commands can be sent to the session.
  * <p>
  * This API is not generally intended for third party application developers.
@@ -53,7 +53,6 @@
  * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
  * for consistent behavior across all devices.
  */
-// TODO: use @link for MediaSession2Service
 public class MediaController2 implements AutoCloseable {
     static final String TAG = "MediaController2";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -325,7 +324,7 @@
                     MediaController2.this, command, args);
             if (resultReceiver != null) {
                 if (result == null) {
-                    throw new RuntimeException("onSessionCommand shouldn't return null");
+                    resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null);
                 } else {
                     resultReceiver.send(result.getResultCode(), result.getResultData());
                 }
@@ -442,8 +441,8 @@
          * @param controller the controller for this event
          * @param command the session command
          * @param args optional arguments
-         * @return the result for the session command. A runtime exception will be thrown if null
-         *         is returned.
+         * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
+         *         will be sent to the session.
          */
         @Nullable
         public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller,
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/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 76ef27a..fdd07fd 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -397,7 +397,7 @@
                     MediaSession2.this, controllerInfo, command, args);
             if (resultReceiver != null) {
                 if (result == null) {
-                    throw new RuntimeException("onSessionCommand shouldn't return null");
+                    resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null);
                 } else {
                     resultReceiver.send(result.getResultCode(), result.getResultData());
                 }
@@ -753,8 +753,8 @@
          * @param controller controller information
          * @param command the session command
          * @param args optional arguments
-         * @return the result for the session command. A runtime exception will be thrown if null
-         *         is returned.
+         * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
+         *         will be sent to the session.
          */
         @Nullable
         public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session,
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 3b51c82..325420b 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -21,18 +21,14 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.media.session.MediaSession;
 import android.media.session.MediaSessionLegacyHelper;
 import android.media.session.PlaybackState;
-import android.media.session.MediaSession;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
 import android.os.SystemClock;
 import android.util.Log;
 
-import java.lang.IllegalArgumentException;
-
 /**
  * RemoteControlClient enables exposing information meant to be consumed by remote controls
  * capable of displaying metadata, artwork and media transport control buttons.
@@ -682,7 +678,7 @@
 
                 // USE_SESSIONS
                 if (mSession != null) {
-                    int pbState = PlaybackState.getStateFromRccState(state);
+                    int pbState = getStateFromRccState(state);
                     long position = hasPosition ? mPlaybackPositionMs
                             : PlaybackState.PLAYBACK_POSITION_UNKNOWN;
 
@@ -718,8 +714,7 @@
             // USE_SESSIONS
             if (mSession != null) {
                 PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
-                bob.setActions(
-                        PlaybackState.getActionsFromRccControlFlags(transportControlFlags));
+                bob.setActions(getActionsFromRccControlFlags(transportControlFlags));
                 mSessionPlaybackState = bob.build();
                 mSession.setPlaybackState(mSessionPlaybackState);
             }
@@ -1001,16 +996,19 @@
      * Period for playback position drift checks, 15s when playing at 1x or slower.
      */
     private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000;
+
     /**
      * Minimum period for playback position drift checks, never more often when every 2s, when
      * fast forwarding or rewinding.
      */
     private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000;
+
     /**
      * The value above which the difference between client-reported playback position and
      * estimated position is considered a drift.
      */
     private final static long POSITION_DRIFT_MAX_MS = 500;
+
     /**
      * Compute the period at which the estimated playback position should be compared against the
      * actual playback position. Is a funciton of playback speed.
@@ -1025,4 +1023,151 @@
                     POSITION_REFRESH_PERIOD_MIN_MS);
         }
     }
+
+    /**
+     * Get the {@link PlaybackState} state for the given
+     * {@link RemoteControlClient} state.
+     *
+     * @param rccState The state used by {@link RemoteControlClient}.
+     * @return The equivalent state used by {@link PlaybackState}.
+     */
+    private static int getStateFromRccState(int rccState) {
+        switch (rccState) {
+            case PLAYSTATE_BUFFERING:
+                return PlaybackState.STATE_BUFFERING;
+            case PLAYSTATE_ERROR:
+                return PlaybackState.STATE_ERROR;
+            case PLAYSTATE_FAST_FORWARDING:
+                return PlaybackState.STATE_FAST_FORWARDING;
+            case PLAYSTATE_NONE:
+                return PlaybackState.STATE_NONE;
+            case PLAYSTATE_PAUSED:
+                return PlaybackState.STATE_PAUSED;
+            case PLAYSTATE_PLAYING:
+                return PlaybackState.STATE_PLAYING;
+            case PLAYSTATE_REWINDING:
+                return PlaybackState.STATE_REWINDING;
+            case PLAYSTATE_SKIPPING_BACKWARDS:
+                return PlaybackState.STATE_SKIPPING_TO_PREVIOUS;
+            case PLAYSTATE_SKIPPING_FORWARDS:
+                return PlaybackState.STATE_SKIPPING_TO_NEXT;
+            case PLAYSTATE_STOPPED:
+                return PlaybackState.STATE_STOPPED;
+            default:
+                return -1;
+        }
+    }
+
+    /**
+     * Get the {@link RemoteControlClient} state for the given
+     * {@link PlaybackState} state.
+     *
+     * @param state The state used by {@link PlaybackState}.
+     * @return The equivalent state used by {@link RemoteControlClient}.
+     */
+    static int getRccStateFromState(int state) {
+        switch (state) {
+            case PlaybackState.STATE_BUFFERING:
+                return PLAYSTATE_BUFFERING;
+            case PlaybackState.STATE_ERROR:
+                return PLAYSTATE_ERROR;
+            case PlaybackState.STATE_FAST_FORWARDING:
+                return PLAYSTATE_FAST_FORWARDING;
+            case PlaybackState.STATE_NONE:
+                return PLAYSTATE_NONE;
+            case PlaybackState.STATE_PAUSED:
+                return PLAYSTATE_PAUSED;
+            case PlaybackState.STATE_PLAYING:
+                return PLAYSTATE_PLAYING;
+            case PlaybackState.STATE_REWINDING:
+                return PLAYSTATE_REWINDING;
+            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+                return PLAYSTATE_SKIPPING_BACKWARDS;
+            case PlaybackState.STATE_SKIPPING_TO_NEXT:
+                return PLAYSTATE_SKIPPING_FORWARDS;
+            case PlaybackState.STATE_STOPPED:
+                return PLAYSTATE_STOPPED;
+            default:
+                return -1;
+        }
+    }
+
+    private static long getActionsFromRccControlFlags(int rccFlags) {
+        long actions = 0;
+        long flag = 1;
+        while (flag <= rccFlags) {
+            if ((flag & rccFlags) != 0) {
+                actions |= getActionForRccFlag((int) flag);
+            }
+            flag = flag << 1;
+        }
+        return actions;
+    }
+
+    static int getRccControlFlagsFromActions(long actions) {
+        int rccFlags = 0;
+        long action = 1;
+        while (action <= actions && action < Integer.MAX_VALUE) {
+            if ((action & actions) != 0) {
+                rccFlags |= getRccFlagForAction(action);
+            }
+            action = action << 1;
+        }
+        return rccFlags;
+    }
+
+    private static long getActionForRccFlag(int flag) {
+        switch (flag) {
+            case FLAG_KEY_MEDIA_PREVIOUS:
+                return PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+            case FLAG_KEY_MEDIA_REWIND:
+                return PlaybackState.ACTION_REWIND;
+            case FLAG_KEY_MEDIA_PLAY:
+                return PlaybackState.ACTION_PLAY;
+            case FLAG_KEY_MEDIA_PLAY_PAUSE:
+                return PlaybackState.ACTION_PLAY_PAUSE;
+            case FLAG_KEY_MEDIA_PAUSE:
+                return PlaybackState.ACTION_PAUSE;
+            case FLAG_KEY_MEDIA_STOP:
+                return PlaybackState.ACTION_STOP;
+            case FLAG_KEY_MEDIA_FAST_FORWARD:
+                return PlaybackState.ACTION_FAST_FORWARD;
+            case FLAG_KEY_MEDIA_NEXT:
+                return PlaybackState.ACTION_SKIP_TO_NEXT;
+            case FLAG_KEY_MEDIA_POSITION_UPDATE:
+                return PlaybackState.ACTION_SEEK_TO;
+            case FLAG_KEY_MEDIA_RATING:
+                return PlaybackState.ACTION_SET_RATING;
+        }
+        return 0;
+    }
+
+    private static int getRccFlagForAction(long action) {
+        // We only care about the lower set of actions that can map to rcc
+        // flags.
+        int testAction = action < Integer.MAX_VALUE ? (int) action : 0;
+        switch (testAction) {
+            case (int) PlaybackState.ACTION_SKIP_TO_PREVIOUS:
+                return FLAG_KEY_MEDIA_PREVIOUS;
+            case (int) PlaybackState.ACTION_REWIND:
+                return FLAG_KEY_MEDIA_REWIND;
+            case (int) PlaybackState.ACTION_PLAY:
+                return FLAG_KEY_MEDIA_PLAY;
+            case (int) PlaybackState.ACTION_PLAY_PAUSE:
+                return FLAG_KEY_MEDIA_PLAY_PAUSE;
+            case (int) PlaybackState.ACTION_PAUSE:
+                return FLAG_KEY_MEDIA_PAUSE;
+            case (int) PlaybackState.ACTION_STOP:
+                return FLAG_KEY_MEDIA_STOP;
+            case (int) PlaybackState.ACTION_FAST_FORWARD:
+                return FLAG_KEY_MEDIA_FAST_FORWARD;
+            case (int) PlaybackState.ACTION_SKIP_TO_NEXT:
+                return FLAG_KEY_MEDIA_NEXT;
+            case (int) PlaybackState.ACTION_SEEK_TO:
+                return FLAG_KEY_MEDIA_POSITION_UPDATE;
+            case (int) PlaybackState.ACTION_SET_RATING:
+                return FLAG_KEY_MEDIA_RATING;
+        }
+        return 0;
+    }
 }
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 5e9eed7..f70963a 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -632,8 +632,8 @@
             l = this.mOnClientUpdateListener;
         }
         if (l != null) {
-            int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState
-                    .getRccStateFromState(state.getState());
+            int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE
+                    : RemoteControlClient.getRccStateFromState(state.getState());
             if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
                 l.onClientPlaybackStateUpdate(playstate);
             } else {
@@ -642,7 +642,7 @@
             }
             if (state != null) {
                 l.onClientTransportControlUpdate(
-                        PlaybackState.getRccControlFlagsFromActions(state.getActions()));
+                        RemoteControlClient.getRccControlFlagsFromActions(state.getActions()));
             }
         }
     }
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/Session2Token.java b/media/java/android/media/Session2Token.java
index 023ee46..238cc2b 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -35,7 +35,7 @@
 import java.util.Objects;
 
 /**
- * Represents an ongoing {@link MediaSession2} or a MediaSession2Service.
+ * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}.
  * If it's representing a session service, it may not be ongoing.
  * <p>
  * This API is not generally intended for third party application developers.
@@ -199,14 +199,6 @@
     }
 
     /**
-     * @hide
-     * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
-     */
-    public ComponentName getComponentName() {
-        return mComponentName;
-    }
-
-    /**
      * @return type of the token
      * @see #TYPE_SESSION
      * @see #TYPE_SESSION_SERVICE
@@ -215,10 +207,7 @@
         return mType;
     }
 
-    /**
-     * @hide
-     */
-    public Session2Link getSessionLink() {
+    Session2Link getSessionLink() {
         return mSessionLink;
     }
 
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/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 52e9ae1..5b4bbce 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -26,7 +26,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.Log;
 
 import java.lang.ref.WeakReference;
@@ -229,7 +228,7 @@
      * The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects
      * enumeration.
      */
-    public static final class Descriptor implements Parcelable {
+    public static class Descriptor {
 
         public Descriptor() {
         }
@@ -294,7 +293,9 @@
             this.implementor = implementor;
         }
 
-        private Descriptor(Parcel in) {
+        /** @hide */
+        @TestApi
+        public Descriptor(Parcel in) {
             type = UUID.fromString(in.readString());
             uuid = UUID.fromString(in.readString());
             connectMode = in.readString();
@@ -302,33 +303,14 @@
             implementor = in.readString();
         }
 
-        public static final Parcelable.Creator<Descriptor> CREATOR =
-                new Parcelable.Creator<Descriptor>() {
-                    /**
-                     * Rebuilds a Descriptor previously stored with writeToParcel().
-                     * @param p Parcel object to read the Descriptor from
-                     * @return a new Descriptor created from the data in the parcel
-                     */
-                    public Descriptor createFromParcel(Parcel p) {
-                        return new Descriptor(p);
-                    }
-                    public Descriptor[] newArray(int size) {
-                        return new Descriptor[size];
-                    }
-        };
-
         @Override
         public int hashCode() {
             return Objects.hash(type, uuid, connectMode, name, implementor);
         }
 
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        /** @hide */
+        @TestApi
+        public void writeToParcel(Parcel dest) {
             dest.writeString(type.toString());
             dest.writeString(uuid.toString());
             dest.writeString(connectMode);
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 eda913e..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;
 
@@ -144,7 +144,7 @@
             SessionLink sessionLink = manager.createSession(cbLink, tag);
             mImpl = new MediaSessionEngine(context, sessionLink, cbLink, cbStub,
                     context.getResources().getDimensionPixelSize(
-                            com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize));
+                            android.R.dimen.config_mediaMetadataBitmapMaxSize));
         } catch (RuntimeException e) {
             throw new RuntimeException("Remote error creating session.", e);
         }
@@ -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;
         }
 
@@ -723,7 +725,7 @@
 
         private QueueItem(Parcel in) {
             mImpl = new MediaSessionEngine.QueueItem(in);
-            mId = in.readLong();
+            mId = mImpl.getQueueId();
         }
 
         /**
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/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 2c57d1f..0d0ec4c 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.Nullable;
-import android.media.RemoteControlClient;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -481,161 +480,6 @@
         return mExtras;
     }
 
-    /**
-     * Get the {@link PlaybackState} state for the given
-     * {@link RemoteControlClient} state.
-     *
-     * @param rccState The state used by {@link RemoteControlClient}.
-     * @return The equivalent state used by {@link PlaybackState}.
-     * @hide
-     */
-    public static int getStateFromRccState(int rccState) {
-        switch (rccState) {
-            case RemoteControlClient.PLAYSTATE_BUFFERING:
-                return STATE_BUFFERING;
-            case RemoteControlClient.PLAYSTATE_ERROR:
-                return STATE_ERROR;
-            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
-                return STATE_FAST_FORWARDING;
-            case RemoteControlClient.PLAYSTATE_NONE:
-                return STATE_NONE;
-            case RemoteControlClient.PLAYSTATE_PAUSED:
-                return STATE_PAUSED;
-            case RemoteControlClient.PLAYSTATE_PLAYING:
-                return STATE_PLAYING;
-            case RemoteControlClient.PLAYSTATE_REWINDING:
-                return STATE_REWINDING;
-            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
-                return STATE_SKIPPING_TO_PREVIOUS;
-            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
-                return STATE_SKIPPING_TO_NEXT;
-            case RemoteControlClient.PLAYSTATE_STOPPED:
-                return STATE_STOPPED;
-            default:
-                return -1;
-        }
-    }
-
-    /**
-     * Get the {@link RemoteControlClient} state for the given
-     * {@link PlaybackState} state.
-     *
-     * @param state The state used by {@link PlaybackState}.
-     * @return The equivalent state used by {@link RemoteControlClient}.
-     * @hide
-     */
-    public static int getRccStateFromState(int state) {
-        switch (state) {
-            case STATE_BUFFERING:
-                return RemoteControlClient.PLAYSTATE_BUFFERING;
-            case STATE_ERROR:
-                return RemoteControlClient.PLAYSTATE_ERROR;
-            case STATE_FAST_FORWARDING:
-                return RemoteControlClient.PLAYSTATE_FAST_FORWARDING;
-            case STATE_NONE:
-                return RemoteControlClient.PLAYSTATE_NONE;
-            case STATE_PAUSED:
-                return RemoteControlClient.PLAYSTATE_PAUSED;
-            case STATE_PLAYING:
-                return RemoteControlClient.PLAYSTATE_PLAYING;
-            case STATE_REWINDING:
-                return RemoteControlClient.PLAYSTATE_REWINDING;
-            case STATE_SKIPPING_TO_PREVIOUS:
-                return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS;
-            case STATE_SKIPPING_TO_NEXT:
-                return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS;
-            case STATE_STOPPED:
-                return RemoteControlClient.PLAYSTATE_STOPPED;
-            default:
-                return -1;
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public static long getActionsFromRccControlFlags(int rccFlags) {
-        long actions = 0;
-        long flag = 1;
-        while (flag <= rccFlags) {
-            if ((flag & rccFlags) != 0) {
-                actions |= getActionForRccFlag((int) flag);
-            }
-            flag = flag << 1;
-        }
-        return actions;
-    }
-
-    /**
-     * @hide
-     */
-    public static int getRccControlFlagsFromActions(long actions) {
-        int rccFlags = 0;
-        long action = 1;
-        while (action <= actions && action < Integer.MAX_VALUE) {
-            if ((action & actions) != 0) {
-                rccFlags |= getRccFlagForAction(action);
-            }
-            action = action << 1;
-        }
-        return rccFlags;
-    }
-
-    private static long getActionForRccFlag(int flag) {
-        switch (flag) {
-            case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS:
-                return ACTION_SKIP_TO_PREVIOUS;
-            case RemoteControlClient.FLAG_KEY_MEDIA_REWIND:
-                return ACTION_REWIND;
-            case RemoteControlClient.FLAG_KEY_MEDIA_PLAY:
-                return ACTION_PLAY;
-            case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE:
-                return ACTION_PLAY_PAUSE;
-            case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE:
-                return ACTION_PAUSE;
-            case RemoteControlClient.FLAG_KEY_MEDIA_STOP:
-                return ACTION_STOP;
-            case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD:
-                return ACTION_FAST_FORWARD;
-            case RemoteControlClient.FLAG_KEY_MEDIA_NEXT:
-                return ACTION_SKIP_TO_NEXT;
-            case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE:
-                return ACTION_SEEK_TO;
-            case RemoteControlClient.FLAG_KEY_MEDIA_RATING:
-                return ACTION_SET_RATING;
-        }
-        return 0;
-    }
-
-    private static int getRccFlagForAction(long action) {
-        // We only care about the lower set of actions that can map to rcc
-        // flags.
-        int testAction = action < Integer.MAX_VALUE ? (int) action : 0;
-        switch (testAction) {
-            case (int) ACTION_SKIP_TO_PREVIOUS:
-                return RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS;
-            case (int) ACTION_REWIND:
-                return RemoteControlClient.FLAG_KEY_MEDIA_REWIND;
-            case (int) ACTION_PLAY:
-                return RemoteControlClient.FLAG_KEY_MEDIA_PLAY;
-            case (int) ACTION_PLAY_PAUSE:
-                return RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE;
-            case (int) ACTION_PAUSE:
-                return RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
-            case (int) ACTION_STOP:
-                return RemoteControlClient.FLAG_KEY_MEDIA_STOP;
-            case (int) ACTION_FAST_FORWARD:
-                return RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD;
-            case (int) ACTION_SKIP_TO_NEXT:
-                return RemoteControlClient.FLAG_KEY_MEDIA_NEXT;
-            case (int) ACTION_SEEK_TO:
-                return RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
-            case (int) ACTION_SET_RATING:
-                return RemoteControlClient.FLAG_KEY_MEDIA_RATING;
-        }
-        return 0;
-    }
-
     public static final Parcelable.Creator<PlaybackState> CREATOR =
             new Parcelable.Creator<PlaybackState>() {
         @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/ExtServices/src/android/ext/services/notification/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
index 39a1676..406b44d 100644
--- a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
+++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
@@ -20,8 +20,11 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.KeyValueListParser;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -29,6 +32,7 @@
  * Observes the settings for {@link Assistant}.
  */
 final class AssistantSettings extends ContentObserver {
+    private static final String LOG_TAG = "AssistantSettings";
     public static Factory FACTORY = AssistantSettings::createAndRegister;
     private static final boolean DEFAULT_GENERATE_REPLIES = true;
     private static final boolean DEFAULT_GENERATE_ACTIONS = true;
@@ -39,19 +43,33 @@
     private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
             Settings.Global.getUriFor(
                     Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
-    private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI =
-            Settings.Global.getUriFor(
-                    Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
     private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI =
             Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL);
 
-    private static final String KEY_GENERATE_REPLIES = "generate_replies";
-    private static final String KEY_GENERATE_ACTIONS = "generate_actions";
+    /**
+     * Flag determining whether the Notification Assistant should generate replies for
+     * notifications.
+     * <p>
+     * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+     */
+    @VisibleForTesting
+    static final String KEY_GENERATE_REPLIES = "notification_assistant_generate_replies";
+
+    /**
+     * Flag determining whether the Notification Assistant should generate contextual actions in
+     * notifications.
+     * <p>
+     * This flag belongs to the namespace: {@link DeviceConfig#NAMESPACE_NOTIFICATION_ASSISTANT}.
+     */
+    @VisibleForTesting
+    static final String KEY_GENERATE_ACTIONS = "notification_assistant_generate_actions";
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final ContentResolver mResolver;
     private final int mUserId;
 
+    private final Handler mHandler;
+
     @VisibleForTesting
     protected final Runnable mOnUpdateRunnable;
 
@@ -65,6 +83,7 @@
     private AssistantSettings(Handler handler, ContentResolver resolver, int userId,
             Runnable onUpdateRunnable) {
         super(handler);
+        mHandler = handler;
         mResolver = resolver;
         mUserId = userId;
         mOnUpdateRunnable = onUpdateRunnable;
@@ -75,6 +94,7 @@
         AssistantSettings assistantSettings =
                 new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
         assistantSettings.register();
+        assistantSettings.registerDeviceConfigs();
         return assistantSettings;
     }
 
@@ -91,13 +111,62 @@
         mResolver.registerContentObserver(
                 DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId);
         mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId);
-        mResolver.registerContentObserver(
-                SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId);
 
         // Update all uris on creation.
         update(null);
     }
 
+    private void registerDeviceConfigs() {
+        DeviceConfig.addOnPropertyChangedListener(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                this::postToHandler,
+                this::onDeviceConfigPropertyChanged);
+
+        // Update the fields in this class from the current state of the device config.
+        updateFromDeviceConfigFlags();
+    }
+
+    private void postToHandler(Runnable r) {
+        this.mHandler.post(r);
+    }
+
+    @VisibleForTesting
+    void onDeviceConfigPropertyChanged(String namespace, String name, String value) {
+        if (!DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT.equals(namespace)) {
+            Log.e(LOG_TAG, "Received update from DeviceConfig for unrelated namespace: "
+                    + namespace + " " + name + "=" + value);
+            return;
+        }
+
+        updateFromDeviceConfigFlags();
+    }
+
+    private void updateFromDeviceConfigFlags() {
+        String generateRepliesFlag = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                KEY_GENERATE_REPLIES);
+        if (TextUtils.isEmpty(generateRepliesFlag)) {
+            mGenerateReplies = DEFAULT_GENERATE_REPLIES;
+        } else {
+            // parseBoolean returns false for everything that isn't 'true' so there's no need to
+            // sanitise the flag string here.
+            mGenerateReplies = Boolean.parseBoolean(generateRepliesFlag);
+        }
+
+        String generateActionsFlag = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                KEY_GENERATE_ACTIONS);
+        if (TextUtils.isEmpty(generateActionsFlag)) {
+            mGenerateActions = DEFAULT_GENERATE_ACTIONS;
+        } else {
+            // parseBoolean returns false for everything that isn't 'true' so there's no need to
+            // sanitise the flag string here.
+            mGenerateActions = Boolean.parseBoolean(generateActionsFlag);
+        }
+
+        mOnUpdateRunnable.run();
+    }
+
     @Override
     public void onChange(boolean selfChange, Uri uri) {
         update(uri);
@@ -114,15 +183,6 @@
                     mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
                     ChannelImpressions.DEFAULT_STREAK_LIMIT);
         }
-        if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) {
-            mParser.setString(
-                    Settings.Global.getString(mResolver,
-                            Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-            mGenerateReplies =
-                    mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES);
-            mGenerateActions =
-                    mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS);
-        }
         if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) {
             int mNewInterruptionModelInt = Settings.Secure.getInt(
                     mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL,
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
index fd23f2b..51b723d 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
@@ -26,6 +26,7 @@
 import android.content.ContentResolver;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
@@ -62,9 +63,6 @@
         Settings.Global.putFloat(mResolver,
                 Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
         Settings.Global.putInt(mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
-                "generate_replies=true,generate_actions=true");
         Settings.Secure.putInt(mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
 
         mAssistantSettings = AssistantSettings.createForTesting(
@@ -73,56 +71,78 @@
 
     @Test
     public void testGenerateRepliesDisabled() {
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
-                "generate_replies=false");
-
-        // Notify for the settings values we updated.
-        mAssistantSettings.onChange(false,
-                Settings.Global.getUriFor(
-                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_REPLIES,
+                "false");
 
         assertFalse(mAssistantSettings.mGenerateReplies);
     }
 
     @Test
     public void testGenerateRepliesEnabled() {
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_replies=true");
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_REPLIES,
+                "true");
 
-        // Notify for the settings values we updated.
-        mAssistantSettings.onChange(false,
-                Settings.Global.getUriFor(
-                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+        assertTrue(mAssistantSettings.mGenerateReplies);
+    }
 
+    @Test
+    public void testGenerateRepliesEmptyFlag() {
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_REPLIES,
+                "false");
+
+        assertFalse(mAssistantSettings.mGenerateReplies);
+
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_REPLIES,
+                "");
+
+        // Go back to the default value.
         assertTrue(mAssistantSettings.mGenerateReplies);
     }
 
     @Test
     public void testGenerateActionsDisabled() {
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=false");
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_ACTIONS,
+                "false");
 
-        // Notify for the settings values we updated.
-        mAssistantSettings.onChange(false,
-                Settings.Global.getUriFor(
-                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
-
-        assertTrue(mAssistantSettings.mGenerateReplies);
+        assertFalse(mAssistantSettings.mGenerateActions);
     }
 
     @Test
     public void testGenerateActionsEnabled() {
-        Settings.Global.putString(mResolver,
-                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=true");
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_ACTIONS,
+                "true");
 
-        // Notify for the settings values we updated.
-        mAssistantSettings.onChange(false,
-                Settings.Global.getUriFor(
-                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+        assertTrue(mAssistantSettings.mGenerateActions);
+    }
 
-        assertTrue(mAssistantSettings.mGenerateReplies);
+    @Test
+    public void testGenerateActionsEmptyFlag() {
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_ACTIONS,
+                "false");
+
+        assertFalse(mAssistantSettings.mGenerateActions);
+
+        mAssistantSettings.onDeviceConfigPropertyChanged(
+                DeviceConfig.NAMESPACE_NOTIFICATION_ASSISTANT,
+                AssistantSettings.KEY_GENERATE_ACTIONS,
+                "");
+
+        // Go back to the default value.
+        assertTrue(mAssistantSettings.mGenerateActions);
     }
 
     @Test
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/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index e843eb4..808739a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1109,9 +1109,6 @@
         dumpSetting(s, p,
                 Settings.Global.POWER_MANAGER_CONSTANTS,
                 GlobalSettingsProto.POWER_MANAGER_CONSTANTS);
-        dumpSetting(s, p,
-                Settings.Global.PRIV_APP_OOB_ENABLED,
-                GlobalSettingsProto.PRIV_APP_OOB_ENABLED);
 
         final long prepaidSetupToken = p.start(GlobalSettingsProto.PREPAID_SETUP);
         dumpSetting(s, p,
@@ -2377,6 +2374,10 @@
                 Settings.Secure.SILENCE_GESTURE,
                 SecureSettingsProto.SILENCE_GESTURE_ENABLED);
 
+        dumpSetting(s, p,
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                SecureSettingsProto.THEME_CUSTOMIZATION_OVERLAY_PACKAGES);
+
         // Please insert new settings using the same order as in SecureSettingsProto.
         p.end(token);
 
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/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b903142..c3c3f25 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -166,6 +166,8 @@
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
     <uses-permission android:name="android.permission.SUSPEND_APPS" />
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+    <!-- Permission needed to wipe the device for Test Harness Mode -->
+    <uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" />
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
 
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..7b94c74 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,
@@ -363,7 +399,9 @@
      * Called when the notification is expanded / collapsed.
      */
     public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
-        mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded);
+        NotificationVisibility.NotificationLocation location =
+                getNotificationLocation(mEntryManager.getNotificationData().get(key));
+        mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location);
     }
 
     /**
@@ -397,10 +435,12 @@
         }
 
         @VisibleForTesting
-        void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
+        void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded,
+                NotificationVisibility.NotificationLocation location) {
             State state = getState(key);
             state.mIsUserAction = isUserAction;
             state.mIsExpanded = isExpanded;
+            state.mLocation = location;
             maybeNotifyOnNotificationExpansionChanged(key, state);
         }
 
@@ -416,6 +456,7 @@
             for (NotificationVisibility nv : newlyVisibleAr) {
                 State state = getState(nv.key);
                 state.mIsVisible = true;
+                state.mLocation = nv.location;
                 maybeNotifyOnNotificationExpansionChanged(nv.key, state);
             }
             for (NotificationVisibility nv : noLongerVisibleAr) {
@@ -460,10 +501,8 @@
             final State stateToBeLogged = new State(state);
             mUiOffloadThread.submit(() -> {
                 try {
-                    mBarService.onNotificationExpansionChanged(
-                            key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded,
-                            // TODO (b/120767764): fill in location
-                            ExpandableViewState.LOCATION_UNKNOWN /* notificationLocation */);
+                    mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction,
+                            stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal());
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
                 }
@@ -477,6 +516,8 @@
             Boolean mIsExpanded;
             @Nullable
             Boolean mIsVisible;
+            @Nullable
+            NotificationVisibility.NotificationLocation mLocation;
 
             private State() {}
 
@@ -484,10 +525,12 @@
                 this.mIsUserAction = state.mIsUserAction;
                 this.mIsExpanded = state.mIsExpanded;
                 this.mIsVisible = state.mIsVisible;
+                this.mLocation = state.mLocation;
             }
 
             private boolean isFullySet() {
-                return mIsUserAction != null && mIsExpanded != null && mIsVisible != null;
+                return mIsUserAction != null && mIsExpanded != null && mIsVisible != null
+                        && mLocation != null;
             }
         }
     }
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/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index d3c6a1d..49e6866 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -243,6 +243,9 @@
             mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0);
         }
         mAccessibilityManagerWrapper.addCallback(mAccessibilityListener);
+
+        // Respect the latest disabled-flags.
+        mCommandQueue.recomputeDisableFlags(mDisplayId, false);
     }
 
     @Override
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
index 2f6b221..5ff9d14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
@@ -28,7 +28,6 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.UiOffloadThread;
@@ -73,7 +72,8 @@
 
     @Test
     public void testExpanded() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         waitForUiOffloadThread();
 
         verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
@@ -82,7 +82,8 @@
 
     @Test
     public void testVisibleAndNotExpanded() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, false,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
@@ -94,26 +95,33 @@
 
     @Test
     public void testVisibleAndExpanded() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
         waitForUiOffloadThread();
 
         verify(mBarService).onNotificationExpansionChanged(
-                NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, true, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum());
     }
 
     @Test
     public void testExpandedAndVisible_expandedBeforeVisible() throws RemoteException {
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         mLogger.onVisibilityChanged(
-                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)),
                 Collections.emptyList());
         waitForUiOffloadThread();
 
         verify(mBarService).onNotificationExpansionChanged(
-                NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, false, true,
+                // The last location seen should be logged (the one passed to onVisibilityChanged).
+                NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.toMetricsEventEnum()
+        );
     }
 
     @Test
@@ -121,11 +129,14 @@
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
         waitForUiOffloadThread();
 
         verify(mBarService).onNotificationExpansionChanged(
-                NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, false, true,
+                // The last location seen should be logged (the one passed to onExpansionChanged).
+                NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP.toMetricsEventEnum());
     }
 
     @Test
@@ -133,15 +144,24 @@
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
-        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         waitForUiOffloadThread();
 
         verify(mBarService).onNotificationExpansionChanged(
-                NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN);
+                NOTIFICATION_KEY, false, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum());
     }
 
     private NotificationVisibility createNotificationVisibility(String key, boolean visibility) {
-        return NotificationVisibility.obtain(key, 0, 0, visibility);
+        return createNotificationVisibility(key, visibility,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+    }
+
+    private NotificationVisibility createNotificationVisibility(String key, boolean visibility,
+            NotificationVisibility.NotificationLocation location) {
+        return NotificationVisibility.obtain(key, 0, 0, visibility, location);
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 35abfd4..49db488 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -676,6 +676,10 @@
                         // 3tap and hold
                         afterLongTapTimeoutTransitionToDraggingState(event);
 
+                    } else if (isTapOutOfDistanceSlop()) {
+
+                        transitionToDelegatingStateAndClear();
+
                     } else if (mDetectTripleTap
                             // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
                             // to ensure reachability of
@@ -921,6 +925,31 @@
             // TODO: multi-display support for magnification gesture handler
             mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state);
         }
+
+        /**
+         * Detects if last action down is out of distance slop between with previous
+         * one, when triple tap is enabled.
+         *
+         * @return true if tap is out of distance slop
+         */
+        boolean isTapOutOfDistanceSlop() {
+            if (!mDetectTripleTap) return false;
+            if (mPreLastDown == null || mLastDown == null) {
+                return false;
+            }
+            final boolean outOfDistanceSlop =
+                    GestureUtils.distance(mPreLastDown, mLastDown) > mMultiTapMaxDistance;
+            if (tapCount() > 0) {
+                return outOfDistanceSlop;
+            }
+            // There's no tap in the queue here. We still need to check if this is the case that
+            // user tap screen quickly and out of distance slop.
+            if (outOfDistanceSlop
+                    && !GestureUtils.isTimedOut(mPreLastDown, mLastDown, mMultiTapMaxDelay)) {
+                return true;
+            }
+            return false;
+        }
     }
 
     private void zoomOn(float centerX, float centerY) {
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/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 121a830..39030aa 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -341,6 +341,29 @@
     @VisibleForTesting
     static final int STATE_QUICK_DOZE_DELAY = 7;
 
+    private static final int ACTIVE_REASON_UNKNOWN = 0;
+    private static final int ACTIVE_REASON_MOTION = 1;
+    private static final int ACTIVE_REASON_SCREEN = 2;
+    private static final int ACTIVE_REASON_CHARGING = 3;
+    private static final int ACTIVE_REASON_UNLOCKED = 4;
+    private static final int ACTIVE_REASON_FROM_BINDER_CALL = 5;
+    private static final int ACTIVE_REASON_FORCED = 6;
+    private static final int ACTIVE_REASON_ALARM = 7;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_UNINIT = -1;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_IGNORED = 0;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_OK = 1;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_NOT_SUPPORT = 2;
+    @VisibleForTesting
+    static final int SET_IDLE_FACTOR_RESULT_INVALID = 3;
+    @VisibleForTesting
+    static final long MIN_STATE_STEP_ALARM_CHANGE = 60 * 1000;
+    @VisibleForTesting
+    static final float MIN_PRE_IDLE_FACTOR_CHANGE = 0.05f;
+
     @VisibleForTesting
     static String stateToString(int state) {
         switch (state) {
@@ -405,6 +428,7 @@
     private long mNextSensingTimeoutAlarmTime;
     private long mCurIdleBudget;
     private long mMaintenanceStartTime;
+    private long mIdleStartTime;
 
     private int mActiveIdleOpCount;
     private PowerManager.WakeLock mActiveIdleWakeLock; // held when there are operations in progress
@@ -415,6 +439,17 @@
     private boolean mAlarmsActive;
     private boolean mReportedMaintenanceActivity;
 
+    /* Factor to apply to INACTIVE_TIMEOUT and IDLE_AFTER_INACTIVE_TIMEOUT in order to enter
+     * STATE_IDLE faster or slower. Don't apply this to SENSING_TIMEOUT or LOCATING_TIMEOUT because:
+     *   - Both of them are shorter
+     *   - Device sensor might take time be to become be stabilized
+     * Also don't apply the factor if the device is in motion because device motion provides a
+     * stronger signal than a prediction algorithm.
+     */
+    private float mPreIdleFactor;
+    private float mLastPreIdleFactor;
+    private int mActiveReason;
+
     public final AtomicFile mConfigFile;
 
     private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners =
@@ -760,6 +795,10 @@
          * exit doze. Default = true
          */
         private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock";
+        private static final String KEY_PRE_IDLE_FACTOR_LONG =
+                "pre_idle_factor_long";
+        private static final String KEY_PRE_IDLE_FACTOR_SHORT =
+                "pre_idle_factor_short";
 
         /**
          * This is the time, after becoming inactive, that we go in to the first
@@ -987,6 +1026,16 @@
          */
         public long NOTIFICATION_WHITELIST_DURATION;
 
+        /**
+         * Pre idle time factor use to make idle delay longer
+         */
+        public float PRE_IDLE_FACTOR_LONG;
+
+        /**
+         * Pre idle time factor use to make idle delay shorter
+         */
+        public float PRE_IDLE_FACTOR_SHORT;
+
         public boolean WAIT_FOR_UNLOCK;
 
         private final ContentResolver mResolver;
@@ -1082,6 +1131,8 @@
                 NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis(
                         KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L);
                 WAIT_FOR_UNLOCK = mParser.getBoolean(KEY_WAIT_FOR_UNLOCK, false);
+                PRE_IDLE_FACTOR_LONG = mParser.getFloat(KEY_PRE_IDLE_FACTOR_LONG, 1.67f);
+                PRE_IDLE_FACTOR_SHORT = mParser.getFloat(KEY_PRE_IDLE_FACTOR_SHORT, 0.33f);
             }
         }
 
@@ -1196,6 +1247,12 @@
 
             pw.print("    "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("=");
             pw.println(WAIT_FOR_UNLOCK);
+
+            pw.print("    "); pw.print(KEY_PRE_IDLE_FACTOR_LONG); pw.print("=");
+            pw.println(PRE_IDLE_FACTOR_LONG);
+
+            pw.print("    "); pw.print(KEY_PRE_IDLE_FACTOR_SHORT); pw.print("=");
+            pw.println(PRE_IDLE_FACTOR_SHORT);
         }
     }
 
@@ -1244,6 +1301,8 @@
     private static final int MSG_FINISH_IDLE_OP = 8;
     private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9;
     private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
+    private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11;
+    private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12;
 
     final class MyHandler extends Handler {
         MyHandler(Looper looper) {
@@ -1373,6 +1432,13 @@
                         constraint.stopMonitoring();
                     }
                 } break;
+                case MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR: {
+                    updatePreIdleFactor();
+                } break;
+                case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: {
+                    updatePreIdleFactor();
+                    maybeDoImmediateMaintenance();
+                } break;
             }
         }
     }
@@ -1526,6 +1592,28 @@
             DeviceIdleController.this.unregisterMaintenanceActivityListener(listener);
         }
 
+        @Override public int setPreIdleTimeoutMode(int mode) {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return DeviceIdleController.this.setPreIdleTimeoutMode(mode);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override public void resetPreIdleTimeoutMode() {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                DeviceIdleController.this.resetPreIdleTimeoutMode();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
         @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             DeviceIdleController.this.dump(fd, pw, args);
         }
@@ -1768,9 +1856,12 @@
             // Start out assuming we are charging.  If we aren't, we will at least get
             // a battery update the next time the level drops.
             mCharging = true;
+            mActiveReason = ACTIVE_REASON_UNKNOWN;
             mState = STATE_ACTIVE;
             mLightState = LIGHT_STATE_ACTIVE;
             mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
+            mPreIdleFactor = 1.0f;
+            mLastPreIdleFactor = 1.0f;
         }
 
         mBinderService = new BinderService();
@@ -2394,6 +2485,7 @@
 
     public void exitIdleInternal(String reason) {
         synchronized (this) {
+            mActiveReason = ACTIVE_REASON_FROM_BINDER_CALL;
             becomeActiveLocked(reason, Binder.getCallingUid());
         }
     }
@@ -2463,6 +2555,7 @@
         } else if (screenOn) {
             mScreenOn = true;
             if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) {
+                mActiveReason = ACTIVE_REASON_SCREEN;
                 becomeActiveLocked("screen", Process.myUid());
             }
         }
@@ -2485,6 +2578,7 @@
         } else if (charging) {
             mCharging = charging;
             if (!mForceIdle) {
+                mActiveReason = ACTIVE_REASON_CHARGING;
                 becomeActiveLocked("charging", Process.myUid());
             }
         }
@@ -2516,6 +2610,7 @@
         if (mScreenLocked != showing) {
             mScreenLocked = showing;
             if (mScreenOn && !mForceIdle && !mScreenLocked) {
+                mActiveReason = ACTIVE_REASON_UNLOCKED;
                 becomeActiveLocked("unlocked", Process.myUid());
             }
         }
@@ -2587,7 +2682,11 @@
                     mState = STATE_INACTIVE;
                     if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
                     resetIdleManagementLocked();
-                    scheduleAlarmLocked(mInactiveTimeout, false);
+                    long delay = mInactiveTimeout;
+                    if (shouldUseIdleTimeoutFactorLocked()) {
+                        delay = (long) (mPreIdleFactor * delay);
+                    }
+                    scheduleAlarmLocked(delay, false);
                     EventLogTags.writeDeviceIdle(mState, "no activity");
                 }
             }
@@ -2605,6 +2704,7 @@
         mNextIdlePendingDelay = 0;
         mNextIdleDelay = 0;
         mNextLightIdleDelay = 0;
+        mIdleStartTime = 0;
         cancelAlarmLocked();
         cancelSensingTimeoutAlarmLocked();
         cancelLocatingLocked();
@@ -2621,6 +2721,7 @@
         if (mForceIdle) {
             mForceIdle = false;
             if (mScreenOn || mCharging) {
+                mActiveReason = ACTIVE_REASON_FORCED;
                 becomeActiveLocked("exit-force", Process.myUid());
             }
         }
@@ -2740,6 +2841,7 @@
         if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
             // Whoops, there is an upcoming alarm.  We don't actually want to go idle.
             if (mState != STATE_ACTIVE) {
+                mActiveReason = ACTIVE_REASON_ALARM;
                 becomeActiveLocked("alarm", Process.myUid());
                 becomeInactiveIfAppropriateLocked();
             }
@@ -2763,7 +2865,11 @@
                 // We have now been inactive long enough, it is time to start looking
                 // for motion and sleep some more while doing so.
                 startMonitoringMotionLocked();
-                scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
+                long delay = mConstants.IDLE_AFTER_INACTIVE_TIMEOUT;
+                if (shouldUseIdleTimeoutFactorLocked()) {
+                    delay = (long) (mPreIdleFactor * delay);
+                }
+                scheduleAlarmLocked(delay, false);
                 moveToStateLocked(STATE_IDLE_PENDING, reason);
                 break;
             case STATE_IDLE_PENDING:
@@ -2834,6 +2940,7 @@
                         " ms.");
                 mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
                 if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
+                mIdleStartTime = SystemClock.elapsedRealtime();
                 mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
                 if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
                     mNextIdleDelay = mConstants.IDLE_TIMEOUT;
@@ -2934,6 +3041,127 @@
         }
     }
 
+    @VisibleForTesting
+    int setPreIdleTimeoutMode(int mode) {
+        return setPreIdleTimeoutFactor(getPreIdleTimeoutByMode(mode));
+    }
+
+    @VisibleForTesting
+    float getPreIdleTimeoutByMode(int mode) {
+        switch (mode) {
+            case PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG: {
+                return mConstants.PRE_IDLE_FACTOR_LONG;
+            }
+            case PowerManager.PRE_IDLE_TIMEOUT_MODE_SHORT: {
+                return mConstants.PRE_IDLE_FACTOR_SHORT;
+            }
+            case PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL: {
+                return 1.0f;
+            }
+            default: {
+                Slog.w(TAG, "Invalid time out factor mode: " + mode);
+                return 1.0f;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    float getPreIdleTimeoutFactor() {
+        return mPreIdleFactor;
+    }
+
+    @VisibleForTesting
+    int setPreIdleTimeoutFactor(float ratio) {
+        if (!mDeepEnabled) {
+            if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Deep Idle disable");
+            return SET_IDLE_FACTOR_RESULT_NOT_SUPPORT;
+        } else if (ratio <= MIN_PRE_IDLE_FACTOR_CHANGE) {
+            if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Invalid input");
+            return SET_IDLE_FACTOR_RESULT_INVALID;
+        } else if (Math.abs(ratio - mPreIdleFactor) < MIN_PRE_IDLE_FACTOR_CHANGE) {
+            if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: New factor same as previous factor");
+            return SET_IDLE_FACTOR_RESULT_IGNORED;
+        }
+        synchronized (this) {
+            mLastPreIdleFactor = mPreIdleFactor;
+            mPreIdleFactor = ratio;
+        }
+        if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: " + ratio);
+        postUpdatePreIdleFactor();
+        return SET_IDLE_FACTOR_RESULT_OK;
+    }
+
+    @VisibleForTesting
+    void resetPreIdleTimeoutMode() {
+        synchronized (this) {
+            mLastPreIdleFactor = mPreIdleFactor;
+            mPreIdleFactor = 1.0f;
+        }
+        if (DEBUG) Slog.d(TAG, "resetPreIdleTimeoutMode to 1.0");
+        postResetPreIdleTimeoutFactor();
+    }
+
+    private void postUpdatePreIdleFactor() {
+        mHandler.sendEmptyMessage(MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR);
+    }
+
+    private void postResetPreIdleTimeoutFactor() {
+        mHandler.sendEmptyMessage(MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR);
+    }
+
+    @VisibleForTesting
+    void updatePreIdleFactor() {
+        synchronized (this) {
+            if (!shouldUseIdleTimeoutFactorLocked()) {
+                return;
+            }
+            if (mState == STATE_INACTIVE || mState == STATE_IDLE_PENDING) {
+                if (mNextAlarmTime == 0) {
+                    return;
+                }
+                long delay = mNextAlarmTime - SystemClock.elapsedRealtime();
+                if (delay < MIN_STATE_STEP_ALARM_CHANGE) {
+                    return;
+                }
+                long newDelay = (long) (delay / mLastPreIdleFactor * mPreIdleFactor);
+                if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) {
+                    return;
+                }
+                scheduleAlarmLocked(newDelay, false);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void maybeDoImmediateMaintenance() {
+        synchronized (this) {
+            if (mState == STATE_IDLE) {
+                long duration = SystemClock.elapsedRealtime() - mIdleStartTime;
+                /* Let's trgger a immediate maintenance,
+                 * if it has been idle for a long time */
+                if (duration > mConstants.IDLE_TIMEOUT) {
+                    scheduleAlarmLocked(0, false);
+                }
+            }
+        }
+    }
+
+    private boolean shouldUseIdleTimeoutFactorLocked() {
+        // exclude ACTIVE_REASON_MOTION, for exclude device in pocket case
+        if (mActiveReason == ACTIVE_REASON_MOTION) {
+            return false;
+        }
+        return true;
+    }
+
+    /** Must only be used in tests. */
+    @VisibleForTesting
+    void setIdleStartTimeForTest(long idleStartTime) {
+        synchronized (this) {
+            mIdleStartTime = idleStartTime;
+        }
+    }
+
     void reportMaintenanceActivityIfNeededLocked() {
         boolean active = mJobsActive;
         if (active == mReportedMaintenanceActivity) {
@@ -2945,6 +3173,11 @@
         mHandler.sendMessage(msg);
     }
 
+    @VisibleForTesting
+    long getNextAlarmTime() {
+        return mNextAlarmTime;
+    }
+
     boolean isOpsInactiveLocked() {
         return mActiveIdleOpCount <= 0 && !mJobsActive && !mAlarmsActive;
     }
@@ -2994,6 +3227,7 @@
                 scheduleReportActiveLocked(type, Process.myUid());
                 addEvent(EVENT_NORMAL, type);
             }
+            mActiveReason = ACTIVE_REASON_MOTION;
             mState = STATE_ACTIVE;
             mInactiveTimeout = timeout;
             mCurIdleBudget = 0;
@@ -3401,6 +3635,11 @@
                 + "and any [-d] is ignored");
         pw.println("  motion");
         pw.println("    Simulate a motion event to bring the device out of deep doze");
+        pw.println("  pre-idle-factor [0|1|2]");
+        pw.println("    Set a new factor to idle time before step to idle"
+                + "(inactive_to and idle_after_inactive_to)");
+        pw.println("  reset-pre-idle-factor");
+        pw.println("    Reset factor to idle time to default");
     }
 
     class Shell extends ShellCommand {
@@ -3571,6 +3810,7 @@
                         }
                     }
                     if (becomeActive) {
+                        mActiveReason = ACTIVE_REASON_FORCED;
                         becomeActiveLocked((arg == null ? "all" : arg) + "-disabled",
                                 Process.myUid());
                     }
@@ -3820,6 +4060,52 @@
                     Binder.restoreCallingIdentity(token);
                 }
             }
+        } else if ("pre-idle-factor".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                int ret  = SET_IDLE_FACTOR_RESULT_UNINIT;
+                try {
+                    String arg = shell.getNextArg();
+                    boolean valid = false;
+                    int mode = 0;
+                    if (arg != null) {
+                        mode = Integer.parseInt(arg);
+                        ret = setPreIdleTimeoutMode(mode);
+                        if (ret == SET_IDLE_FACTOR_RESULT_OK) {
+                            pw.println("pre-idle-factor: " + mode);
+                            valid = true;
+                        } else if (ret == SET_IDLE_FACTOR_RESULT_NOT_SUPPORT) {
+                            valid = true;
+                            pw.println("Deep idle not supported");
+                        } else if (ret == SET_IDLE_FACTOR_RESULT_IGNORED) {
+                            valid = true;
+                            pw.println("Idle timeout factor not changed");
+                        }
+                    }
+                    if (!valid) {
+                        pw.println("Unknown idle timeout factor: " + arg
+                                + ",(error code: " + ret + ")");
+                    }
+                } catch (NumberFormatException e) {
+                    pw.println("Unknown idle timeout factor"
+                            + ",(error code: " + ret + ")");
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        } else if ("reset-pre-idle-factor".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    resetPreIdleTimeoutMode();
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
         } else {
             return shell.handleDefaultCommands(cmd);
         }
@@ -4053,6 +4339,9 @@
             if (mAlarmsActive) {
                 pw.print("  mAlarmsActive="); pw.println(mAlarmsActive);
             }
+            if (Math.abs(mPreIdleFactor - 1.0f) > MIN_PRE_IDLE_FACTOR_CHANGE) {
+                pw.print("  mPreIdleFactor="); pw.println(mPreIdleFactor);
+            }
         }
     }
 
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/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 1e9a007..190fff1 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -31,6 +31,19 @@
      */
     byte[] getFrpCredentialHandle();
 
+    /** Stores the data used to enable the Test Harness Mode after factory-resetting. */
+    void setTestHarnessModeData(byte[] data);
+
+    /**
+     * Retrieves the data used to place the device into Test Harness Mode.
+     *
+     * @throws IllegalStateException if the underlying storage is corrupt or inaccessible.
+     */
+    byte[] getTestHarnessModeData();
+
+    /** Clear out the Test Harness Mode data. */
+    void clearTestHarnessModeData();
+
     /** Update the OEM unlock enabled bit, bypassing user restriction checks. */
     void forceOemUnlockEnabled(boolean enabled);
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 21093b9..bd5ad96 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.Manifest;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -28,12 +30,10 @@
 import android.os.UserManager;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
 
 import libcore.io.IoUtils;
 
@@ -65,6 +65,40 @@
  *
  * Clients can read any number of bytes from the currently written block up to its total size by
  * invoking {@link IPersistentDataBlockService#read}
+ *
+ * The persistent data block is currently laid out as follows:
+ * | ---------BEGINNING OF PARTITION-------------|
+ * | Partition digest (32 bytes)                 |
+ * | --------------------------------------------|
+ * | PARTITION_TYPE_MARKER (4 bytes)             |
+ * | --------------------------------------------|
+ * | FRP data block length (4 bytes)             |
+ * | --------------------------------------------|
+ * | FRP data (variable length)                  |
+ * | --------------------------------------------|
+ * | ...                                         |
+ * | --------------------------------------------|
+ * | Test mode data block (10000 bytes)          |
+ * | --------------------------------------------|
+ * |     | Test mode data length (4 bytes)       |
+ * | --------------------------------------------|
+ * |     | Test mode data (variable length)      |
+ * |     | ...                                   |
+ * | --------------------------------------------|
+ * | FRP credential handle block (1000 bytes)    |
+ * | --------------------------------------------|
+ * |     | FRP credential handle length (4 bytes)|
+ * | --------------------------------------------|
+ * |     | FRP credential handle (variable len)  |
+ * |     | ...                                   |
+ * | --------------------------------------------|
+ * | OEM Unlock bit (1 byte)                     |
+ * | ---------END OF PARTITION-------------------|
+ *
+ * TODO: now that the persistent partition contains several blocks, next time someone wants a new
+ * block, we should look at adding more generic block definitions and get rid of the various raw
+ * XXX_RESERVED_SIZE and XXX_DATA_SIZE constants. That will ensure the code is easier to maintain
+ * and less likely to introduce out-of-bounds read/write.
  */
 public class PersistentDataBlockService extends SystemService {
     private static final String TAG = PersistentDataBlockService.class.getSimpleName();
@@ -73,10 +107,16 @@
     private static final int HEADER_SIZE = 8;
     // Magic number to mark block device as adhering to the format consumed by this service
     private static final int PARTITION_TYPE_MARKER = 0x19901873;
-    /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */
+    /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */
     private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000;
     /** Maximum size of the FRP credential handle that can be stored. */
     private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
+    /**
+     * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header.
+     */
+    private static final int TEST_MODE_RESERVED_SIZE = 10000;
+    /** Maximum size of the Test Harness Mode data that can be stored. */
+    private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4;
     // Limit to 100k as blocks larger than this might cause strain on Binder.
     private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
 
@@ -221,6 +261,14 @@
         return mBlockDeviceSize;
     }
 
+    private long getFrpCredentialDataOffset() {
+        return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE;
+    }
+
+    private long getTestHarnessModeDataOffset() {
+        return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE;
+    }
+
     private boolean enforceChecksumValidity() {
         byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
 
@@ -383,7 +431,7 @@
 
     private long doGetMaximumDataBlockSize() {
         long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
-                - FRP_CREDENTIAL_RESERVED_SIZE - 1;
+                - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1;
         return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
     }
 
@@ -391,6 +439,13 @@
     private native int nativeWipe(String path);
 
     private final IBinder mService = new IPersistentDataBlockService.Stub() {
+
+        /**
+         * Write the data to the persistent data block.
+         *
+         * @return a positive integer of the number of bytes that were written if successful,
+         * otherwise a negative integer indicating there was a problem
+         */
         @Override
         public int write(byte[] data) throws RemoteException {
             enforceUid(Binder.getCallingUid());
@@ -597,12 +652,51 @@
 
         @Override
         public void setFrpCredentialHandle(byte[] handle) {
-            Preconditions.checkArgument(handle == null || handle.length > 0,
-                    "handle must be null or non-empty");
-            Preconditions.checkArgument(handle == null
-                            || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE,
-                    "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+            writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+        }
 
+        @Override
+        public byte[] getFrpCredentialHandle() {
+            return readInternal(getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+        }
+
+        @Override
+        public void setTestHarnessModeData(byte[] data) {
+            writeInternal(data, getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+        }
+
+        @Override
+        public byte[] getTestHarnessModeData() {
+            byte[] data = readInternal(getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE);
+            if (data == null) {
+                return new byte[0];
+            }
+            return data;
+        }
+
+        @Override
+        public void clearTestHarnessModeData() {
+            int size = Math.min(MAX_TEST_MODE_DATA_SIZE, getTestHarnessModeData().length) + 4;
+            writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
+        }
+
+        private void writeInternal(byte[] data, long offset, int dataLength) {
+            checkArgument(data == null || data.length > 0, "data must be null or non-empty");
+            checkArgument(
+                    data == null || data.length <= dataLength,
+                    "data must not be longer than " + dataLength);
+
+            ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength + 4);
+            dataBuffer.putInt(data == null ? 0 : data.length);
+            if (data != null) {
+                dataBuffer.put(data);
+            }
+            dataBuffer.flip();
+
+            writeDataBuffer(offset, dataBuffer);
+        }
+
+        private void writeDataBuffer(long offset, ByteBuffer dataBuffer) {
             FileOutputStream outputStream;
             try {
                 outputStream = new FileOutputStream(new File(mDataBlockFile));
@@ -610,25 +704,15 @@
                 Slog.e(TAG, "partition not available", e);
                 return;
             }
-
-            ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE);
-            data.putInt(handle == null ? 0 : handle.length);
-            if (handle != null) {
-                data.put(handle);
-            }
-            data.flip();
-
             synchronized (mLock) {
                 if (!mIsWritable) {
                     IoUtils.closeQuietly(outputStream);
                     return;
                 }
-
                 try {
                     FileChannel channel = outputStream.getChannel();
-
-                    channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
-                    channel.write(data);
+                    channel.position(offset);
+                    channel.write(dataBuffer);
                     outputStream.flush();
                 } catch (IOException e) {
                     Slog.e(TAG, "unable to access persistent partition", e);
@@ -641,8 +725,7 @@
             }
         }
 
-        @Override
-        public byte[] getFrpCredentialHandle() {
+        private byte[] readInternal(long offset, int maxLength) {
             if (!enforceChecksumValidity()) {
                 throw new IllegalStateException("invalid checksum");
             }
@@ -652,14 +735,14 @@
                 inputStream = new DataInputStream(
                         new FileInputStream(new File(mDataBlockFile)));
             } catch (FileNotFoundException e) {
-                throw new IllegalStateException("frp partition not available");
+                throw new IllegalStateException("persistent partition not available");
             }
 
             try {
                 synchronized (mLock) {
-                    inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
+                    inputStream.skip(offset);
                     int length = inputStream.readInt();
-                    if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) {
+                    if (length <= 0 || length > maxLength) {
                         return null;
                     }
                     byte[] bytes = new byte[length];
@@ -667,7 +750,7 @@
                     return bytes;
                 }
             } catch (IOException e) {
-                throw new IllegalStateException("frp handle not readable", e);
+                throw new IllegalStateException("persistent partition not readable", e);
             } finally {
                 IoUtils.closeQuietly(inputStream);
             }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e879efd..c826df0 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5202,7 +5202,7 @@
                     Process.SYSTEM_UID, null /* packageName */, false);
             fout.println("Accounts: " + accounts.length);
             for (Account account : accounts) {
-                fout.println("  " + account.toSafeString());
+                fout.println("  " + account.toString());
             }
 
             // Add debug information.
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/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index fe27c49..fd402cc 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -64,7 +64,7 @@
 
     final private String COMPACT_ACTION_FILE = "file";
     final private String COMPACT_ACTION_ANON = "anon";
-    final private String COMPACT_ACTION_FULL = "full";
+    final private String COMPACT_ACTION_FULL = "all";
 
     final private String compactActionSome;
     final private String compactActionFull;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c1be387..3223f61 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -145,6 +145,11 @@
     static final int CACHED_APP_MAX_ADJ = 999;
     static final int CACHED_APP_MIN_ADJ = 900;
 
+    // This is the oom_adj level that we allow to die first. This cannot be equal to
+    // CACHED_APP_MAX_ADJ unless processes are actively being assigned an oom_score_adj of
+    // CACHED_APP_MAX_ADJ.
+    static final int CACHED_APP_LMK_FIRST_ADJ = 950;
+
     // Number of levels we have available for different service connection group importance
     // levels.
     static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
@@ -266,7 +271,7 @@
     // can't give it a different value for every possible kind of process.
     private final int[] mOomAdj = new int[] {
             FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
-            BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
+            BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_LMK_FIRST_ADJ
     };
     // These are the low-end OOM level limits.  This is appropriate for an
     // HVGA or smaller phone with less than 512MB.  Values are in KB.
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/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index d4b8eb2..944a95d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
-import android.content.ComponentName;
 import android.view.inputmethod.InputMethodInfo;
 
 import com.android.server.LocalServices;
@@ -42,11 +41,6 @@
     public abstract void hideCurrentInputMethod();
 
     /**
-     * Switches to VR InputMethod defined in the packageName of {@param componentName}.
-     */
-    public abstract void startVrInputMethodNoCheck(ComponentName componentName);
-
-    /**
      * Returns the list of installed input methods for the specified user.
      *
      * @param userId The user ID to be queried.
@@ -76,10 +70,6 @@
                 }
 
                 @Override
-                public void startVrInputMethodNoCheck(ComponentName componentName) {
-                }
-
-                @Override
                 public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
                     return Collections.emptyList();
                 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 52074a7..3fd3945 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -91,8 +91,6 @@
 import android.os.UserManager;
 import android.os.UserManagerInternal;
 import android.provider.Settings;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
 import android.text.TextUtils;
 import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
@@ -203,7 +201,6 @@
     static final int MSG_CREATE_SESSION = 1050;
 
     static final int MSG_START_INPUT = 2000;
-    static final int MSG_START_VR_INPUT = 2010;
 
     static final int MSG_UNBIND_CLIENT = 3000;
     static final int MSG_BIND_CLIENT = 3010;
@@ -381,28 +378,6 @@
         }
     }
 
-    /**
-     * VR state callback.
-     * Listens for when VR mode finishes.
-     */
-    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
-        @Override
-        public void onVrStateChanged(boolean enabled) {
-            if (!enabled) {
-                restoreNonVrImeFromSettingsNoCheck();
-            }
-        }
-    };
-
-    private void restoreNonVrImeFromSettingsNoCheck() {
-        // switch back to non-VR InputMethod from settings.
-        synchronized (mMethodMap) {
-            if (!mIsVrImeStarted) return;
-            mIsVrImeStarted = false;
-            updateFromSettingsLocked(false);
-        }
-    }
-
     private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
         private final InputMethodManagerService mImms;
         private final IInputMethodClient mClient;
@@ -597,9 +572,6 @@
 
     final ImeDisplayValidator mImeDisplayValidator;
 
-    /** True if VR IME started by {@link #startVrInputMethodNoCheck}. */
-    boolean mIsVrImeStarted;
-
     /**
      * If non-null, this is the input method service we are currently connected
      * to.
@@ -977,31 +949,6 @@
     }
 
     /**
-     * Start a VR InputMethod that matches IME with package name of {@param component}.
-     * Note: This method is called from {@link android.app.VrManager}.
-     */
-    private void startVrInputMethodNoCheck(@Nullable ComponentName component) {
-        if (component == null) {
-            // clear the current VR-only IME (if any) and restore normal IME.
-            restoreNonVrImeFromSettingsNoCheck();
-            return;
-        }
-
-        synchronized (mMethodMap) {
-            String packageName = component.getPackageName();
-            for (InputMethodInfo info : mMethodList) {
-                if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) {
-                    // set this is as current inputMethod without updating settings.
-                    setInputMethodEnabledLocked(info.getId(), true);
-                    setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID);
-                    mIsVrImeStarted = true;
-                    break;
-                }
-            }
-        }
-    }
-
-    /**
      * Handles {@link Intent#ACTION_LOCALE_CHANGED}.
      *
      * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
@@ -1452,15 +1399,6 @@
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
         mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
                 mSettings, context);
-        // Register VR-state listener.
-        IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE);
-        if (vrManager != null) {
-            try {
-                vrManager.registerListener(mVrStateCallbacks);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to register VR mode state listener.");
-            }
-        }
     }
 
     private void resetDefaultImeLocked(Context context) {
@@ -1688,25 +1626,7 @@
             }
             final long ident = Binder.clearCallingIdentity();
             try {
-                return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-
-    @Override
-    public List<InputMethodInfo> getVrInputMethodList() {
-        final int callingUserId = UserHandle.getCallingUserId();
-        synchronized (mMethodMap) {
-            final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
-                    mSettings.getCurrentUserId(), null);
-            if (resolvedUserIds.length != 1) {
-                return Collections.emptyList();
-            }
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]);
+                return getInputMethodListLocked(resolvedUserIds[0]);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1732,8 +1652,7 @@
     }
 
     @GuardedBy("mMethodMap")
-    private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly,
-            @UserIdInt int userId) {
+    private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId) {
         final ArrayList<InputMethodInfo> methodList;
         if (userId == mSettings.getCurrentUserId()) {
             // Create a copy.
@@ -1747,7 +1666,6 @@
             queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
                     methodList);
         }
-        methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly);
         return methodList;
     }
 
@@ -2021,8 +1939,8 @@
         }
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
-        final int displayIdToShowIme = computeImeDisplayIdForTarget(
-                cs.selfReportedDisplayId, mIsVrImeStarted, mImeDisplayValidator);
+        final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
+                mImeDisplayValidator);
 
         if (mCurClient != cs) {
             // Was the keyguard locked when switching over to the new client?
@@ -2131,17 +2049,11 @@
      * Find the display where the IME should be shown.
      *
      * @param displayId the ID of the display where the IME client target is.
-     * @param isVrImeStarted {@code true} if VR IME started, {@code false} otherwise.
      * @param checker instance of {@link ImeDisplayValidator} which is used for
      *                checking display config to adjust the final target display.
      * @return The ID of the display where the IME should be shown.
      */
-    static int computeImeDisplayIdForTarget(int displayId, boolean isVrImeStarted,
-            @NonNull ImeDisplayValidator checker) {
-        // For VR IME, we always show in default display.
-        if (isVrImeStarted) {
-            return DEFAULT_DISPLAY;
-        }
+    static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
         if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
             // We always assume that the default display id suitable to show the IME window.
             return DEFAULT_DISPLAY;
@@ -3627,9 +3539,6 @@
             case MSG_SET_INTERACTIVE:
                 handleSetInteractive(msg.arg1 != 0);
                 return true;
-            case MSG_START_VR_INPUT:
-                startVrInputMethodNoCheck((ComponentName) msg.obj);
-                return true;
             case MSG_REPORT_FULLSCREEN_MODE: {
                 final boolean fullscreen = msg.arg1 != 0;
                 final ClientState clientState = (ClientState)msg.obj;
@@ -3716,6 +3625,9 @@
             try {
                 final InputMethodInfo imi = new InputMethodInfo(context, ri,
                         additionalSubtypeMap.get(imeId));
+                if (imi.isVrOnly()) {
+                    continue;  // Skip VR-only IME, which isn't supported for now.
+                }
                 methodList.add(imi);
                 methodMap.put(imi.getId(), imi);
                 if (DEBUG) {
@@ -4099,17 +4011,7 @@
 
     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
             boolean setSubtypeOnly) {
-        // Updates to InputMethod are transient in VR mode. Its not included in history.
-        final boolean isVrInput = imi != null && imi.isVrOnly();
-        if (!isVrInput) {
-            // Update the history of InputMethod and Subtype
-            mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
-        }
-
-        if (isVrInput) {
-            // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
-            return;
-        }
+        mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
 
         // Set Subtype here
         if (imi == null || subtypeId < 0) {
@@ -4204,28 +4106,9 @@
         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(false, userId);
+            return getInputMethodListLocked(userId);
         }
     }
 
@@ -4257,11 +4140,6 @@
         }
 
         @Override
-        public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
-            mService.mHandler.obtainMessage(MSG_START_VR_INPUT, componentName).sendToTarget();
-        }
-
-        @Override
         public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
             return mService.getInputMethodListAsUser(userId);
         }
@@ -4648,7 +4526,7 @@
                     mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
             for (int userId : userIds) {
                 final List<InputMethodInfo> methods = all
-                        ? getInputMethodListLocked(false, userId)
+                        ? getInputMethodListLocked(userId)
                         : getEnabledInputMethodListLocked(userId);
                 if (userIds.length > 1) {
                     pr.print("User #");
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 6f0c5e8..3222143 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -159,11 +159,6 @@
                         }
 
                         @Override
-                        public void startVrInputMethodNoCheck(ComponentName componentName) {
-                            reportNotSupported();
-                        }
-
-                        @Override
                         public List<InputMethodInfo> getInputMethodListAsUser(
                                 @UserIdInt int userId) {
                             return userIdToInputMethodInfoMapper.getAsList(userId);
@@ -1244,13 +1239,6 @@
 
         @BinderThread
         @Override
-        public List<InputMethodInfo> getVrInputMethodList() {
-            reportNotSupported();
-            return Collections.emptyList();
-        }
-
-        @BinderThread
-        @Override
         public List<InputMethodInfo> getEnabledInputMethodList() {
             return mInputMethodInfoMap.getAsList(UserHandle.getUserId(Binder.getCallingUid()));
         }
@@ -1531,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/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index cc0ac9a..bd12075 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -443,6 +443,16 @@
                 "qc_window_size_rare_ms";
         private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
                 "qc_max_execution_time_ms";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+                "qc_max_job_count_active";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+                "qc_max_job_count_working";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+                "qc_max_job_count_frequent";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+                "qc_max_job_count_rare";
+        private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME =
+                "qc_max_count_per_allowed_time";
 
         private static final int DEFAULT_MIN_IDLE_COUNT = 1;
         private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -479,6 +489,15 @@
                 24 * 60 * 60 * 1000L; // 24 hours
         private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
                 4 * 60 * 60 * 1000L; // 4 hours
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+                200; // 1200/hr
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+                1200; // 600/hr
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+                1800; // 225/hr
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+                2400; // 100/hr
+        private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20;
 
         /**
          * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -682,6 +701,41 @@
         public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
                 DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS;
 
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE;
+
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING;
+
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT;
+
+        /**
+         * The maximum number of jobs an app can run within this particular standby bucket's
+         * window size.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE;
+
+        /**
+         * The maximum number of jobs that can run within the past
+         * {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
+         */
+        public int QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME =
+                DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
         void updateConstantsLocked(String value) {
@@ -767,6 +821,21 @@
             QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
                     KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
                     DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE);
+            QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt(
+                    KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME,
+                    DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
         }
 
         void dump(IndentingPrintWriter pw) {
@@ -823,6 +892,16 @@
                     QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println();
             pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
                     QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE).println();
+            pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME).println();
             pw.decreaseIndent();
         }
 
@@ -872,6 +951,16 @@
                     QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS);
             proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
                     QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME,
+                    QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
             proto.end(qcToken);
 
             proto.end(token);
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index c16d1b4..5a0b991 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -230,10 +230,10 @@
     @VisibleForTesting
     static class ExecutionStats {
         /**
-         * The time at which this record should be considered invalid, in the elapsed realtime
-         * timebase.
+         * The time after which this record should be considered invalid (out of date), in the
+         * elapsed realtime timebase.
          */
-        public long invalidTimeElapsed;
+        public long expirationTimeElapsed;
 
         public long windowSizeMs;
 
@@ -241,29 +241,45 @@
         public long executionTimeInWindowMs;
         public int bgJobCountInWindow;
 
-        /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */
+        /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */
         public long executionTimeInMaxPeriodMs;
         public int bgJobCountInMaxPeriod;
 
         /**
-         * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals
-         * the quota. This is only valid if
-         * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or
-         * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}.
+         * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs}
+         * equals the quota. This is only valid if
+         * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or
+         * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}.
          */
         public long quotaCutoffTimeElapsed;
 
+        /**
+         * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the
+         * elapsed realtime timebase.
+         */
+        public long jobCountExpirationTimeElapsed;
+
+        /**
+         * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}.
+         * It may contain a few stale entries since cleanup won't happen exactly every
+         * {@link #mAllowedTimePerPeriodMs}.
+         */
+        public int jobCountInAllowedTime;
+
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("invalidTime=").append(invalidTimeElapsed).append(", ")
+                    .append("expirationTime=").append(expirationTimeElapsed).append(", ")
                     .append("windowSize=").append(windowSizeMs).append(", ")
                     .append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ")
                     .append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ")
                     .append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs)
                     .append(", ")
                     .append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ")
-                    .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed)
+                    .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed).append(", ")
+                    .append("jobCountExpirationTime").append(jobCountExpirationTimeElapsed)
+                    .append(", ")
+                    .append("jobCountInAllowedTime").append(jobCountInAllowedTime)
                     .toString();
         }
 
@@ -271,13 +287,15 @@
         public boolean equals(Object obj) {
             if (obj instanceof ExecutionStats) {
                 ExecutionStats other = (ExecutionStats) obj;
-                return this.invalidTimeElapsed == other.invalidTimeElapsed
+                return this.expirationTimeElapsed == other.expirationTimeElapsed
                         && this.windowSizeMs == other.windowSizeMs
                         && this.executionTimeInWindowMs == other.executionTimeInWindowMs
                         && this.bgJobCountInWindow == other.bgJobCountInWindow
                         && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
                         && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
-                        && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed;
+                        && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed
+                        && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed
+                        && this.jobCountInAllowedTime == other.jobCountInAllowedTime;
             } else {
                 return false;
             }
@@ -286,13 +304,15 @@
         @Override
         public int hashCode() {
             int result = 0;
-            result = 31 * result + hashLong(invalidTimeElapsed);
+            result = 31 * result + hashLong(expirationTimeElapsed);
             result = 31 * result + hashLong(windowSizeMs);
             result = 31 * result + hashLong(executionTimeInWindowMs);
             result = 31 * result + bgJobCountInWindow;
             result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
             result = 31 * result + bgJobCountInMaxPeriod;
             result = 31 * result + hashLong(quotaCutoffTimeElapsed);
+            result = 31 * result + hashLong(jobCountExpirationTimeElapsed);
+            result = 31 * result + jobCountInAllowedTime;
             return result;
         }
     }
@@ -320,7 +340,7 @@
 
     /**
      * List of jobs that started while the UID was in the TOP state. There will be no more than
-     * 16 ({@link JobSchedulerService.MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
+     * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
      * fine.
      */
     private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
@@ -343,7 +363,7 @@
     private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
 
     /**
-     * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS}
+     * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
      * window.
      */
     private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS;
@@ -355,17 +375,20 @@
     private long mQuotaBufferMs = 30 * 1000L; // 30 seconds
 
     /**
-     * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when
-     * an app will have enough quota to transition from out-of-quota to in-quota.
+     * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
+     * when an app will have enough quota to transition from out-of-quota to in-quota.
      */
     private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
 
     /**
-     * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an
+     * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an
      * app will have enough quota to transition from out-of-quota to in-quota.
      */
     private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
 
+    /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */
+    private int mMaxJobCountPerAllowedTime = 20;
+
     private long mNextCleanupTimeElapsed = 0;
     private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
             new AlarmManager.OnAlarmListener() {
@@ -412,6 +435,23 @@
     /** The maximum period any bucket can have. */
     private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS;
 
+    /**
+     * The maximum number of jobs based on its standby bucket. For each max value count in the
+     * array, the app will not be allowed to run more than that many number of jobs within the
+     * latest time interval of its rolling window size.
+     *
+     * @see #mBucketPeriodsMs
+     */
+    private final int[] mMaxBucketJobCounts = new int[] {
+            200,  // ACTIVE   -- 1200/hr
+            1200, // WORKING  -- 600/hr
+            1800, // FREQUENT -- 225/hr
+            2400  // RARE     -- 100/hr
+    };
+
+    /** The minimum number of jobs that any bucket will be allowed to run. */
+    private static final int MIN_BUCKET_JOB_COUNT = 100;
+
     /** An app has reached its quota. The message should contain a {@link Package} object. */
     private static final int MSG_REACHED_QUOTA = 0;
     /** Drop any old timing sessions. */
@@ -463,17 +503,21 @@
     @Override
     public void prepareForExecutionLocked(JobStatus jobStatus) {
         if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
+
+        final int uid = jobStatus.getSourceUid();
+        if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
+            mTopStartedJobs.add(jobStatus);
+            // Top jobs won't count towards quota so there's no need to involve the Timer.
+            return;
+        }
+
         final int userId = jobStatus.getSourceUserId();
         final String packageName = jobStatus.getSourcePackageName();
-        final int uid = jobStatus.getSourceUid();
         Timer timer = mPkgTimers.get(userId, packageName);
         if (timer == null) {
             timer = new Timer(uid, userId, packageName);
             mPkgTimers.add(userId, packageName, timer);
         }
-        if (mActivityManagerInternal.getUidProcessState(uid) == ActivityManager.PROCESS_STATE_TOP) {
-            mTopStartedJobs.add(jobStatus);
-        }
         timer.startTrackingJob(jobStatus);
     }
 
@@ -548,6 +592,36 @@
             mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
             changed = true;
         }
+        int newMaxCountPerAllowedPeriod = Math.max(10,
+                mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME);
+        if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) {
+            mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod;
+            changed = true;
+        }
+        int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+                Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE));
+        if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
+            mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
+            changed = true;
+        }
+        int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+                Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING));
+        if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
+            mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
+            changed = true;
+        }
+        int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+                Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT));
+        if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
+            mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
+            changed = true;
+        }
+        int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
+                Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE));
+        if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
+            mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
+            changed = true;
+        }
 
         if (changed) {
             // Update job bookkeeping out of band.
@@ -631,18 +705,39 @@
         return isTopStartedJob(jobStatus)
                 || isUidInForeground(jobStatus.getSourceUid())
                 || isWithinQuotaLocked(
-                      jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
     }
 
-    private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
+    @VisibleForTesting
+    boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
             final int standbyBucket) {
         if (standbyBucket == NEVER_INDEX) return false;
         // This check is needed in case the flag is toggled after a job has been registered.
         if (!mShouldThrottle) return true;
 
         // Quota constraint is not enforced while charging or when parole is on.
-        return mChargeTracker.isCharging() || mInParole
-                || getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0;
+        if (mChargeTracker.isCharging() || mInParole) {
+            return true;
+        }
+
+        return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0
+                && isUnderJobCountQuotaLocked(userId, packageName, standbyBucket);
+    }
+
+    private boolean isUnderJobCountQuotaLocked(final int userId, @NonNull final String packageName,
+            final int standbyBucket) {
+        ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket, false);
+        return isUnderJobCountQuotaLocked(stats, standbyBucket);
+    }
+
+    private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
+            final int standbyBucket) {
+        final long now = sElapsedRealtimeClock.millis();
+        final boolean isUnderAllowedTimeQuota =
+                (stats.jobCountExpirationTimeElapsed <= now
+                        || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime);
+        return isUnderAllowedTimeQuota
+                && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
     }
 
     @VisibleForTesting
@@ -679,6 +774,13 @@
     @NonNull
     ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
             final int standbyBucket) {
+        return getExecutionStatsLocked(userId, packageName, standbyBucket, true);
+    }
+
+    @NonNull
+    private ExecutionStats getExecutionStatsLocked(final int userId,
+            @NonNull final String packageName, final int standbyBucket,
+            final boolean refreshStatsIfOld) {
         if (standbyBucket == NEVER_INDEX) {
             Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app.");
             return new ExecutionStats();
@@ -693,14 +795,16 @@
             stats = new ExecutionStats();
             appStats[standbyBucket] = stats;
         }
-        final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
-        Timer timer = mPkgTimers.get(userId, packageName);
-        if ((timer != null && timer.isActive())
-                || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis()
-                || stats.windowSizeMs != bucketWindowSizeMs) {
-            // The stats are no longer valid.
-            stats.windowSizeMs = bucketWindowSizeMs;
-            updateExecutionStatsLocked(userId, packageName, stats);
+        if (refreshStatsIfOld) {
+            final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
+            Timer timer = mPkgTimers.get(userId, packageName);
+            if ((timer != null && timer.isActive())
+                    || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
+                    || stats.windowSizeMs != bucketWindowSizeMs) {
+                // The stats are no longer valid.
+                stats.windowSizeMs = bucketWindowSizeMs;
+                updateExecutionStatsLocked(userId, packageName, stats);
+            }
         }
 
         return stats;
@@ -717,14 +821,14 @@
 
         Timer timer = mPkgTimers.get(userId, packageName);
         final long nowElapsed = sElapsedRealtimeClock.millis();
-        stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS;
+        stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS;
         if (timer != null && timer.isActive()) {
             stats.executionTimeInWindowMs =
                     stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed);
             stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount();
             // If the timer is active, the value will be stale at the next method call, so
             // invalidate now.
-            stats.invalidTimeElapsed = nowElapsed;
+            stats.expirationTimeElapsed = nowElapsed;
             if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
                 stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
                         nowElapsed - mAllowedTimeIntoQuotaMs);
@@ -800,7 +904,7 @@
                 break;
             }
         }
-        stats.invalidTimeElapsed = nowElapsed + emptyTimeMs;
+        stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
     }
 
     private void invalidateAllExecutionStatsLocked(final int userId,
@@ -811,13 +915,35 @@
             for (int i = 0; i < appStats.length; ++i) {
                 ExecutionStats stats = appStats[i];
                 if (stats != null) {
-                    stats.invalidTimeElapsed = nowElapsed;
+                    stats.expirationTimeElapsed = nowElapsed;
                 }
             }
         }
     }
 
     @VisibleForTesting
+    void incrementJobCount(final int userId, @NonNull final String packageName, int count) {
+        final long now = sElapsedRealtimeClock.millis();
+        ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
+        if (appStats == null) {
+            appStats = new ExecutionStats[mBucketPeriodsMs.length];
+            mExecutionStatsCache.add(userId, packageName, appStats);
+        }
+        for (int i = 0; i < appStats.length; ++i) {
+            ExecutionStats stats = appStats[i];
+            if (stats == null) {
+                stats = new ExecutionStats();
+                appStats[i] = stats;
+            }
+            if (stats.jobCountExpirationTimeElapsed <= now) {
+                stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
+                stats.jobCountInAllowedTime = 0;
+            }
+            stats.jobCountInAllowedTime += count;
+        }
+    }
+
+    @VisibleForTesting
     void saveTimingSession(final int userId, @NonNull final String packageName,
             @NonNull final TimingSession session) {
         synchronized (mLock) {
@@ -1023,9 +1149,12 @@
 
         final String pkgString = string(userId, packageName);
         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
+        final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
+
         QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
         if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
-                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) {
+                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
+                && isUnderJobCountQuota) {
             // Already in quota. Why was this method called?
             if (DEBUG) {
                 Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
@@ -1042,18 +1171,22 @@
             mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
             return;
         }
+
         if (alarmListener == null) {
             alarmListener = new QcAlarmListener(userId, packageName);
             mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
         }
 
         // The time this app will have quota again.
-        long inQuotaTimeElapsed =
-                stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
+        long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
         if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) {
             inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
                     stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS);
         }
+        if (!isUnderJobCountQuota) {
+            inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
+                    stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
+        }
         // Only schedule the alarm if:
         // 1. There isn't one currently scheduled
         // 2. The new alarm is significantly earlier than the previous alarm (which could be the
@@ -1228,6 +1361,7 @@
                 mRunningBgJobs.add(jobStatus);
                 if (shouldTrackLocked()) {
                     mBgJobCount++;
+                    incrementJobCount(mPkg.userId, mPkg.packageName, 1);
                     if (mRunningBgJobs.size() == 1) {
                         // Started tracking the first job.
                         mStartTimeElapsed = sElapsedRealtimeClock.millis();
@@ -1324,6 +1458,7 @@
                         // repeatedly plugged in and unplugged, or an app changes foreground state
                         // very frequently, the job count for a package may be artificially high.
                         mBgJobCount = mRunningBgJobs.size();
+                        incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount);
                         // Starting the timer means that all cached execution stats are now
                         // incorrect.
                         invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
@@ -1604,6 +1739,12 @@
 
     @VisibleForTesting
     @NonNull
+    int[] getBucketMaxJobCounts() {
+        return mMaxBucketJobCounts;
+    }
+
+    @VisibleForTesting
+    @NonNull
     long[] getBucketWindowSizes() {
         return mBucketPeriodsMs;
     }
@@ -1631,6 +1772,11 @@
     }
 
     @VisibleForTesting
+    int getMaxJobCountPerAllowedTime() {
+        return mMaxJobCountPerAllowedTime;
+    }
+
+    @VisibleForTesting
     @Nullable
     List<TimingSession> getTimingSessions(int userId, String packageName) {
         return mTimingSessions.get(userId, packageName);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8734ceb6..a9ae74f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -947,6 +947,10 @@
     public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
             String managedUserPassword) {
         checkWritePermission(userId);
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires secure lock screen feature.");
+        }
         synchronized (mSeparateChallengeLock) {
             setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword);
         }
@@ -1305,6 +1309,10 @@
     public void setLockCredential(String credential, int type, String savedCredential,
             int requestedQuality, int userId)
             throws RemoteException {
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            throw new UnsupportedOperationException(
+                    "This operation requires secure lock screen feature");
+        }
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
             setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId);
@@ -2906,6 +2914,10 @@
         @Override
         public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
                 byte[] token, int requestedQuality, int userId) {
+            if (!mLockPatternUtils.hasSecureLockScreen()) {
+                throw new UnsupportedOperationException(
+                        "This operation requires secure lock screen feature.");
+            }
             try {
                 return LockSettingsService.this.setLockCredentialWithToken(credential, type,
                         tokenHandle, token, requestedQuality, userId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 07f23ce..6163077 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -18,6 +18,7 @@
 
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+
 import static com.android.internal.widget.LockPatternUtils.stringToPattern;
 
 import android.app.ActivityManager;
@@ -58,6 +59,18 @@
             mCurrentUserId = ActivityManager.getService().getCurrentUser().id;
 
             parseArgs();
+            if (!mLockPatternUtils.hasSecureLockScreen()) {
+                switch (cmd) {
+                    case COMMAND_HELP:
+                    case COMMAND_GET_DISABLED:
+                    case COMMAND_SET_DISABLED:
+                        break;
+                    default:
+                        getErrPrintWriter().println(
+                                "The device does not support lock screen - ignoring the command.");
+                        return -1;
+                }
+            }
             if (!checkCredential()) {
                 return -1;
             }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 47a5597..a164686 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -845,10 +845,12 @@
                         reportSeen(r);
                     }
                     r.setVisibility(true, nv.rank, nv.count);
+                    boolean isHun = (nv.location
+                            == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
                     // the NotificationRecord to ensure the expansion state is up-to-date.
-                    if (r.hasBeenVisiblyExpanded()) {
-                        logSmartSuggestionsVisible(r);
+                    if (isHun || r.hasBeenVisiblyExpanded()) {
+                        logSmartSuggestionsVisible(r, nv.location.toMetricsEventEnum());
                     }
                     maybeRecordInterruptionLocked(r);
                     nv.recycle();
@@ -876,7 +878,7 @@
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
                     // the NotificationRecord to ensure the expansion state is up-to-date.
                     if (r.hasBeenVisiblyExpanded()) {
-                        logSmartSuggestionsVisible(r);
+                        logSmartSuggestionsVisible(r, notificationLocation);
                     }
                     if (userAction) {
                         MetricsLogger.action(r.getItemLogMaker()
@@ -952,7 +954,7 @@
     };
 
     @VisibleForTesting
-    void logSmartSuggestionsVisible(NotificationRecord r) {
+    void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
         // If the newly visible notification has smart suggestions
         // then log that the user has seen them.
         if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0)
@@ -966,7 +968,10 @@
                             r.getNumSmartActionsAdded())
                     .addTaggedData(
                             MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
-                            r.getSuggestionsGeneratedByAssistant() ? 1 : 0);
+                            r.getSuggestionsGeneratedByAssistant() ? 1 : 0)
+                    // The fields in the NotificationVisibility.NotificationLocation enum map
+                    // directly to the fields in the MetricsEvent.NotificationLocation enum.
+                    .addTaggedData(MetricsEvent.NOTIFICATION_LOCATION, notificationLocation);
             mMetricsLogger.write(logMaker);
         }
     }
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/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
index 2ae424d..5b765df 100644
--- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -22,63 +22,117 @@
 import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Process;
 import android.os.ServiceManager;
+import android.util.ByteStringUtils;
+import android.util.EventLog;
 import android.util.Log;
 
 import com.android.server.pm.dex.DexLogger;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
- * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and
- * charging. The actual logging is performed by {@link DexLogger}.
+ * Scheduled jobs related to logging of app dynamic code loading. The idle logging job runs daily
+ * while idle and charging  and calls {@link DexLogger} to write dynamic code information to the
+ * event log. The audit watching job scans the event log periodically while idle to find AVC audit
+ * messages indicating use of dynamic native code and adds the information to {@link DexLogger}.
  * {@hide}
  */
 public class DynamicCodeLoggingService extends JobService {
     private static final String TAG = DynamicCodeLoggingService.class.getName();
 
-    private static final int JOB_ID = 2030028;
-    private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
-
-    private volatile boolean mStopRequested = false;
-
     private static final boolean DEBUG = false;
 
+    private static final int IDLE_LOGGING_JOB_ID = 2030028;
+    private static final int AUDIT_WATCHING_JOB_ID = 203142925;
+
+    private static final long IDLE_LOGGING_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
+    private static final long AUDIT_WATCHING_PERIOD_MILLIS = TimeUnit.HOURS.toMillis(2);
+
+    private static final int AUDIT_AVC = 1400;  // Defined in linux/audit.h
+    private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " ";
+
+    private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN =
+            Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*"
+                    + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*"
+                    + "\\bscontext=u:r:untrusted_app_2(?:5|7):.*"
+                    + "\\btcontext=u:object_r:app_data_file:.*"
+                    + "\\btclass=file\\b.*");
+
+    private volatile boolean mIdleLoggingStopRequested = false;
+    private volatile boolean mAuditWatchingStopRequested = false;
+
     /**
-     * Schedule our job with the {@link JobScheduler}.
+     * Schedule our jobs with the {@link JobScheduler}.
      */
     public static void schedule(Context context) {
         ComponentName serviceName = new ComponentName(
                 "android", DynamicCodeLoggingService.class.getName());
 
         JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
-        js.schedule(new JobInfo.Builder(JOB_ID, serviceName)
+        js.schedule(new JobInfo.Builder(IDLE_LOGGING_JOB_ID, serviceName)
                 .setRequiresDeviceIdle(true)
                 .setRequiresCharging(true)
-                .setPeriodic(PERIOD_MILLIS)
+                .setPeriodic(IDLE_LOGGING_PERIOD_MILLIS)
                 .build());
+        js.schedule(new JobInfo.Builder(AUDIT_WATCHING_JOB_ID, serviceName)
+                .setRequiresDeviceIdle(true)
+                .setRequiresBatteryNotLow(true)
+                .setPeriodic(AUDIT_WATCHING_PERIOD_MILLIS)
+                .build());
+
         if (DEBUG) {
-            Log.d(TAG, "Job scheduled");
+            Log.d(TAG, "Jobs scheduled");
         }
     }
 
     @Override
     public boolean onStartJob(JobParameters params) {
+        int jobId = params.getJobId();
         if (DEBUG) {
-            Log.d(TAG, "onStartJob");
+            Log.d(TAG, "onStartJob " + jobId);
         }
-        mStopRequested = false;
-        new IdleLoggingThread(params).start();
-        return true;  // Job is running on another thread
+        switch (jobId) {
+            case IDLE_LOGGING_JOB_ID:
+                mIdleLoggingStopRequested = false;
+                new IdleLoggingThread(params).start();
+                return true;  // Job is running on another thread
+            case AUDIT_WATCHING_JOB_ID:
+                mAuditWatchingStopRequested = false;
+                new AuditWatchingThread(params).start();
+                return true;  // Job is running on another thread
+            default:
+                // Shouldn't happen, but indicate nothing is running.
+                return false;
+        }
     }
 
     @Override
     public boolean onStopJob(JobParameters params) {
+        int jobId = params.getJobId();
         if (DEBUG) {
-            Log.d(TAG, "onStopJob");
+            Log.d(TAG, "onStopJob " + jobId);
         }
-        mStopRequested = true;
-        return true;  // Requests job be re-scheduled.
+        switch (jobId) {
+            case IDLE_LOGGING_JOB_ID:
+                mIdleLoggingStopRequested = true;
+                return true;  // Requests job be re-scheduled.
+            case AUDIT_WATCHING_JOB_ID:
+                mAuditWatchingStopRequested = true;
+                return true;  // Requests job be re-scheduled.
+            default:
+                return false;
+        }
+    }
+
+    private static DexLogger getDexLogger() {
+        PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
+        return pm.getDexManager().getDexLogger();
     }
 
     private class IdleLoggingThread extends Thread {
@@ -92,14 +146,13 @@
         @Override
         public void run() {
             if (DEBUG) {
-                Log.d(TAG, "Starting logging run");
+                Log.d(TAG, "Starting IdleLoggingJob run");
             }
 
-            PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
-            DexLogger dexLogger = pm.getDexManager().getDexLogger();
+            DexLogger dexLogger = getDexLogger();
             for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) {
-                if (mStopRequested) {
-                    Log.w(TAG, "Stopping logging run at scheduler request");
+                if (mIdleLoggingStopRequested) {
+                    Log.w(TAG, "Stopping IdleLoggingJob run at scheduler request");
                     return;
                 }
 
@@ -108,8 +161,128 @@
 
             jobFinished(mParams, /* reschedule */ false);
             if (DEBUG) {
-                Log.d(TAG, "Finished logging run");
+                Log.d(TAG, "Finished IdleLoggingJob run");
             }
         }
     }
+
+    private class AuditWatchingThread extends Thread {
+        private final JobParameters mParams;
+
+        AuditWatchingThread(JobParameters params) {
+            super("DynamicCodeLoggingService_AuditWatchingJob");
+            mParams = params;
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG) {
+                Log.d(TAG, "Starting AuditWatchingJob run");
+            }
+
+            if (processAuditEvents()) {
+                jobFinished(mParams, /* reschedule */ false);
+                if (DEBUG) {
+                    Log.d(TAG, "Finished AuditWatchingJob run");
+                }
+            }
+        }
+
+        private boolean processAuditEvents() {
+            // Scan the event log for SELinux (avc) audit messages indicating when an
+            // (untrusted) app has executed native code from an app data
+            // file. Matches are recorded in DexLogger.
+            //
+            // These messages come from the kernel audit system via logd. (Note that
+            // some devices may not generate these messages at all, or the format may
+            // be different, in which case nothing will be recorded.)
+            //
+            // The messages use the auditd tag and the uid of the app that executed
+            // the code.
+            //
+            // A typical message might look like this:
+            // type=1400 audit(0.0:521): avc: granted { execute } for comm="executable"
+            //  path="/data/data/com.dummy.app/executable" dev="sda13" ino=1655302
+            //  scontext=u:r:untrusted_app_27:s0:c66,c257,c512,c768
+            //  tcontext=u:object_r:app_data_file:s0:c66,c257,c512,c768 tclass=file
+            //
+            // The information we want is the uid and the path. (Note this may be
+            // either a quoted string, as shown above, or a sequence of hex-encoded
+            // bytes.)
+            //
+            // On each run we process all the matching events in the log. This may
+            // mean re-processing events we have already seen, and in any case there
+            // may be duplicate events for the same app+file. These are de-duplicated
+            // by DexLogger.
+            //
+            // Note that any app can write a message to the event log, including one
+            // that looks exactly like an AVC audit message, so the information may
+            // be spoofed by an app; in such a case the uid we see will be the app
+            // that generated the spoof message.
+
+            try {
+                int[] tags = { EventLog.getTagCode("auditd") };
+                if (tags[0] == -1) {
+                    // auditd is not a registered tag on this system, so there can't be any messages
+                    // of interest.
+                    return true;
+                }
+
+                DexLogger dexLogger = getDexLogger();
+
+                List<EventLog.Event> events = new ArrayList<>();
+                EventLog.readEvents(tags, events);
+
+                for (int i = 0; i < events.size(); ++i) {
+                    if (mAuditWatchingStopRequested) {
+                        Log.w(TAG, "Stopping AuditWatchingJob run at scheduler request");
+                        return false;
+                    }
+
+                    EventLog.Event event = events.get(i);
+
+                    // Discard clearly unrelated messages as quickly as we can.
+                    int uid = event.getUid();
+                    if (!Process.isApplicationUid(uid)) {
+                        continue;
+                    }
+                    Object data = event.getData();
+                    if (!(data instanceof String)) {
+                        continue;
+                    }
+                    String message = (String) data;
+                    if (!message.startsWith(AVC_PREFIX)) {
+                        continue;
+                    }
+
+                    // And then use a regular expression to verify it's one of the messages we're
+                    // interested in and to extract the path of the file being loaded.
+                    Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher(message);
+                    if (!matcher.matches()) {
+                        continue;
+                    }
+                    String path = matcher.group(1);
+                    if (path == null) {
+                        // If the path contains spaces or various weird characters the kernel
+                        // hex-encodes the bytes; we need to undo that.
+                        path = unhex(matcher.group(2));
+                    }
+                    dexLogger.recordNative(uid, path);
+                }
+
+                return true;
+            } catch (Exception e) {
+                Log.e(TAG, "AuditWatchingJob failed", e);
+                return true;
+            }
+        }
+    }
+
+    private static String unhex(String hexEncodedPath) {
+        byte[] bytes = ByteStringUtils.fromHexToByteArray(hexEncodedPath);
+        if (bytes == null || bytes.length == 0) {
+            return "";
+        }
+        return new String(bytes);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 8a6105c..efafdfa 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -611,6 +611,31 @@
         }
     }
 
+    public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags)
+            throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+
+        try {
+            mInstalld.snapshotAppData(null, pkg, userId, storageFlags);
+            return true;
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    public boolean restoreAppDataSnapshot(String pkg, @AppIdInt  int appId, long ceDataInode,
+            String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+
+        try {
+            mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId,
+                    storageFlags);
+            return true;
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     private static void assertValidInstructionSet(String instructionSet)
             throws InstallerException {
         for (String abi : Build.SUPPORTED_ABIS) {
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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 09fe26d..9100f6a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -42,6 +42,7 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -201,6 +202,7 @@
 import android.content.pm.dex.ArtManager;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.dex.IArtManager;
+import android.content.rollback.IRollbackManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -10572,8 +10574,6 @@
                 Log.d(TAG, "Scanning package " + pkg.packageName);
         }
 
-        DexManager.maybeLogUnexpectedPackageDetails(pkg);
-
         // Initialize package source and resource directories
         final File scanFile = new File(pkg.codePath);
         final File destCodeFile = new File(pkg.applicationInfo.getCodePath());
@@ -13872,6 +13872,38 @@
             }
         }
 
+        // If this is an update to a package that might be potentially downgraded, then we
+        // need to check with the rollback manager whether there's any userdata that might
+        // need to be restored for the package.
+        //
+        // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
+        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
+            IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
+
+            final String packageName = res.pkg.applicationInfo.packageName;
+            final String seInfo = res.pkg.applicationInfo.seInfo;
+            final PackageSetting ps;
+            int appId = -1;
+            long ceDataInode = -1;
+            synchronized (mSettings) {
+                ps = mSettings.getPackageLPr(packageName);
+                if (ps != null) {
+                    appId = ps.appId;
+                    ceDataInode = ps.getCeDataInode(userId);
+                }
+            }
+
+            if (ps != null) {
+                try {
+                    rm.restoreUserData(packageName, userId, appId, ceDataInode, seInfo, token);
+                } catch (RemoteException re) {
+                    // Cannot happen, the RollbackManager is hosted in the same process.
+                }
+                doRestore = true;
+            }
+        }
+
         if (!doRestore) {
             // No restore possible, or the Backup Manager was mysteriously not
             // available -- just fire the post-install work request directly.
@@ -14569,6 +14601,9 @@
                     enableRollbackIntent.putExtra(
                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS,
                             installFlags);
+                    enableRollbackIntent.putExtra(
+                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS,
+                            resolveUserIds(args.user.getIdentifier()));
                     enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
                             PACKAGE_MIME_TYPE);
                     enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -20635,7 +20670,6 @@
         storage.registerListener(mStorageListener);
 
         mInstallerService.systemReady();
-        mDexManager.systemReady();
         mPackageDexOptimizer.systemReady();
 
         getStorageManagerInternal().addExternalStoragePolicy(
@@ -23791,6 +23825,11 @@
             }
             return mArtManagerService.compileLayouts(pkg);
         }
+
+        @Override
+        public void finishPackageInstall(int token, boolean didLaunch) {
+            PackageManagerService.this.finishPackageInstall(token, didLaunch);
+        }
     }
 
     @GuardedBy("mPackages")
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 022c1aa..3562630 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -40,10 +40,10 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ApkLite;
 import android.content.pm.PackageParser.PackageLite;
@@ -634,9 +634,9 @@
                 if (showVersionCode) {
                     pw.print(" versionCode:");
                     if (info.applicationInfo != null) {
-                        pw.print(info.applicationInfo.versionCode);
+                        pw.print(info.applicationInfo.longVersionCode);
                     } else {
-                        pw.print(info.versionCode);
+                        pw.print(info.getLongVersionCode());
                     }
                 }
                 if (listInstaller && !isApex) {
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
index 78fa82c..59cc0cf 100644
--- a/services/core/java/com/android/server/pm/dex/DexLogger.java
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -16,11 +16,15 @@
 
 package com.android.server.pm.dex;
 
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_NATIVE;
+
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.os.FileUtils;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.util.ByteStringUtils;
 import android.util.EventLog;
@@ -35,20 +39,23 @@
 import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Map;
 import java.util.Set;
 
 /**
- * This class is responsible for logging data about secondary dex files.
- * The data logged includes hashes of the name and content of each file.
+ * This class is responsible for logging data about secondary dex files and, despite the name,
+ * native code executed from an app's private directory. The data logged includes hashes of the
+ * name and content of each file.
  */
 public class DexLogger {
     private static final String TAG = "DexLogger";
 
-    // Event log tag & subtag used for SafetyNet logging of dynamic
-    // code loading (DCL) - see b/63927552.
+    // Event log tag & subtags used for SafetyNet logging of dynamic code loading (DCL) -
+    // see b/63927552.
     private static final int SNET_TAG = 0x534e4554;
-    private static final String DCL_SUBTAG = "dcl";
+    private static final String DCL_DEX_SUBTAG = "dcl";
+    private static final String DCL_NATIVE_SUBTAG = "dcln";
 
     private final IPackageManager mPackageManager;
     private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
@@ -114,12 +121,11 @@
             }
 
             int storageFlags;
-            if (appInfo.deviceProtectedDataDir != null
-                    && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) {
-                storageFlags = StorageManager.FLAG_STORAGE_DE;
-            } else if (appInfo.credentialProtectedDataDir != null
-                    && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) {
+
+            if (fileIsUnder(filePath, appInfo.credentialProtectedDataDir)) {
                 storageFlags = StorageManager.FLAG_STORAGE_CE;
+            } else if (fileIsUnder(filePath, appInfo.deviceProtectedDataDir)) {
+                storageFlags = StorageManager.FLAG_STORAGE_DE;
             } else {
                 Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath);
                 needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId);
@@ -139,6 +145,9 @@
                         + ": " + e.getMessage());
             }
 
+            String subtag = fileInfo.mFileType == FILE_TYPE_DEX
+                    ? DCL_DEX_SUBTAG
+                    : DCL_NATIVE_SUBTAG;
             String fileName = new File(filePath).getName();
             String message = PackageUtils.computeSha256Digest(fileName.getBytes());
 
@@ -165,7 +174,7 @@
                 }
 
                 if (loadingUid != -1) {
-                    writeDclEvent(loadingUid, message);
+                    writeDclEvent(subtag, loadingUid, message);
                 }
             }
         }
@@ -175,21 +184,58 @@
         }
     }
 
+    private boolean fileIsUnder(String filePath, String directoryPath) {
+        if (directoryPath == null) {
+            return false;
+        }
+
+        try {
+            return FileUtils.contains(new File(directoryPath).getCanonicalPath(),
+                    new File(filePath).getCanonicalPath());
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
     @VisibleForTesting
     PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
         return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
     }
 
     @VisibleForTesting
-    void writeDclEvent(int uid, String message) {
-        EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message);
+    void writeDclEvent(String subtag, int uid, String message) {
+        EventLog.writeEvent(SNET_TAG, subtag, uid, message);
     }
 
-    void record(int loaderUserId, String dexPath,
-            String owningPackageName, String loadingPackageName) {
+    void recordDex(int loaderUserId, String dexPath, String owningPackageName,
+            String loadingPackageName) {
         if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath,
-                PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
-                loadingPackageName)) {
+                FILE_TYPE_DEX, loaderUserId, loadingPackageName)) {
+            mPackageDynamicCodeLoading.maybeWriteAsync();
+        }
+    }
+
+    /**
+     * Record that an app running in the specified uid has executed native code from the file at
+     * {@link path}.
+     */
+    public void recordNative(int loadingUid, String path) {
+        String[] packages;
+        try {
+            packages = mPackageManager.getPackagesForUid(loadingUid);
+            if (packages == null || packages.length == 0) {
+                return;
+            }
+        } catch (RemoteException e) {
+            // Can't happen, we're local.
+            return;
+        }
+
+        String loadingPackageName = packages[0];
+        int loadingUserId = UserHandle.getUserId(loadingUid);
+
+        if (mPackageDynamicCodeLoading.record(loadingPackageName, path,
+                FILE_TYPE_NATIVE, loadingUserId, loadingPackageName)) {
             mPackageDynamicCodeLoading.maybeWriteAsync();
         }
     }
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index b546836..7ac7395 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,31 +16,28 @@
 
 package com.android.server.pm.dex;
 
+import static android.provider.DeviceConfig.FsiBoot;
+
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageParser;
-import android.database.ContentObserver;
-import android.os.Build;
 import android.os.FileUtils;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.provider.Settings.Global;
+import android.provider.DeviceConfig;
 import android.util.Log;
 import android.util.Slog;
 import android.util.jar.StrictJarFile;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageDexOptimizer;
@@ -136,10 +133,6 @@
         return mDexLogger;
     }
 
-    public void systemReady() {
-        registerSettingObserver();
-    }
-
     /**
      * Notify about dex files loads.
      * Note that this method is invoked when apps load dex files and it should
@@ -235,7 +228,7 @@
                     continue;
                 }
 
-                mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName,
+                mDexLogger.recordDex(loaderUserId, dexPath, searchResult.mOwningPackageName,
                         loadingAppInfo.packageName);
 
                 if (classLoaderContexts != null) {
@@ -699,47 +692,10 @@
         mDexLogger.writeNow();
     }
 
-    private void registerSettingObserver() {
-        final ContentResolver resolver = mContext.getContentResolver();
-
-        // This observer provides a one directional mapping from Global.PRIV_APP_OOB_ENABLED to
-        // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
-        // it is done.
-        ContentObserver privAppOobObserver = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean selfChange) {
-                int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
-                SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
-                        oobEnabled == 1 ? "true" : "false");
-            }
-        };
-        resolver.registerContentObserver(
-                Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
-                UserHandle.USER_SYSTEM);
-        // At boot, restore the value from the setting, which persists across reboot.
-        privAppOobObserver.onChange(true);
-
-        ContentObserver privAppOobListObserver = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean selfChange) {
-                String oobList = Global.getString(resolver, Global.PRIV_APP_OOB_LIST);
-                if (oobList == null) {
-                    oobList = "ALL";
-                }
-                SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, oobList);
-            }
-        };
-        resolver.registerContentObserver(
-                Global.getUriFor(Global.PRIV_APP_OOB_LIST), false, privAppOobListObserver,
-                UserHandle.USER_SYSTEM);
-        // At boot, restore the value from the setting, which persists across reboot.
-        privAppOobListObserver.onChange(true);
-    }
-
     /**
      * Returns whether the given package is in the list of privilaged apps that should run out of
-     * box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that when
-     * the the OOB list is empty, all priv apps will run in OOB mode.
+     * box. This only makes sense if the feature is enabled. Note that when the the OOB list is
+     * empty, all priv apps will run in OOB mode.
      */
     public static boolean isPackageSelectedToRunOob(String packageName) {
         return isPackageSelectedToRunOob(Arrays.asList(packageName));
@@ -747,19 +703,35 @@
 
     /**
      * Returns whether any of the given packages are in the list of privilaged apps that should run
-     * out of box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that
-     * when the the OOB list is empty, all priv apps will run in OOB mode.
+     * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list
+     * is empty, all priv apps will run in OOB mode.
      */
     public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
-        if (!SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
+        return isPackageSelectedToRunOobInternal(
+                SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false),
+                SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"),
+                DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_ENABLED),
+                DeviceConfig.getProperty(FsiBoot.NAMESPACE, FsiBoot.OOB_WHITELIST),
+                packageNamesInSameProcess);
+    }
+
+    @VisibleForTesting
+    /* package */ static boolean isPackageSelectedToRunOobInternal(
+            boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled,
+            String overrideWhitelist, Collection<String> packageNamesInSameProcess) {
+        // Allow experiment (if exists) to override device configuration.
+        boolean enabled = overrideEnabled != null ? overrideEnabled.equals("true")
+                : isDefaultEnabled;
+        if (!enabled) {
             return false;
         }
-        String oobListProperty = SystemProperties.get(
-                PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL");
-        if ("ALL".equals(oobListProperty)) {
+
+        // Similarly, experiment flag can override the whitelist.
+        String whitelist = overrideWhitelist != null ? overrideWhitelist : defaultWhitelist;
+        if ("ALL".equals(whitelist)) {
             return true;
         }
-        for (String oobPkgName : oobListProperty.split(",")) {
+        for (String oobPkgName : whitelist.split(",")) {
             if (packageNamesInSameProcess.contains(oobPkgName)) {
                 return true;
             }
@@ -768,32 +740,6 @@
     }
 
     /**
-     * Generates package related log if the package has code stored in unexpected way.
-     */
-    public static void maybeLogUnexpectedPackageDetails(PackageParser.Package pkg) {
-        if (!Build.IS_DEBUGGABLE) {
-            return;
-        }
-
-        if (pkg.isPrivileged() && isPackageSelectedToRunOob(pkg.packageName)) {
-            logIfPackageHasUncompressedCode(pkg);
-        }
-    }
-
-    /**
-     * Generates log if the APKs in the given package have uncompressed dex file and so
-     * files that can be direclty mapped.
-     */
-    private static void logIfPackageHasUncompressedCode(PackageParser.Package pkg) {
-        auditUncompressedCodeInApk(pkg.baseCodePath);
-        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
-            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
-                auditUncompressedCodeInApk(pkg.splitCodePaths[i]);
-            }
-        }
-    }
-
-    /**
      * Generates log if the archive located at {@code fileName} has uncompressed dex file and so
      * files that can be direclty mapped.
      */
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/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index 6d4bc82..cc26c9b 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -53,6 +53,9 @@
     // is represented in the text file format.)
     static final int FILE_TYPE_DEX = 'D';
 
+    // Type code to indicate a secondary file containing native code.
+    static final int FILE_TYPE_NATIVE = 'N';
+
     private static final String TAG = "PackageDynamicCodeLoading";
 
     private static final String FILE_VERSION_HEADER = "DCL1";
@@ -107,7 +110,7 @@
      */
     boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId,
             String loadingPackageName) {
-        if (fileType != FILE_TYPE_DEX) {
+        if (!isValidFileType(fileType)) {
             throw new IllegalArgumentException("Bad file type: " + fileType);
         }
         synchronized (mLock) {
@@ -120,6 +123,10 @@
         }
     }
 
+    private static boolean isValidFileType(int fileType) {
+        return fileType == FILE_TYPE_DEX || fileType == FILE_TYPE_NATIVE;
+    }
+
     /**
      * Return all packages that contain records of secondary dex files. (Note that data updates
      * asynchronously, so {@link #getPackageDynamicCodeInfo} may still return null if passed
@@ -407,7 +414,7 @@
             if (packages.length == 0) {
                 throw new IOException("Malformed line: " + line);
             }
-            if (type != FILE_TYPE_DEX) {
+            if (!isValidFileType(type)) {
                 throw new IOException("Unknown file type: " + line);
             }
 
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index d5af313..20d6d4e 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1290,6 +1290,7 @@
             return mContext.getPackageManager().getPackageInfo(pkg,
                     DEFAULT_PACKAGE_INFO_QUERY_FLAGS | extraFlags);
         } catch (NameNotFoundException e) {
+            Slog.e(TAG, "PackageNot found: " + pkg, e);
             return null;
         }
     }
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 9df0f72..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;
@@ -43,14 +42,20 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+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;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageManagerServiceUtils;
 
 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;
@@ -59,9 +64,9 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * Implementation of service that manages APK level rollbacks.
@@ -79,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")
@@ -103,9 +115,14 @@
 
     private final Context mContext;
     private final HandlerThread mHandlerThread;
+    private final Installer mInstaller;
 
     RollbackManagerServiceImpl(Context context) {
         mContext = context;
+        // Note that we're calling onStart here because this object is only constructed on
+        // SystemService#onStart.
+        mInstaller = new Installer(mContext);
+        mInstaller.onStart();
         mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
         mHandlerThread.start();
 
@@ -120,8 +137,8 @@
         // expiration.
         getHandler().post(() -> ensureRollbackDataLoaded());
 
-        PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
-        installer.registerSessionCallback(new SessionCallback(), getHandler());
+        PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
+        packageInstaller.registerSessionCallback(new SessionCallback(), getHandler());
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
@@ -158,10 +175,13 @@
                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1);
                     int installFlags = intent.getIntExtra(
                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0);
+                    int[] installedUsers = intent.getIntArrayExtra(
+                            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS);
                     File newPackageCodePath = new File(intent.getData().getPath());
 
                     getHandler().post(() -> {
-                        boolean success = enableRollback(installFlags, newPackageCodePath);
+                        boolean success = enableRollback(installFlags, newPackageCodePath,
+                                installedUsers);
                         int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED;
                         if (!success) {
                             ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED;
@@ -198,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;
@@ -219,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());
                 }
             }
         }
@@ -261,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.
@@ -271,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
@@ -291,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;
@@ -342,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)) {
@@ -356,28 +369,30 @@
                 parentSession.addChildSessionId(sessionId);
             }
 
-            final LocalIntentReceiver receiver = new LocalIntentReceiver();
+            final LocalIntentReceiver receiver = new LocalIntentReceiver(
+                    (Intent result) -> {
+                        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                                PackageInstaller.STATUS_FAILURE);
+                        if (status != PackageInstaller.STATUS_SUCCESS) {
+                            sendFailure(statusReceiver, "Rollback downgrade install failed: "
+                                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
+                            return;
+                        }
+
+                        addRecentlyExecutedRollback(rollback);
+                        sendSuccess(statusReceiver);
+
+                        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);
+                    }
+            );
+
             parentSession.commit(receiver.getIntentSender());
-
-            Intent result = receiver.getResult();
-            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_FAILURE);
-            if (status != PackageInstaller.STATUS_SUCCESS) {
-                sendFailure(statusReceiver, "Rollback downgrade install failed: "
-                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
-                return;
-            }
-
-            addRecentlyExecutedRollback(rollback);
-            sendSuccess(statusReceiver);
-
-            Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
-                    Uri.fromParts("package", targetPackageName,
-                        Manifest.permission.MANAGE_ROLLBACKS));
-
-            // TODO: This call emits the warning "Calling a method in the
-            // system process without a qualified user". Fix that.
-            mContext.sendBroadcast(broadcast);
         } catch (IOException e) {
             Log.e(TAG, "Unable to roll back " + targetPackageName, e);
             sendFailure(statusReceiver, "IOException: " + e.toString());
@@ -414,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;
@@ -456,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);
     }
 
@@ -468,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();
@@ -477,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;
@@ -501,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;
                 }
@@ -620,12 +644,13 @@
      * staged for install with rollback enabled. Called before the package has
      * been installed.
      *
-     * @param id the id of the enable rollback request
      * @param installFlags information about what is being installed.
      * @param newPackageCodePath path to the package about to be installed.
+     * @param installedUsers the set of users for which a given package is installed.
      * @return true if enabling the rollback succeeds, false otherwise.
      */
-    private boolean enableRollback(int installFlags, File newPackageCodePath) {
+    private boolean enableRollback(int installFlags, File newPackageCodePath,
+            int[] installedUsers) {
         if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
             Log.e(TAG, "Rollbacks not supported for instant app install");
             return false;
@@ -675,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);
@@ -687,11 +711,29 @@
             Log.e(TAG, packageName + " is not installed");
             return false;
         }
-        PackageRollbackInfo.PackageVersion installedVersion =
-                new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());
+        VersionedPackage installedVersion = new VersionedPackage(packageName,
+                installedPackage.getLongVersionCode());
 
-        PackageRollbackInfo info = new PackageRollbackInfo(
-                packageName, newVersion, installedVersion);
+        for (int user : installedUsers) {
+            final int storageFlags;
+            if (StorageManager.isFileEncryptedNativeOrEmulated()
+                    && !StorageManager.isUserKeyUnlocked(user)) {
+                // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
+                // across app user data until the user unlocks their device.
+                Log.e(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
+                storageFlags = Installer.FLAG_STORAGE_DE;
+            } else {
+                storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
+            }
+
+            try {
+                mInstaller.snapshotAppData(packageName, user, storageFlags);
+            } catch (InstallerException ie) {
+                Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie);
+            }
+        }
+
+        PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion);
 
         RollbackData data;
         try {
@@ -699,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);
@@ -722,40 +765,71 @@
         return true;
     }
 
-    // TODO: Don't copy this from PackageManagerShellCommand like this?
-    private static class LocalIntentReceiver {
-        private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
+    @Override
+    public void restoreUserData(String packageName, int userId, int appId, long ceDataInode,
+            String seInfo, int token) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("restoureUserData may only be called by the system.");
+        }
+
+        getHandler().post(() -> {
+            PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+            // TODO(narayan): Should we make sure we're in the middle of a session commit for a
+            // a package with this package name ? Otherwise it's possible we may roll back data
+            // for some other downgrade.
+            if (getRollbackForPackage(packageName) == null) {
+                pmi.finishPackageInstall(token, false);
+                return;
+            }
+
+            final int storageFlags;
+            if (StorageManager.isFileEncryptedNativeOrEmulated()
+                    && !StorageManager.isUserKeyUnlocked(userId)) {
+                // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
+                // across app user data until the user unlocks their device.
+                Log.e(TAG, "User: " + userId + " isn't unlocked, skipping CE userdata restore.");
+
+                storageFlags = Installer.FLAG_STORAGE_DE;
+            } else {
+                storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
+            }
+
+            try {
+                mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode,
+                        seInfo, userId, storageFlags);
+            } catch (InstallerException ie) {
+                Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie);
+            }
+
+            pmi.finishPackageInstall(token, false);
+        });
+    }
+
+    private class LocalIntentReceiver {
+        final Consumer<Intent> mConsumer;
+
+        LocalIntentReceiver(Consumer<Intent> consumer) {
+            mConsumer = consumer;
+        }
 
         private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
             @Override
             public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
                     IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
-                try {
-                    mResult.offer(intent, 5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
+                getHandler().post(() -> mConsumer.accept(intent));
             }
         };
 
         public IntentSender getIntentSender() {
             return new IntentSender((IIntentSender) mLocalSender);
         }
-
-        public Intent getResult() {
-            try {
-                return mResult.take();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
     }
 
     /**
      * 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 {
@@ -764,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 {
@@ -840,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;
                     }
                 }
@@ -848,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 f9a838f..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());
 
@@ -178,6 +182,8 @@
      * rollback.
      */
     void deleteAvailableRollback(RollbackData data) {
+        // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the
+        // actual app.
         removeFile(data.backupDir);
     }
 
@@ -193,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);
             }
 
@@ -214,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/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
new file mode 100644
index 0000000..23c042a
--- /dev/null
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -0,0 +1,328 @@
+/*
+ * 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.testharness;
+
+import android.annotation.Nullable;
+import android.app.KeyguardManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.BatteryManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.SystemService;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+
+/**
+ * Manages the Test Harness Mode service for setting up test harness mode on the device.
+ *
+ * <p>Test Harness Mode is a feature that allows the user to clean their device, retain ADB keys,
+ * and provision the device for Instrumentation testing. This means that all parts of the device
+ * that would otherwise interfere with testing (auto-syncing accounts, package verification,
+ * automatic updates, etc.) are all disabled by default but may be re-enabled by the user.
+ */
+public class TestHarnessModeService extends SystemService {
+    private static final String TAG = TestHarnessModeService.class.getSimpleName();
+    private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness";
+
+    private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+
+    public TestHarnessModeService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService("testharness", mService);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        switch (phase) {
+            case PHASE_SYSTEM_SERVICES_READY:
+                setUpTestHarnessMode();
+                break;
+            case PHASE_BOOT_COMPLETED:
+                disableAutoSync();
+                break;
+        }
+        super.onBootPhase(phase);
+    }
+
+    private void setUpTestHarnessMode() {
+        Slog.d(TAG, "Setting up test harness mode");
+        byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData();
+        if (testHarnessModeData == null || testHarnessModeData.length == 0) {
+            // There's no data to apply, so leave it as-is.
+            return;
+        }
+        PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);
+
+        SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
+        writeAdbKeysFile(persistentData);
+        // Clear out the data block so that we don't revert the ADB keys on every boot.
+        getPersistentDataBlock().clearTestHarnessModeData();
+
+        ContentResolver cr = getContext().getContentResolver();
+        if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) {
+            // Enable ADB
+            Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
+        } else {
+            // ADB is already enabled, we should restart the service so it picks up the new keys
+            android.os.SystemService.restart("adbd");
+        }
+
+        Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
+        Settings.Global.putInt(
+                cr,
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
+                BatteryManager.BATTERY_PLUGGED_ANY);
+        Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1);
+        Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+
+        setDeviceProvisioned();
+    }
+
+    private void disableAutoSync() {
+        UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+        ContentResolver
+            .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier());
+    }
+
+    private void writeAdbKeysFile(PersistentData persistentData) {
+        Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+        try {
+            OutputStream fileOutputStream = Files.newOutputStream(adbKeys);
+            fileOutputStream.write(persistentData.mAdbKeys);
+            fileOutputStream.close();
+
+            Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(adbKeys);
+            permissions.add(PosixFilePermission.GROUP_READ);
+            Files.setPosixFilePermissions(adbKeys, permissions);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to set up adb keys", e);
+            // Note: if a device enters this block, it will remain UNAUTHORIZED in ADB, but all
+            // other settings will be set up.
+        }
+    }
+
+    // Setting the device as provisioned skips the setup wizard.
+    private void setDeviceProvisioned() {
+        ContentResolver cr = getContext().getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.DEVICE_PROVISIONED, 1);
+        Settings.Secure.putIntForUser(
+                cr,
+                Settings.Secure.USER_SETUP_COMPLETE,
+                1,
+                UserHandle.USER_CURRENT);
+    }
+
+    @Nullable
+    private PersistentDataBlockManagerInternal getPersistentDataBlock() {
+        if (mPersistentDataBlockManagerInternal == null) {
+            Slog.d(TAG, "Getting PersistentDataBlockManagerInternal from LocalServices");
+            mPersistentDataBlockManagerInternal =
+                    LocalServices.getService(PersistentDataBlockManagerInternal.class);
+        }
+        return mPersistentDataBlockManagerInternal;
+    }
+
+    private final IBinder mService = new Binder() {
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+            (new TestHarnessModeShellCommand())
+                .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+    };
+
+    private class TestHarnessModeShellCommand extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            switch (cmd) {
+                case "enable":
+                case "restore":
+                    checkPermissions();
+                    final long originalId = Binder.clearCallingIdentity();
+                    try {
+                        if (isDeviceSecure()) {
+                            getErrPrintWriter().println(
+                                    "Test Harness Mode cannot be enabled if there is a lock "
+                                            + "screen");
+                            return 2;
+                        }
+                        return handleEnable();
+                    } finally {
+                        Binder.restoreCallingIdentity(originalId);
+                    }
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        }
+
+        private void checkPermissions() {
+            getContext().enforceCallingPermission(
+                    android.Manifest.permission.ENABLE_TEST_HARNESS_MODE,
+                    "You must hold android.permission.ENABLE_TEST_HARNESS_MODE "
+                            + "to enable Test Harness Mode");
+        }
+
+        private boolean isDeviceSecure() {
+            UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
+            KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class);
+            return keyguardManager.isDeviceSecure(primaryUser.id);
+        }
+
+        private int handleEnable() {
+            Path adbKeys = Paths.get("/data/misc/adb/adb_keys");
+            if (!Files.exists(adbKeys)) {
+                // This should only be accessible on eng builds that haven't yet set up ADB keys
+                getErrPrintWriter()
+                    .println("No ADB keys stored; not enabling test harness mode");
+                return 1;
+            }
+
+            try (InputStream inputStream = Files.newInputStream(adbKeys)) {
+                long size = Files.size(adbKeys);
+                byte[] adbKeysBytes = new byte[(int) size];
+                int numBytes = inputStream.read(adbKeysBytes);
+                if (numBytes != size) {
+                    getErrPrintWriter().println("Failed to read all bytes of adb_keys");
+                    return 1;
+                }
+                PersistentData persistentData = new PersistentData(true, adbKeysBytes);
+                getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes());
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to store ADB keys.", e);
+                getErrPrintWriter().println("Failed to enable Test Harness Mode");
+                return 1;
+            }
+
+            Intent i = new Intent(Intent.ACTION_FACTORY_RESET);
+            i.setPackage("android");
+            i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            i.putExtra(Intent.EXTRA_REASON, TAG);
+            i.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
+            getContext().sendBroadcastAsUser(i, UserHandle.SYSTEM);
+            return 0;
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            pw.println("About:");
+            pw.println("  Test Harness Mode is a mode that the device can be placed in to prepare");
+            pw.println("  the device for running UI tests. The device is placed into this mode by");
+            pw.println("  first wiping all data from the device, preserving ADB keys.");
+            pw.println();
+            pw.println("  By default, the following settings are configured:");
+            pw.println("    * Package Verifier is disabled");
+            pw.println("    * Stay Awake While Charging is enabled");
+            pw.println("    * OTA Updates are disabled");
+            pw.println("    * Auto-Sync for accounts is disabled");
+            pw.println();
+            pw.println("  Other apps may configure themselves differently in Test Harness Mode by");
+            pw.println("  checking ActivityManager.isRunningInUserTestHarness()");
+            pw.println();
+            pw.println("Test Harness Mode commands:");
+            pw.println("  help");
+            pw.println("    Print this help text.");
+            pw.println();
+            pw.println("  enable|restore");
+            pw.println("    Erase all data from this device and enable Test Harness Mode,");
+            pw.println("    preserving the stored ADB keys currently on the device and toggling");
+            pw.println("    settings in a way that are conducive to Instrumentation testing.");
+        }
+    }
+
+    /**
+     * The object that will serialize/deserialize the Test Harness Mode data to and from the
+     * persistent data block.
+     */
+    public static class PersistentData {
+        static final byte VERSION_1 = 1;
+
+        final int mVersion;
+        final boolean mEnabled;
+        final byte[] mAdbKeys;
+
+        PersistentData(boolean enabled, byte[] adbKeys) {
+            this(VERSION_1, enabled, adbKeys);
+        }
+
+        PersistentData(int version, boolean enabled, byte[] adbKeys) {
+            this.mVersion = version;
+            this.mEnabled = enabled;
+            this.mAdbKeys = adbKeys;
+        }
+
+        static PersistentData fromBytes(byte[] bytes) {
+            try {
+                DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
+                int version = is.readInt();
+                boolean enabled = is.readBoolean();
+                int adbKeysLength = is.readInt();
+                byte[] adbKeys = new byte[adbKeysLength];
+                is.readFully(adbKeys);
+                return new PersistentData(version, enabled, adbKeys);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        byte[] toBytes() {
+            try {
+                ByteArrayOutputStream os = new ByteArrayOutputStream();
+                DataOutputStream dos = new DataOutputStream(os);
+                dos.writeInt(VERSION_1);
+                dos.writeBoolean(mEnabled);
+                dos.writeInt(mAdbKeys.length);
+                dos.write(mAdbKeys);
+                dos.close();
+                return os.toByteArray();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
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/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index b3eafa4..45689ce 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -65,7 +65,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
-import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.utils.ManagedApplicationService;
 import com.android.server.utils.ManagedApplicationService.BinderChecker;
 import com.android.server.utils.ManagedApplicationService.LogEvent;
@@ -623,14 +622,6 @@
         }
 
         @Override
-        public void setVrInputMethod(ComponentName componentName) {
-            enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
-            InputMethodManagerInternal imm =
-                    LocalServices.getService(InputMethodManagerInternal.class);
-            imm.startVrInputMethodNoCheck(componentName);
-        }
-
-        @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index e817dd4..65d66f4 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -558,26 +558,22 @@
     }
 
     /**
-     * Pause all activities in either all of the stacks or just the back stacks. This is done before
-     * resuming a new activity and to make sure that previously active activities are
-     * paused in stacks that are no longer visible or in pinned windowing mode. This does not
-     * pause activities in visible stacks, so if an activity is launched within the same stack/task,
-     * then we should explicitly pause that stack's top activity.
+     * Pause all activities in either all of the stacks or just the back stacks.
      * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
      * @param resuming The resuming activity.
      * @param dontWait The resuming activity isn't going to wait for all activities to be paused
      *                 before resuming.
-     * @return {@code true} if any activity was paused as a result of this call.
+     * @return true if any activity was paused as a result of this call.
      */
     boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
         boolean someActivityPaused = false;
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
-            final ActivityRecord resumedActivity = stack.getResumedActivity();
-            if (resumedActivity != null
-                    && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) {
+            // TODO(b/111541062): Check if resumed activity on this display instead
+            if (!mRootActivityContainer.isTopDisplayFocusedStack(stack)
+                    && stack.getResumedActivity() != null) {
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
-                        " mResumedActivity=" + resumedActivity);
+                        " mResumedActivity=" + stack.getResumedActivity());
                 someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
                         dontWait);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6213fa0..b8634d8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1946,84 +1946,30 @@
         try {
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                     WindowVisibilityItem.obtain(true /* showWindow */));
-            makeActiveIfNeeded(null /* activeActivity*/);
+            if (shouldPauseWhenBecomingVisible()) {
+                // An activity must be in the {@link PAUSING} state for the system to validate
+                // the move to {@link PAUSED}.
+                setState(PAUSING, "makeVisibleIfNeeded");
+                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                        PauseActivityItem.obtain(finishing, false /* userLeaving */,
+                                configChangeFlags, false /* dontReport */));
+            }
         } catch (Exception e) {
             Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
         }
     }
 
-    /**
-     * Make activity resumed or paused if needed.
-     * @param activeActivity an activity that is resumed or just completed pause action.
-     *                       We won't change the state of this activity.
-     */
-    boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
-        if (shouldResumeActivity(activeActivity)) {
-            if (DEBUG_VISIBILITY) {
-                Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
-            }
-            return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
-                    null /* options */);
-        } else if (shouldPauseActivity(activeActivity)) {
-            if (DEBUG_VISIBILITY) {
-                Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
-            }
-            // An activity must be in the {@link PAUSING} state for the system to validate
-            // the move to {@link PAUSED}.
-            setState(PAUSING, "makeVisibleIfNeeded");
-            try {
-                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                        PauseActivityItem.obtain(finishing, false /* userLeaving */,
-                                configChangeFlags, false /* dontReport */));
-            } catch (Exception e) {
-                Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Check if activity should be moved to PAUSED state. The activity:
-     * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
-     * - should be non-focusable
-     * - should not be currently pausing or paused
-     * @param activeActivity the activity that is active or just completed pause action. We won't
-     *                       resume if this activity is active.
-     */
-    private boolean shouldPauseActivity(ActivityRecord activeActivity) {
-        return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED);
-    }
-
-    /**
-     * Check if activity should be moved to RESUMED state. The activity:
-     * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
-     * - should be focusable
-     * @param activeActivity the activity that is active or just completed pause action. We won't
-     *                       resume if this activity is active.
-     */
-    private boolean shouldResumeActivity(ActivityRecord activeActivity) {
-        return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED);
-    }
-
-    /**
-     * Check if activity is eligible to be made active (resumed of paused). The activity:
-     * - should be paused, stopped or stopping
-     * - should not be the currently active one
-     * - should be either the topmost in task, or right below the top activity that is finishing
-     * If all of these conditions are not met at the same time, the activity cannot be made active.
-     */
-    private boolean shouldMakeActive(ActivityRecord activeActivity) {
-        // If the activity is stopped, stopping, cycle to an active state. We avoid doing
+    /** Check if activity should be moved to PAUSED state when it becomes visible. */
+    private boolean shouldPauseWhenBecomingVisible() {
+        // If the activity is stopped or stopping, cycle to the paused state. We avoid doing
         // this when there is an activity waiting to become translucent as the extra binder
         // calls will lead to noticeable jank. A later call to
-        // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper
-        // active state.
-        if (!isState(RESUMED, PAUSED, STOPPED, STOPPING)
-                || getActivityStack().mTranslucentActivityWaiting != null) {
-            return false;
-        }
-
-        if (this == activeActivity) {
+        // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper
+        // paused state. We also avoid doing this for the activity the stack supervisor
+        // considers the resumed activity, as normal means will bring the activity from STOPPED
+        // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles.
+        if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null
+                || isResumedActivityOnDisplay()) {
             return false;
         }
 
@@ -2033,14 +1979,14 @@
             throw new IllegalStateException("Activity not found in its task");
         }
         if (positionInTask == task.mActivities.size() - 1) {
-            // It's the topmost activity in the task - should become resumed now
+            // It's the topmost activity in the task - should become paused now
             return true;
         }
         // Check if activity above is finishing now and this one becomes the topmost in task.
         final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
         if (activityAbove.finishing && results == null) {
-            // We will only allow making active if activity above wasn't launched for result.
-            // Otherwise it will cause this activity to resume before getting result.
+            // We will only allow pausing if activity above wasn't launched for result. Otherwise it
+            // will cause this activity to resume before getting result.
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 3aef8e1f..891c3da 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -357,11 +357,6 @@
      */
     boolean mForceHidden = false;
 
-    /**
-     * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
-     */
-    boolean mInResumeTopActivity = false;
-
     private boolean mUpdateBoundsDeferred;
     private boolean mUpdateBoundsDeferredCalled;
     private boolean mUpdateDisplayedBoundsDeferredCalled;
@@ -1737,7 +1732,6 @@
             "Activity paused: token=" + token + ", timeout=" + timeout);
 
         final ActivityRecord r = isInStackLocked(token);
-
         if (r != null) {
             mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
             if (mPausingActivity == r) {
@@ -2094,7 +2088,8 @@
             boolean aboveTop = top != null;
             final boolean stackShouldBeVisible = shouldBeVisible(starting);
             boolean behindFullscreenActivity = !stackShouldBeVisible;
-            boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null;
+            boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this)
+                    && (isInStackLocked(starting) == null);
             final boolean isTopNotPinnedStack =
                     isAttached() && getDisplay().isTopNotPinnedStack(this);
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -2155,10 +2150,6 @@
                             if (r.handleAlreadyVisible()) {
                                 resumeNextActivity = false;
                             }
-
-                            if (notifyClients) {
-                                r.makeActiveIfNeeded(starting);
-                            }
                         } else {
                             r.makeVisibleIfNeeded(starting, notifyClients);
                         }
@@ -2336,7 +2327,7 @@
                 r.setVisible(true);
             }
             if (r != starting) {
-                mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */);
+                mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
                 return true;
             }
         }
@@ -2514,7 +2505,7 @@
      */
     @GuardedBy("mService")
     boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
-        if (mInResumeTopActivity) {
+        if (mStackSupervisor.inResumeTopActivity) {
             // Don't even start recursing.
             return false;
         }
@@ -2522,7 +2513,7 @@
         boolean result = false;
         try {
             // Protect against recursion.
-            mInResumeTopActivity = true;
+            mStackSupervisor.inResumeTopActivity = true;
             result = resumeTopActivityInnerLocked(prev, options);
 
             // When resuming the top activity, it may be necessary to pause the top activity (for
@@ -2537,7 +2528,7 @@
                 checkReadyForSleep();
             }
         } finally {
-            mInResumeTopActivity = false;
+            mStackSupervisor.inResumeTopActivity = false;
         }
 
         return result;
@@ -2570,7 +2561,7 @@
         // Find the next top-most activity to resume in this stack that is not finishing and is
         // focusable. If it is not focusable, we will fall into the case below to resume the
         // top activity in the next focusable task.
-        ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+        final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
 
         final boolean hasRunningActivity = next != null;
 
@@ -2658,12 +2649,6 @@
         if (!mRootActivityContainer.allPausedActivitiesComplete()) {
             if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
                     "resumeTopActivityLocked: Skip resume: some activity pausing.");
-
-            // Adding previous activity to the waiting visible list, or it would be stopped
-            // before top activity being visible.
-            if (prev != null && !next.nowVisible) {
-                mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev);
-            }
             return false;
         }
 
@@ -2873,9 +2858,7 @@
             // the screen based on the new activity order.
             boolean notUpdated = true;
 
-            // Activity should also be visible if set mLaunchTaskBehind to true (see
-            // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
-            if (shouldBeVisible(next)) {
+            if (isFocusedStackOnDisplay()) {
                 // We have special rotation behavior when here is some active activity that
                 // requests specific orientation or Keyguard is locked. Make sure all activity
                 // visibilities are set correctly as well as the transition is updated if needed
@@ -4104,12 +4087,6 @@
         mStackSupervisor.mFinishingActivities.add(r);
         r.resumeKeyDispatchingLocked();
         mRootActivityContainer.resumeFocusedStacksTopActivities();
-        // If activity was not paused at this point - explicitly pause it to start finishing
-        // process. Finishing will be completed once it reports pause back.
-        if (r.isState(RESUMED) && mPausingActivity != null) {
-            startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */,
-                    false /* dontWait */);
-        }
         return r;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a83ef34..3a288ca 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -327,6 +327,9 @@
      */
     PowerManager.WakeLock mGoingToSleep;
 
+    /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
+    boolean inResumeTopActivity;
+
     /**
      * Temporary rect used during docked stack resize calculation so we don't need to create a new
      * object each time.
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 43c1206..2807094 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -179,10 +179,7 @@
                 .setActivityOptions(options.toBundle())
                 .execute();
         mLastHomeActivityStartRecord = tmpOutRecord[0];
-        final ActivityDisplay display =
-                mService.mRootActivityContainer.getActivityDisplay(displayId);
-        final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
-        if (homeStack != null && homeStack.mInResumeTopActivity) {
+        if (mSupervisor.inResumeTopActivity) {
             // If we are in resume section already, home activity will be initialized, but not
             // resumed (to avoid recursive resume) and will stay that way until something pokes it
             // again. We need to schedule another resume.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4e2dffc..d36e545 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1629,7 +1629,7 @@
                 // Also, we don't want to resume activities in a task that currently has an overlay
                 // as the starting activity just needs to be in the visible paused state until the
                 // over is removed.
-                mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
+                mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
                 mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
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/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index c4a853d..9b72141 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1108,41 +1108,28 @@
             return false;
         }
 
-        boolean result = false;
         if (targetStack != null && (targetStack.isTopStackOnDisplay()
                 || getTopDisplayFocusedStack() == targetStack)) {
-            result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+            return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
         }
 
+        // Resume all top activities in focused stacks on all displays.
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            boolean resumedOnDisplay = false;
             final ActivityDisplay display = mActivityDisplays.get(displayNdx);
-            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = display.getChildAt(stackNdx);
-                final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();
-                if (!stack.isFocusableAndVisible() || topRunningActivity == null) {
-                    continue;
-                }
-                if (topRunningActivity.isState(RESUMED)) {
-                    // Kick off any lingering app transitions form the MoveTaskToFront operation.
-                    stack.executeAppTransition(targetOptions);
-                } else {
-                    resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target);
-                }
+            final ActivityStack focusedStack = display.getFocusedStack();
+            if (focusedStack == null) {
+                continue;
             }
-            if (!resumedOnDisplay) {
-                // In cases when there are no valid activities (e.g. device just booted or launcher
-                // crashed) it's possible that nothing was resumed on a display. Requesting resume
-                // of top activity in focused stack explicitly will make sure that at least home
-                // activity is started and resumed, and no recursion occurs.
-                final ActivityStack focusedStack = display.getFocusedStack();
-                if (focusedStack != null) {
-                    focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
-                }
+            final ActivityRecord r = focusedStack.topRunningActivityLocked();
+            if (r == null || !r.isState(RESUMED)) {
+                focusedStack.resumeTopActivityUncheckedLocked(null, null);
+            } else if (r.isState(RESUMED)) {
+                // Kick off any lingering app transitions form the MoveTaskToFront operation.
+                focusedStack.executeAppTransition(targetOptions);
             }
         }
 
-        return result;
+        return false;
     }
 
     void applySleepTokens(boolean applyToStacks) {
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 0529ed1..69dcaf4 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -698,14 +698,6 @@
             return false;
         }
 
-        final boolean toTopOfStack = position == MAX_VALUE;
-        if (toTopOfStack && toStack.getResumedActivity() != null
-                && toStack.topRunningActivityLocked() != null) {
-            // Pause the resumed activity on the target stack while re-parenting task on top of it.
-            toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
-                    null /* resuming */, false /* pauseImmediately */);
-        }
-
         final int toStackWindowingMode = toStack.getWindowingMode();
         final ActivityRecord topActivity = getTopActivity();
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0977323..54053a8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4223,7 +4223,7 @@
 
     @Override
     public void setPasswordHistoryLength(ComponentName who, int length, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4246,13 +4246,16 @@
 
     @Override
     public int getPasswordHistoryLength(ComponentName who, int userHandle, boolean parent) {
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            return 0;
+        }
         return getStrictestPasswordRequirement(who, userHandle, parent,
                 admin -> admin.passwordHistoryLength, PASSWORD_QUALITY_UNSPECIFIED);
     }
 
     @Override
     public void setPasswordExpirationTimeout(ComponentName who, long timeout, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4288,7 +4291,7 @@
      */
     @Override
     public long getPasswordExpirationTimeout(ComponentName who, int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return 0L;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -4423,7 +4426,7 @@
 
     @Override
     public long getPasswordExpiration(ComponentName who, int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return 0L;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -4770,6 +4773,9 @@
 
     @Override
     public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) {
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            return 0;
+        }
         enforceFullCrossUsersPermission(userHandle);
         synchronized (getLockObject()) {
             if (!isCallerWithSystemUid()) {
@@ -4789,7 +4795,7 @@
 
     @Override
     public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -4815,7 +4821,7 @@
 
     @Override
     public int getMaximumFailedPasswordsForWipe(ComponentName who, int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return 0;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -4829,7 +4835,7 @@
 
     @Override
     public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return UserHandle.USER_NULL;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -4910,6 +4916,11 @@
     }
     @Override
     public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
+        if (!mLockPatternUtils.hasSecureLockScreen()) {
+            Slog.w(LOG_TAG, "Cannot reset password when the device has no lock screen");
+            return false;
+        }
+
         final int callingUid = mInjector.binderGetCallingUid();
         final int userHandle = mInjector.userHandleGetCallingUserId();
 
@@ -5252,7 +5263,7 @@
     @Override
     public void setRequiredStrongAuthTimeout(ComponentName who, long timeoutMs,
             boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -5285,7 +5296,7 @@
      */
     @Override
     public long getRequiredStrongAuthTimeout(ComponentName who, int userId, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS;
         }
         enforceFullCrossUsersPermission(userId);
@@ -6494,7 +6505,7 @@
      */
     @Override
     public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         enforceFullCrossUsersPermission(userHandle);
@@ -6514,7 +6525,7 @@
 
     @Override
     public void reportPasswordChanged(@UserIdInt int userId) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         enforceFullCrossUsersPermission(userId);
@@ -7656,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);
@@ -7684,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);
@@ -7941,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);
@@ -7967,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) {
@@ -8800,7 +8801,7 @@
     @Override
     public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent,
             PersistableBundle args, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
         Preconditions.checkNotNull(admin, "admin is null");
@@ -8817,7 +8818,7 @@
     @Override
     public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin,
             ComponentName agent, int userHandle, boolean parent) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return null;
         }
         Preconditions.checkNotNull(agent, "agent null");
@@ -12737,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
@@ -12748,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);
             }
@@ -13215,7 +13227,7 @@
 
     @Override
     public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return false;
         }
         if (token == null || token.length < 32) {
@@ -13243,7 +13255,7 @@
 
     @Override
     public boolean clearResetPasswordToken(ComponentName admin) {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return false;
         }
         synchronized (getLockObject()) {
@@ -13269,6 +13281,9 @@
 
     @Override
     public boolean isResetPasswordTokenActive(ComponentName admin) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
+            return false;
+        }
         synchronized (getLockObject()) {
             final int userHandle = mInjector.userHandleGetCallingUserId();
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -13290,6 +13305,9 @@
     @Override
     public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token,
             int flags) {
+        if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
+            return false;
+        }
         Preconditions.checkNotNull(token);
         synchronized (getLockObject()) {
             final int userHandle = mInjector.userHandleGetCallingUserId();
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
index 32513c1..e99dd4f 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -21,9 +21,12 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteCursorDriver;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQuery;
 import android.net.NetworkUtils;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.Status;
@@ -35,6 +38,7 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.StringJoiner;
 
 /**
  * Encapsulating class for using the SQLite database backing the memory store.
@@ -46,6 +50,9 @@
  */
 public class IpMemoryStoreDatabase {
     private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName();
+    // A pair of NetworkAttributes objects is group-close if the confidence that they are
+    // the same is above this cutoff. See NetworkAttributes and SameL3NetworkResponse.
+    private static final float GROUPCLOSE_CONFIDENCE = 0.5f;
 
     /**
      * Contract class for the Network Attributes table.
@@ -187,30 +194,35 @@
         return addresses;
     }
 
+    @NonNull
+    private static ContentValues toContentValues(@Nullable final NetworkAttributes attributes) {
+        final ContentValues values = new ContentValues();
+        if (null == attributes) return values;
+        if (null != attributes.assignedV4Address) {
+            values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
+                    NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
+        }
+        if (null != attributes.groupHint) {
+            values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
+        }
+        if (null != attributes.dnsAddresses) {
+            values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
+                    encodeAddressList(attributes.dnsAddresses));
+        }
+        if (null != attributes.mtu) {
+            values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
+        }
+        return values;
+    }
+
     // Convert a NetworkAttributes object to content values to store them in a table compliant
     // with the contract defined in NetworkAttributesContract.
     @NonNull
     private static ContentValues toContentValues(@NonNull final String key,
             @Nullable final NetworkAttributes attributes, final long expiry) {
-        final ContentValues values = new ContentValues();
+        final ContentValues values = toContentValues(attributes);
         values.put(NetworkAttributesContract.COLNAME_L2KEY, key);
         values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry);
-        if (null != attributes) {
-            if (null != attributes.assignedV4Address) {
-                values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
-                        NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
-            }
-            if (null != attributes.groupHint) {
-                values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
-            }
-            if (null != attributes.dnsAddresses) {
-                values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
-                        encodeAddressList(attributes.dnsAddresses));
-            }
-            if (null != attributes.mtu) {
-                values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
-            }
-        }
         return values;
     }
 
@@ -228,6 +240,32 @@
         return values;
     }
 
+    @Nullable
+    private static NetworkAttributes readNetworkAttributesLine(@NonNull final Cursor cursor) {
+        // Make sure the data hasn't expired
+        final long expiry = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, -1L);
+        if (expiry < System.currentTimeMillis()) return null;
+
+        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+        final int assignedV4AddressInt = getInt(cursor,
+                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
+        final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
+        final byte[] dnsAddressesBlob =
+                getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
+        final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+        if (0 != assignedV4AddressInt) {
+            builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
+        }
+        builder.setGroupHint(groupHint);
+        if (null != dnsAddressesBlob) {
+            builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
+        }
+        if (mtu >= 0) {
+            builder.setMtu(mtu);
+        }
+        return builder.build();
+    }
+
     private static final String[] EXPIRY_COLUMN = new String[] {
         NetworkAttributesContract.COLNAME_EXPIRYDATE
     };
@@ -313,32 +351,9 @@
         // result here. 0 results means the key was not found.
         if (cursor.getCount() != 1) return null;
         cursor.moveToFirst();
-
-        // Make sure the data hasn't expired
-        final long expiry = cursor.getLong(
-                cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE));
-        if (expiry < System.currentTimeMillis()) return null;
-
-        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
-        final int assignedV4AddressInt = getInt(cursor,
-                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
-        final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
-        final byte[] dnsAddressesBlob =
-                getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
-        final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+        final NetworkAttributes attributes = readNetworkAttributesLine(cursor);
         cursor.close();
-
-        if (0 != assignedV4AddressInt) {
-            builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
-        }
-        builder.setGroupHint(groupHint);
-        if (null != dnsAddressesBlob) {
-            builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
-        }
-        if (mtu >= 0) {
-            builder.setMtu(mtu);
-        }
-        return builder.build();
+        return attributes;
     }
 
     private static final String[] DATA_COLUMN = new String[] {
@@ -365,17 +380,134 @@
         return result;
     }
 
+    /**
+     * The following is a horrible hack that is necessary because the Android SQLite API does not
+     * have a way to query a binary blob. This, almost certainly, is an overlook.
+     *
+     * The Android SQLite API has two family of methods : one for query that returns data, and
+     * one for more general SQL statements that can execute any statement but may not return
+     * anything. All the query methods, however, take only String[] for the arguments.
+     *
+     * In principle it is simple to write a function that will encode the binary blob in the
+     * way SQLite expects it. However, because the API forces the argument to be coerced into a
+     * String, the SQLiteQuery object generated by the default query methods will bind all
+     * arguments as Strings and SQL will *sanitize* them. This works okay for numeric types,
+     * but the format for blobs is x'<hex string>'. Note the presence of quotes, which will
+     * be sanitized, changing the contents of the field, and the query will fail to match the
+     * blob.
+     *
+     * As far as I can tell, there are two possible ways around this problem. The first one
+     * is to put the data in the query string and eschew it being an argument. This would
+     * require doing the sanitizing by hand. The other is to call bindBlob directly on the
+     * generated SQLiteQuery object, which not only is a lot less dangerous than rolling out
+     * sanitizing, but also will do the right thing if the underlying format ever changes.
+     *
+     * But none of the methods that take an SQLiteQuery object can return data ; this *must*
+     * be called with SQLiteDatabase#query. This object is not accessible from outside.
+     * However, there is a #query version that accepts a CursorFactory and this is pretty
+     * straightforward to implement as all the arguments are coming in and the SQLiteCursor
+     * class is public API.
+     * With this, it's possible to intercept the SQLiteQuery object, and assuming the args
+     * are available, to bind them directly and work around the API's oblivious coercion into
+     * Strings.
+     *
+     * This is really sad, but I don't see another way of having this work than this or the
+     * hand-rolled sanitizing, and this is the lesser evil.
+     */
+    private static class CustomCursorFactory implements SQLiteDatabase.CursorFactory {
+        @NonNull
+        private final ArrayList<Object> mArgs;
+        CustomCursorFactory(@NonNull final ArrayList<Object> args) {
+            mArgs = args;
+        }
+        @Override
+        public Cursor newCursor(final SQLiteDatabase db, final SQLiteCursorDriver masterQuery,
+                final String editTable,
+                final SQLiteQuery query) {
+            int index = 1; // bind is 1-indexed
+            for (final Object arg : mArgs) {
+                if (arg instanceof String) {
+                    query.bindString(index++, (String) arg);
+                } else if (arg instanceof Long) {
+                    query.bindLong(index++, (Long) arg);
+                } else if (arg instanceof Integer) {
+                    query.bindLong(index++, Long.valueOf((Integer) arg));
+                } else if (arg instanceof byte[]) {
+                    query.bindBlob(index++, (byte[]) arg);
+                } else {
+                    throw new IllegalStateException("Unsupported type CustomCursorFactory "
+                            + arg.getClass().toString());
+                }
+            }
+            return new SQLiteCursor(masterQuery, editTable, query);
+        }
+    }
+
+    // Returns the l2key of the closest match, if and only if it matches
+    // closely enough (as determined by group-closeness).
+    @Nullable
+    static String findClosestAttributes(@NonNull final SQLiteDatabase db,
+            @NonNull final NetworkAttributes attr) {
+        if (attr.isEmpty()) return null;
+        final ContentValues values = toContentValues(attr);
+
+        // Build the selection and args. To cut down on the number of lines to search, limit
+        // the search to those with at least one argument equals to the requested attributes.
+        // This works only because null attributes match only will not result in group-closeness.
+        final StringJoiner sj = new StringJoiner(" OR ");
+        final ArrayList<Object> args = new ArrayList<>();
+        args.add(System.currentTimeMillis());
+        for (final String field : values.keySet()) {
+            sj.add(field + " = ?");
+            args.add(values.get(field));
+        }
+
+        final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND ("
+                + sj.toString() + ")";
+        final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
+                false, // distinct
+                NetworkAttributesContract.TABLENAME,
+                null, // columns, null means everything
+                selection, // selection
+                null, // selectionArgs, horrendously passed to the cursor factory instead
+                null, // groupBy
+                null, // having
+                null, // orderBy
+                null); // limit
+        if (cursor.getCount() <= 0) return null;
+        cursor.moveToFirst();
+        String bestKey = null;
+        float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
+        while (!cursor.isAfterLast()) {
+            final NetworkAttributes read = readNetworkAttributesLine(cursor);
+            final float confidence = read.getNetworkGroupSamenessConfidence(attr);
+            if (confidence > bestMatchConfidence) {
+                bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
+                bestMatchConfidence = confidence;
+            }
+            cursor.moveToNext();
+        }
+        cursor.close();
+        return bestKey;
+    }
+
     // Helper methods
-    static String getString(final Cursor cursor, final String columnName) {
+    private static String getString(final Cursor cursor, final String columnName) {
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getString(columnIndex) : null;
     }
-    static byte[] getBlob(final Cursor cursor, final String columnName) {
+    private static byte[] getBlob(final Cursor cursor, final String columnName) {
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null;
     }
-    static int getInt(final Cursor cursor, final String columnName, final int defaultValue) {
+    private static int getInt(final Cursor cursor, final String columnName,
+            final int defaultValue) {
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue;
     }
+    private static long getLong(final Cursor cursor, final String columnName,
+            final long defaultValue) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue;
+    }
 }
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index 8b521f4..d43dc6a 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -250,9 +250,26 @@
      * Through the listener, returns the L2 key if one matched, or null.
      */
     @Override
-    public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
-            @NonNull final IOnL2KeyResponseListener listener) {
-        // TODO : implement this.
+    public void findL2Key(@Nullable final NetworkAttributesParcelable attributes,
+            @Nullable final IOnL2KeyResponseListener listener) {
+        if (null == listener) return;
+        mExecutor.execute(() -> {
+            try {
+                if (null == attributes) {
+                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    return;
+                }
+                if (null == mDb) {
+                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    return;
+                }
+                final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb,
+                        new NetworkAttributes(attributes));
+                listener.onL2KeyResponse(makeStatus(SUCCESS), key);
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
     }
 
     /**
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 98385c9..65eaf554 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -136,6 +136,7 @@
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
+import com.android.server.testharness.TestHarnessModeService;
 import com.android.server.textclassifier.TextClassificationManagerService;
 import com.android.server.textservices.TextServicesManagerService;
 import com.android.server.trust.TrustManagerService;
@@ -872,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();
@@ -1157,6 +1158,10 @@
                 traceBeginAndSlog("StartPersistentDataBlock");
                 mSystemServiceManager.startService(PersistentDataBlockService.class);
                 traceEnd();
+
+                traceBeginAndSlog("StartTestHarnessMode");
+                mSystemServiceManager.startService(TestHarnessModeService.class);
+                traceEnd();
             }
 
             if (hasPdb || OemLockService.isHalPresent()) {
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/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 1a16e56..53d72bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -1376,6 +1376,45 @@
         verifyLightStateConditions(LIGHT_STATE_ACTIVE);
     }
 
+    @Test
+    public void testStepToIdleMode() {
+        float delta = mDeviceIdleController.MIN_PRE_IDLE_FACTOR_CHANGE;
+        for (int mode = PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL;
+                mode <= PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG;
+                mode++) {
+            int ret = mDeviceIdleController.setPreIdleTimeoutMode(mode);
+            if (mode == PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL) {
+                assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
+                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+            } else {
+                assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
+                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+            }
+            //TODO(b/123045185): Mocked Handler of DeviceIdleController to make message loop
+            //workable in this test class
+            mDeviceIdleController.updatePreIdleFactor();
+            float expectedfactor = mDeviceIdleController.getPreIdleTimeoutByMode(mode);
+            float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
+            assertEquals("Pre idle time factor of mode [" + mode + "].",
+                    expectedfactor, curfactor, delta);
+            mDeviceIdleController.resetPreIdleTimeoutMode();
+            mDeviceIdleController.updatePreIdleFactor();
+
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_INACTIVE);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_PENDING);
+
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_SENSING);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_LOCATING);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_QUICK_DOZE_DELAY);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_MAINTENANCE);
+            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE);
+            checkMaybeDoAnImmediateMaintenance(expectedfactor);
+        }
+        float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
+        assertEquals("Pre idle time factor of mode default.",
+                1.0f, curfactor, delta);
+    }
+
     private void enterDeepState(int state) {
         switch (state) {
             case STATE_ACTIVE:
@@ -1599,4 +1638,84 @@
                 fail("Conditions for " + lightStateToString(expectedLightState) + " unknown.");
         }
     }
+
+    private void checkNextAlarmTimeWithNewPreIdleFactor(float factor, int state) {
+        final long errorTolerance = 1000;
+        enterDeepState(state);
+        long now = SystemClock.elapsedRealtime();
+        long alarm = mDeviceIdleController.getNextAlarmTime();
+        if (state == STATE_INACTIVE || state == STATE_IDLE_PENDING) {
+            int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+            if (Float.compare(factor, 1.0f) == 0) {
+                assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+            } else {
+                assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+            }
+            if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
+                mDeviceIdleController.updatePreIdleFactor();
+                long newAlarm = mDeviceIdleController.getNextAlarmTime();
+                long newDelay = (long) ((alarm - now) * factor);
+                assertTrue("setPreIdleTimeoutFactor: " + factor,
+                        Math.abs(newDelay - (newAlarm - now)) <  errorTolerance);
+                mDeviceIdleController.resetPreIdleTimeoutMode();
+                mDeviceIdleController.updatePreIdleFactor();
+                mDeviceIdleController.maybeDoImmediateMaintenance();
+                newAlarm = mDeviceIdleController.getNextAlarmTime();
+                assertTrue("resetPreIdleTimeoutMode from: " + factor,
+                        Math.abs(newAlarm - alarm) < errorTolerance);
+                mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+                now = SystemClock.elapsedRealtime();
+                enterDeepState(state);
+                newAlarm = mDeviceIdleController.getNextAlarmTime();
+                assertTrue("setPreIdleTimeoutFactor: " + factor + " before step to idle",
+                        Math.abs(newDelay - (newAlarm - now)) <  errorTolerance);
+                mDeviceIdleController.resetPreIdleTimeoutMode();
+                mDeviceIdleController.updatePreIdleFactor();
+                mDeviceIdleController.maybeDoImmediateMaintenance();
+            }
+        } else {
+            mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+            mDeviceIdleController.updatePreIdleFactor();
+            long newAlarm = mDeviceIdleController.getNextAlarmTime();
+            assertTrue("setPreIdleTimeoutFactor: " + factor
+                    + " shounld not change next alarm" ,
+                    (newAlarm == alarm));
+            mDeviceIdleController.resetPreIdleTimeoutMode();
+            mDeviceIdleController.updatePreIdleFactor();
+            mDeviceIdleController.maybeDoImmediateMaintenance();
+        }
+    }
+
+    private void checkMaybeDoAnImmediateMaintenance(float factor) {
+        int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
+        final long minuteInMillis = 60 * 1000;
+        if (Float.compare(factor, 1.0f) == 0) {
+            assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+                    mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
+        } else {
+            assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
+                    mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
+        }
+        if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
+            enterDeepState(STATE_IDLE);
+            long now = SystemClock.elapsedRealtime();
+            long alarm = mDeviceIdleController.getNextAlarmTime();
+            mDeviceIdleController.setIdleStartTimeForTest(
+                    now - (long) (mConstants.IDLE_TIMEOUT * 0.6));
+            mDeviceIdleController.maybeDoImmediateMaintenance();
+            long newAlarm = mDeviceIdleController.getNextAlarmTime();
+            assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6",
+                    newAlarm == alarm);
+            mDeviceIdleController.setIdleStartTimeForTest(
+                    now - (long) (mConstants.IDLE_TIMEOUT * 1.2));
+            mDeviceIdleController.maybeDoImmediateMaintenance();
+            newAlarm = mDeviceIdleController.getNextAlarmTime();
+            assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2",
+                    (newAlarm - now) < minuteInMillis);
+            mDeviceIdleController.resetPreIdleTimeoutMode();
+            mDeviceIdleController.updatePreIdleFactor();
+        }
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 57ee6dc..cad71a2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -25,6 +25,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
 
@@ -370,16 +371,19 @@
         mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
 
         ExecutionStats expectedStats = new ExecutionStats();
-        expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
 
-        mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001);
+        final int uid = 10001;
+        mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
         assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
         assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
         assertEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
         assertNotEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
+
+        assertFalse(mQuotaController.getForegroundUids().get(uid));
     }
 
     @Test
@@ -405,7 +409,7 @@
         mQuotaController.saveTimingSession(10, "com.android.test", one);
 
         ExecutionStats expectedStats = new ExecutionStats();
-        expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
 
         mQuotaController.onUserRemovedLocked(0);
@@ -440,14 +444,14 @@
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
         // Invalid time is now +24 hours since there are no sessions at all for the app.
-        expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
         // Invalid time is now +18 hours since there are no sessions in the window but the earliest
         // session is 6 hours ago.
-        expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 0;
         expectedStats.bgJobCountInWindow = 0;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -457,7 +461,7 @@
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
         // Invalid time is now since the session straddles the window cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 3;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -468,7 +472,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
         // Invalid time is now since the start of the session is at the very edge of the window
         // cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 3;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -479,7 +483,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
         // Invalid time is now +44 minutes since the earliest session in the window is now-5
         // minutes.
-        expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 3;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -489,7 +493,7 @@
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
         // Invalid time is now since the session is at the very edge of the window cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 4;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -500,7 +504,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
         // Invalid time is now since the start of the session is at the very edge of the window
         // cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 5;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -510,7 +514,7 @@
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
         // Invalid time is now since the session straddles the window cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 10;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -523,7 +527,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
         // Invalid time is now +59 minutes since the earliest session in the window is now-121
         // minutes.
-        expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 10;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -536,7 +540,7 @@
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
         // Invalid time is now since the start of the session is at the very edge of the window
         // cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
@@ -546,14 +550,14 @@
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
-        // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period.
+        // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period.
         mQuotaController.getTimingSessions(0, "com.android.test")
                 .add(0,
                         createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
         // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
         // before the end of the max period cutoff time.
-        expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
@@ -569,7 +573,7 @@
                                 2 * MINUTE_IN_MILLIS, 2));
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
         // Invalid time is now since the earlist session straddles the max period cutoff time.
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
@@ -599,7 +603,7 @@
 
         // Active
         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
-        expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 5;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -609,7 +613,7 @@
 
         // Working
         expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
-        expectedStats.invalidTimeElapsed = now;
+        expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 10;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -621,7 +625,7 @@
 
         // Frequent
         expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
-        expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -633,7 +637,7 @@
 
         // Rare
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
-        expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 20;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
@@ -675,7 +679,7 @@
 
         ExecutionStats expectedStats = new ExecutionStats();
         expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
-        expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed;
+        expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
         expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
@@ -688,7 +692,7 @@
         assertEquals(expectedStats, newStatsActive);
 
         expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
-        expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed;
+        expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
         expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
@@ -698,7 +702,7 @@
         assertNotEquals(expectedStats, newStatsWorking);
 
         expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
-        expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed;
+        expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
         expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
@@ -708,7 +712,7 @@
         assertNotEquals(expectedStats, newStatsFrequent);
 
         expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
-        expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed;
+        expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
         expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
@@ -719,6 +723,77 @@
     }
 
     @Test
+    public void testIsWithinQuotaLocked_NeverApp() {
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_Charging() {
+        setCharging();
+        assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() {
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.incrementJobCount(0, "com.android.test", 5);
+        assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        mQuotaController.saveTimingSession(0, "com.android.test.spam",
+                createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
+        mQuotaController.saveTimingSession(0, "com.android.test.spam",
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
+        mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount);
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam",
+                WORKING_INDEX));
+
+        mQuotaController.saveTimingSession(0, "com.android.test.frequent",
+                createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000));
+        mQuotaController.saveTimingSession(0, "com.android.test.frequent",
+                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500));
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent",
+                FREQUENT_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() {
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5));
+        mQuotaController.incrementJobCount(0, "com.android.test", 5);
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+    }
+
+    @Test
+    public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
+        mQuotaController.incrementJobCount(0, "com.android.test", jobCount);
+        assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+    }
+
+    @Test
     public void testMaybeScheduleCleanupAlarmLocked() {
         // No sessions saved yet.
         mQuotaController.maybeScheduleCleanupAlarmLocked();
@@ -752,6 +827,7 @@
 
         // Active window size is 10 minutes.
         final int standbyBucket = ACTIVE_INDEX;
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
 
         // No sessions saved yet.
         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -1016,11 +1092,37 @@
                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
 
         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
-        inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
-                any());
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
         inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
     }
 
+    @Test
+    public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final int standbyBucket = WORKING_INDEX;
+        ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+                SOURCE_PACKAGE, standbyBucket);
+        stats.jobCountInAllowedTime =
+                mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
+
+        // Invalid time in the past, so the count shouldn't be used.
+        stats.jobCountExpirationTimeElapsed =
+                now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
+        mQuotaController.maybeScheduleStartAlarmLocked(
+                SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+        // Invalid time in the future, so the count should be used.
+        stats.jobCountExpirationTimeElapsed =
+                now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
+        final long expectedWorkingAlarmTime =
+                stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
+        mQuotaController.maybeScheduleStartAlarmLocked(
+                SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
+        verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+    }
 
     /**
      * Tests that the start alarm is properly rescheduled if the earliest session that contributes
@@ -1172,6 +1274,11 @@
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
 
         mQuotaController.onConstantsUpdatedLocked();
 
@@ -1183,11 +1290,16 @@
                 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+        assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
+        assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+        assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+        assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+        assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
     }
 
     @Test
     public void testConstantsUpdating_InvalidValues() {
-        // Test negatives
+        // Test negatives/too low.
         mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
@@ -1195,6 +1307,11 @@
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
         mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1;
+        mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
 
         mQuotaController.onConstantsUpdatedLocked();
 
@@ -1205,6 +1322,11 @@
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+        assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
+        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
 
         // Test larger than a day. Controller should cap at one day.
         mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
@@ -1246,6 +1368,7 @@
     @Test
     public void testTimerTracking_Discharging() {
         setDischarging();
+        setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
 
         JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
@@ -1293,6 +1416,8 @@
      */
     @Test
     public void testTimerTracking_ChargingAndDischarging() {
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
         JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
         JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
@@ -1363,6 +1488,7 @@
     @Test
     public void testTimerTracking_AllBackground() {
         setDischarging();
+        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
 
         JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
@@ -1503,6 +1629,64 @@
     }
 
     /**
+     * Tests that Timers don't track job counts while in the foreground.
+     */
+    @Test
+    public void testTimerTracking_JobCount_Foreground() {
+        setDischarging();
+
+        final int standbyBucket = ACTIVE_INDEX;
+        JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1);
+        JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2);
+
+        mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobFg2, null);
+        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+        ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+                SOURCE_PACKAGE, standbyBucket);
+        assertEquals(0, stats.jobCountInAllowedTime);
+
+        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mQuotaController.prepareForExecutionLocked(jobFg1);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.prepareForExecutionLocked(jobFg2);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
+        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        assertEquals(0, stats.jobCountInAllowedTime);
+    }
+
+    /**
+     * Tests that Timers properly track job counts while in the background.
+     */
+    @Test
+    public void testTimerTracking_JobCount_Background() {
+        final int standbyBucket = WORKING_INDEX;
+        JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1);
+        JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2);
+        mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+
+        ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
+                SOURCE_PACKAGE, standbyBucket);
+        assertEquals(0, stats.jobCountInAllowedTime);
+
+        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        mQuotaController.prepareForExecutionLocked(jobBg1);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.prepareForExecutionLocked(jobBg2);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+
+        assertEquals(2, stats.jobCountInAllowedTime);
+    }
+
+    /**
      * Tests that Timers properly track overlapping top and background jobs.
      */
     @Test
@@ -1680,6 +1864,7 @@
         JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
         setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
+        setProcessState(ActivityManager.PROCESS_STATE_HOME);
         // Now the package only has two seconds to run.
         final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -1707,6 +1892,7 @@
         JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
         setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
+        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
         Handler handler = mQuotaController.getHandler();
         spyOn(handler);
 
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/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index 5083110..d91ce39 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -308,6 +308,24 @@
         assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED);
     }
 
+    @Test
+    public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
+        // All delay motion events should be sent, if multi-tap with out of distance slop.
+        // STATE_IDLE will check if tapCount() < 2.
+        allowEventDelegation();
+        assertStaysIn(STATE_IDLE, () -> {
+            tap();
+            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+        });
+        assertStaysIn(STATE_IDLE, () -> {
+            tap();
+            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+            tap();
+            tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+            tap();
+        });
+    }
+
     private void assertZoomsImmediatelyOnSwipeFrom(int state) {
         goFromStateIdleTo(state);
         swipeAndHold();
@@ -531,6 +549,11 @@
         send(upEvent());
     }
 
+    private void tap(float x, float y) {
+        send(downEvent(x, y));
+        send(upEvent(x, y));
+    }
+
     private void swipe() {
         swipeAndHold();
         send(upEvent());
@@ -572,18 +595,26 @@
     }
 
     private MotionEvent downEvent() {
+        return downEvent(DEFAULT_X, DEFAULT_Y);
+    }
+
+    private MotionEvent downEvent(float x, float y) {
         mLastDownTime = mClock.now();
         return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime,
-                ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0));
+                ACTION_DOWN, x, y, 0));
     }
 
     private MotionEvent upEvent() {
-        return upEvent(mLastDownTime);
+        return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime);
     }
 
-    private MotionEvent upEvent(long downTime) {
+    private MotionEvent upEvent(float x, float y) {
+        return upEvent(x, y, mLastDownTime);
+    }
+
+    private MotionEvent upEvent(float x, float y, long downTime) {
         return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(),
-                MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0));
+                MotionEvent.ACTION_UP, x, y, 0));
     }
 
     private MotionEvent pointerEvent(int action, float x, float y) {
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/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 38e8ac2..0813e6fa 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -206,6 +206,8 @@
         mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
 
         setUpUserManager();
+
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
     }
 
     private TransferOwnershipMetadataManager getMockTransferMetadataManager() {
@@ -836,6 +838,7 @@
                 MockUtils.checkIntent(intent),
                 MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID));
     }
+
     /**
      * Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully.
      */
@@ -2618,6 +2621,7 @@
         when(getServices().lockPatternUtils
                 .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false);
         dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER);
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
 
         verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER);
         verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM);
@@ -4233,6 +4237,41 @@
         assertTrue(dpm.isActivePasswordSufficient());
     }
 
+    public void testIsActivePasswordSufficient_noLockScreen() throws Exception {
+        // If there is no lock screen, the password is considered empty no matter what, because
+        // it provides no security.
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        mContext.packageName = admin1.getPackageName();
+        setupDeviceOwner();
+
+        // If no password requirements are set, isActivePasswordSufficient should succeed.
+        assertTrue(dpm.isActivePasswordSufficient());
+
+        // Now set some password quality requirements.
+        dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+
+        reset(mContext.spiedContext);
+        final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
+        PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
+                DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
+                8, 2,
+                6, 1,
+                0, 1);
+        // This should be ignored, as there is no lock screen.
+        dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle);
+        dpm.reportPasswordChanged(userHandle);
+
+        // No broadcast should be sent.
+        verify(mContext.spiedContext, times(0)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED),
+                MockUtils.checkUserHandle(userHandle));
+
+        // The active (nonexistent) password doesn't comply with the requirements.
+        assertFalse(dpm.isActivePasswordSufficient());
+    }
+
     private void setActivePasswordState(PasswordMetrics passwordMetrics)
             throws Exception {
         final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index 0328621..8afc3d3 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -58,8 +57,7 @@
         // Make sure that there is a short-circuit for DEFAULT_DISPLAY.
         assertEquals(DEFAULT_DISPLAY,
                 InputMethodManagerService.computeImeDisplayIdForTarget(
-                        DEFAULT_DISPLAY, false /* isVrImeStarted */,
-                        sMustNotBeCalledChecker));
+                        DEFAULT_DISPLAY, sMustNotBeCalledChecker));
     }
 
     @Test
@@ -67,17 +65,7 @@
         // Make sure that there is a short-circuit for INVALID_DISPLAY.
         assertEquals(DEFAULT_DISPLAY,
                 InputMethodManagerService.computeImeDisplayIdForTarget(
-                        INVALID_DISPLAY, false /* isVrImeStarted */,
-                        sMustNotBeCalledChecker));
-    }
-
-    @Test
-    public void testComputeImeDisplayId_VrIme() {
-        // Make sure that there is a short-circuit for VR IME.
-        assertEquals(DEFAULT_DISPLAY,
-                InputMethodManagerService.computeImeDisplayIdForTarget(
-                        SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, true /* isVrImeStarted */,
-                        sMustNotBeCalledChecker));
+                        INVALID_DISPLAY, sMustNotBeCalledChecker));
     }
 
     @Test
@@ -86,8 +74,7 @@
         // Make sure IME displayId is DEFAULT_DISPLAY.
         assertEquals(DEFAULT_DISPLAY,
                 InputMethodManagerService.computeImeDisplayIdForTarget(
-                        NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
-                        sChecker));
+                        NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
     }
 
     @Test
@@ -96,7 +83,6 @@
         // Make sure IME displayId is the same display.
         assertEquals(SYSTEM_DECORATION_SUPPORT_DISPLAY_ID,
                 InputMethodManagerService.computeImeDisplayIdForTarget(
-                        SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
-                        sChecker));
+                        SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 2dc3510..cf89cb8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -37,8 +37,8 @@
 import android.os.IProgressListener;
 import android.os.RemoteException;
 import android.os.UserManager;
-import android.os.storage.StorageManager;
 import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
 import android.security.KeyStore;
 import android.test.AndroidTestCase;
 
@@ -46,6 +46,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
 
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -85,6 +86,8 @@
     KeyStore mKeyStore;
     MockSyntheticPasswordManager mSpManager;
     IAuthSecret mAuthSecretService;
+    WindowManagerInternal mMockWindowManager;
+    protected boolean mHasSecureLockScreen;
 
     @Override
     protected void setUp() throws Exception {
@@ -97,10 +100,13 @@
         mActivityManager = mock(IActivityManager.class);
         mDevicePolicyManager = mock(DevicePolicyManager.class);
         mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class);
+        mMockWindowManager = mock(WindowManagerInternal.class);
 
         LocalServices.removeServiceForTest(LockSettingsInternal.class);
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
+        LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);
 
         mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
                 mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
@@ -114,11 +120,17 @@
             storageDir.mkdirs();
         }
 
+        mHasSecureLockScreen = true;
         mLockPatternUtils = new LockPatternUtils(mContext) {
             @Override
             public ILockSettings getLockSettings() {
                 return mService;
             }
+
+            @Override
+            public boolean hasSecureLockScreen() {
+                return mHasSecureLockScreen;
+            }
         };
         mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
                 mUserManager);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index e12f6d3..5124803 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -26,13 +26,12 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
 
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.service.gatekeeper.GateKeeperResponse;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 
 /**
  * runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests
@@ -54,11 +53,21 @@
                 PASSWORD_QUALITY_ALPHABETIC);
     }
 
+    public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
+        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password",
+                CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+    }
+
     public void testCreatePatternPrimaryUser() throws RemoteException {
         testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
                 PASSWORD_QUALITY_SOMETHING);
     }
 
+    public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
+        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789",
+                CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+    }
+
     public void testChangePasswordPrimaryUser() throws RemoteException {
         testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
                 "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
@@ -198,6 +207,21 @@
         assertVerifyCredentials(userId, credential, type, -1);
     }
 
+    private void testCreateCredentialFailsWithoutLockScreen(
+            int userId, String credential, int type, int quality) throws RemoteException {
+        mHasSecureLockScreen = false;
+
+        try {
+            mService.setLockCredential(credential, type, null, quality, userId);
+            fail("An exception should have been thrown.");
+        } catch (UnsupportedOperationException e) {
+            // Success - the exception was expected.
+        }
+
+        assertFalse(mService.havePassword(userId));
+        assertFalse(mService.havePattern(userId));
+    }
+
     private void testChangeCredentials(int userId, String newCredential, int newType,
             String oldCredential, int oldType, int quality) throws RemoteException {
         final long sid = 1234;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index a28a5a1..929c3b5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -77,6 +78,7 @@
         final Context context = InstrumentationRegistry.getTargetContext();
         mUserId = ActivityManager.getCurrentUser();
         mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
     }
 
     @Test
@@ -103,6 +105,16 @@
     }
 
     @Test
+    public void testChangePin_noLockScreen() throws Exception {
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+        assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-pin", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils).hasSecureLockScreen();
+        verifyNoMoreInteractions(mLockPatternUtils);
+    }
+
+    @Test
     public void testChangePassword() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
@@ -115,6 +127,16 @@
     }
 
     @Test
+    public void testChangePassword_noLockScreen() throws Exception {
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+        assertEquals(-1,  mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-password", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils).hasSecureLockScreen();
+        verifyNoMoreInteractions(mLockPatternUtils);
+    }
+
+    @Test
     public void testChangePattern() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
@@ -126,6 +148,16 @@
     }
 
     @Test
+    public void testChangePattern_noLockScreen() throws Exception {
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+        assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-pattern", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils).hasSecureLockScreen();
+        verifyNoMoreInteractions(mLockPatternUtils);
+    }
+
+    @Test
     public void testClear() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 94e02bc..0595a5b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -40,10 +40,10 @@
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
 import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
 
-import java.util.ArrayList;
-
 import org.mockito.ArgumentCaptor;
 
+import java.util.ArrayList;
+
 
 /**
  * runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
@@ -448,6 +448,37 @@
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
 
+    public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
+        final String password = "password";
+        final String pattern = "123654";
+        final String token = "some-high-entropy-secure-token";
+
+        mHasSecureLockScreen = false;
+        enableSyntheticPassword();
+        long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID);
+        assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+        try {
+            mLocalService.setLockCredentialWithToken(password,
+                    LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token.getBytes(),
+                    PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+            fail("An exception should have been thrown.");
+        } catch (UnsupportedOperationException e) {
+            // Success - the exception was expected.
+        }
+        assertFalse(mService.havePassword(PRIMARY_USER_ID));
+
+        try {
+            mLocalService.setLockCredentialWithToken(pattern,
+                    LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token.getBytes(),
+                    PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+            fail("An exception should have been thrown.");
+        } catch (UnsupportedOperationException e) {
+            // Success - the exception was expected.
+        }
+        assertFalse(mService.havePattern(PRIMARY_USER_ID));
+    }
+
     public void testgetHashFactorPrimaryUser() throws RemoteException {
         final String password = "password";
         mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
index f817e8e..6da202b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
@@ -16,8 +16,6 @@
 
 package com.android.server.pm.dex;
 
-import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.atMost;
@@ -26,10 +24,12 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.os.UserHandle;
 import android.os.storage.StorageManager;
 
 import androidx.test.filters.SmallTest;
@@ -56,40 +56,44 @@
 public class DexLoggerTests {
     private static final String OWNING_PACKAGE_NAME = "package.name";
     private static final String VOLUME_UUID = "volUuid";
-    private static final String DEX_PATH = "/bar/foo.jar";
+    private static final String FILE_PATH = "/bar/foo.jar";
     private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE;
     private static final int OWNER_UID = 43;
     private static final int OWNER_USER_ID = 44;
 
     // Obtained via: echo -n "foo.jar" | sha256sum
-    private static final String DEX_FILENAME_HASH =
+    private static final String FILENAME_HASH =
             "91D7B844D7CC9673748FF057D8DC83972280FC28537D381AA42015A9CF214B9F";
 
-    private static final byte[] CONTENT_HASH_BYTES = new byte[] {
-        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
-        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
+    private static final byte[] CONTENT_HASH_BYTES = new byte[]{
+            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
     };
     private static final String CONTENT_HASH =
             "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20";
     private static final byte[] EMPTY_BYTES = {};
 
-    @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+    private static final String EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH =
+            "dcl:" + FILENAME_HASH;
+    private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
+            EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH + " " + CONTENT_HASH;
+    private static final String EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH =
+            "dcln:" + FILENAME_HASH + " " + CONTENT_HASH;
+
+    @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.LENIENT);
 
     @Mock IPackageManager mPM;
     @Mock Installer mInstaller;
 
-    private PackageDynamicCodeLoading mPackageDynamicCodeLoading;
     private DexLogger mDexLogger;
 
     private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create();
     private boolean mWriteTriggered = false;
-    private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
-            DEX_FILENAME_HASH + " " + CONTENT_HASH;
 
     @Before
     public void setup() throws Exception {
         // Disable actually attempting to do file writes.
-        mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() {
+        PackageDynamicCodeLoading packageDynamicCodeLoading = new PackageDynamicCodeLoading() {
             @Override
             void maybeWriteAsync() {
                 mWriteTriggered = true;
@@ -102,13 +106,13 @@
         };
 
         // For test purposes capture log messages as well as sending to the event log.
-        mDexLogger = new DexLogger(mPM, mInstaller, mPackageDynamicCodeLoading) {
+        mDexLogger = new DexLogger(mPM, mInstaller, packageDynamicCodeLoading) {
             @Override
-                void writeDclEvent(int uid, String message) {
-                    super.writeDclEvent(uid, message);
-                    mMessagesForUid.put(uid, message);
-                }
-            };
+            void writeDclEvent(String subtag, int uid, String message) {
+                super.writeDclEvent(subtag, uid, message);
+                mMessagesForUid.put(uid, subtag + ":" + message);
+            }
+        };
 
         // Make the owning package exist in our mock PackageManager.
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -124,9 +128,9 @@
 
     @Test
     public void testOneLoader_ownFile_withFileHash() throws Exception {
-        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
 
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
@@ -139,13 +143,13 @@
 
     @Test
     public void testOneLoader_ownFile_noFileHash() throws Exception {
-        whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(EMPTY_BYTES));
 
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
-        assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+        assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
 
         // File should be removed from the DCL list, since we can't hash it.
         assertThat(mWriteTriggered).isTrue();
@@ -154,13 +158,14 @@
 
     @Test
     public void testOneLoader_ownFile_hashingFails() throws Exception {
-        whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test")));
+        whenFileIsHashed(FILE_PATH,
+                doThrow(new InstallerException("Intentional failure for test")));
 
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
-        assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+        assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
 
         // File should be removed from the DCL list, since we can't hash it.
         assertThat(mWriteTriggered).isTrue();
@@ -178,11 +183,23 @@
     }
 
     @Test
+    public void testOneLoader_pathTraversal() throws Exception {
+        String filePath = "/bar/../secret/foo.jar";
+        whenFileIsHashed(filePath, doReturn(CONTENT_HASH_BYTES));
+        setPackageUid(OWNING_PACKAGE_NAME, -1);
+
+        recordLoad(OWNING_PACKAGE_NAME, filePath);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+        assertThat(mMessagesForUid).isEmpty();
+    }
+
+    @Test
     public void testOneLoader_differentOwner() throws Exception {
-        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
         setPackageUid("other.package.name", 1001);
 
-        recordLoad("other.package.name", DEX_PATH);
+        recordLoad("other.package.name", FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(1001);
@@ -192,10 +209,10 @@
 
     @Test
     public void testOneLoader_differentOwner_uninstalled() throws Exception {
-        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
         setPackageUid("other.package.name", -1);
 
-        recordLoad("other.package.name", DEX_PATH);
+        recordLoad("other.package.name", FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid).isEmpty();
@@ -203,22 +220,38 @@
     }
 
     @Test
+    public void testNativeCodeLoad() throws Exception {
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
+
+        recordLoadNative(FILE_PATH);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+        assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
+        assertThat(mMessagesForUid)
+                .containsEntry(OWNER_UID, EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH);
+
+        assertThat(mWriteTriggered).isFalse();
+        assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading())
+                .containsExactly(OWNING_PACKAGE_NAME);
+    }
+
+    @Test
     public void testMultipleLoadersAndFiles() throws Exception {
         String otherDexPath = "/bar/nosuchdir/foo.jar";
-        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES));
         whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES));
         setPackageUid("other.package.name1", 1001);
         setPackageUid("other.package.name2", 1002);
 
-        recordLoad("other.package.name1", DEX_PATH);
+        recordLoad("other.package.name1", FILE_PATH);
         recordLoad("other.package.name1", otherDexPath);
-        recordLoad("other.package.name2", DEX_PATH);
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad("other.package.name2", FILE_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID);
         assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
-        assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH);
+        assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH);
         assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
         assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
 
@@ -233,7 +266,7 @@
     @Test
     public void testUnknownOwner() {
         reset(mPM);
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading("other.package.name");
 
         assertThat(mMessagesForUid).isEmpty();
@@ -244,7 +277,7 @@
     @Test
     public void testUninstalledPackage() {
         reset(mPM);
-        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, FILE_PATH);
         mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid).isEmpty();
@@ -262,7 +295,16 @@
     }
 
     private void recordLoad(String loadingPackageName, String dexPath) {
-        mPackageDynamicCodeLoading.record(
-                OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName);
+        mDexLogger.recordDex(OWNER_USER_ID, dexPath, OWNING_PACKAGE_NAME, loadingPackageName);
+        mWriteTriggered = false;
+    }
+
+    private void recordLoadNative(String nativePath) throws Exception {
+        int loadingUid = UserHandle.getUid(OWNER_USER_ID, OWNER_UID);
+        String[] packageNames = { OWNING_PACKAGE_NAME };
+        when(mPM.getPackagesForUid(loadingUid)).thenReturn(packageNames);
+
+        mDexLogger.recordNative(loadingUid, nativePath);
+        mWriteTriggered = false;
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 7cd8cedd..2ddc71f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -55,6 +55,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -685,4 +686,68 @@
             return mPackageInfo.applicationInfo.splitSourceDirs[length - 1];
         }
     }
+
+    private boolean shouldPackageRunOob(
+            boolean isDefaultEnabled, String defaultWhitelist, String overrideEnabled,
+            String overrideWhitelist, Collection<String> packageNamesInSameProcess) {
+        return DexManager.isPackageSelectedToRunOobInternal(
+                isDefaultEnabled, defaultWhitelist, overrideEnabled, overrideWhitelist,
+                packageNamesInSameProcess);
+    }
+
+    @Test
+    public void testOobPackageSelectionSwitch() {
+        // Feature is off by default, not overriden
+        assertFalse(shouldPackageRunOob(false, "ALL", null, null, null));
+
+        // Feature is off by default, overriden
+        assertTrue(shouldPackageRunOob(false, "ALL", "true", "ALL", null));
+        assertFalse(shouldPackageRunOob(false, "ALL", "false", null, null));
+        assertFalse(shouldPackageRunOob(false, "ALL", "false", "ALL", null));
+        assertFalse(shouldPackageRunOob(false, "ALL", "false", null, null));
+
+        // Feature is on by default, not overriden
+        assertTrue(shouldPackageRunOob(true, "ALL", null, null, null));
+        assertTrue(shouldPackageRunOob(true, "ALL", null, null, null));
+        assertTrue(shouldPackageRunOob(true, "ALL", null, "ALL", null));
+
+        // Feature is on by default, overriden
+        assertTrue(shouldPackageRunOob(true, "ALL", "true", null, null));
+        assertTrue(shouldPackageRunOob(true, "ALL", "true", "ALL", null));
+        assertFalse(shouldPackageRunOob(true, "ALL", "false", null, null));
+        assertFalse(shouldPackageRunOob(true, "ALL", "false", "ALL", null));
+    }
+
+    @Test
+    public void testOobPackageSelectionWhitelist() {
+        // Various whitelist of apps to run in OOB mode.
+        final String kWhitelistApp0 = "com.priv.app0";
+        final String kWhitelistApp1 = "com.priv.app1";
+        final String kWhitelistApp2 = "com.priv.app2";
+        final String kWhitelistApp1AndApp2 = "com.priv.app1,com.priv.app2";
+
+        // Packages that shares the targeting process.
+        final Collection<String> runningPackages = Arrays.asList("com.priv.app1", "com.priv.app2");
+
+        // Feature is off, whitelist does not matter
+        assertFalse(shouldPackageRunOob(false, kWhitelistApp0, null, null, runningPackages));
+        assertFalse(shouldPackageRunOob(false, kWhitelistApp1, null, null, runningPackages));
+        assertFalse(shouldPackageRunOob(false, "", null, kWhitelistApp1, runningPackages));
+        assertFalse(shouldPackageRunOob(false, "", null, "ALL", runningPackages));
+        assertFalse(shouldPackageRunOob(false, "ALL", null, "ALL", runningPackages));
+        assertFalse(shouldPackageRunOob(false, "ALL", null, "", runningPackages));
+
+        // Feature is on, app not in default or overridden whitelist
+        assertFalse(shouldPackageRunOob(true, kWhitelistApp0, null, null, runningPackages));
+        assertFalse(shouldPackageRunOob(true, "", null, kWhitelistApp0, runningPackages));
+        assertFalse(shouldPackageRunOob(true, "ALL", null, kWhitelistApp0, runningPackages));
+
+        // Feature is on, app in default or overridden whitelist
+        assertTrue(shouldPackageRunOob(true, kWhitelistApp1, null, null, runningPackages));
+        assertTrue(shouldPackageRunOob(true, kWhitelistApp2, null, null, runningPackages));
+        assertTrue(shouldPackageRunOob(true, kWhitelistApp1AndApp2, null, null, runningPackages));
+        assertTrue(shouldPackageRunOob(true, kWhitelistApp1, null, "ALL", runningPackages));
+        assertTrue(shouldPackageRunOob(true, "", null, kWhitelistApp1, runningPackages));
+        assertTrue(shouldPackageRunOob(true, "ALL", null, kWhitelistApp1, runningPackages));
+    }
 }
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/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c0f9b80..6222923 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -245,8 +245,8 @@
         }
 
         @Override
-        void logSmartSuggestionsVisible(NotificationRecord r) {
-            super.logSmartSuggestionsVisible(r);
+        void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
+            super.logSmartSuggestionsVisible(r, notificationLocation);
             countLogSmartSuggestionsVisible++;
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 319ffed..8be63fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -31,7 +31,6 @@
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
 import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
-import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
 
@@ -76,9 +75,6 @@
         mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
         mTask = mStack.getChildAt(0);
         mActivity = mTask.getTopActivity();
-
-        doReturn(false).when(mService).isBooting();
-        doReturn(true).when(mService).isBooted();
     }
 
     @Test
@@ -121,23 +117,22 @@
 
         mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
 
-        // The activity is in the focused stack so it should be resumed.
+        // The activity is in the focused stack so it should not move to paused.
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
-        assertTrue(mActivity.isState(RESUMED));
+        assertTrue(mActivity.isState(STOPPED));
         assertFalse(pauseFound.value);
 
-        // Make the activity non focusable
-        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
-        doReturn(false).when(mActivity).isFocusable();
+        // Clear focused stack
+        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        when(display.getFocusedStack()).thenReturn(null);
 
-        // If the activity is not focusable, it should move to paused.
+        // In the unfocused stack, the activity should move to paused.
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
         assertTrue(mActivity.isState(PAUSING));
         assertTrue(pauseFound.value);
 
         // Make sure that the state does not change for current non-stopping states.
         mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
-        doReturn(true).when(mActivity).isFocusable();
 
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index ea8f33f..68df87e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -55,7 +55,6 @@
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.PowerManager;
 import android.os.Process;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
@@ -426,7 +425,6 @@
             doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
             // allow background activity starts by default
             doReturn(true).when(this).isBackgroundActivityStartsEnabled();
-            doNothing().when(this).updateCpuStats();
         }
 
         void setup(IntentFirewall intentFirewall, PendingIntentController intentController,
@@ -582,8 +580,6 @@
             doNothing().when(this).acquireLaunchWakelock();
             doReturn(mKeyguardController).when(this).getKeyguardController();
 
-            mLaunchingActivity = mock(PowerManager.WakeLock.class);
-
             initialize();
         }
 
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/startop/OWNERS b/startop/OWNERS
index 762cd8e..5cf9582 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -2,5 +2,5 @@
 chriswailes@google.com
 eholk@google.com
 iam@google.com
-sehr@google.com
 mathieuc@google.com
+sehr@google.com
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 826ad82..818ebd9 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.app.Service;
@@ -32,6 +33,9 @@
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This service can be implemented by the default dialer (see
  * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -88,6 +92,128 @@
  * </pre>
  */
 public abstract class CallScreeningService extends Service {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = { "CALL_DURATION_" },
+            value = {CALL_DURATION_VERY_SHORT, CALL_DURATION_SHORT, CALL_DURATION_MEDIUM,
+                    CALL_DURATION_LONG})
+    public @interface CallDuration {}
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was < 3 seconds in duration.
+     */
+    public static final int CALL_DURATION_VERY_SHORT = 1;
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was greater than 3 seconds, but less than 60 seconds in duration.
+     */
+    public static final int CALL_DURATION_SHORT = 2;
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was greater than 60 seconds, but less than 120 seconds in duration.
+     */
+    public static final int CALL_DURATION_MEDIUM = 3;
+
+    /**
+     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
+     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
+     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
+     * {@link CallScreeningService} can use this as a signal for training nuisance detection
+     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
+     * identifying call log information to the {@link CallScreeningService}.
+     * <p>
+     * Indicates the call was greater than 120 seconds.
+     */
+    public static final int CALL_DURATION_LONG = 4;
+
+    /**
+     * Telecom sends this intent to the {@link CallScreeningService} which the user has chosen to
+     * fill the call screening role when the user indicates through the default dialer whether a
+     * call is a nuisance call or not (see
+     * {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).
+     * <p>
+     * The following extra values are provided for the call:
+     * <ol>
+     *     <li>{@link #EXTRA_CALL_HANDLE} - the handle of the call.</li>
+     *     <li>{@link #EXTRA_IS_NUISANCE} - {@code true} if the user reported the call as a nuisance
+     *     call, {@code false} otherwise.</li>
+     *     <li>{@link #EXTRA_CALL_TYPE} - reports the type of call (incoming, rejected, missed,
+     *     blocked).</li>
+     *     <li>{@link #EXTRA_CALL_DURATION} - the duration of the call (see
+     *     {@link #EXTRA_CALL_DURATION} for valid values).</li>
+     * </ol>
+     * <p>
+     * {@link CallScreeningService} implementations which want to track whether the user reports
+     * calls are nuisance calls should use declare a broadcast receiver in their manifest for this
+     * intent.
+     * <p>
+     * Note: Only {@link CallScreeningService} implementations which have provided
+     * {@link CallIdentification} information for calls at some point will receive this intent.
+     */
+    public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED =
+            "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
+
+    /**
+     * Extra used to provide the handle of the call for
+     * {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.  The call handle is reported as a
+     * {@link Uri}.
+     */
+    public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
+
+    /**
+     * Boolean extra used to indicate whether the user reported a call as a nuisance call.
+     * When {@code true}, the user reported that a call was a nuisance call, {@code false}
+     * otherwise.  Sent with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.
+     */
+    public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
+
+    /**
+     * Integer extra used with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report the type of
+     * call. Valid values are:
+     * <UL>
+     *   <li>{@link android.provider.CallLog.Calls#MISSED_TYPE}</li>
+     *   <li>{@link android.provider.CallLog.Calls#INCOMING_TYPE}</li>
+     *   <li>{@link android.provider.CallLog.Calls#BLOCKED_TYPE}</li>
+     *   <li>{@link android.provider.CallLog.Calls#REJECTED_TYPE}</li>
+     * </UL>
+     */
+    public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
+
+    /**
+     * Integer extra used to with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report how long
+     * the call lasted.  Valid values are:
+     * <UL>
+     *     <LI>{@link #CALL_DURATION_VERY_SHORT}</LI>
+     *     <LI>{@link #CALL_DURATION_SHORT}</LI>
+     *     <LI>{@link #CALL_DURATION_MEDIUM}</LI>
+     *     <LI>{@link #CALL_DURATION_LONG}</LI>
+     * </UL>
+     */
+    public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+
     /**
      * The {@link Intent} that must be declared as handled by the service.
      */
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 0fe5e08..12a5344 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1970,6 +1970,33 @@
     }
 
     /**
+     * Called by the default dialer to report to Telecom when the user has marked a previous
+     * incoming call as a nuisance call or not.
+     * <p>
+     * Where the user has chosen a {@link CallScreeningService} to fill the call screening role,
+     * Telecom will notify that {@link CallScreeningService} of the user's report.
+     * <p>
+     * Requires that the caller is the default dialer app.
+     *
+     * @param handle The phone number of an incoming call which the user is reporting as either a
+     *               nuisance of non-nuisance call.
+     * @param isNuisanceCall {@code true} if the user is reporting the call as a nuisance call,
+     *                       {@code false} if the user is reporting the call as a non-nuisance call.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    public void reportNuisanceCallStatus(@NonNull Uri handle, boolean isNuisanceCall) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                service.reportNuisanceCallStatus(handle, isNuisanceCall,
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
+            }
+        }
+    }
+
+    /**
      * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
      * @param intent The {@link Intent#ACTION_CALL} intent to handle.
      * @hide
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index e1d5c17..5030f90 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -285,6 +285,8 @@
      */
     boolean isInEmergencyCall();
 
+    oneway void reportNuisanceCallStatus(in Uri address, boolean isNuisance, String callingPackage);
+
     /**
      * @see TelecomServiceImpl#handleCallIntent
      */
@@ -299,4 +301,5 @@
     void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded);
 
     void setTestAutoModeApp(String packageName);
+
 }
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/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 0e5c71d..0fa1b41 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -129,6 +129,66 @@
             "android.telephony.euicc.action.RESOLVE_ERROR";
 
     /**
+     * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+     * enable or disable a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+     * {@link #EXTRA_ENABLE_SUBSCRIPTION}.
+     *
+     * <p>Unlike {@link #switchToSubscription(int, PendingIntent)}, using this action allows the
+     * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+     * operation. The action is received by the Telephony framework, which in turn selects and
+     * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+     * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+     *
+     * <p>The launched activity will immediately finish with
+     * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+            "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+    /**
+     * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+     * delete a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID}.
+     *
+     * <p>Unlike {@link #deleteSubscription(int, PendingIntent)}, using this action allows the
+     * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+     * operation. The action is received by the Telephony framework, which in turn selects and
+     * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+     * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+     *
+     * <p>The launched activity will immediately finish with
+     * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+            "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+    /**
+     * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+     * rename a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+     * {@link #EXTRA_SUBSCRIPTION_NICKNAME}.
+     *
+     * <p>Unlike {@link #updateSubscriptionNickname(int, String, PendingIntent)}, using this action
+     * allows the the underlying eUICC service (i.e. the LPA app) to control the UI experience
+     * during this operation. The action is received by the Telephony framework, which in turn
+     * selects and launches an appropriate LPA activity to present UI to the user. For example, the
+     * activity may show a confirmation dialog, a progress dialog, or an error dialog when
+     * necessary.
+     *
+     * <p>The launched activity will immediately finish with
+     * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+            "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
+    /**
      * Result code for an operation indicating that the operation succeeded.
      */
     public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0;
@@ -219,6 +279,37 @@
             "android.telephony.euicc.extra.FORCE_PROVISION";
 
     /**
+     * Key for an extra set on privileged actions {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED},
+     * {@link #ACTION_DELETE_SUBSCRIPTION_PRIVILEGED}, and
+     * {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing the ID of the targeted subscription.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_SUBSCRIPTION_ID =
+            "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+
+    /**
+     * Key for an extra set on {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED} providing a boolean
+     * value of whether to enable or disable the targeted subscription.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_ENABLE_SUBSCRIPTION =
+            "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
+
+    /**
+     * Key for an extra set on {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing a new
+     * nickname for the targeted subscription.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_SUBSCRIPTION_NICKNAME =
+            "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
+
+    /**
      * Optional meta-data attribute for a carrier app providing an icon to use to represent the
      * carrier. If not provided, the app's launcher icon will be used as a fallback.
      */
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/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
index ee2ec0a..979d13a 100644
--- a/tests/DexLoggerIntegrationTests/Android.mk
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -29,6 +29,35 @@
 dexloggertest_jar := $(LOCAL_BUILT_MODULE)
 
 
+# Also build a native library that the test app can dynamically load
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerNativeTestLibrary
+LOCAL_MULTILIB := first
+LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp
+LOCAL_C_INCLUDES += \
+    $(JNI_H_INCLUDE)
+LOCAL_SDK_VERSION := 28
+LOCAL_NDK_STL_VARIANT := c++_static
+
+include $(BUILD_SHARED_LIBRARY)
+
+dexloggertest_so := $(LOCAL_BUILT_MODULE)
+
+# And a standalone native executable that we can exec.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := DexLoggerNativeExecutable
+LOCAL_SRC_FILES := src/cpp/test_executable.cpp
+
+include $(BUILD_EXECUTABLE)
+
+dexloggertest_executable := $(LOCAL_BUILT_MODULE)
+
 # Build the test app itself
 
 include $(CLEAR_VARS)
@@ -37,14 +66,18 @@
 LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests
 LOCAL_SDK_VERSION := current
 LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
+LOCAL_CERTIFICATE := shared
 LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     truth-prebuilt \
 
-# This gets us the javalib.jar built by DexLoggerTestLibrary above.
-LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar)
+# This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various
+# native binaries.
+LOCAL_JAVA_RESOURCE_FILES := \
+    $(dexloggertest_jar) \
+    $(dexloggertest_so) \
+    $(dexloggertest_executable)
 
 include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index 75ee089..d68769b 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -17,6 +17,7 @@
 package com.android.server.pm.dex;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.app.UiAutomation;
 import android.content.Context;
@@ -25,6 +26,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.util.EventLog;
+import android.util.EventLog.Event;
 
 import dalvik.system.DexClassLoader;
 
@@ -65,14 +67,13 @@
     // Event log tag used for SNET related events
     private static final int SNET_TAG = 0x534e4554;
 
-    // Subtag used to distinguish dynamic code loading events
-    private static final String DCL_SUBTAG = "dcl";
+    // Subtags used to distinguish dynamic code loading events
+    private static final String DCL_DEX_SUBTAG = "dcl";
+    private static final String DCL_NATIVE_SUBTAG = "dcln";
 
-    // All the tags we care about
-    private static final int[] TAG_LIST = new int[] { SNET_TAG };
-
-    // This is {@code DynamicCodeLoggingService#JOB_ID}
-    private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028;
+    // These are job IDs from DynamicCodeLoggingService
+    private static final int IDLE_LOGGING_JOB_ID = 2030028;
+    private static final int AUDIT_WATCHING_JOB_ID = 203142925;
 
     private static Context sContext;
     private static int sMyUid;
@@ -89,15 +90,20 @@
         // Without this the first test passes and others don't - we don't see new events in the
         // log. The exact reason is unclear.
         EventLog.writeEvent(SNET_TAG, "Dummy event");
+
+        // Audit log messages are throttled by the kernel (at the request of logd) to 5 per
+        // second, so running the tests too quickly in sequence means we lose some and get
+        // spurious failures. Sigh.
+        SystemClock.sleep(1000);
     }
 
     @Test
-    public void testDexLoggerGeneratesEvents() throws Exception {
-        File privateCopyFile = fileForJar("copied.jar");
+    public void testDexLoggerGeneratesEvents_standardClassLoader() throws Exception {
+        File privateCopyFile = privateFile("copied.jar");
         // Obtained via "echo -n copied.jar | sha256sum"
         String expectedNameHash =
                 "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
-        String expectedContentHash = copyAndHashJar(privateCopyFile);
+        String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
 
         // Feed the jar to a class loader and make sure it contains what we expect.
         ClassLoader parentClassLoader = sContext.getClass().getClassLoader();
@@ -107,18 +113,18 @@
 
         // And make sure we log events about it
         long previousEventNanos = mostRecentEventTimeNanos();
-        runDexLogger();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
 
-        assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+        assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
+                expectedNameHash, expectedContentHash);
     }
 
     @Test
-
     public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception {
-        File privateCopyFile = fileForJar("copied2.jar");
+        File privateCopyFile = privateFile("copied2.jar");
         String expectedNameHash =
                 "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93";
-        String expectedContentHash = copyAndHashJar(privateCopyFile);
+        String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
 
         // This time make sure an unknown class loader is an ancestor of the class loader we use.
         ClassLoader knownClassLoader = sContext.getClass().getClassLoader();
@@ -129,22 +135,185 @@
 
         // And make sure we log events about it
         long previousEventNanos = mostRecentEventTimeNanos();
-        runDexLogger();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
 
-        assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+        assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG,
+                expectedNameHash, expectedContentHash);
     }
 
-    private static File fileForJar(String name) {
-        return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name);
+    @Test
+    public void testDexLoggerGeneratesEvents_nativeLibrary() throws Exception {
+        File privateCopyFile = privateFile("copied.so");
+        String expectedNameHash =
+                "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E";
+        String expectedContentHash =
+                copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+
+        System.load(privateCopyFile.toString());
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+                expectedNameHash, expectedContentHash);
     }
 
-    private static String copyAndHashJar(File copyTo) throws Exception {
+    @Test
+    public void testDexLoggerGeneratesEvents_nativeLibrary_escapedName() throws Exception {
+        // A file name with a space will be escaped in the audit log; verify we un-escape it
+        // correctly.
+        File privateCopyFile = privateFile("second copy.so");
+        String expectedNameHash =
+                "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA";
+        String expectedContentHash =
+                copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+
+        System.load(privateCopyFile.toString());
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+                expectedNameHash, expectedContentHash);
+    }
+
+    @Test
+    public void testDexLoggerGeneratesEvents_nativeExecutable() throws Exception {
+        File privateCopyFile = privateFile("test_executable");
+        String expectedNameHash =
+                "3FBEC3F925A132D18F347F11AE9A5BB8DE1238828F8B4E064AA86EB68BD46DCF";
+        String expectedContentHash =
+                copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile);
+        assertThat(privateCopyFile.setExecutable(true)).isTrue();
+
+        Process process = Runtime.getRuntime().exec(privateCopyFile.toString());
+        int exitCode = process.waitFor();
+        assertThat(exitCode).isEqualTo(0);
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+                expectedNameHash, expectedContentHash);
+    }
+
+    @Test
+    public void testDexLoggerGeneratesEvents_spoofed_validFile() throws Exception {
+        File privateCopyFile = privateFile("spoofed");
+
+        String expectedContentHash =
+                copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile);
+
+        EventLog.writeEvent(EventLog.getTagCode("auditd"),
+                "type=1400 avc: granted { execute_no_trans } "
+                        + "path=\"" + privateCopyFile + "\" "
+                        + "scontext=u:r:untrusted_app_27: "
+                        + "tcontext=u:object_r:app_data_file: "
+                        + "tclass=file ");
+
+        String expectedNameHash =
+                "1CF36F503A02877BB775DC23C1C5A47A95F2684B6A1A83B11795B856D88861E3";
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG,
+                expectedNameHash, expectedContentHash);
+    }
+
+    @Test
+    public void testDexLoggerGeneratesEvents_spoofed_pathTraversal() throws Exception {
+        File privateDir = privateFile("x").getParentFile();
+
+        // Transform /a/b/c -> /a/b/c/../../.. so we get back to the root
+        File pathTraversalToRoot = privateDir;
+        File root = new File("/");
+        while (!privateDir.equals(root)) {
+            pathTraversalToRoot = new File(pathTraversalToRoot, "..");
+            privateDir = privateDir.getParentFile();
+        }
+
+        File spoofedFile = new File(pathTraversalToRoot, "dev/urandom");
+
+        assertWithMessage("Expected " + spoofedFile + " to be readable")
+                .that(spoofedFile.canRead()).isTrue();
+
+        EventLog.writeEvent(EventLog.getTagCode("auditd"),
+                "type=1400 avc: granted { execute_no_trans } "
+                        + "path=\"" + spoofedFile + "\" "
+                        + "scontext=u:r:untrusted_app_27: "
+                        + "tcontext=u:object_r:app_data_file: "
+                        + "tclass=file ");
+
+        String expectedNameHash =
+                "65528FE876BD676B0DFCC9A8ACA8988E026766F99EEC1E1FB48F46B2F635E225";
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then trigger generating DCL events
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
+    }
+
+    @Test
+    public void testDexLoggerGeneratesEvents_spoofed_otherAppFile() throws Exception {
+        File ourPath = sContext.getDatabasePath("android_pay");
+        File targetPath = new File(ourPath.toString()
+                .replace("com.android.frameworks.dexloggertest", "com.google.android.gms"));
+
+        assertWithMessage("Expected " + targetPath + " to not be readable")
+                .that(targetPath.canRead()).isFalse();
+
+        EventLog.writeEvent(EventLog.getTagCode("auditd"),
+                "type=1400 avc: granted { execute_no_trans } "
+                        + "path=\"" + targetPath + "\" "
+                        + "scontext=u:r:untrusted_app_27: "
+                        + "tcontext=u:object_r:app_data_file: "
+                        + "tclass=file ");
+
+        String expectedNameHash =
+                "CBE04E8AB9E7199FC19CBAAF9C774B88E56B3B19E823F2251693380AD6F515E6";
+
+        // Run the job to scan generated audit log entries
+        runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID);
+
+        // And then trigger generating DCL events
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID);
+
+        assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash);
+    }
+
+    private static File privateFile(String name) {
+        return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name);
+    }
+
+    private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception {
         MessageDigest hasher = MessageDigest.getInstance("SHA-256");
 
         // Copy the jar from our Java resources to a private data directory
         Class<?> thisClass = DexLoggerIntegrationTests.class;
-        try (InputStream input = thisClass.getResourceAsStream("/javalib.jar");
-                OutputStream output = new FileOutputStream(copyTo)) {
+        try (InputStream input = thisClass.getResourceAsStream(resourcePath);
+             OutputStream output = new FileOutputStream(copyTo)) {
             byte[] buffer = new byte[1024];
             while (true) {
                 int numRead = input.read(buffer);
@@ -166,24 +335,18 @@
         return formatter.toString();
     }
 
-    private static long mostRecentEventTimeNanos() throws Exception {
-        List<EventLog.Event> events = new ArrayList<>();
-
-        EventLog.readEvents(TAG_LIST, events);
-        return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
-    }
-
-    private static void runDexLogger() throws Exception {
-        // This forces {@code DynamicCodeLoggingService} to start now.
-        runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+    private static void runDynamicCodeLoggingJob(int jobId) throws Exception {
+        // This forces the DynamicCodeLoggingService job to start now.
+        runCommand("cmd jobscheduler run -f android " + jobId);
         // Wait for the job to have run.
         long startTime = SystemClock.elapsedRealtime();
         while (true) {
             String response = runCommand(
-                    "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+                    "cmd jobscheduler get-job-state android " + jobId);
             if (!response.contains("pending") && !response.contains("active")) {
                 break;
             }
+            // Don't wait forever - if it's taken > 10s then something is very wrong.
             if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) {
                 throw new AssertionError("Job has not completed: " + response);
             }
@@ -208,37 +371,68 @@
         return response.toString("UTF-8");
     }
 
-    private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash,
-            String expectedContentHash) throws Exception {
-        List<EventLog.Event> events = new ArrayList<>();
-        EventLog.readEvents(TAG_LIST, events);
-        int found = 0;
-        for (EventLog.Event event : events) {
+    private static long mostRecentEventTimeNanos() throws Exception {
+        List<Event> events = readSnetEvents();
+        return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
+    }
+
+    private static void assertDclLoggedSince(long previousEventNanos, String expectedSubTag,
+            String expectedNameHash, String expectedContentHash) throws Exception {
+        List<String> messages =
+                findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
+
+        assertWithMessage("Expected exactly one matching log entry").that(messages).hasSize(1);
+        assertThat(messages.get(0)).endsWith(expectedContentHash);
+    }
+
+    private static void assertNoDclLoggedSince(long previousEventNanos, String expectedSubTag,
+            String expectedNameHash) throws Exception {
+        List<String> messages =
+                findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash);
+
+        assertWithMessage("Expected no matching log entries").that(messages).isEmpty();
+    }
+
+    private static List<String> findMatchingEvents(long previousEventNanos, String expectedSubTag,
+            String expectedNameHash) throws Exception {
+        List<String> messages = new ArrayList<>();
+
+        for (Event event : readSnetEvents()) {
             if (event.getTimeNanos() <= previousEventNanos) {
                 continue;
             }
-            Object[] data = (Object[]) event.getData();
 
-            // We only care about DCL events that we generated.
-            String subTag = (String) data[0];
-            if (!DCL_SUBTAG.equals(subTag)) {
+            Object data = event.getData();
+            if (!(data instanceof Object[])) {
                 continue;
             }
-            int uid = (int) data[1];
+            Object[] fields = (Object[]) data;
+
+            // We only care about DCL events that we generated.
+            String subTag = (String) fields[0];
+            if (!expectedSubTag.equals(subTag)) {
+                continue;
+            }
+            int uid = (int) fields[1];
             if (uid != sMyUid) {
                 continue;
             }
 
-            String message = (String) data[2];
+            String message = (String) fields[2];
             if (!message.startsWith(expectedNameHash)) {
                 continue;
             }
 
-            assertThat(message).endsWith(expectedContentHash);
-            ++found;
+            messages.add(message);
+            //assertThat(message).endsWith(expectedContentHash);
         }
+        return messages;
+    }
 
-        assertThat(found).isEqualTo(1);
+    private static List<Event> readSnetEvents() throws Exception {
+        List<Event> events = new ArrayList<>();
+        EventLog.readEvents(new int[] { SNET_TAG }, events);
+        return events;
     }
 
     /**
diff --git a/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
new file mode 100644
index 0000000..0608883
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+
+extern "C" jint JNI_OnLoad(JavaVM* /* vm */, void* /* reserved */)
+{
+    return JNI_VERSION_1_6;
+}
diff --git a/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
new file mode 100644
index 0000000..ad025e6
--- /dev/null
+++ b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+int main() {
+    // This program just has to run, it doesn't need to do anything. So we don't.
+    return 0;
+}
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 c2e735e..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;
@@ -38,7 +38,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -99,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;
@@ -117,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).
@@ -136,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
@@ -154,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);
@@ -209,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();
@@ -226,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);
@@ -279,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();
@@ -296,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);
@@ -349,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();
@@ -365,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();
         }
@@ -405,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);
@@ -470,7 +442,7 @@
      * Test that app user data is rolled back.
      * TODO: Stop ignoring this test once user data rollback is supported.
      */
-    @Ignore @Test
+    @Test
     public void testUserDataRollback() throws Exception {
         try {
             RollbackTestUtils.adoptShellPermissionIdentity(
@@ -479,9 +451,9 @@
                     Manifest.permission.MANAGE_ROLLBACKS);
 
             RollbackTestUtils.uninstall(TEST_APP_A);
-            RollbackTestUtils.install("RollbackTestAppV1.apk", false);
+            RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
             processUserData(TEST_APP_A);
-            RollbackTestUtils.install("RollbackTestAppV2.apk", true);
+            RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
             processUserData(TEST_APP_A);
 
             RollbackManager rm = RollbackTestUtils.getRollbackManager();
@@ -500,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.");
@@ -550,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);
@@ -671,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
@@ -681,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/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java
new file mode 100644
index 0000000..032e526
--- /dev/null
+++ b/tests/net/java/android/net/DnsPacketTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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 android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsPacketTest {
+    private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag,
+            int qCount, int aCount, int nsCount, int arCount) {
+        assertEquals(header.id, id);
+        assertEquals(header.flags, flag);
+        assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount);
+        assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount);
+        assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount);
+        assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount);
+    }
+
+    private void assertSectionParses(DnsPacket.DnsSection section, String dname,
+            int dtype, int dclass, int ttl, byte[] rr) {
+        assertEquals(section.dName, dname);
+        assertEquals(section.nsType, dtype);
+        assertEquals(section.nsClass, dclass);
+        assertEquals(section.ttl, ttl);
+        assertTrue(Arrays.equals(section.getRR(), rr));
+    }
+
+    class TestDnsPacket extends DnsPacket {
+        TestDnsPacket(byte[] data) throws ParseException {
+            super(data);
+        }
+
+        public DnsHeader getHeader() {
+            return mHeader;
+        }
+        public List<DnsSection> getSectionList(int secType) {
+            return mSections[secType];
+        }
+    }
+
+    @Test
+    public void testNullDisallowed() {
+        try {
+            new TestDnsPacket(null);
+            fail("Exception not thrown for null byte array");
+        } catch (DnsPacket.ParseException e) {
+        }
+    }
+
+    @Test
+    public void testV4Answer() throws Exception {
+        final byte[] v4blob = new byte[] {
+            /* Header */
+            0x55, 0x66, /* Transaction ID */
+            (byte) 0x81, (byte) 0x80, /* Flags */
+            0x00, 0x01, /* Questions */
+            0x00, 0x01, /* Answer RRs */
+            0x00, 0x00, /* Authority RRs */
+            0x00, 0x00, /* Additional RRs */
+            /* Queries */
+            0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+            0x00, 0x01, /* Type */
+            0x00, 0x01, /* Class */
+            /* Answers */
+            (byte) 0xc0, 0x0c, /* Name */
+            0x00, 0x01, /* Type */
+            0x00, 0x01, /* Class */
+            0x00, 0x00, 0x01, 0x2b, /* TTL */
+            0x00, 0x04, /* Data length */
+            (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+        };
+        TestDnsPacket packet = new TestDnsPacket(v4blob);
+
+        // Header part
+        assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0);
+
+        // Section part
+        List<DnsPacket.DnsSection> qdSectionList =
+                packet.getSectionList(DnsPacket.QDSECTION);
+        assertEquals(qdSectionList.size(), 1);
+        assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null);
+
+        List<DnsPacket.DnsSection> anSectionList =
+                packet.getSectionList(DnsPacket.ANSECTION);
+        assertEquals(anSectionList.size(), 1);
+        assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b,
+                new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 });
+    }
+
+    @Test
+    public void testV6Answer() throws Exception {
+        final byte[] v6blob = new byte[] {
+            /* Header */
+            0x77, 0x22, /* Transaction ID */
+            (byte) 0x81, (byte) 0x80, /* Flags */
+            0x00, 0x01, /* Questions */
+            0x00, 0x01, /* Answer RRs */
+            0x00, 0x00, /* Authority RRs */
+            0x00, 0x00, /* Additional RRs */
+            /* Queries */
+            0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+            0x00, 0x1c, /* Type */
+            0x00, 0x01, /* Class */
+            /* Answers */
+            (byte) 0xc0, 0x0c, /* Name */
+            0x00, 0x1c, /* Type */
+            0x00, 0x01, /* Class */
+            0x00, 0x00, 0x00, 0x37, /* TTL */
+            0x00, 0x10, /* Data length */
+            0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */
+        };
+        TestDnsPacket packet = new TestDnsPacket(v6blob);
+
+        // Header part
+        assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0);
+
+        // Section part
+        List<DnsPacket.DnsSection> qdSectionList =
+                packet.getSectionList(DnsPacket.QDSECTION);
+        assertEquals(qdSectionList.size(), 1);
+        assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null);
+
+        List<DnsPacket.DnsSection> anSectionList =
+                packet.getSectionList(DnsPacket.ANSECTION);
+        assertEquals(anSectionList.size(), 1);
+        assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37,
+                new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 });
+    }
+}
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/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index c748d0f..f2ecef9 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.net.ipmemorystore.Blob;
 import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
 import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
 import android.net.ipmemorystore.IOnSameNetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusListener;
@@ -67,7 +68,14 @@
     private static final String TEST_CLIENT_ID = "testClientId";
     private static final String TEST_DATA_NAME = "testData";
 
-    private static final String[] FAKE_KEYS = { "fakeKey1", "fakeKey2", "fakeKey3", "fakeKey4" };
+    private static final int FAKE_KEY_COUNT = 20;
+    private static final String[] FAKE_KEYS;
+    static {
+        FAKE_KEYS = new String[FAKE_KEY_COUNT];
+        for (int i = 0; i < FAKE_KEYS.length; ++i) {
+            FAKE_KEYS[i] = "fakeKey" + i;
+        }
+    }
 
     @Mock
     private Context mMockContext;
@@ -170,6 +178,25 @@
         };
     }
 
+    /** Helper method to make an IOnL2KeyResponseListener */
+    private interface OnL2KeyResponseListener {
+        void onL2KeyResponse(Status status, String key);
+    }
+    private IOnL2KeyResponseListener onL2KeyResponse(final OnL2KeyResponseListener functor) {
+        return new IOnL2KeyResponseListener() {
+            @Override
+            public void onL2KeyResponse(final StatusParcelable status, final String key)
+                    throws RemoteException {
+                functor.onL2KeyResponse(new Status(status), key);
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+    }
+
     // Helper method to factorize some boilerplate
     private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) {
         final CountDownLatch latch = new CountDownLatch(1);
@@ -195,12 +222,9 @@
     }
 
     @Test
-    public void testNetworkAttributes() {
+    public void testNetworkAttributes() throws UnknownHostException {
         final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        try {
-            na.setAssignedV4Address(
-                    (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
-        } catch (UnknownHostException e) { /* Can't happen */ }
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
         na.setGroupHint("hint1");
         na.setMtu(219);
         final String l2Key = FAKE_KEYS[0];
@@ -218,10 +242,8 @@
                         })));
 
         final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder();
-        try {
-            na.setDnsAddresses(Arrays.asList(
-                    new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
-        } catch (UnknownHostException e) { /* Still can't happen */ }
+        na.setDnsAddresses(Arrays.asList(
+                new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
         final NetworkAttributes attributes2 = na2.build();
         storeAttributes("Did not complete storing attributes 2", l2Key, attributes2);
 
@@ -333,8 +355,93 @@
     }
 
     @Test
-    public void testFindL2Key() {
-        // TODO : implement this
+    public void testFindL2Key() throws UnknownHostException {
+        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+        na.setGroupHint("hint0");
+        storeAttributes(FAKE_KEYS[0], na.build());
+
+        na.setDnsAddresses(Arrays.asList(
+                new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")}));
+        na.setMtu(219);
+        storeAttributes(FAKE_KEYS[1], na.build());
+        na.setMtu(null);
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        na.setDnsAddresses(Arrays.asList(
+                new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
+        na.setGroupHint("hint1");
+        storeAttributes(FAKE_KEYS[2], na.build());
+        na.setMtu(219);
+        storeAttributes(FAKE_KEYS[3], na.build());
+        na.setMtu(240);
+        storeAttributes(FAKE_KEYS[4], na.build());
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8"));
+        storeAttributes(FAKE_KEYS[5], na.build());
+
+        // Matches key 5 exactly
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[5], key);
+                })));
+
+        // MTU matches key 4 but v4 address matches key 5. The latter is stronger.
+        na.setMtu(240);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[5], key);
+                })));
+
+        // Closest to key 3 (indeed, identical)
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        na.setMtu(219);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[3], key);
+                })));
+
+        // Group hint alone must not be strong enough to override the rest
+        na.setGroupHint("hint0");
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[3], key);
+                })));
+
+        // Still closest to key 3, though confidence is lower
+        na.setGroupHint("hint1");
+        na.setDnsAddresses(null);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[3], key);
+                })));
+
+        // But changing the MTU makes this closer to key 4
+        na.setMtu(240);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[4], key);
+                })));
+
+        // MTU alone not strong enough to make this group-close
+        na.setGroupHint(null);
+        na.setDnsAddresses(null);
+        na.setAssignedV4Address(null);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertNull(key);
+                })));
     }
 
     private void assertNetworksSameness(final String key1, final String key2, final int sameness) {
@@ -349,7 +456,7 @@
     @Test
     public void testIsSameNetwork() throws UnknownHostException {
         final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
         na.setGroupHint("hint1");
         na.setMtu(219);
         na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
new file mode 100644
index 0000000..d0350af
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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.filters;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * JUnit filter to select tests.
+ *
+ * <p>This filter selects tests specified by package name, class name, and method name. With this
+ * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the
+ * restriction that prevents using the package and the class options can be mitigated.
+ *
+ * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option.
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.test.filters.SelectTest \
+ *     -e selectTest package1.,package2. \
+ *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * Note that the ending {@code .} in package name is mandatory.
+ *
+ * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option.
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.test.filters.SelectTest      \
+ *     -e selectTest package1.ClassA,package2.ClassB \
+ *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * <p><b>Select out test methods from Java classes:</b>
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.test.filters.SelectTest                      \
+ *     -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \
+ *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * Those options can be used simultaneously. For example
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.test.filters.SelectTest                        \
+ *     -e selectTest package1.,package2.classA,package3.ClassB#methodZ \
+ *     com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * will select out all tests in package1, all tests in classA, and ClassB#methodZ test.
+ *
+ * <p>Note that when this option is specified with either {@code -e package} or {@code -e class}
+ * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage},
+ * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected
+ * with this SelectTest option.
+ *
+ * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely
+ * logs to logcat while parsing {@code -e selectTest} option.
+ */
+public class SelectTest extends Filter {
+
+    private static final String TAG = SelectTest.class.getSimpleName();
+
+    @VisibleForTesting
+    static final String OPTION_SELECT_TEST = "selectTest";
+    @VisibleForTesting
+    static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose";
+
+    private static final String ARGUMENT_ITEM_SEPARATOR = ",";
+    private static final String PACKAGE_NAME_SEPARATOR = ".";
+    private static final String METHOD_SEPARATOR = "#";
+
+    @Nullable
+    private final PackageSet mPackageSet;
+
+    /**
+     * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}.
+     *
+     * @param testArgs instrumentation test arguments.
+     */
+    public SelectTest(@NonNull Bundle testArgs) {
+        mPackageSet = parseSelectTest(testArgs);
+    }
+
+    @Override
+    public boolean shouldRun(Description description) {
+        if (mPackageSet == null) {
+            // Accept all tests because this filter is disabled.
+            return true;
+        }
+        String testClassName = description.getClassName();
+        String testMethodName = description.getMethodName();
+        return mPackageSet.accept(testClassName, testMethodName);
+    }
+
+    @Override
+    public String describe() {
+        return OPTION_SELECT_TEST + "=" + mPackageSet;
+    }
+
+    /**
+     * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}.
+     *
+     * <p>This method is intended to be used at constructor of extended {@link Filter} class.
+     *
+     * @param testArgs instrumentation test arguments.
+     * @param selectTests array of class name to be selected to run.
+     * @return modified instrumentation test arguments.
+     */
+    @NonNull
+    protected static Bundle addSelectTest(
+            @NonNull Bundle testArgs, @NonNull String... selectTests) {
+        if (selectTests.length == 0) {
+            return testArgs;
+        }
+        testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests)));
+        return testArgs;
+    }
+
+    /**
+     * Parse {@code -e selectTest} argument.
+     * @param testArgs instrumentation test arguments.
+     * @return {@link PackageSet} that will filter tests. Returns {@code null} when no
+     *     {@code -e selectTest} option is specified, thus this filter gets disabled.
+     */
+    @Nullable
+    private static PackageSet parseSelectTest(Bundle testArgs) {
+        final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST);
+        if (selectTestArgs == null) {
+            Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified");
+            return null;
+        }
+
+        final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE));
+        final PackageSet packageSet = new PackageSet(verbose);
+        for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) {
+            packageSet.add(selectTestArg);
+        }
+        return packageSet;
+    }
+
+    private static String getPackageName(String selectTestArg) {
+        int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR);
+        return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos);
+    }
+
+    @Nullable
+    private static String getClassName(String selectTestArg) {
+        if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) {
+            return null;
+        }
+        int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+        return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos);
+    }
+
+    @Nullable
+    private static String getMethodName(String selectTestArg) {
+        int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+        return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1);
+    }
+
+    /** Package level filter */
+    private static class PackageSet {
+        private final boolean mVerbose;
+        /**
+         * Java package name to {@link ClassSet} map. To represent package filtering, a map value
+         * can be {@code null}.
+         */
+        private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>();
+
+        PackageSet(boolean verbose) {
+            mVerbose = verbose;
+        }
+
+        void add(final String selectTestArg) {
+            final String packageName = getPackageName(selectTestArg);
+            final String className = getClassName(selectTestArg);
+
+            if (className == null) {
+                ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering.
+                if (mVerbose) {
+                    logging("Select package " + selectTestArg, classSet != null,
+                            "; supersede " + classSet);
+                }
+                return;
+            }
+
+            ClassSet classSet = mClassSetMap.get(packageName);
+            if (classSet == null) {
+                if (mClassSetMap.containsKey(packageName)) {
+                    if (mVerbose) {
+                        logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true,
+                                " ignore " + selectTestArg);
+                    }
+                    return;
+                }
+                classSet = new ClassSet(mVerbose);
+                mClassSetMap.put(packageName, classSet);
+            }
+            classSet.add(selectTestArg);
+        }
+
+        boolean accept(String className, @Nullable String methodName) {
+            String packageName = getPackageName(className);
+            if (!mClassSetMap.containsKey(packageName)) {
+                return false;
+            }
+            ClassSet classSet = mClassSetMap.get(packageName);
+            return classSet == null || classSet.accept(className, methodName);
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+            for (String packageName : mClassSetMap.keySet()) {
+                ClassSet classSet = mClassSetMap.get(packageName);
+                joiner.add(classSet == null
+                        ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString());
+            }
+            return joiner.toString();
+        }
+    }
+
+    /** Class level filter */
+    private static class ClassSet {
+        private final boolean mVerbose;
+        /**
+         * Java class name to set of method names map. To represent class filtering, a map value
+         * can be {@code null}.
+         */
+        private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>();
+
+        ClassSet(boolean verbose) {
+            mVerbose = verbose;
+        }
+
+        void add(String selectTestArg) {
+            final String className = getClassName(selectTestArg);
+            final String methodName = getMethodName(selectTestArg);
+
+            if (methodName == null) {
+                Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering.
+                if (mVerbose) {
+                    logging("Select class " + selectTestArg, methodSet != null,
+                            "; supersede " + toString(className, methodSet));
+                }
+                return;
+            }
+
+            Set<String> methodSet = mMethodSetMap.get(className);
+            if (methodSet == null) {
+                if (mMethodSetMap.containsKey(className)) {
+                    if (mVerbose) {
+                        logging("Select class " + className, true, "; ignore " + selectTestArg);
+                    }
+                    return;
+                }
+                methodSet = new LinkedHashSet<>();
+                mMethodSetMap.put(className, methodSet);
+            }
+
+            methodSet.add(methodName);
+            if (mVerbose) {
+                logging("Select method " + selectTestArg, false, null);
+            }
+        }
+
+        boolean accept(String className, @Nullable String methodName) {
+            if (!mMethodSetMap.containsKey(className)) {
+                return false;
+            }
+            Set<String> methodSet = mMethodSetMap.get(className);
+            return methodName == null || methodSet == null || methodSet.contains(methodName);
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+            for (String className : mMethodSetMap.keySet()) {
+                joiner.add(toString(className, mMethodSetMap.get(className)));
+            }
+            return joiner.toString();
+        }
+
+        private static String toString(String className, @Nullable Set<String> methodSet) {
+            if (methodSet == null) {
+                return className;
+            }
+            StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+            for (String methodName : methodSet) {
+                joiner.add(className + METHOD_SEPARATOR + methodName);
+            }
+            return joiner.toString();
+        }
+    }
+
+    private static void logging(String infoLog, boolean isWarning, String warningLog) {
+        if (isWarning) {
+            Log.w(TAG, infoLog + warningLog);
+        } else {
+            Log.i(TAG, infoLog);
+        }
+    }
+
+    private static String join(Collection<String> list) {
+        StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+        for (String text : list) {
+            joiner.add(text);
+        }
+        return joiner.toString();
+    }
+}
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
new file mode 100644
index 0000000..163b00a
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
@@ -0,0 +1,220 @@
+/*
+ * 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.filters;
+
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST;
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.util.ArraySet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+public class SelectTestTests {
+
+    private static final String PACKAGE_A = "packageA.";
+    private static final String PACKAGE_B = "packageB.";
+    private static final String PACKAGE_C = "packageC.";
+    private static final String CLASS_A1 = PACKAGE_A + "Class1";
+    private static final String CLASS_A2 = PACKAGE_A + "Class2";
+    private static final String CLASS_B3 = PACKAGE_B + "Class3";
+    private static final String CLASS_B4 = PACKAGE_B + "Class4";
+    private static final String CLASS_C5 = PACKAGE_C + "Class5";
+    private static final String CLASS_C6 = PACKAGE_C + "Class6";
+    private static final String METHOD_A1K = CLASS_A1 + "#methodK";
+    private static final String METHOD_A1L = CLASS_A1 + "#methodL";
+    private static final String METHOD_A2M = CLASS_A2 + "#methodM";
+    private static final String METHOD_A2N = CLASS_A2 + "#methodN";
+    private static final String METHOD_B3P = CLASS_B3 + "#methodP";
+    private static final String METHOD_B3Q = CLASS_B3 + "#methodQ";
+    private static final String METHOD_B4R = CLASS_B4 + "#methodR";
+    private static final String METHOD_B4S = CLASS_B4 + "#methodS";
+    private static final String METHOD_C5W = CLASS_C5 + "#methodW";
+    private static final String METHOD_C5X = CLASS_C5 + "#methodX";
+    private static final String METHOD_C6Y = CLASS_C6 + "#methodY";
+    private static final String METHOD_C6Z = CLASS_C6 + "#methodZ";
+
+    private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K);
+    private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L);
+    private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M);
+    private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N);
+    private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P);
+    private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q);
+    private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R);
+    private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S);
+    private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W);
+    private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X);
+    private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y);
+    private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z);
+    private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L);
+    private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N);
+    private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q);
+    private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S);
+    private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X);
+    private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z);
+    private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2);
+    private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4);
+    private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6);
+    private static final Set<Description> TEST_ALL =
+            merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C);
+
+    private SelectTestBuilder mBuilder;
+
+    @Before
+    public void setUp() {
+        mBuilder = new SelectTestBuilder();
+    }
+
+    private static class SelectTestBuilder {
+        private final Bundle mTestArgs = new Bundle();
+
+        Filter build() {
+            mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString());
+            return new SelectTest(mTestArgs);
+        }
+
+        SelectTestBuilder withSelectTest(String... selectTestArgs) {
+            putTestOption(OPTION_SELECT_TEST, selectTestArgs);
+            return this;
+        }
+
+        private void putTestOption(String option, String... args) {
+            if (args.length > 0) {
+                StringJoiner joiner = new StringJoiner(",");
+                for (String arg : args) {
+                    joiner.add(arg);
+                }
+                mTestArgs.putString(option, joiner.toString());
+            }
+        }
+    }
+
+    private static Set<Description> methodTest(String testName) {
+        int methodSep = testName.indexOf("#");
+        String className = testName.substring(0, methodSep);
+        String methodName = testName.substring(methodSep + 1);
+        final Set<Description> tests = new ArraySet<>();
+        tests.add(Description.createSuiteDescription(className));
+        tests.add(Description.createTestDescription(className, methodName));
+        return Collections.unmodifiableSet(tests);
+    }
+
+    @SafeVarargs
+    private static Set<Description> merge(Set<Description>... testSpecs) {
+        final Set<Description> merged = new LinkedHashSet<>();
+        for (Set<Description> testSet : testSpecs) {
+            merged.addAll(testSet);
+        }
+        return Collections.unmodifiableSet(merged);
+    }
+
+    @SafeVarargs
+    private static void acceptTests(Filter filter, Set<Description>... testSpecs) {
+        final Set<Description> accepts = merge(testSpecs);
+        for (Description test : TEST_ALL) {
+            if (accepts.contains(test)) {
+                assertTrue("accept " + test, filter.shouldRun(test));
+            } else {
+                assertFalse("reject " + test, filter.shouldRun(test));
+            }
+        }
+    }
+
+    @Test
+    public void testFilterDisabled() {
+        final Filter filter = mBuilder.build();
+        acceptTests(filter, TEST_ALL);
+    }
+
+    @Test
+    public void testSelectPackage() {
+        final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build();
+        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+    }
+
+    @Test
+    public void testSelectClass() {
+        final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build();
+        acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3);
+    }
+
+    @Test
+    public void testSelectMethod() {
+        final Filter filter = mBuilder
+                .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build();
+        acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P);
+    }
+
+    @Test
+    public void testSelectClassAndPackage() {
+        final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build();
+        acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5);
+    }
+
+    @Test
+    public void testSelectMethodAndPackage() {
+        final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build();
+        acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W);
+    }
+
+    @Test
+    public void testSelectMethodAndClass() {
+        final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build();
+        acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P);
+    }
+
+    @Test
+    public void testSelectClassAndSamePackage() {
+        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_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,
+                PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build();
+        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
+    }
+
+    @Test
+    public void testSelectMethodAndClassAndPackage() {
+        final Filter filter = mBuilder.withSelectTest(
+                METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+                PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build();
+        acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+    }
+}
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/**/*",
     ],