Merge "Move global grayscale to ColorDisplayService"
diff --git a/Android.bp b/Android.bp
index f6d49e9..ea6a631 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,6 +104,11 @@
         "core/java/android/app/backup/IRestoreObserver.aidl",
         "core/java/android/app/backup/IRestoreSession.aidl",
         "core/java/android/app/backup/ISelectBackupTransportCallback.aidl",
+        "core/java/android/app/contentsuggestions/IClassificationsCallback.aidl",
+        "core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl",
+        "core/java/android/app/contentsuggestions/ISelectionsCallback.aidl",
+        "core/java/android/app/prediction/IPredictionCallback.aidl",
+        "core/java/android/app/prediction/IPredictionManager.aidl",
         "core/java/android/app/role/IOnRoleHoldersChangedListener.aidl",
         "core/java/android/app/role/IRoleManager.aidl",
         "core/java/android/app/role/IRoleManagerCallback.aidl",
@@ -273,6 +278,7 @@
         "core/java/android/rolecontrollerservice/IRoleControllerService.aidl",
         ":keystore_aidl",
         "core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl",
+        "core/java/android/service/appprediction/IPredictionService.aidl",
         "core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl",
         "core/java/android/service/autofill/augmented/IFillCallback.aidl",
         "core/java/android/service/autofill/IAutoFillService.aidl",
@@ -282,6 +288,7 @@
         "core/java/android/service/carrier/ICarrierService.aidl",
         "core/java/android/service/carrier/ICarrierMessagingCallback.aidl",
         "core/java/android/service/carrier/ICarrierMessagingService.aidl",
+        "core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl",
         "core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl",
         "core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl",
         "core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl",
@@ -731,6 +738,7 @@
         "android.hardware.contexthub-V1.0-java",
         "android.hardware.health-V1.0-java-constants",
         "android.hardware.thermal-V1.0-java-constants",
+        "android.hardware.thermal-V1.0-java",
         "android.hardware.thermal-V1.1-java",
         "android.hardware.thermal-V2.0-java",
         "android.hardware.tv.input-V1.0-java-constants",
@@ -739,11 +747,13 @@
         "android.hardware.vibrator-V1.0-java",
         "android.hardware.vibrator-V1.1-java",
         "android.hardware.vibrator-V1.2-java",
+        "android.hardware.vibrator-V1.3-java",
         "android.hardware.wifi-V1.0-java-constants",
         "android.hardware.radio-V1.0-java",
         "android.hardware.radio-V1.3-java",
         "android.hardware.radio-V1.4-java",
         "android.hardware.usb.gadget-V1.0-java",
+        "networkstack-aidl-interfaces-java",
         "netd_aidl_interface-java",
         "devicepolicyprotosnano",
     ],
@@ -871,11 +881,16 @@
     name: "networkstack-aidl-interfaces",
     local_include_dir: "core/java",
     srcs: [
+        "core/java/android/net/INetworkMonitor.aidl",
+        "core/java/android/net/INetworkMonitorCallbacks.aidl",
+        "core/java/android/net/IIpMemoryStore.aidl",
         "core/java/android/net/INetworkStackConnector.aidl",
         "core/java/android/net/INetworkStackStatusCallback.aidl",
+        "core/java/android/net/PrivateDnsConfigParcel.aidl",
         "core/java/android/net/dhcp/DhcpServingParamsParcel.aidl",
         "core/java/android/net/dhcp/IDhcpServer.aidl",
         "core/java/android/net/dhcp/IDhcpServerCallbacks.aidl",
+        "core/java/android/net/ipmemorystore/**/*.aidl",
     ],
     api_dir: "aidl/networkstack",
 }
diff --git a/api/current.txt b/api/current.txt
index 7e0c33c..0ff1743 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26076,6 +26076,27 @@
     field public static final int URI_COLUMN_INDEX = 2; // 0x2
   }
 
+  public final class Session2Command implements android.os.Parcelable {
+    ctor public Session2Command(int);
+    ctor public Session2Command(java.lang.String, android.os.Bundle);
+    method public int describeContents();
+    method public int getCommandCode();
+    method public java.lang.String getCustomCommand();
+    method public android.os.Bundle getExtras();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
+    field public static final android.os.Parcelable.Creator<android.media.Session2Command> CREATOR;
+    field public static final int RESULT_ERROR_UNKNOWN_ERROR = -1; // 0xffffffff
+    field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+  }
+
+  public static final class Session2Command.Result {
+    ctor public Session2Command.Result(int, android.os.Bundle);
+    method public int getResultCode();
+    method public android.os.Bundle getResultData();
+  }
+
   public class SoundPool {
     ctor public deprecated SoundPool(int, int, int);
     method public final void autoPause();
@@ -27121,6 +27142,21 @@
 
 package android.media.session {
 
+  public final class ControllerCallbackLink implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.IBinder getBinder();
+    method public void notifyEvent(java.lang.String, android.os.Bundle);
+    method public void notifyExtrasChanged(android.os.Bundle);
+    method public void notifyMetadataChanged(android.media.MediaMetadata);
+    method public void notifyPlaybackStateChanged(android.media.session.PlaybackState);
+    method public void notifyQueueChanged(java.util.List<android.media.session.MediaSession.QueueItem>);
+    method public void notifyQueueTitleChanged(java.lang.CharSequence);
+    method public void notifySessionDestroyed();
+    method public void notifyVolumeInfoChanged(android.media.session.MediaController.PlaybackInfo);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.session.ControllerCallbackLink> CREATOR;
+  }
+
   public final class MediaController {
     ctor public MediaController(android.content.Context, android.media.session.MediaSession.Token);
     method public void adjustVolume(int, int);
@@ -27355,6 +27391,36 @@
     method public android.media.session.PlaybackState.CustomAction.Builder setExtras(android.os.Bundle);
   }
 
+  public final class SessionCallbackLink implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.IBinder getBinder();
+    method public void notifyAdjustVolume(java.lang.String, int, int, android.media.session.ControllerCallbackLink, int);
+    method public void notifyCommand(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+    method public void notifyCustomAction(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+    method public void notifyFastForward(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+    method public void notifyMediaButton(java.lang.String, int, int, android.content.Intent, int, android.os.ResultReceiver);
+    method public void notifyMediaButtonFromController(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.content.Intent);
+    method public void notifyNext(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+    method public void notifyPause(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+    method public void notifyPlay(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+    method public void notifyPlayFromMediaId(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+    method public void notifyPlayFromSearch(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+    method public void notifyPlayFromUri(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+    method public void notifyPrepare(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+    method public void notifyPrepareFromMediaId(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+    method public void notifyPrepareFromSearch(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle);
+    method public void notifyPrepareFromUri(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+    method public void notifyPrevious(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+    method public void notifyRate(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.media.Rating);
+    method public void notifyRewind(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+    method public void notifySeekTo(java.lang.String, int, int, android.media.session.ControllerCallbackLink, long);
+    method public void notifySetVolumeTo(java.lang.String, int, int, android.media.session.ControllerCallbackLink, int);
+    method public void notifySkipToTrack(java.lang.String, int, int, android.media.session.ControllerCallbackLink, long);
+    method public void notifyStop(java.lang.String, int, int, android.media.session.ControllerCallbackLink);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.session.SessionCallbackLink> CREATOR;
+  }
+
 }
 
 package android.media.tv {
@@ -29682,9 +29748,6 @@
     method public void setReferenceCounted(boolean);
   }
 
-  public static abstract class WifiManager.NetworkSuggestionsStatusCode implements java.lang.annotation.Annotation {
-  }
-
   public class WifiManager.WifiLock {
     method public void acquire();
     method public boolean isHeld();
@@ -34374,6 +34437,8 @@
     method public java.util.ArrayList<java.lang.String> createStringArrayList();
     method public <T> T[] createTypedArray(android.os.Parcelable.Creator<T>);
     method public <T> java.util.ArrayList<T> createTypedArrayList(android.os.Parcelable.Creator<T>);
+    method public <T extends android.os.Parcelable> android.util.ArrayMap<java.lang.String, T> createTypedArrayMap(android.os.Parcelable.Creator<T>);
+    method public <T extends android.os.Parcelable> android.util.SparseArray<T> createTypedSparseArray(android.os.Parcelable.Creator<T>);
     method public int dataAvail();
     method public int dataCapacity();
     method public int dataPosition();
@@ -34415,7 +34480,7 @@
     method public java.io.Serializable readSerializable();
     method public android.util.Size readSize();
     method public android.util.SizeF readSizeF();
-    method public android.util.SparseArray readSparseArray(java.lang.ClassLoader);
+    method public <T> android.util.SparseArray<T> readSparseArray(java.lang.ClassLoader);
     method public android.util.SparseBooleanArray readSparseBooleanArray();
     method public java.lang.String readString();
     method public void readStringArray(java.lang.String[]);
@@ -34461,7 +34526,7 @@
     method public void writeSerializable(java.io.Serializable);
     method public void writeSize(android.util.Size);
     method public void writeSizeF(android.util.SizeF);
-    method public void writeSparseArray(android.util.SparseArray<java.lang.Object>);
+    method public <T> void writeSparseArray(android.util.SparseArray<T>);
     method public void writeSparseBooleanArray(android.util.SparseBooleanArray);
     method public void writeString(java.lang.String);
     method public void writeStringArray(java.lang.String[]);
@@ -34469,8 +34534,10 @@
     method public void writeStrongBinder(android.os.IBinder);
     method public void writeStrongInterface(android.os.IInterface);
     method public <T extends android.os.Parcelable> void writeTypedArray(T[], int);
+    method public <T extends android.os.Parcelable> void writeTypedArrayMap(android.util.ArrayMap<java.lang.String, T>, int);
     method public <T extends android.os.Parcelable> void writeTypedList(java.util.List<T>);
     method public <T extends android.os.Parcelable> void writeTypedObject(T, int);
+    method public <T extends android.os.Parcelable> void writeTypedSparseArray(android.util.SparseArray<T>, int);
     method public void writeValue(java.lang.Object);
     field public static final android.os.Parcelable.Creator<java.lang.String> STRING_CREATOR;
   }
@@ -42662,6 +42729,7 @@
     method public static java.lang.String capabilitiesToString(int);
     method public android.telecom.PhoneAccountHandle getAccountHandle();
     method public int getCallCapabilities();
+    method public int getCallDirection();
     method public android.telecom.CallIdentification getCallIdentification();
     method public int getCallProperties();
     method public java.lang.String getCallerDisplayName();
@@ -42698,6 +42766,9 @@
     field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000
     field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
     field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+    field public static final int DIRECTION_INCOMING = 0; // 0x0
+    field public static final int DIRECTION_OUTGOING = 1; // 0x1
+    field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff
     field public static final int PROPERTY_CONFERENCE = 1; // 0x1
     field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
     field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
@@ -44853,6 +44924,7 @@
     method public java.util.List<java.lang.Integer> getEmergencyNumberSources();
     method public java.util.List<java.lang.Integer> getEmergencyServiceCategories();
     method public int getEmergencyServiceCategoryBitmask();
+    method public java.util.List<java.lang.String> getEmergencyUrns();
     method public java.lang.String getMnc();
     method public java.lang.String getNumber();
     method public boolean isFromSources(int);
@@ -44906,6 +44978,7 @@
     method public boolean isEnabled();
     method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException;
     method public void switchToSubscription(int, android.app.PendingIntent);
+    method public void updateSubscriptionNickname(int, java.lang.String, android.app.PendingIntent);
     field public static final java.lang.String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
     field public static final java.lang.String ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE = "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE";
     field public static final int EMBEDDED_SUBSCRIPTION_RESULT_ERROR = 2; // 0x2
@@ -49118,6 +49191,7 @@
     method public float getAxisValue(int);
     method public float getAxisValue(int, int);
     method public int getButtonState();
+    method public int getClassification();
     method public int getDeviceId();
     method public long getDownTime();
     method public int getEdgeFlags();
@@ -49269,6 +49343,9 @@
     field public static final int BUTTON_STYLUS_PRIMARY = 32; // 0x20
     field public static final int BUTTON_STYLUS_SECONDARY = 64; // 0x40
     field public static final int BUTTON_TERTIARY = 4; // 0x4
+    field public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1; // 0x1
+    field public static final int CLASSIFICATION_DEEP_PRESS = 2; // 0x2
+    field public static final int CLASSIFICATION_NONE = 0; // 0x0
     field public static final android.os.Parcelable.Creator<android.view.MotionEvent> CREATOR;
     field public static final int EDGE_BOTTOM = 2; // 0x2
     field public static final int EDGE_LEFT = 4; // 0x4
@@ -52319,6 +52396,8 @@
     method public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(android.view.contentcapture.ContentCaptureContext);
     method public final void destroy();
     method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
+    method public android.view.autofill.AutofillId newAutofillId(android.view.autofill.AutofillId, int);
+    method public final android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int);
     method public final void notifyViewAppeared(android.view.ViewStructure);
     method public final void notifyViewDisappeared(android.view.autofill.AutofillId);
     method public final void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index bf367cb..b190e9f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -86,11 +86,13 @@
     field public static final java.lang.String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
     field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final java.lang.String MANAGE_APP_OPS_RESTRICTIONS = "android.permission.MANAGE_APP_OPS_RESTRICTIONS";
+    field public static final java.lang.String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS";
     field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
     field public static final java.lang.String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL";
     field public static final java.lang.String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE";
     field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
     field public static final java.lang.String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
+    field public static final java.lang.String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
     field public static final java.lang.String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
     field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
@@ -284,10 +286,11 @@
   }
 
   public class AppOpsManager {
-    method public java.util.List<android.app.AppOpsManager.HistoricalPackageOps> getAllHistoricPackagesOps(java.lang.String[], long, long);
-    method public android.app.AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int, java.lang.String, java.lang.String[], long, long);
+    method public void getHistoricalOps(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
     method public static java.lang.String[] getOpStrs();
-    method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]);
+    method public deprecated java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]);
+    method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, java.lang.String...);
+    method public java.util.List<android.app.AppOpsManager.PackageOps> getPackagesForOps(java.lang.String[]);
     method public static int opToDefaultMode(java.lang.String);
     method public static java.lang.String opToPermission(java.lang.String);
     method public void setMode(java.lang.String, int, java.lang.String, int);
@@ -343,7 +346,7 @@
     field public static final int UID_STATE_TOP = 1; // 0x1
   }
 
-  public static final class AppOpsManager.HistoricalOpEntry implements android.os.Parcelable {
+  public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
     method public int describeContents();
     method public long getAccessCount(int);
     method public long getAccessDuration(int);
@@ -353,23 +356,43 @@
     method public long getForegroundAccessCount();
     method public long getForegroundAccessDuration();
     method public long getForegroundRejectCount();
-    method public java.lang.String getOp();
+    method public java.lang.String getOpName();
     method public long getRejectCount(int);
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOpEntry> CREATOR;
+    field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOp> CREATOR;
+  }
+
+  public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getBeginTimeMillis();
+    method public long getEndTimeMillis();
+    method public int getUidCount();
+    method public android.app.AppOpsManager.HistoricalUidOps getUidOps(int);
+    method public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR;
   }
 
   public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
     method public int describeContents();
-    method public android.app.AppOpsManager.HistoricalOpEntry getEntry(java.lang.String);
-    method public android.app.AppOpsManager.HistoricalOpEntry getEntryAt(int);
-    method public int getEntryCount();
+    method public android.app.AppOpsManager.HistoricalOp getOp(java.lang.String);
+    method public android.app.AppOpsManager.HistoricalOp getOpAt(int);
+    method public int getOpCount();
     method public java.lang.String getPackageName();
-    method public int getUid();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalPackageOps> CREATOR;
   }
 
+  public static final class AppOpsManager.HistoricalUidOps implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getPackageCount();
+    method public android.app.AppOpsManager.HistoricalPackageOps getPackageOps(java.lang.String);
+    method public android.app.AppOpsManager.HistoricalPackageOps getPackageOpsAt(int);
+    method public int getUid();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR;
+  }
+
   public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
     method public int describeContents();
     method public int getDuration();
@@ -537,6 +560,13 @@
     method public void onVrStateChanged(boolean);
   }
 
+  public final class WallpaperColors implements android.os.Parcelable {
+    ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int);
+    method public int getColorHints();
+    field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1
+    field public static final int HINT_SUPPORTS_DARK_THEME = 2; // 0x2
+  }
+
   public final class WallpaperInfo implements android.os.Parcelable {
     method public boolean supportsAmbientMode();
   }
@@ -841,6 +871,73 @@
 
 }
 
+package android.app.contentsuggestions {
+
+  public final class ClassificationsRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.Bundle getExtras();
+    method public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ClassificationsRequest> CREATOR;
+  }
+
+  public static final class ClassificationsRequest.Builder {
+    ctor public ClassificationsRequest.Builder(java.util.List<android.app.contentsuggestions.ContentSelection>);
+    method public android.app.contentsuggestions.ClassificationsRequest build();
+    method public android.app.contentsuggestions.ClassificationsRequest.Builder setExtras(android.os.Bundle);
+  }
+
+  public final class ContentClassification implements android.os.Parcelable {
+    ctor public ContentClassification(java.lang.String, android.os.Bundle);
+    method public int describeContents();
+    method public android.os.Bundle getExtras();
+    method public java.lang.String getId();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentClassification> CREATOR;
+  }
+
+  public final class ContentSelection implements android.os.Parcelable {
+    ctor public ContentSelection(java.lang.String, android.os.Bundle);
+    method public int describeContents();
+    method public android.os.Bundle getExtras();
+    method public java.lang.String getId();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentSelection> CREATOR;
+  }
+
+  public final class ContentSuggestionsManager {
+    method public void classifyContentSelections(android.app.contentsuggestions.ClassificationsRequest, java.util.concurrent.Executor, android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
+    method public void notifyInteraction(java.lang.String, android.os.Bundle);
+    method public void provideContextImage(int, android.os.Bundle);
+    method public void suggestContentSelections(android.app.contentsuggestions.SelectionsRequest, java.util.concurrent.Executor, android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback);
+  }
+
+  public static abstract interface ContentSuggestionsManager.ClassificationsCallback {
+    method public abstract void onContentClassificationsAvailable(int, java.util.List<android.app.contentsuggestions.ContentClassification>);
+  }
+
+  public static abstract interface ContentSuggestionsManager.SelectionsCallback {
+    method public abstract void onContentSelectionsAvailable(int, java.util.List<android.app.contentsuggestions.ContentSelection>);
+  }
+
+  public final class SelectionsRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.Bundle getExtras();
+    method public android.graphics.Point getInterestPoint();
+    method public int getTaskId();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.SelectionsRequest> CREATOR;
+  }
+
+  public static final class SelectionsRequest.Builder {
+    ctor public SelectionsRequest.Builder(int);
+    method public android.app.contentsuggestions.SelectionsRequest build();
+    method public android.app.contentsuggestions.SelectionsRequest.Builder setExtras(android.os.Bundle);
+    method public android.app.contentsuggestions.SelectionsRequest.Builder setInterestPoint(android.graphics.Point);
+  }
+
+}
+
 package android.app.job {
 
   public abstract class JobScheduler {
@@ -849,6 +946,87 @@
 
 }
 
+package android.app.prediction {
+
+  public final class AppPredictionContext implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.Bundle getExtras();
+    method public java.lang.String getPackageName();
+    method public int getPredictedTargetCount();
+    method public java.lang.String getUiSurface();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.prediction.AppPredictionContext> CREATOR;
+  }
+
+  public static final class AppPredictionContext.Builder {
+    method public android.app.prediction.AppPredictionContext build();
+    method public android.app.prediction.AppPredictionContext.Builder setExtras(android.os.Bundle);
+    method public android.app.prediction.AppPredictionContext.Builder setPredictedTargetCount(int);
+    method public android.app.prediction.AppPredictionContext.Builder setUiSurface(java.lang.String);
+  }
+
+  public final class AppPredictionManager {
+    method public android.app.prediction.AppPredictor createAppPredictionSession(android.app.prediction.AppPredictionContext);
+  }
+
+  public final class AppPredictionSessionId 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.app.prediction.AppPredictionSessionId> CREATOR;
+  }
+
+  public final class AppPredictor {
+    method public void destroy();
+    method public void notifyAppTargetEvent(android.app.prediction.AppTargetEvent);
+    method public void notifyLocationShown(java.lang.String, java.util.List<android.app.prediction.AppTargetId>);
+    method public void registerPredictionUpdates(java.util.concurrent.Executor, android.app.prediction.AppPredictor.Callback);
+    method public void requestPredictionUpdate();
+    method public void sortTargets(java.util.List<android.app.prediction.AppTarget>, java.util.concurrent.Executor, java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
+    method public void unregisterPredictionUpdates(android.app.prediction.AppPredictor.Callback);
+  }
+
+  public static abstract interface AppPredictor.Callback {
+    method public abstract void onTargetsAvailable(java.util.List<android.app.prediction.AppTarget>);
+  }
+
+  public final class AppTarget implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getClassName();
+    method public android.app.prediction.AppTargetId getId();
+    method public java.lang.String getPackageName();
+    method public int getRank();
+    method public android.content.pm.ShortcutInfo getShortcutInfo();
+    method public android.os.UserHandle getUser();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.prediction.AppTarget> CREATOR;
+  }
+
+  public final class AppTargetEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAction();
+    method public java.lang.String getLaunchLocation();
+    method public android.app.prediction.AppTarget getTarget();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int ACTION_DISMISS = 2; // 0x2
+    field public static final int ACTION_LAUNCH = 1; // 0x1
+    field public static final int ACTION_PIN = 3; // 0x3
+    field public static final android.os.Parcelable.Creator<android.app.prediction.AppTargetEvent> CREATOR;
+  }
+
+  public static final class AppTargetEvent.Builder {
+    ctor public AppTargetEvent.Builder(android.app.prediction.AppTarget, int);
+    method public android.app.prediction.AppTargetEvent build();
+    method public android.app.prediction.AppTargetEvent.Builder setLaunchLocation(java.lang.String);
+  }
+
+  public final class AppTargetId 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.app.prediction.AppTargetId> CREATOR;
+  }
+
+}
+
 package android.app.role {
 
   public abstract interface OnRoleHoldersChangedListener {
@@ -1035,7 +1213,9 @@
     method public abstract void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
     method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public void startActivityAsUser(android.content.Intent, android.os.UserHandle);
+    field public static final java.lang.String APP_PREDICTION_SERVICE = "app_prediction";
     field public static final java.lang.String BACKUP_SERVICE = "backup";
+    field public static final java.lang.String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
     field public static final java.lang.String CONTEXTHUB_SERVICE = "contexthub";
     field public static final java.lang.String EUICC_CARD_SERVICE = "euicc_card";
     field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
@@ -1121,6 +1301,7 @@
 package android.content.pm {
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+    method public boolean isEncryptionAware();
     method public boolean isInstantApp();
     field public java.lang.String credentialProtectedDataDir;
     field public int targetSandboxVersion;
@@ -2951,7 +3132,7 @@
     method public void setLocationControllerExtraPackage(java.lang.String);
     method public void setLocationControllerExtraPackageEnabled(boolean);
     method public void setLocationEnabledForUser(boolean, android.os.UserHandle);
-    method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle);
+    method public deprecated boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle);
     method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback);
   }
 
@@ -3211,6 +3392,22 @@
 
 package android.media.session {
 
+  public final class ControllerCallbackLink implements android.os.Parcelable {
+    ctor public ControllerCallbackLink(android.media.session.ControllerCallbackLink.CallbackStub);
+  }
+
+  public static abstract class ControllerCallbackLink.CallbackStub {
+    ctor public ControllerCallbackLink.CallbackStub();
+    method public void onEvent(java.lang.String, android.os.Bundle);
+    method public void onExtrasChanged(android.os.Bundle);
+    method public void onMetadataChanged(android.media.MediaMetadata);
+    method public void onPlaybackStateChanged(android.media.session.PlaybackState);
+    method public void onQueueChanged(java.util.List<android.media.session.MediaSession.QueueItem>);
+    method public void onQueueTitleChanged(java.lang.CharSequence);
+    method public void onSessionDestroyed();
+    method public void onVolumeInfoChanged(android.media.session.MediaController.PlaybackInfo);
+  }
+
   public final class MediaSessionManager {
     method public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, android.os.Handler);
     method public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler);
@@ -5143,6 +5340,24 @@
 
 }
 
+package android.service.appprediction {
+
+  public abstract class AppPredictionService extends android.app.Service {
+    ctor public AppPredictionService();
+    method public abstract void onAppTargetEvent(android.app.prediction.AppPredictionSessionId, android.app.prediction.AppTargetEvent);
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method public void onCreatePredictionSession(android.app.prediction.AppPredictionContext, android.app.prediction.AppPredictionSessionId);
+    method public void onDestroyPredictionSession(android.app.prediction.AppPredictionSessionId);
+    method public abstract void onLocationShown(android.app.prediction.AppPredictionSessionId, java.lang.String, java.util.List<android.app.prediction.AppTargetId>);
+    method public abstract void onRequestPredictionUpdate(android.app.prediction.AppPredictionSessionId);
+    method public abstract void onSortAppTargets(android.app.prediction.AppPredictionSessionId, java.util.List<android.app.prediction.AppTarget>, android.os.CancellationSignal, java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
+    method public void onStartPredictionUpdates();
+    method public void onStopPredictionUpdates();
+    method public final void updatePredictions(android.app.prediction.AppPredictionSessionId, java.util.List<android.app.prediction.AppTarget>);
+  }
+
+}
+
 package android.service.autofill {
 
   public abstract class AutofillFieldClassificationService extends android.app.Service {
@@ -5253,6 +5468,7 @@
     method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId);
     method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId);
     method public void onDisconnected();
+    method public void onUserDataRemovalRequest(android.view.contentcapture.UserDataRemovalRequest);
     method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean);
     method public final void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>);
     method public final void setPackageContentCaptureEnabled(java.lang.String, boolean);
@@ -5270,6 +5486,19 @@
 
 }
 
+package android.service.contentsuggestions {
+
+  public abstract class ContentSuggestionsService extends android.app.Service {
+    ctor public ContentSuggestionsService();
+    method public abstract void classifyContentSelections(android.app.contentsuggestions.ClassificationsRequest, android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
+    method public abstract void notifyInteraction(java.lang.String, android.os.Bundle);
+    method public abstract void processContextImage(int, android.graphics.Bitmap, android.os.Bundle);
+    method public abstract void suggestContentSelections(android.app.contentsuggestions.SelectionsRequest, android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.service.contentsuggestions.ContentSuggestionsService";
+  }
+
+}
+
 package android.service.euicc {
 
   public final class DownloadSubscriptionResult implements android.os.Parcelable {
@@ -6380,6 +6609,7 @@
   public class SubscriptionInfo implements android.os.Parcelable {
     method public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
     method public int getCardId();
+    method public int getProfileClass();
   }
 
   public class SubscriptionManager {
@@ -6389,6 +6619,11 @@
     method public void setDefaultDataSubId(int);
     method public void setDefaultSmsSubId(int);
     field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
+    field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
+    field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
+    field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
+    field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
+    field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
     field public static final android.net.Uri VT_ENABLED_CONTENT_URI;
     field public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
     field public static final android.net.Uri WFC_MODE_CONTENT_URI;
@@ -6850,6 +7085,7 @@
     method public static int getCallTypeFromVideoState(int);
     method public int getEmergencyCallRouting();
     method public int getEmergencyServiceCategories();
+    method public java.util.List<java.lang.String> getEmergencyUrns();
     method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile();
     method public int getRestrictCause();
     method public int getServiceType();
@@ -6864,6 +7100,7 @@
     method public void setCallRestrictCause(int);
     method public void setEmergencyCallRouting(int);
     method public void setEmergencyServiceCategories(int);
+    method public void setEmergencyUrns(java.util.List<java.lang.String>);
     method public void updateCallExtras(android.telephony.ims.ImsCallProfile);
     method public void updateCallType(android.telephony.ims.ImsCallProfile);
     method public void updateMediaProfile(android.telephony.ims.ImsCallProfile);
diff --git a/api/test-current.txt b/api/test-current.txt
index ae3c1e0..575875d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -89,18 +89,26 @@
   }
 
   public class AppOpsManager {
-    method public java.util.List<android.app.AppOpsManager.HistoricalPackageOps> getAllHistoricPackagesOps(java.lang.String[], long, long);
-    method public android.app.AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int, java.lang.String, java.lang.String[], long, long);
+    method public void addHistoricalOps(android.app.AppOpsManager.HistoricalOps);
+    method public void clearHistory();
+    method public void getHistoricalOps(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
+    method public void getHistoricalOpsFromDiskRaw(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
     method public static int getNumOps();
     method public static java.lang.String[] getOpStrs();
     method public boolean isOperationActive(int, int, java.lang.String);
+    method public void offsetHistory(long);
     method public static java.lang.String opToPermission(int);
     method public static int permissionToOpCode(java.lang.String);
+    method public void resetHistoryParameters();
+    method public void setHistoryParameters(int, long, int);
     method public void setMode(int, int, java.lang.String, int);
     method public void setUidMode(java.lang.String, int, int);
     method public void startWatchingActive(int[], android.app.AppOpsManager.OnOpActiveChangedListener);
     method public void stopWatchingActive(android.app.AppOpsManager.OnOpActiveChangedListener);
     method public static int strOpToOp(java.lang.String);
+    field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
+    field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
+    field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2
     field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
     field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
     field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
@@ -144,11 +152,18 @@
     field public static final java.lang.String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms";
     field public static final java.lang.String OPSTR_WRITE_SMS = "android:write_sms";
     field public static final java.lang.String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper";
+    field public static final int OP_COARSE_LOCATION = 0; // 0x0
     field public static final int OP_RECORD_AUDIO = 27; // 0x1b
     field public static final int OP_SYSTEM_ALERT_WINDOW = 24; // 0x18
+    field public static final int UID_STATE_BACKGROUND = 4; // 0x4
+    field public static final int UID_STATE_CACHED = 5; // 0x5
+    field public static final int UID_STATE_FOREGROUND = 3; // 0x3
+    field public static final int UID_STATE_FOREGROUND_SERVICE = 2; // 0x2
+    field public static final int UID_STATE_PERSISTENT = 0; // 0x0
+    field public static final int UID_STATE_TOP = 1; // 0x1
   }
 
-  public static final class AppOpsManager.HistoricalOpEntry implements android.os.Parcelable {
+  public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
     method public int describeContents();
     method public long getAccessCount(int);
     method public long getAccessDuration(int);
@@ -158,23 +173,48 @@
     method public long getForegroundAccessCount();
     method public long getForegroundAccessDuration();
     method public long getForegroundRejectCount();
-    method public java.lang.String getOp();
+    method public java.lang.String getOpName();
     method public long getRejectCount(int);
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOpEntry> CREATOR;
+    field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOp> CREATOR;
+  }
+
+  public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable {
+    ctor public AppOpsManager.HistoricalOps(long, long);
+    method public int describeContents();
+    method public long getBeginTimeMillis();
+    method public long getEndTimeMillis();
+    method public int getUidCount();
+    method public android.app.AppOpsManager.HistoricalUidOps getUidOps(int);
+    method public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(int);
+    method public void increaseAccessCount(int, int, java.lang.String, int, long);
+    method public void increaseAccessDuration(int, int, java.lang.String, int, long);
+    method public void increaseRejectCount(int, int, java.lang.String, int, long);
+    method public void offsetBeginAndEndTime(long);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR;
   }
 
   public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
     method public int describeContents();
-    method public android.app.AppOpsManager.HistoricalOpEntry getEntry(java.lang.String);
-    method public android.app.AppOpsManager.HistoricalOpEntry getEntryAt(int);
-    method public int getEntryCount();
+    method public android.app.AppOpsManager.HistoricalOp getOp(java.lang.String);
+    method public android.app.AppOpsManager.HistoricalOp getOpAt(int);
+    method public int getOpCount();
     method public java.lang.String getPackageName();
-    method public int getUid();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalPackageOps> CREATOR;
   }
 
+  public static final class AppOpsManager.HistoricalUidOps implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getPackageCount();
+    method public android.app.AppOpsManager.HistoricalPackageOps getPackageOps(java.lang.String);
+    method public android.app.AppOpsManager.HistoricalPackageOps getPackageOpsAt(int);
+    method public int getUid();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR;
+  }
+
   public static abstract interface AppOpsManager.OnOpActiveChangedListener {
     method public abstract void onOpActiveChanged(int, int, java.lang.String, boolean);
   }
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
index 4918747..4f88127 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -39,6 +39,17 @@
 using android::idmap2::utils::FindFiles;
 
 namespace {
+
+struct InputOverlay {
+  bool operator<(InputOverlay const& rhs) const {
+    return priority < rhs.priority || (priority == rhs.priority && apk_path < rhs.apk_path);
+  }
+
+  std::string apk_path;    // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string idmap_path;  // NOLINT(misc-non-private-member-variables-in-classes)
+  int priority;            // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
 std::unique_ptr<std::vector<std::string>> FindApkFiles(const std::vector<std::string>& dirs,
                                                        bool recursive, std::ostream& out_error) {
   const auto predicate = [](unsigned char type, const std::string& path) -> bool {
@@ -58,6 +69,7 @@
   }
   return std::make_unique<std::vector<std::string>>(paths.cbegin(), paths.cend());
 }
+
 }  // namespace
 
 bool Scan(const std::vector<std::string>& args, std::ostream& out_error) {
@@ -87,7 +99,7 @@
     return false;
   }
 
-  std::vector<std::string> interesting_apks;
+  std::vector<InputOverlay> interesting_apks;
   for (const std::string& path : *apk_paths) {
     std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
     if (!zip) {
@@ -132,27 +144,29 @@
       continue;
     }
 
+    // Sort the static overlays in ascending priority order
+    std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, path);
+    InputOverlay input{path, idmap_path, priority};
     interesting_apks.insert(
-        std::lower_bound(interesting_apks.begin(), interesting_apks.end(), path), path);
+        std::lower_bound(interesting_apks.begin(), interesting_apks.end(), input), input);
   }
 
   std::stringstream stream;
-  for (const auto& apk : interesting_apks) {
-    const std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, apk);
+  for (const auto& overlay : interesting_apks) {
     std::stringstream dev_null;
-    if (!Verify(std::vector<std::string>({"--idmap-path", idmap_path}), dev_null) &&
+    if (!Verify(std::vector<std::string>({"--idmap-path", overlay.idmap_path}), dev_null) &&
         !Create(std::vector<std::string>({
                     "--target-apk-path",
                     target_apk_path,
                     "--overlay-apk-path",
-                    apk,
+                    overlay.apk_path,
                     "--idmap-path",
-                    idmap_path,
+                    overlay.idmap_path,
                 }),
                 out_error)) {
       return false;
     }
-    stream << idmap_path << std::endl;
+    stream <<  overlay.idmap_path << std::endl;
   }
 
   std::cout << stream.str();
diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh
index 560ccb6..ad9830b 100755
--- a/cmds/idmap2/static-checks.sh
+++ b/cmds/idmap2/static-checks.sh
@@ -70,7 +70,15 @@
 function _cpplint()
 {
     local cpplint="${ANDROID_BUILD_TOP}/tools/repohooks/tools/cpplint.py"
-    $cpplint --quiet $cpp_files
+    local output="$($cpplint --quiet $cpp_files 2>&1 >/dev/null | grep -v \
+        -e 'Found C system header after C++ system header.' \
+        -e 'Unknown NOLINT error category: misc-non-private-member-variables-in-classes' \
+    )"
+    if [[ "$output" ]]; then
+        echo "$output"
+        return 1
+    fi
+    return 0
 }
 
 function _parse_args()
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 6788f7d..d160b73 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -19,12 +19,12 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
-import android.content.pm.ParceledListSlice;
 import android.media.MediaMetadata;
+import android.media.session.ControllerCallbackLink;
 import android.media.session.ISessionController;
-import android.media.session.ISessionControllerCallback;
 import android.media.session.ISessionManager;
 import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.QueueItem;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.HandlerThread;
@@ -178,13 +178,7 @@
                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
     }
 
-    class ControllerMonitor extends ISessionControllerCallback.Stub {
-        private final ISessionController mController;
-
-        public ControllerMonitor(ISessionController controller) {
-            mController = controller;
-        }
-
+    class ControllerCallbackStub extends ControllerCallbackLink.CallbackStub {
         @Override
         public void onSessionDestroyed() {
             System.out.println("onSessionDestroyed. Enter q to quit.");
@@ -208,25 +202,35 @@
         }
 
         @Override
-        public void onQueueChanged(ParceledListSlice queue) throws RemoteException {
+        public void onQueueChanged(List<QueueItem> queue) {
             System.out.println("onQueueChanged, "
-                    + (queue == null ? "null queue" : " size=" + queue.getList().size()));
+                    + (queue == null ? "null queue" : " size=" + queue.size()));
         }
 
         @Override
-        public void onQueueTitleChanged(CharSequence title) throws RemoteException {
+        public void onQueueTitleChanged(CharSequence title) {
             System.out.println("onQueueTitleChange " + title);
         }
 
         @Override
-        public void onExtrasChanged(Bundle extras) throws RemoteException {
+        public void onExtrasChanged(Bundle extras) {
             System.out.println("onExtrasChanged " + extras);
         }
 
         @Override
-        public void onVolumeInfoChanged(PlaybackInfo info) throws RemoteException {
+        public void onVolumeInfoChanged(PlaybackInfo info) {
             System.out.println("onVolumeInfoChanged " + info);
         }
+    }
+
+    private class ControllerMonitor {
+        private final ISessionController mController;
+        private final ControllerCallbackLink mControllerCallbackLink;
+
+        ControllerMonitor(ISessionController controller) {
+            mController = controller;
+            mControllerCallbackLink = new ControllerCallbackLink(new ControllerCallbackStub());
+        }
 
         void printUsageMessage() {
             try {
@@ -244,7 +248,7 @@
                 @Override
                 protected void onLooperPrepared() {
                     try {
-                        mController.registerCallbackListener(PACKAGE_NAME, ControllerMonitor.this);
+                        mController.registerCallbackListener(PACKAGE_NAME, mControllerCallbackLink);
                     } catch (RemoteException e) {
                         System.out.println("Error registering monitor callback");
                     }
@@ -287,7 +291,7 @@
             } finally {
                 cbThread.getLooper().quit();
                 try {
-                    mController.unregisterCallbackListener(this);
+                    mController.unregisterCallbackListener(mControllerCallbackLink);
                 } catch (Exception e) {
                     // ignoring
                 }
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index e0e1b72..ed22df5 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -490,7 +490,6 @@
 Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I
 Landroid/location/ILocationManager;->getAllProviders()Ljava/util/List;
 Landroid/location/ILocationManager;->getNetworkProviderPackage()Ljava/lang/String;
-Landroid/location/ILocationManager;->reportLocation(Landroid/location/Location;Z)V
 Landroid/location/INetInitiatedListener$Stub;-><init>()V
 Landroid/location/INetInitiatedListener;->sendNiResponse(II)Z
 Landroid/location/LocationManager$ListenerTransport;-><init>(Landroid/location/LocationManager;Landroid/location/LocationListener;Landroid/os/Looper;)V
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index fdb71bb..c89848e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -121,6 +121,7 @@
 import android.view.autofill.AutofillManager.AutofillClient;
 import android.view.autofill.AutofillPopupWindow;
 import android.view.autofill.IAutofillWindowPresenter;
+import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureManager;
 import android.widget.AdapterView;
 import android.widget.Toast;
@@ -1045,14 +1046,19 @@
 
     private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
         final ContentCaptureManager cm = getContentCaptureManager();
-        if (cm == null || !cm.isContentCaptureEnabled()) {
+        if (cm == null) {
             return;
         }
         switch (type) {
             case CONTENT_CAPTURE_START:
                 //TODO(b/111276913): decide whether the InteractionSessionId should be
                 // saved / restored in the activity bundle - probably not
-                cm.onActivityStarted(mToken, getComponentName());
+                int flags = 0;
+                if ((getWindow().getAttributes().flags
+                        & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+                    flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
+                }
+                cm.onActivityStarted(mToken, getComponentName(), flags);
                 break;
             case CONTENT_CAPTURE_FLUSH:
                 cm.flush();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8faf08a..7767f04 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -271,6 +271,13 @@
     @UnsupportedAppUsage
     final H mH = new H();
     final Executor mExecutor = new HandlerExecutor(mH);
+    /**
+     * Maps from activity token to local record of running activities in this process.
+     *
+     * This variable is readable if the code is running in activity thread or holding {@link
+     * #mResourcesManager}. It's only writable if the code is running in activity thread and holding
+     * {@link #mResourcesManager}.
+     */
     @UnsupportedAppUsage
     final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
     /** The activities to be truly destroyed (not include relaunch). */
@@ -434,6 +441,10 @@
         Configuration newConfig;
         Configuration createdConfig;
         Configuration overrideConfig;
+        // Used to save the last reported configuration from server side so that activity
+        // configuration transactions can always use the latest configuration.
+        @GuardedBy("this")
+        private Configuration mPendingOverrideConfig;
         // Used for consolidating configs before sending on to Activity.
         private Configuration tmpConfig = new Configuration();
         // Callback used for updating activity override config.
@@ -3064,7 +3075,12 @@
             }
             r.setState(ON_CREATE);
 
-            mActivities.put(r.token, r);
+            // updatePendingActivityConfiguration() reads from mActivities to update
+            // ActivityClientRecord which runs in a different thread. Protect modifications to
+            // mActivities to avoid race.
+            synchronized (mResourcesManager) {
+                mActivities.put(r.token, r);
+            }
 
         } catch (SuperNotCalledException e) {
             throw e;
@@ -4639,7 +4655,12 @@
             r.setState(ON_DESTROY);
         }
         schedulePurgeIdler();
-        mActivities.remove(token);
+        // updatePendingActivityConfiguration() reads from mActivities to update
+        // ActivityClientRecord which runs in a different thread. Protect modifications to
+        // mActivities to avoid race.
+        synchronized (mResourcesManager) {
+            mActivities.remove(token);
+        }
         StrictMode.decrementExpectedActivityCount(activityClass);
         return r;
     }
@@ -5382,6 +5403,26 @@
         }
     }
 
+    @Override
+    public void updatePendingActivityConfiguration(IBinder activityToken,
+            Configuration overrideConfig) {
+        final ActivityClientRecord r;
+        synchronized (mResourcesManager) {
+            r = mActivities.get(activityToken);
+        }
+
+        if (r == null) {
+            if (DEBUG_CONFIGURATION) {
+                Slog.w(TAG, "Not found target activity to update its pending config.");
+            }
+            return;
+        }
+
+        synchronized (r) {
+            r.mPendingOverrideConfig = overrideConfig;
+        }
+    }
+
     /**
      * Handle new activity configuration and/or move to a different display.
      * @param activityToken Target activity token.
@@ -5401,6 +5442,24 @@
         final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY
                 && displayId != r.activity.getDisplayId();
 
+        synchronized (r) {
+            if (r.mPendingOverrideConfig != null
+                    && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
+                overrideConfig = r.mPendingOverrideConfig;
+            }
+            r.mPendingOverrideConfig = null;
+        }
+
+        if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
+                && !movedToDifferentDisplay) {
+            if (DEBUG_CONFIGURATION) {
+                Slog.v(TAG, "Activity already handled newer configuration so drop this"
+                        + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig="
+                        + r.overrideConfig);
+            }
+            return;
+        }
+
         // Perform updates.
         r.overrideConfig = overrideConfig;
         final ViewRootImpl viewRoot = r.activity.mDecor != null
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
index 9329fbc..2240302 100644
--- a/core/java/android/app/AppOpsManager.aidl
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -19,5 +19,7 @@
 parcelable AppOpsManager.PackageOps;
 parcelable AppOpsManager.OpEntry;
 
+parcelable AppOpsManager.HistoricalOp;
+parcelable AppOpsManager.HistoricalOps;
 parcelable AppOpsManager.HistoricalPackageOps;
-parcelable AppOpsManager.HistoricalOpEntry;
+parcelable AppOpsManager.HistoricalUidOps;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 78fe002..e155fe2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -36,26 +36,33 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.ArrayMap;
 
 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;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Arrays;
 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.function.Consumer;
 
 /**
  * API for interacting with "application operation" tracking.
@@ -106,6 +113,51 @@
 
     static IBinder sToken;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "HISTORICAL_MODE_" }, value = {
+            HISTORICAL_MODE_DISABLED,
+            HISTORICAL_MODE_ENABLED_ACTIVE,
+            HISTORICAL_MODE_ENABLED_PASSIVE
+    })
+    public @interface HistoricalMode {}
+
+    /**
+     * Mode in which app op history is completely disabled.
+     * @hide
+     */
+    @TestApi
+    public static final int HISTORICAL_MODE_DISABLED = 0;
+
+    /**
+     * Mode in which app op history is enabled and app ops performed by apps would
+     * be tracked. This is the mode in which the feature is completely enabled.
+     * @hide
+     */
+    @TestApi
+    public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1;
+
+    /**
+     * Mode in which app op history is enabled but app ops performed by apps would
+     * not be tracked and the only way to add ops to the history is via explicit calls
+     * to dedicated APIs. This mode is useful for testing to allow full control of
+     * the historical content.
+     * @hide
+     */
+    @TestApi
+    public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODE_" }, value = {
+            MODE_ALLOWED,
+            MODE_IGNORED,
+            MODE_ERRORED,
+            MODE_DEFAULT,
+            MODE_FOREGROUND
+    })
+    public @interface Mode {}
+
     /**
      * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is
      * allowed to perform the given operation.
@@ -159,7 +211,6 @@
             "foreground",   // MODE_FOREGROUND
     };
 
-
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "UID_STATE_" }, value = {
@@ -173,9 +224,16 @@
     public @interface UidState {}
 
     /**
+     * Invalid UID state.
+     * @hide
+     */
+    public static final int UID_STATE_INVALID = -1;
+
+    /**
      * Metrics about an op when its uid is persistent.
      * @hide
      */
+    @TestApi
     @SystemApi
     public static final int UID_STATE_PERSISTENT = 0;
 
@@ -183,6 +241,7 @@
      * Metrics about an op when its uid is at the top.
      * @hide
      */
+    @TestApi
     @SystemApi
     public static final int UID_STATE_TOP = 1;
 
@@ -190,6 +249,7 @@
      * Metrics about an op when its uid is running a foreground service.
      * @hide
      */
+    @TestApi
     @SystemApi
     public static final int UID_STATE_FOREGROUND_SERVICE = 2;
 
@@ -203,6 +263,7 @@
      * Metrics about an op when its uid is in the foreground for any other reasons.
      * @hide
      */
+    @TestApi
     @SystemApi
     public static final int UID_STATE_FOREGROUND = 3;
 
@@ -210,6 +271,7 @@
      * Metrics about an op when its uid is in the background for any reason.
      * @hide
      */
+    @TestApi
     @SystemApi
     public static final int UID_STATE_BACKGROUND = 4;
 
@@ -217,6 +279,7 @@
      * Metrics about an op when its uid is cached.
      * @hide
      */
+    @TestApi
     @SystemApi
     public static final int UID_STATE_CACHED = 5;
 
@@ -237,7 +300,7 @@
     @UnsupportedAppUsage
     public static final int OP_NONE = -1;
     /** @hide Access to coarse location information. */
-    @UnsupportedAppUsage
+    @TestApi
     public static final int OP_COARSE_LOCATION = 0;
     /** @hide Access to fine location information. */
     @UnsupportedAppUsage
@@ -1645,6 +1708,9 @@
         }
     }
 
+    /** @hide */
+    public static final String KEY_HISTORICAL_OPS = "historical_ops";
+
     /**
      * Retrieve the op switch that controls the given operation.
      * @hide
@@ -1731,7 +1797,7 @@
      * Retrieve the default mode for the operation.
      * @hide
      */
-    public static int opToDefaultMode(int op) {
+    public static @Mode int opToDefaultMode(int op) {
         // STOPSHIP b/118520006: Hardcode the default values once the feature is stable.
         switch (op) {
             // SMS permissions
@@ -1795,7 +1861,7 @@
      * Retrieve the human readable mode.
      * @hide
      */
-    public static String modeToName(int mode) {
+    public static String modeToName(@Mode int mode) {
         if (mode >= 0 && mode < MODE_NAMES.length) {
             return MODE_NAMES[mode];
         }
@@ -1885,7 +1951,7 @@
     @SystemApi
     public static final class OpEntry implements Parcelable {
         private final int mOp;
-        private final int mMode;
+        private final @Mode int mMode;
         private final long[] mTimes;
         private final long[] mRejectTimes;
         private final int mDuration;
@@ -1896,7 +1962,7 @@
         /**
          * @hide
          */
-        public OpEntry(int op, int mode, long time, long rejectTime, int duration,
+        public OpEntry(int op, @Mode int mode, long time, long rejectTime, int duration,
                 int proxyUid, String proxyPackage) {
             mOp = op;
             mMode = mode;
@@ -1913,7 +1979,7 @@
         /**
          * @hide
          */
-        public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration,
+        public OpEntry(int op, @Mode int mode, long[] times, long[] rejectTimes, int duration,
                 boolean running, int proxyUid, String proxyPackage) {
             mOp = op;
             mMode = mode;
@@ -1930,7 +1996,7 @@
         /**
          * @hide
          */
-        public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration,
+        public OpEntry(int op, @Mode int mode, long[] times, long[] rejectTimes, int duration,
                 int proxyUid, String proxyPackage) {
             this(op, mode, times, rejectTimes, duration, duration == -1, proxyUid, proxyPackage);
         }
@@ -1953,7 +2019,7 @@
         /**
          * Return this entry's current mode, such as {@link #MODE_ALLOWED}.
          */
-        public int getMode() {
+        public @Mode int getMode() {
             return mMode;
         }
 
@@ -2086,41 +2152,774 @@
         };
     }
 
+    /** @hide */
+    public interface HistoricalOpsVisitor {
+        void visitHistoricalOps(@NonNull HistoricalOps ops);
+        void visitHistoricalUidOps(@NonNull HistoricalUidOps ops);
+        void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops);
+        void visitHistoricalOp(@NonNull HistoricalOp ops);
+    }
+
     /**
-     * This class represents historical app op information about a package. The history
-     * is aggregated information about ops for a certain amount of time such
-     * as the times the op was accessed, the times the op was rejected, the total
-     * duration the app op has been accessed.
+     * This class represents historical app op state of all UIDs for a given time interval.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final class HistoricalOps implements Parcelable {
+        private long mBeginTimeMillis;
+        private long mEndTimeMillis;
+        private @Nullable SparseArray<HistoricalUidOps> mHistoricalUidOps;
+
+        /** @hide */
+        @TestApi
+        public HistoricalOps(long beginTimeMillis, long endTimeMillis) {
+            Preconditions.checkState(beginTimeMillis <= endTimeMillis);
+            mBeginTimeMillis = beginTimeMillis;
+            mEndTimeMillis = endTimeMillis;
+        }
+
+        /** @hide */
+        public HistoricalOps(@NonNull HistoricalOps other) {
+            mBeginTimeMillis = other.mBeginTimeMillis;
+            mEndTimeMillis = other.mEndTimeMillis;
+            Preconditions.checkState(mBeginTimeMillis <= mEndTimeMillis);
+            if (other.mHistoricalUidOps != null) {
+                final int opCount = other.getUidCount();
+                for (int i = 0; i < opCount; i++) {
+                    final HistoricalUidOps origOps = other.getUidOpsAt(i);
+                    final HistoricalUidOps clonedOps = new HistoricalUidOps(origOps);
+                    if (mHistoricalUidOps == null) {
+                        mHistoricalUidOps = new SparseArray<>(opCount);
+                    }
+                    mHistoricalUidOps.put(clonedOps.getUid(), clonedOps);
+                }
+            }
+        }
+
+        private HistoricalOps(Parcel parcel) {
+            mBeginTimeMillis = parcel.readLong();
+            mEndTimeMillis = parcel.readLong();
+            final int[] uids = parcel.createIntArray();
+            if (!ArrayUtils.isEmpty(uids)) {
+                final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable(
+                        HistoricalOps.class.getClassLoader());
+                final List<HistoricalUidOps> uidOps = (listSlice != null)
+                        ? listSlice.getList() : null;
+                if (uidOps == null) {
+                    return;
+                }
+                for (int i = 0; i < uids.length; i++) {
+                    if (mHistoricalUidOps == null) {
+                        mHistoricalUidOps = new SparseArray<>();
+                    }
+                    mHistoricalUidOps.put(uids[i], uidOps.get(i));
+                }
+            }
+        }
+
+        /**
+         * Splice a piece from the beginning of these ops.
+         *
+         * @param splicePoint The fraction of the data to be spliced off.
+         *
+         * @hide
+         */
+        public @NonNull HistoricalOps spliceFromBeginning(double splicePoint) {
+            return splice(splicePoint, true);
+        }
+
+        /**
+         * Splice a piece from the end of these ops.
+         *
+         * @param fractionToRemove The fraction of the data to be spliced off.
+         *
+         * @hide
+         */
+        public @NonNull HistoricalOps spliceFromEnd(double fractionToRemove) {
+            return splice(fractionToRemove, false);
+        }
+
+        /**
+         * Splice a piece from the beginning or end of these ops.
+         *
+         * @param fractionToRemove The fraction of the data to be spliced off.
+         * @param beginning Whether to splice off the beginning or the end.
+         *
+         * @return The spliced off part.
+         *
+         * @hide
+         */
+        private @Nullable HistoricalOps splice(double fractionToRemove, boolean beginning) {
+            final long spliceBeginTimeMills;
+            final long spliceEndTimeMills;
+            if (beginning) {
+                spliceBeginTimeMills = mBeginTimeMillis;
+                spliceEndTimeMills = (long) (mBeginTimeMillis
+                        + getDurationMillis() * fractionToRemove);
+                mBeginTimeMillis = spliceEndTimeMills;
+            } else {
+                spliceBeginTimeMills = (long) (mEndTimeMillis
+                        - getDurationMillis() * fractionToRemove);
+                spliceEndTimeMills = mEndTimeMillis;
+                mEndTimeMillis = spliceBeginTimeMills;
+            }
+
+            HistoricalOps splice = null;
+            final int uidCount = getUidCount();
+            for (int i = 0; i < uidCount; i++) {
+                final HistoricalUidOps origOps = getUidOpsAt(i);
+                final HistoricalUidOps spliceOps = origOps.splice(fractionToRemove);
+                if (spliceOps != null) {
+                    if (splice == null) {
+                        splice = new HistoricalOps(spliceBeginTimeMills, spliceEndTimeMills);
+                    }
+                    if (splice.mHistoricalUidOps == null) {
+                        splice.mHistoricalUidOps = new SparseArray<>();
+                    }
+                    splice.mHistoricalUidOps.put(spliceOps.getUid(), spliceOps);
+                }
+            }
+            return splice;
+        }
+
+        /**
+         * Merge the passed ops into the current ones. The time interval is a
+         * union of the current and passed in one and the passed in data is
+         * folded into the data of this instance.
+         *
+         * @hide
+         */
+        public void merge(@NonNull HistoricalOps other) {
+            mBeginTimeMillis = Math.min(mBeginTimeMillis, other.mBeginTimeMillis);
+            mEndTimeMillis = Math.max(mEndTimeMillis, other.mEndTimeMillis);
+            final int uidCount = other.getUidCount();
+            for (int i = 0; i < uidCount; i++) {
+                final HistoricalUidOps otherUidOps = other.getUidOpsAt(i);
+                final HistoricalUidOps thisUidOps = getUidOps(otherUidOps.getUid());
+                if (thisUidOps != null) {
+                    thisUidOps.merge(otherUidOps);
+                } else {
+                    if (mHistoricalUidOps == null) {
+                        mHistoricalUidOps = new SparseArray<>();
+                    }
+                    mHistoricalUidOps.put(otherUidOps.getUid(), otherUidOps);
+                }
+            }
+        }
+
+        /**
+         * AppPermissionUsage the ops to leave only the data we filter for.
+         *
+         * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all.
+         * @param packageName Package to filter for or null for all.
+         * @param opNames Ops to filter for or null for all.
+         * @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all.
+         * @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all.
+         *
+         * @hide
+         */
+        public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames,
+                long beginTimeMillis, long endTimeMillis) {
+            final long durationMillis = getDurationMillis();
+            mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis);
+            mEndTimeMillis = Math.min(mEndTimeMillis, endTimeMillis);
+            final double scaleFactor = Math.min((double) (endTimeMillis - beginTimeMillis)
+                    / (double) durationMillis, 1);
+            final int uidCount = getUidCount();
+            for (int i = uidCount - 1; i >= 0; i--) {
+                final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
+                if (uid != Process.INVALID_UID && uid != uidOp.getUid()) {
+                    mHistoricalUidOps.removeAt(i);
+                } else {
+                    uidOp.filter(packageName, opNames, scaleFactor);
+                }
+            }
+        }
+
+        /** @hide */
+        public boolean isEmpty() {
+            if (getBeginTimeMillis() >= getEndTimeMillis()) {
+                return true;
+            }
+            final int uidCount = getUidCount();
+            for (int i = uidCount - 1; i >= 0; i--) {
+                final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
+                if (!uidOp.isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /** @hide */
+        public long getDurationMillis() {
+            return mEndTimeMillis - mBeginTimeMillis;
+        }
+
+        /** @hide */
+        @TestApi
+        public void increaseAccessCount(int opCode, int uid, @NonNull String packageName,
+                @UidState int uidState, long increment) {
+            getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode,
+                    packageName, uidState, increment);
+        }
+
+        /** @hide */
+        @TestApi
+        public void increaseRejectCount(int opCode, int uid, @NonNull String packageName,
+                @UidState int uidState, long increment) {
+            getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode,
+                    packageName, uidState, increment);
+        }
+
+        /** @hide */
+        @TestApi
+        public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName,
+                @UidState int uidState, long increment) {
+            getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode,
+                    packageName, uidState, increment);
+        }
+
+        /** @hide */
+        @TestApi
+        public void offsetBeginAndEndTime(long offsetMillis) {
+            mBeginTimeMillis += offsetMillis;
+            mEndTimeMillis += offsetMillis;
+        }
+
+        /** @hide */
+        public void setBeginAndEndTime(long beginTimeMillis, long endTimeMillis) {
+            mBeginTimeMillis = beginTimeMillis;
+            mEndTimeMillis = endTimeMillis;
+        }
+
+        /** @hide */
+        public void setBeginTime(long beginTimeMillis) {
+            mBeginTimeMillis = beginTimeMillis;
+        }
+
+        /** @hide */
+        public void setEndTime(long endTimeMillis) {
+            mEndTimeMillis = endTimeMillis;
+        }
+
+        /**
+         * @return The beginning of the interval in milliseconds since
+         *    epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         */
+        public long getBeginTimeMillis() {
+            return mBeginTimeMillis;
+        }
+
+        /**
+         * @return The end of the interval in milliseconds since
+         *    epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         */
+        public long getEndTimeMillis() {
+            return mEndTimeMillis;
+        }
+
+        /**
+         * Gets number of UIDs with historical ops.
+         *
+         * @return The number of UIDs with historical ops.
+         *
+         * @see #getUidOpsAt(int)
+         */
+        public int getUidCount() {
+            if (mHistoricalUidOps == null) {
+                return 0;
+            }
+            return mHistoricalUidOps.size();
+        }
+
+        /**
+         * Gets the historical UID ops at a given index.
+         *
+         * @param index The index.
+         *
+         * @return The historical UID ops at the given index.
+         *
+         * @see #getUidCount()
+         */
+        public @NonNull HistoricalUidOps getUidOpsAt(int index) {
+            if (mHistoricalUidOps == null) {
+                throw new IndexOutOfBoundsException();
+            }
+            return mHistoricalUidOps.valueAt(index);
+        }
+
+        /**
+         * Gets the historical UID ops for a given UID.
+         *
+         * @param uid The UID.
+         *
+         * @return The historical ops for the UID.
+         */
+        public @Nullable HistoricalUidOps getUidOps(int uid) {
+            if (mHistoricalUidOps == null) {
+                return null;
+            }
+            return mHistoricalUidOps.get(uid);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeLong(mBeginTimeMillis);
+            parcel.writeLong(mEndTimeMillis);
+            if (mHistoricalUidOps != null) {
+                final int uidCount = mHistoricalUidOps.size();
+                parcel.writeInt(uidCount);
+                for (int i = 0; i < uidCount; i++) {
+                    parcel.writeInt(mHistoricalUidOps.keyAt(i));
+                }
+                final List<HistoricalUidOps> opsList = new ArrayList<>(uidCount);
+                for (int i = 0; i < uidCount; i++) {
+                    opsList.add(mHistoricalUidOps.valueAt(i));
+                }
+                parcel.writeParcelable(new ParceledListSlice<>(opsList), flags);
+            } else {
+                parcel.writeInt(-1);
+            }
+        }
+
+        /**
+         * Accepts a visitor to traverse the ops tree.
+         *
+         * @param visitor The visitor.
+         *
+         * @hide
+         */
+        public void accept(@NonNull HistoricalOpsVisitor visitor) {
+            visitor.visitHistoricalOps(this);
+            final int uidCount = getUidCount();
+            for (int i = 0; i < uidCount; i++) {
+                getUidOpsAt(i).accept(visitor);
+            }
+        }
+
+        private @NonNull HistoricalUidOps getOrCreateHistoricalUidOps(int uid) {
+            if (mHistoricalUidOps == null) {
+                mHistoricalUidOps = new SparseArray<>();
+            }
+            HistoricalUidOps historicalUidOp = mHistoricalUidOps.get(uid);
+            if (historicalUidOp == null) {
+                historicalUidOp = new HistoricalUidOps(uid);
+                mHistoricalUidOps.put(uid, historicalUidOp);
+            }
+            return historicalUidOp;
+        }
+
+        /**
+         * @return Rounded value up at the 0.5 boundary.
+         *
+         * @hide
+         */
+        public static double round(double value) {
+            final BigDecimal decimalScale = new BigDecimal(value);
+            return decimalScale.setScale(0, RoundingMode.HALF_UP).doubleValue();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final HistoricalOps other = (HistoricalOps) obj;
+            if (mBeginTimeMillis != other.mBeginTimeMillis) {
+                return false;
+            }
+            if (mEndTimeMillis != other.mEndTimeMillis) {
+                return false;
+            }
+            if (mHistoricalUidOps == null) {
+                if (other.mHistoricalUidOps != null) {
+                    return false;
+                }
+            } else if (!mHistoricalUidOps.equals(other.mHistoricalUidOps)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = (int) (mBeginTimeMillis ^ (mBeginTimeMillis >>> 32));
+            result = 31 * result + mHistoricalUidOps.hashCode();
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + "[from:"
+                    + mBeginTimeMillis + " to:" + mEndTimeMillis + "]";
+        }
+
+        public static final Creator<HistoricalOps> CREATOR = new Creator<HistoricalOps>() {
+            @Override
+            public @NonNull HistoricalOps createFromParcel(@NonNull Parcel parcel) {
+                return new HistoricalOps(parcel);
+            }
+
+            @Override
+            public @NonNull HistoricalOps[] newArray(int size) {
+                return new HistoricalOps[size];
+            }
+        };
+    }
+
+    /**
+     * This class represents historical app op state for a UID.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final class HistoricalUidOps implements Parcelable {
+        private final int mUid;
+        private @Nullable ArrayMap<String, HistoricalPackageOps> mHistoricalPackageOps;
+
+        /** @hide */
+        public HistoricalUidOps(int uid) {
+            mUid = uid;
+        }
+
+        private HistoricalUidOps(@NonNull HistoricalUidOps other) {
+            mUid = other.mUid;
+            final int opCount = other.getPackageCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalPackageOps origOps = other.getPackageOpsAt(i);
+                final HistoricalPackageOps cloneOps = new HistoricalPackageOps(origOps);
+                if (mHistoricalPackageOps == null) {
+                    mHistoricalPackageOps = new ArrayMap<>(opCount);
+                }
+                mHistoricalPackageOps.put(cloneOps.getPackageName(), cloneOps);
+            }
+        }
+
+        private HistoricalUidOps(@NonNull Parcel parcel) {
+            // No arg check since we always read from a trusted source.
+            mUid = parcel.readInt();
+            mHistoricalPackageOps = parcel.createTypedArrayMap(HistoricalPackageOps.CREATOR);
+        }
+
+        private @Nullable HistoricalUidOps splice(double fractionToRemove) {
+            HistoricalUidOps splice = null;
+            final int packageCount = getPackageCount();
+            for (int i = 0; i < packageCount; i++) {
+                final HistoricalPackageOps origOps = getPackageOpsAt(i);
+                final HistoricalPackageOps spliceOps = origOps.splice(fractionToRemove);
+                if (spliceOps != null) {
+                    if (splice == null) {
+                        splice = new HistoricalUidOps(mUid);
+                    }
+                    if (splice.mHistoricalPackageOps == null) {
+                        splice.mHistoricalPackageOps = new ArrayMap<>();
+                    }
+                    splice.mHistoricalPackageOps.put(spliceOps.getPackageName(), spliceOps);
+                }
+            }
+            return splice;
+        }
+
+        private void merge(@NonNull HistoricalUidOps other) {
+            final int packageCount = other.getPackageCount();
+            for (int i = 0; i < packageCount; i++) {
+                final HistoricalPackageOps otherPackageOps = other.getPackageOpsAt(i);
+                final HistoricalPackageOps thisPackageOps = getPackageOps(
+                        otherPackageOps.getPackageName());
+                if (thisPackageOps != null) {
+                    thisPackageOps.merge(otherPackageOps);
+                } else {
+                    if (mHistoricalPackageOps == null) {
+                        mHistoricalPackageOps = new ArrayMap<>();
+                    }
+                    mHistoricalPackageOps.put(otherPackageOps.getPackageName(), otherPackageOps);
+                }
+            }
+        }
+
+        private void filter(@Nullable String packageName, @Nullable String[] opNames,
+                double fractionToRemove) {
+            final int packageCount = getPackageCount();
+            for (int i = packageCount - 1; i >= 0; i--) {
+                final HistoricalPackageOps packageOps = getPackageOpsAt(i);
+                if (packageName != null && !packageName.equals(packageOps.getPackageName())) {
+                    mHistoricalPackageOps.removeAt(i);
+                } else {
+                    packageOps.filter(opNames, fractionToRemove);
+                }
+            }
+        }
+
+        private boolean isEmpty() {
+            final int packageCount = getPackageCount();
+            for (int i = packageCount - 1; i >= 0; i--) {
+                final HistoricalPackageOps packageOps = mHistoricalPackageOps.valueAt(i);
+                if (!packageOps.isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private void increaseAccessCount(int opCode, @NonNull String packageName,
+                @UidState int uidState, long increment) {
+            getOrCreateHistoricalPackageOps(packageName).increaseAccessCount(
+                    opCode, uidState, increment);
+        }
+
+        private void increaseRejectCount(int opCode, @NonNull String packageName,
+                @UidState int uidState, long increment) {
+            getOrCreateHistoricalPackageOps(packageName).increaseRejectCount(
+                    opCode, uidState, increment);
+        }
+
+        private void increaseAccessDuration(int opCode, @NonNull String packageName,
+                @UidState int uidState, long increment) {
+            getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration(
+                    opCode, uidState, increment);
+        }
+
+        /**
+         * @return The UID for which the data is related.
+         */
+        public int getUid() {
+            return mUid;
+        }
+
+        /**
+         * Gets number of packages with historical ops.
+         *
+         * @return The number of packages with historical ops.
+         *
+         * @see #getPackageOpsAt(int)
+         */
+        public int getPackageCount() {
+            if (mHistoricalPackageOps == null) {
+                return 0;
+            }
+            return mHistoricalPackageOps.size();
+        }
+
+        /**
+         * Gets the historical package ops at a given index.
+         *
+         * @param index The index.
+         *
+         * @return The historical package ops at the given index.
+         *
+         * @see #getPackageCount()
+         */
+        public @NonNull HistoricalPackageOps getPackageOpsAt(int index) {
+            if (mHistoricalPackageOps == null) {
+                throw new IndexOutOfBoundsException();
+            }
+            return mHistoricalPackageOps.valueAt(index);
+        }
+
+        /**
+         * Gets the historical package ops for a given package.
+         *
+         * @param packageName The package.
+         *
+         * @return The historical ops for the package.
+         */
+        public @Nullable HistoricalPackageOps getPackageOps(@NonNull String packageName) {
+            if (mHistoricalPackageOps == null) {
+                return null;
+            }
+            return mHistoricalPackageOps.get(packageName);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeInt(mUid);
+            parcel.writeTypedArrayMap(mHistoricalPackageOps, flags);
+        }
+
+        private void accept(@NonNull HistoricalOpsVisitor visitor) {
+            visitor.visitHistoricalUidOps(this);
+            final int packageCount = getPackageCount();
+            for (int i = 0; i < packageCount; i++) {
+                getPackageOpsAt(i).accept(visitor);
+            }
+        }
+
+        private @NonNull HistoricalPackageOps getOrCreateHistoricalPackageOps(
+                @NonNull String packageName) {
+            if (mHistoricalPackageOps == null) {
+                mHistoricalPackageOps = new ArrayMap<>();
+            }
+            HistoricalPackageOps historicalPackageOp = mHistoricalPackageOps.get(packageName);
+            if (historicalPackageOp == null) {
+                historicalPackageOp = new HistoricalPackageOps(packageName);
+                mHistoricalPackageOps.put(packageName, historicalPackageOp);
+            }
+            return historicalPackageOp;
+        }
+
+
+        public static final Creator<HistoricalUidOps> CREATOR = new Creator<HistoricalUidOps>() {
+            @Override
+            public @NonNull HistoricalUidOps createFromParcel(@NonNull Parcel parcel) {
+                return new HistoricalUidOps(parcel);
+            }
+
+            @Override
+            public @NonNull HistoricalUidOps[] newArray(int size) {
+                return new HistoricalUidOps[size];
+            }
+        };
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final HistoricalUidOps other = (HistoricalUidOps) obj;
+            if (mUid != other.mUid) {
+                return false;
+            }
+            if (mHistoricalPackageOps == null) {
+                if (other.mHistoricalPackageOps != null) {
+                    return false;
+                }
+            } else if (!mHistoricalPackageOps.equals(other.mHistoricalPackageOps)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mUid;
+            result = 31 * result + (mHistoricalPackageOps != null
+                    ? mHistoricalPackageOps.hashCode() : 0);
+            return result;
+        }
+    }
+
+    /**
+     * This class represents historical app op information about a package.
      *
      * @hide
      */
     @TestApi
     @SystemApi
     public static final class HistoricalPackageOps implements Parcelable {
-        private final int mUid;
         private final @NonNull String mPackageName;
-        private final @NonNull List<HistoricalOpEntry> mEntries;
+        private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
 
-        /**
-         * @hide
-         */
-        public HistoricalPackageOps(int uid, @NonNull String packageName) {
-            mUid = uid;
+        /** @hide */
+        public HistoricalPackageOps(@NonNull String packageName) {
             mPackageName = packageName;
-            mEntries = new ArrayList<>();
         }
 
-        HistoricalPackageOps(@NonNull Parcel parcel) {
-            mUid = parcel.readInt();
+        private HistoricalPackageOps(@NonNull HistoricalPackageOps other) {
+            mPackageName = other.mPackageName;
+            final int opCount = other.getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOp origOp = other.getOpAt(i);
+                final HistoricalOp cloneOp = new HistoricalOp(origOp);
+                if (mHistoricalOps == null) {
+                    mHistoricalOps = new ArrayMap<>(opCount);
+                }
+                mHistoricalOps.put(cloneOp.getOpName(), cloneOp);
+            }
+        }
+
+        private HistoricalPackageOps(@NonNull Parcel parcel) {
             mPackageName = parcel.readString();
-            mEntries = parcel.createTypedArrayList(HistoricalOpEntry.CREATOR);
+            mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR);
         }
 
-        /**
-         * @hide
-         */
-        public void addEntry(@NonNull HistoricalOpEntry entry) {
-            mEntries.add(entry);
+        private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
+            HistoricalPackageOps splice = null;
+            final int opCount = getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOp origOps = getOpAt(i);
+                final HistoricalOp spliceOps = origOps.splice(fractionToRemove);
+                if (spliceOps != null) {
+                    if (splice == null) {
+                        splice = new HistoricalPackageOps(mPackageName);
+                    }
+                    if (splice.mHistoricalOps == null) {
+                        splice.mHistoricalOps = new ArrayMap<>();
+                    }
+                    splice.mHistoricalOps.put(spliceOps.getOpName(), spliceOps);
+                }
+            }
+            return splice;
+        }
+
+        private void merge(@NonNull HistoricalPackageOps other) {
+            final int opCount = other.getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOp otherOp = other.getOpAt(i);
+                final HistoricalOp thisOp = getOp(otherOp.getOpName());
+                if (thisOp != null) {
+                    thisOp.merge(otherOp);
+                } else {
+                    if (mHistoricalOps == null) {
+                        mHistoricalOps = new ArrayMap<>();
+                    }
+                    mHistoricalOps.put(otherOp.getOpName(), otherOp);
+                }
+            }
+        }
+
+        private void filter(@Nullable String[] opNames, double scaleFactor) {
+            final int opCount = getOpCount();
+            for (int i = opCount - 1; i >= 0; i--) {
+                final HistoricalOp op = mHistoricalOps.valueAt(i);
+                if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) {
+                    mHistoricalOps.removeAt(i);
+                } else {
+                    op.filter(scaleFactor);
+                }
+            }
+        }
+
+        private boolean isEmpty() {
+            final int opCount = getOpCount();
+            for (int i = opCount - 1; i >= 0; i--) {
+                final HistoricalOp op = mHistoricalOps.valueAt(i);
+                if (!op.isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private void increaseAccessCount(int opCode, @UidState int uidState, long increment) {
+            getOrCreateHistoricalOp(opCode).increaseAccessCount(uidState, increment);
+        }
+
+        private void increaseRejectCount(int opCode, @UidState int uidState, long increment) {
+            getOrCreateHistoricalOp(opCode).increaseRejectCount(uidState, increment);
+        }
+
+        private void increaseAccessDuration(int opCode, @UidState int uidState, long increment) {
+            getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, increment);
         }
 
         /**
@@ -2133,36 +2932,33 @@
         }
 
         /**
-         *  Gets the UID which the data represents.
+         * Gets number historical app ops.
          *
-         * @return The UID which the data represents.
+         * @return The number historical app ops.
+         *
+         * @see #getOpAt(int)
          */
-        public int getUid() {
-            return mUid;
+        public int getOpCount() {
+            if (mHistoricalOps == null) {
+                return 0;
+            }
+            return mHistoricalOps.size();
         }
 
         /**
-         * Gets number historical app op entries.
-         *
-         * @return The number historical app op entries.
-         *
-         * @see #getEntryAt(int)
-         */
-        public int getEntryCount() {
-            return mEntries.size();
-        }
-
-        /**
-         * Gets the historical at a given index.
+         * Gets the historical op at a given index.
          *
          * @param index The index to lookup.
          *
-         * @return The entry at the given index.
+         * @return The op at the given index.
          *
-         * @see #getEntryCount()
+         * @see #getOpCount()
          */
-        public @NonNull HistoricalOpEntry getEntryAt(int index) {
-            return mEntries.get(index);
+        public @NonNull HistoricalOp getOpAt(int index) {
+            if (mHistoricalOps == null) {
+                throw new IndexOutOfBoundsException();
+            }
+            return mHistoricalOps.valueAt(index);
         }
 
         /**
@@ -2172,15 +2968,11 @@
          *
          * @return The historical entry for that op name.
          */
-        public @Nullable HistoricalOpEntry getEntry(@NonNull String opName) {
-            final int entryCount = mEntries.size();
-            for (int i = 0; i < entryCount; i++) {
-                final HistoricalOpEntry entry = mEntries.get(i);
-                if (entry.getOp().equals(opName)) {
-                    return entry;
-                }
+        public @Nullable HistoricalOp getOp(@NonNull String opName) {
+            if (mHistoricalOps == null) {
+                return null;
             }
-            return null;
+            return mHistoricalOps.get(opName);
         }
 
         @Override
@@ -2190,9 +2982,29 @@
 
         @Override
         public void writeToParcel(@NonNull Parcel parcel, int flags) {
-            parcel.writeInt(mUid);
             parcel.writeString(mPackageName);
-            parcel.writeTypedList(mEntries, flags);
+            parcel.writeTypedArrayMap(mHistoricalOps, flags);
+        }
+
+        private void accept(@NonNull HistoricalOpsVisitor visitor) {
+            visitor.visitHistoricalPackageOps(this);
+            final int opCount = getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                getOpAt(i).accept(visitor);
+            }
+        }
+
+        private @NonNull HistoricalOp getOrCreateHistoricalOp(int opCode) {
+            if (mHistoricalOps == null) {
+                mHistoricalOps = new ArrayMap<>();
+            }
+            final String opStr = sOpToString[opCode];
+            HistoricalOp op = mHistoricalOps.get(opStr);
+            if (op == null) {
+                op = new HistoricalOp(opCode);
+                mHistoricalOps.put(opStr, op);
+            }
+            return op;
         }
 
         public static final Creator<HistoricalPackageOps> CREATOR =
@@ -2207,49 +3019,177 @@
                 return new HistoricalPackageOps[size];
             }
         };
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final HistoricalPackageOps other = (HistoricalPackageOps) obj;
+            if (!mPackageName.equals(other.mPackageName)) {
+                return false;
+            }
+            if (mHistoricalOps == null) {
+                if (other.mHistoricalOps != null) {
+                    return false;
+                }
+            } else if (!mHistoricalOps.equals(other.mHistoricalOps)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mPackageName != null ? mPackageName.hashCode() : 0;
+            result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0);
+            return result;
+        }
     }
 
     /**
-     * This class represents historical information about an app op. The history
-     * is aggregated information about the op for a certain amount of time such
-     * as the times the op was accessed, the times the op was rejected, the total
-     * duration the app op has been accessed.
+     * This class represents historical information about an app op.
      *
      * @hide
      */
     @TestApi
     @SystemApi
-    public static final class HistoricalOpEntry implements Parcelable {
+    public static final class HistoricalOp implements Parcelable {
         private final int mOp;
-        private final long[] mAccessCount;
-        private final long[] mRejectCount;
-        private final long[] mAccessDuration;
+        private @Nullable long[] mAccessCount;
+        private @Nullable long[] mRejectCount;
+        private @Nullable long[] mAccessDuration;
 
-        /**
-         * @hide
-         */
-        public HistoricalOpEntry(int op) {
+        /** @hide */
+        public HistoricalOp(int op) {
             mOp = op;
             mAccessCount = new long[_NUM_UID_STATE];
             mRejectCount = new long[_NUM_UID_STATE];
             mAccessDuration = new long[_NUM_UID_STATE];
         }
 
-        HistoricalOpEntry(@NonNull Parcel parcel) {
+        private HistoricalOp(@NonNull HistoricalOp other) {
+            mOp = other.mOp;
+            if (other.mAccessCount != null) {
+                System.arraycopy(other.mAccessCount, 0, getOrCreateAccessCount(),
+                        0, other.mAccessCount.length);
+            }
+            if (other.mRejectCount != null) {
+                System.arraycopy(other.mRejectCount, 0, getOrCreateRejectCount(),
+                        0, other.mRejectCount.length);
+            }
+            if (other.mAccessDuration != null) {
+                System.arraycopy(other.mAccessDuration, 0, getOrCreateAccessDuration(),
+                        0, other.mAccessDuration.length);
+            }
+        }
+
+        private HistoricalOp(@NonNull Parcel parcel) {
             mOp = parcel.readInt();
             mAccessCount = parcel.createLongArray();
             mRejectCount = parcel.createLongArray();
             mAccessDuration = parcel.createLongArray();
         }
 
-        /**
-         * @hide
-         */
-        public void addEntry(@UidState int uidState, long accessCount,
-                long rejectCount, long accessDuration) {
-            mAccessCount[uidState] = accessCount;
-            mRejectCount[uidState] = rejectCount;
-            mAccessDuration[uidState] = accessDuration;
+        private void filter(double scaleFactor) {
+            scale(mAccessCount, scaleFactor);
+            scale(mRejectCount, scaleFactor);
+            scale(mAccessDuration, scaleFactor);
+        }
+
+        private boolean isEmpty() {
+            return !hasData(mAccessCount)
+                    && !hasData(mRejectCount)
+                    && !hasData(mAccessDuration);
+        }
+
+        private boolean hasData(@NonNull long[] array) {
+            for (long value : array) {
+                if (value != 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private @Nullable HistoricalOp splice(double fractionToRemove) {
+            HistoricalOp splice = null;
+            if (mAccessCount != null) {
+                for (int i = 0; i < _NUM_UID_STATE; i++) {
+                    final long spliceAccessCount = Math.round(
+                            mAccessCount[i] * fractionToRemove);
+                    if (spliceAccessCount > 0) {
+                        if (splice == null) {
+                            splice = new HistoricalOp(mOp);
+                        }
+                        splice.getOrCreateAccessCount()[i] = spliceAccessCount;
+                        mAccessCount[i] -= spliceAccessCount;
+                    }
+                }
+            }
+
+            if (mRejectCount != null) {
+                for (int i = 0; i < _NUM_UID_STATE; i++) {
+                    final long spliceRejectCount = Math.round(
+                            mRejectCount[i] * fractionToRemove);
+
+                    if (spliceRejectCount > 0) {
+                        if (splice == null) {
+                            splice = new HistoricalOp(mOp);
+                        }
+                        splice.getOrCreateRejectCount()[i] = spliceRejectCount;
+                        mRejectCount[i] -= spliceRejectCount;
+                    }
+                }
+            }
+
+            if (mAccessDuration != null) {
+                for (int i = 0; i < _NUM_UID_STATE; i++) {
+                    final long spliceAccessDuration =  Math.round(
+                            mAccessDuration[i] * fractionToRemove);
+                    if (spliceAccessDuration > 0) {
+                        if (splice == null) {
+                            splice = new HistoricalOp(mOp);
+                        }
+                        splice.getOrCreateAccessDuration()[i] = spliceAccessDuration;
+                        mAccessDuration[i] -= spliceAccessDuration;
+                    }
+                }
+            }
+            return splice;
+        }
+
+        private void merge(@NonNull HistoricalOp other) {
+            if (other.mAccessCount != null) {
+                for (int i = 0; i < _NUM_UID_STATE; i++) {
+                    getOrCreateAccessCount()[i] += other.mAccessCount[i];
+                }
+            }
+            if (other.mRejectCount != null) {
+                for (int i = 0; i < _NUM_UID_STATE; i++) {
+                    getOrCreateRejectCount()[i] += other.mRejectCount[i];
+                }
+            }
+            if (other.mAccessDuration != null) {
+                for (int i = 0; i < _NUM_UID_STATE; i++) {
+                    getOrCreateAccessDuration()[i] += other.mAccessDuration[i];
+                }
+            }
+        }
+
+        private void increaseAccessCount(@UidState int uidState, long increment) {
+            getOrCreateAccessCount()[uidState] += increment;
+        }
+
+        private void increaseRejectCount(@UidState int uidState, long increment) {
+            getOrCreateRejectCount()[uidState] += increment;
+        }
+
+        private void increaseAccessDuration(@UidState int uidState, long increment) {
+            getOrCreateAccessDuration()[uidState] += increment;
         }
 
         /**
@@ -2257,10 +3197,15 @@
          *
          * @return The op name.
          */
-        public @NonNull String getOp() {
+        public @NonNull String getOpName() {
             return sOpToString[mOp];
         }
 
+        /** @hide */
+        public int getOpCode() {
+            return mOp;
+        }
+
         /**
          * Gets the number times the op was accessed (performed) in the foreground.
          *
@@ -2270,6 +3215,9 @@
          * @see #getAccessCount(int)
          */
         public long getForegroundAccessCount() {
+            if (mAccessCount == null) {
+                return 0;
+            }
             return sum(mAccessCount, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1);
         }
 
@@ -2282,6 +3230,9 @@
          * @see #getAccessCount(int)
          */
         public long getBackgroundAccessCount() {
+            if (mAccessCount == null) {
+                return 0;
+            }
             return sum(mAccessCount, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE);
         }
 
@@ -2299,6 +3250,9 @@
          * @see #getBackgroundAccessCount()
          */
         public long getAccessCount(@UidState int uidState) {
+            if (mAccessCount == null) {
+                return 0;
+            }
             return mAccessCount[uidState];
         }
 
@@ -2311,6 +3265,9 @@
          * @see #getRejectCount(int)
          */
         public long getForegroundRejectCount() {
+            if (mRejectCount == null) {
+                return 0;
+            }
             return sum(mRejectCount, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1);
         }
 
@@ -2323,6 +3280,9 @@
          * @see #getRejectCount(int)
          */
         public long getBackgroundRejectCount() {
+            if (mRejectCount == null) {
+                return 0;
+            }
             return sum(mRejectCount, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE);
         }
 
@@ -2340,6 +3300,9 @@
          * @see #getBackgroundRejectCount()
          */
         public long getRejectCount(@UidState int uidState) {
+            if (mRejectCount == null) {
+                return 0;
+            }
             return mRejectCount[uidState];
         }
 
@@ -2352,6 +3315,9 @@
          * @see #getAccessDuration(int)
          */
         public long getForegroundAccessDuration() {
+            if (mAccessDuration == null) {
+                return 0;
+            }
             return sum(mAccessDuration, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1);
         }
 
@@ -2364,6 +3330,9 @@
          * @see #getAccessDuration(int)
          */
         public long getBackgroundAccessDuration() {
+            if (mAccessDuration == null) {
+                return 0;
+            }
             return sum(mAccessDuration, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE);
         }
 
@@ -2381,6 +3350,9 @@
          * @see #getBackgroundAccessDuration()
          */
         public long getAccessDuration(@UidState int uidState) {
+            if (mAccessDuration == null) {
+                return 0;
+            }
             return mAccessDuration[uidState];
         }
 
@@ -2397,34 +3369,29 @@
             parcel.writeLongArray(mAccessDuration);
         }
 
-        @Override
-        public boolean equals(Object other) {
-            if (this == other) {
-                return true;
-            }
-            if (other == null || getClass() != other.getClass()) {
-                return false;
-            }
-            final HistoricalOpEntry otherInstance = (HistoricalOpEntry) other;
-            if (mOp != otherInstance.mOp) {
-                return false;
-            }
-            if (!Arrays.equals(mAccessCount, otherInstance.mAccessCount)) {
-                return false;
-            }
-            if (!Arrays.equals(mRejectCount, otherInstance.mRejectCount)) {
-                return false;
-            }
-            return Arrays.equals(mAccessDuration, otherInstance.mAccessDuration);
+        private void accept(@NonNull HistoricalOpsVisitor visitor) {
+            visitor.visitHistoricalOp(this);
         }
 
-        @Override
-        public int hashCode() {
-            int result = mOp;
-            result = 31 * result + Arrays.hashCode(mAccessCount);
-            result = 31 * result + Arrays.hashCode(mRejectCount);
-            result = 31 * result + Arrays.hashCode(mAccessDuration);
-            return result;
+        private @NonNull long[] getOrCreateAccessCount() {
+            if (mAccessCount == null) {
+                mAccessCount = new long[_NUM_UID_STATE];
+            }
+            return mAccessCount;
+        }
+
+        private @NonNull long[] getOrCreateRejectCount() {
+            if (mRejectCount == null) {
+                mRejectCount = new long[_NUM_UID_STATE];
+            }
+            return mRejectCount;
+        }
+
+        private @NonNull long[] getOrCreateAccessDuration() {
+            if (mAccessDuration == null) {
+                mAccessDuration = new long[_NUM_UID_STATE];
+            }
+            return mAccessDuration;
         }
 
         /**
@@ -2444,17 +3411,62 @@
             return totalCount;
         }
 
-        public static final Creator<HistoricalOpEntry> CREATOR = new Creator<HistoricalOpEntry>() {
+        /**
+         * Multiplies the entries in the array with the passed in scale factor and
+         * rounds the result at up 0.5 boundary.
+         *
+         * @param data The data to scale.
+         * @param scaleFactor The scale factor.
+         */
+        private static void scale(@NonNull long[] data, double scaleFactor) {
+            if (data != null) {
+                for (int i = 0; i < _NUM_UID_STATE; i++) {
+                    data[i] = (long) HistoricalOps.round((double) data[i] * scaleFactor);
+                }
+            }
+        }
+
+        public static final Creator<HistoricalOp> CREATOR = new Creator<HistoricalOp>() {
             @Override
-            public @NonNull HistoricalOpEntry createFromParcel(@NonNull Parcel source) {
-                return new HistoricalOpEntry(source);
+            public @NonNull HistoricalOp createFromParcel(@NonNull Parcel source) {
+                return new HistoricalOp(source);
             }
 
             @Override
-            public @NonNull HistoricalOpEntry[] newArray(int size) {
-                return new HistoricalOpEntry[size];
+            public @NonNull HistoricalOp[] newArray(int size) {
+                return new HistoricalOp[size];
             }
         };
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final HistoricalOp other = (HistoricalOp) obj;
+            if (mOp != other.mOp) {
+                return false;
+            }
+            if (!Arrays.equals(mAccessCount, other.mAccessCount)) {
+                return false;
+            }
+            if (!Arrays.equals(mRejectCount, other.mRejectCount)) {
+                return false;
+            }
+            return Arrays.equals(mAccessDuration, other.mAccessDuration);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mOp;
+            result = 31 * result + Arrays.hashCode(mAccessCount);
+            result = 31 * result + Arrays.hashCode(mRejectCount);
+            result = 31 * result + Arrays.hashCode(mAccessDuration);
+            return result;
+        }
     }
 
     /**
@@ -2520,6 +3532,24 @@
      * @param ops The set of operations you are interested in, or null if you want all of them.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+    public @NonNull List<AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[] ops) {
+        final int opCount = ops.length;
+        final int[] opCodes = new int[opCount];
+        for (int i = 0; i < opCount; i++) {
+            opCodes[i] = sOpStrToOp.get(ops[i]);
+        }
+        final List<AppOpsManager.PackageOps> result = getPackagesForOps(opCodes);
+        return (result != null) ? result : Collections.emptyList();
+    }
+
+    /**
+     * Retrieve current operation state for all applications.
+     *
+     * @param ops The set of operations you are interested in, or null if you want all of them.
+     * @hide
+     */
     @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
     @UnsupportedAppUsage
     public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
@@ -2536,11 +3566,17 @@
      * @param uid The uid of the application of interest.
      * @param packageName The name of the application of interest.
      * @param ops The set of operations you are interested in, or null if you want all of them.
+     *
+     * @deprecated The int op codes are not stable and you should use the string based op
+     * names which are stable and namespaced. Use
+     * {@link #getOpsForPackage(int, String, String...)})}.
+     *
      * @hide
      */
+    @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
-    public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
+    public List<PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
         try {
             return mService.getOpsForPackage(uid, packageName, ops);
         } catch (RemoteException e) {
@@ -2549,7 +3585,49 @@
     }
 
     /**
-     * Retrieve historical app op stats for a package.
+     * Retrieve current operation state for one application. The UID and the
+     * package must match.
+     *
+     * @param uid The uid of the application of interest.
+     * @param packageName The name of the application of interest.
+     * @param ops The set of operations you are interested in, or null if you want all of them.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+    public @NonNull List<AppOpsManager.PackageOps> getOpsForPackage(int uid,
+            @NonNull String packageName, @Nullable String... ops) {
+        int[] opCodes = null;
+        if (ops != null) {
+            opCodes = new int[ops.length];
+            for (int i = 0; i < ops.length; i++) {
+                opCodes[i] = strOpToOp(ops[i]);
+            }
+        }
+        try {
+            final List<PackageOps> result = mService.getOpsForPackage(uid, packageName, opCodes);
+            if (result == null) {
+                return Collections.emptyList();
+            }
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieve historical app op stats for a period.
+     *
+     * <p>Historical data can be obtained
+     * for a specific package by specifying the <code>packageName</code> argument,
+     * for a specific UID if specifying the <code>uid</code> argument, for a
+     * specific package in a UID by specifying the <code>packageName</code>
+     * and the <code>uid</code> arguments, for all packages by passing
+     * {@link android.os.Process#INVALID_UID} and <code>null</code> for the
+     *  <code>uid</code> and <code>packageName</code> arguments, respectively.
+     *  Similarly, you can specify the <code>opNames</code> argument to get
+     *  data only for these ops or <code>null</code> for all ops.
      *
      * @param uid The UID to query for.
      * @param packageName The package to query for.
@@ -2558,10 +3636,12 @@
      *     negative.
      * @param endTimeMillis The end of the interval in milliseconds since
      *     epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after
-     *     {@code beginTimeMillis}.
+     *     {@code beginTimeMillis}. Pass {@link Long#MAX_VALUE} to get the most recent
+     *     history including ops that happen while this call is in flight.
      * @param opNames The ops to query for. Pass {@code null} for all ops.
-     *
-     * @return The historical ops or {@code null} if there are no ops for this package.
+     * @param executor Executor on which to run the callback. If <code>null</code>
+     *     the callback is executed on the default executor running on the main thread.
+     * @param callback Callback on which to deliver the result.
      *
      * @throws IllegalArgumentException If any of the argument contracts is violated.
      *
@@ -2570,47 +3650,78 @@
     @TestApi
     @SystemApi
     @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
-    public @Nullable HistoricalPackageOps getHistoricalPackagesOps(
-            int uid, @NonNull String packageName, @Nullable String[] opNames,
-            long beginTimeMillis, long endTimeMillis) {
+    public void getHistoricalOps(int uid, @Nullable String packageName,
+            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+            @NonNull Executor executor, @NonNull Consumer<HistoricalOps> callback) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
         try {
-            return mService.getHistoricalPackagesOps(uid, packageName, opNames,
-                    beginTimeMillis, endTimeMillis);
+            mService.getHistoricalOps(uid, packageName, opNames, beginTimeMillis, endTimeMillis,
+                    new RemoteCallback((result) -> {
+                final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> callback.accept(ops));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Retrieve historical app op stats for all packages.
+     * Retrieve historical app op stats for a period.
      *
+     * <p>Historical data can be obtained
+     * for a specific package by specifying the <code>packageName</code> argument,
+     * for a specific UID if specifying the <code>uid</code> argument, for a
+     * specific package in a UID by specifying the <code>packageName</code>
+     * and the <code>uid</code> arguments, for all packages by passing
+     * {@link android.os.Process#INVALID_UID} and <code>null</code> for the
+     *  <code>uid</code> and <code>packageName</code> arguments, respectively.
+     *  Similarly, you can specify the <code>opNames</code> argument to get
+     *  data only for these ops or <code>null</code> for all ops.
+     *  <p>
+     *  This method queries only the on disk state and the returned ops are raw,
+     *  which is their times are relative to the history start as opposed to the
+     *  epoch start.
+     *
+     * @param uid The UID to query for.
+     * @param packageName The package to query for.
      * @param beginTimeMillis The beginning of the interval in milliseconds since
-     *     epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be non
-     *     negative.
+     *      history start. History time grows as one goes into the past.
      * @param endTimeMillis The end of the interval in milliseconds since
-     *     epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after
+     *      history start. History time grows as one goes into the past. Must be after
      *     {@code beginTimeMillis}.
      * @param opNames The ops to query for. Pass {@code null} for all ops.
-     *
-     * @return The historical ops or an empty list if there are no ops for any package.
+     * @param executor Executor on which to run the callback. If <code>null</code>
+     *     the callback is executed on the default executor running on the main thread.
+     * @param callback Callback on which to deliver the result.
      *
      * @throws IllegalArgumentException If any of the argument contracts is violated.
      *
      * @hide
      */
     @TestApi
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
-    public @NonNull List<HistoricalPackageOps> getAllHistoricPackagesOps(
-            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis) {
+    public void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
+            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+            @Nullable Executor executor, @NonNull Consumer<HistoricalOps> callback) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
         try {
-            @SuppressWarnings("unchecked")
-            final ParceledListSlice<HistoricalPackageOps> payload =
-                    mService.getAllHistoricalPackagesOps(opNames, beginTimeMillis, endTimeMillis);
-            if (payload != null) {
-                return payload.getList();
-            }
-            return Collections.emptyList();
+            mService.getHistoricalOpsFromDiskRaw(uid, packageName, opNames, beginTimeMillis,
+                    endTimeMillis, new RemoteCallback((result) -> {
+               final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> callback.accept(ops));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2627,7 +3738,7 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
-    public void setUidMode(int code, int uid, int mode) {
+    public void setUidMode(int code, int uid, @Mode int mode) {
         try {
             mService.setUidMode(code, uid, mode);
         } catch (RemoteException e) {
@@ -2648,7 +3759,7 @@
     @SystemApi
     @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
-    public void setUidMode(String appOp, int uid, int mode) {
+    public void setUidMode(String appOp, int uid, @Mode int mode) {
         try {
             mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
         } catch (RemoteException e) {
@@ -2680,7 +3791,7 @@
     /** @hide */
     @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
-    public void setMode(int code, int uid, String packageName, int mode) {
+    public void setMode(int code, int uid, String packageName, @Mode int mode) {
         try {
             mService.setMode(code, uid, packageName, mode);
         } catch (RemoteException e) {
@@ -2701,7 +3812,7 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
-    public void setMode(String op, int uid, String packageName, int mode) {
+    public void setMode(String op, int uid, String packageName, @Mode int mode) {
         try {
             mService.setMode(strOpToOp(op), uid, packageName, mode);
         } catch (RemoteException e) {
@@ -2722,7 +3833,7 @@
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     @UnsupportedAppUsage
-    public void setRestriction(int code, @AttributeUsage int usage, int mode,
+    public void setRestriction(int code, @AttributeUsage int usage, @Mode int mode,
             String[] exceptionPackages) {
         try {
             final int uid = Binder.getCallingUid();
@@ -3507,6 +4618,104 @@
     }
 
     /**
+     * Configures the app ops persistence for testing.
+     *
+     * @param mode The mode in which the historical registry operates.
+     * @param baseSnapshotInterval The base interval on which we would be persisting a snapshot of
+     *   the historical data. The history is recursive where every subsequent step encompasses
+     *   {@code compressionStep} longer interval with {@code compressionStep} distance between
+     *    snapshots.
+     * @param compressionStep The compression step in every iteration.
+     *
+     * @see #HISTORICAL_MODE_DISABLED
+     * @see #HISTORICAL_MODE_ENABLED_ACTIVE
+     * @see #HISTORICAL_MODE_ENABLED_PASSIVE
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void setHistoryParameters(@HistoricalMode int mode, long baseSnapshotInterval,
+            int compressionStep) {
+        try {
+            mService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Offsets the history by the given duration.
+     *
+     * @param offsetMillis The offset duration.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void offsetHistory(long offsetMillis) {
+        try {
+            mService.offsetHistory(offsetMillis);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Adds ops to the history directly. This could be useful for testing especially
+     * when the historical registry operates in {@link #HISTORICAL_MODE_ENABLED_PASSIVE}
+     * mode.
+     *
+     * @param ops The ops to add to the history.
+     *
+     * @see #setHistoryParameters(int, long, int)
+     * @see #HISTORICAL_MODE_ENABLED_PASSIVE
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void addHistoricalOps(@NonNull HistoricalOps ops) {
+        try {
+            mService.addHistoricalOps(ops);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resets the app ops persistence for testing.
+     *
+     * @see #setHistoryParameters(int, long, int)
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void resetHistoryParameters() {
+        try {
+            mService.resetHistoryParameters();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clears all app ops history.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void clearHistory() {
+        try {
+            mService.clearHistory();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns all supported operation names.
      * @hide
      */
@@ -3538,4 +4747,64 @@
         }
         return time;
     }
+
+    /** @hide */
+    public static String uidStateToString(@UidState int uidState) {
+        switch (uidState) {
+            case UID_STATE_PERSISTENT: {
+                return "UID_STATE_PERSISTENT";
+            }
+            case UID_STATE_TOP: {
+                return "UID_STATE_TOP";
+            }
+            case UID_STATE_FOREGROUND_SERVICE: {
+                return "UID_STATE_FOREGROUND_SERVICE";
+            }
+            case UID_STATE_FOREGROUND: {
+                return "UID_STATE_FOREGROUND";
+            }
+            case UID_STATE_BACKGROUND: {
+                return "UID_STATE_BACKGROUND";
+            }
+            case UID_STATE_CACHED: {
+                return "UID_STATE_CACHED";
+            }
+            default: {
+                return "UNKNOWN";
+            }
+        }
+    }
+
+    /** @hide */
+    public static int parseHistoricalMode(@NonNull String mode) {
+        switch (mode) {
+            case "HISTORICAL_MODE_ENABLED_ACTIVE": {
+                return HISTORICAL_MODE_ENABLED_ACTIVE;
+            }
+            case "HISTORICAL_MODE_ENABLED_PASSIVE": {
+                return HISTORICAL_MODE_ENABLED_PASSIVE;
+            }
+            default: {
+                return HISTORICAL_MODE_DISABLED;
+            }
+        }
+    }
+
+    /** @hide */
+    public static String historicalModeToString(@HistoricalMode int mode) {
+        switch (mode) {
+            case HISTORICAL_MODE_DISABLED: {
+                return "HISTORICAL_MODE_DISABLED";
+            }
+            case HISTORICAL_MODE_ENABLED_ACTIVE: {
+                return "HISTORICAL_MODE_ENABLED_ACTIVE";
+            }
+            case HISTORICAL_MODE_ENABLED_PASSIVE: {
+                return "HISTORICAL_MODE_ENABLED_PASSIVE";
+            }
+            default: {
+                return "UNKNOWN";
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index b5295d1..07dbb6b 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -124,6 +124,10 @@
     /** Restart the activity after it was stopped. */
     public abstract void performRestartActivity(IBinder token, boolean start);
 
+    /** Set pending activity configuration in case it will be updated by other transaction item. */
+    public abstract void updatePendingActivityConfiguration(IBinder activityToken,
+            Configuration overrideConfig);
+
     /** Deliver activity (override) configuration change. */
     public abstract void handleActivityConfigurationChanged(IBinder activityToken,
             Configuration overrideConfig, int displayId);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9ddf4bd..d827f6c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -21,8 +21,11 @@
 import android.app.ContextImpl.ServiceInitializationState;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.IDevicePolicyManager;
+import android.app.contentsuggestions.ContentSuggestionsManager;
+import android.app.contentsuggestions.IContentSuggestionsManager;
 import android.app.job.IJobScheduler;
 import android.app.job.JobScheduler;
+import android.app.prediction.AppPredictionManager;
 import android.app.role.RoleManager;
 import android.app.slice.SliceManager;
 import android.app.timedetector.TimeDetector;
@@ -95,8 +98,10 @@
 import android.net.EthernetManager;
 import android.net.IConnectivityManager;
 import android.net.IEthernetManager;
+import android.net.IIpMemoryStore;
 import android.net.IIpSecService;
 import android.net.INetworkPolicyManager;
+import android.net.IpMemoryStore;
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
@@ -120,6 +125,7 @@
 import android.nfc.NfcManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.BugreportManager;
 import android.os.Build;
 import android.os.DeviceIdleManager;
 import android.os.DropBoxManager;
@@ -127,6 +133,7 @@
 import android.os.IBatteryPropertiesRegistrar;
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
+import android.os.IDumpstate;
 import android.os.IHardwarePropertiesManager;
 import android.os.IPowerManager;
 import android.os.IRecoverySystem;
@@ -321,10 +328,21 @@
 
         registerService(Context.NETWORK_STACK_SERVICE, NetworkStack.class,
                 new StaticServiceFetcher<NetworkStack>() {
-                @Override
-                public NetworkStack createService() {
-                    return new NetworkStack();
-                }});
+                    @Override
+                    public NetworkStack createService() {
+                        return new NetworkStack();
+                    }});
+
+        registerService(Context.IP_MEMORY_STORE_SERVICE, IpMemoryStore.class,
+                new CachedServiceFetcher<IpMemoryStore>() {
+                    @Override
+                    public IpMemoryStore createService(final ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(
+                                Context.IP_MEMORY_STORE_SERVICE);
+                        IIpMemoryStore service = IIpMemoryStore.Stub.asInterface(b);
+                        return new IpMemoryStore(ctx, service);
+                    }});
 
         registerService(Context.IPSEC_SERVICE, IpSecManager.class,
                 new CachedServiceFetcher<IpSecManager>() {
@@ -1069,6 +1087,16 @@
                 return new IncidentManager(ctx);
             }});
 
+        registerService(Context.BUGREPORT_SERVICE, BugreportManager.class,
+                new CachedServiceFetcher<BugreportManager>() {
+                    @Override
+                    public BugreportManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(Context.BUGREPORT_SERVICE);
+                        return new BugreportManager(ctx.getOuterContext(),
+                                IDumpstate.Stub.asInterface(b));
+                    }});
+
         registerService(Context.AUTOFILL_MANAGER_SERVICE, AutofillManager.class,
                 new CachedServiceFetcher<AutofillManager>() {
             @Override
@@ -1095,6 +1123,29 @@
                 return null;
             }});
 
+        registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
+                new CachedServiceFetcher<AppPredictionManager>() {
+            @Override
+            public AppPredictionManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                return new AppPredictionManager(ctx);
+            }
+        });
+
+        registerService(Context.CONTENT_SUGGESTIONS_SERVICE,
+                ContentSuggestionsManager.class,
+                new CachedServiceFetcher<ContentSuggestionsManager>() {
+                    @Override
+                    public ContentSuggestionsManager createService(ContextImpl ctx) {
+                        // No throw as this is an optional service
+                        IBinder b = ServiceManager.getService(
+                                Context.CONTENT_SUGGESTIONS_SERVICE);
+                        IContentSuggestionsManager service =
+                                IContentSuggestionsManager.Stub.asInterface(b);
+                        return new ContentSuggestionsManager(service);
+                    }
+                });
+
         registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() {
             @Override
             public VrManager createService(ContextImpl ctx) throws ServiceNotFoundException {
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index ace814a..38a98d3c8 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -56,6 +56,7 @@
      * eg. A launcher may set its text color to black if this flag is specified.
      * @hide
      */
+    @SystemApi
     public static final int HINT_SUPPORTS_DARK_TEXT = 1 << 0;
 
     /**
@@ -64,6 +65,7 @@
      * eg. A launcher may set its drawer color to black if this flag is specified.
      * @hide
      */
+    @SystemApi
     public static final int HINT_SUPPORTS_DARK_THEME = 1 << 1;
 
     /**
@@ -234,7 +236,7 @@
      * @see WallpaperColors#fromDrawable(Drawable)
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor,
             @Nullable Color tertiaryColor, int colorHints) {
 
@@ -349,7 +351,7 @@
      * @return True if dark text is supported.
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public int getColorHints() {
         return mColorHints;
     }
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 2990b57..e0a15a5 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityThread.isSystem;
 import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
 import static android.app.WindowConfigurationProto.APP_BOUNDS;
+import static android.app.WindowConfigurationProto.BOUNDS;
 import static android.app.WindowConfigurationProto.WINDOWING_MODE;
 import static android.view.Surface.rotationToString;
 
@@ -585,6 +586,9 @@
         }
         protoOutputStream.write(WINDOWING_MODE, mWindowingMode);
         protoOutputStream.write(ACTIVITY_TYPE, mActivityType);
+        if (mBounds != null) {
+            mBounds.writeToProto(protoOutputStream, BOUNDS);
+        }
         protoOutputStream.end(token);
     }
 
@@ -606,6 +610,10 @@
                         mAppBounds = new Rect();
                         mAppBounds.readFromProto(proto, APP_BOUNDS);
                         break;
+                    case (int) BOUNDS:
+                        mBounds = new Rect();
+                        mBounds.readFromProto(proto, BOUNDS);
+                        break;
                     case (int) WINDOWING_MODE:
                         mWindowingMode = proto.readInt(WINDOWING_MODE);
                         break;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7da67d9..9247486 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6815,8 +6815,12 @@
     }
 
     /**
-     * Returns the list of input methods permitted by the device or profiles
-     * owners of the current user.  (*Not* calling user, due to a limitation in InputMethodManager.)
+     * Returns the list of input methods permitted by the device or profiles owners.
+     *
+     * <p>On {@link android.os.Build.VERSION_CODES#Q} and later devices, this method returns the
+     * result for the calling user.</p>
+     *
+     * <p>On Android P and prior devices, this method returns the result for the current user.</p>
      *
      * <p>Null means all input methods are allowed, if a non-null list is returned
      * it will contain the intersection of the permitted lists for any device or profile
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index cc4d4b1a..7d03f00 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1699,7 +1699,8 @@
 
         @Override
         public void setVisibility(int visibility) {
-            mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_VISIBILITY_MASK) | visibility;
+            mNode.mFlags = (mNode.mFlags & ~ViewNode.FLAGS_VISIBILITY_MASK)
+                    | (visibility & ViewNode.FLAGS_VISIBILITY_MASK);
         }
 
         @Override
diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl b/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl
new file mode 100644
index 0000000..a66413b
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app.contentsuggestions;
+
+parcelable ClassificationsRequest;
diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.java b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
new file mode 100644
index 0000000..3f71518
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
@@ -0,0 +1,113 @@
+/*
+ * 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 android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ClassificationsRequest implements Parcelable {
+    @NonNull
+    private final List<ContentSelection> mSelections;
+    @Nullable
+    private final Bundle mExtras;
+
+    private ClassificationsRequest(@NonNull List<ContentSelection> selections,
+            @Nullable Bundle extras) {
+        mSelections = selections;
+        mExtras = extras;
+    }
+
+    /**
+     * Return request selections.
+     */
+    public List<ContentSelection> getSelections() {
+        return mSelections;
+    }
+
+    /**
+     * Return the request extras.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedList(mSelections);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final Creator<ClassificationsRequest> CREATOR =
+            new Creator<ClassificationsRequest>() {
+        @Override
+        public ClassificationsRequest createFromParcel(Parcel source) {
+            return new ClassificationsRequest(
+                    source.createTypedArrayList(ContentSelection.CREATOR),
+                    source.readBundle());
+        }
+
+        @Override
+        public ClassificationsRequest[] newArray(int size) {
+            return new ClassificationsRequest[size];
+        }
+    };
+
+    /**
+     * A builder for classifications request events.
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+
+        private final List<ContentSelection> mSelections;
+        private Bundle mExtras;
+
+        public Builder(@NonNull List<ContentSelection> selections) {
+            mSelections = selections;
+        }
+
+        /**
+         * Sets the request extras.
+         */
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds a new request instance.
+         */
+        public ClassificationsRequest build() {
+            return new ClassificationsRequest(mSelections, mExtras);
+        }
+    }
+}
diff --git a/core/java/android/app/contentsuggestions/ContentClassification.aidl b/core/java/android/app/contentsuggestions/ContentClassification.aidl
new file mode 100644
index 0000000..f67b2c8
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentClassification.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app.contentsuggestions;
+
+parcelable ContentClassification;
diff --git a/core/java/android/app/contentsuggestions/ContentClassification.java b/core/java/android/app/contentsuggestions/ContentClassification.java
new file mode 100644
index 0000000..f18520f
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentClassification.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ContentClassification implements Parcelable {
+    @NonNull
+    private final String mClassificationId;
+    @NonNull
+    private final Bundle mExtras;
+
+    public ContentClassification(@NonNull String classificationId, @NonNull Bundle extras) {
+        mClassificationId = classificationId;
+        mExtras = extras;
+    }
+
+    /**
+     * Return the classification id.
+     */
+    public String getId() {
+        return mClassificationId;
+    }
+
+    /**
+     * Return the classification extras.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mClassificationId);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final Creator<ContentClassification> CREATOR =
+            new Creator<ContentClassification>() {
+        @Override
+        public ContentClassification createFromParcel(Parcel source) {
+            return new ContentClassification(
+                    source.readString(),
+                    source.readBundle());
+        }
+
+        @Override
+        public ContentClassification[] newArray(int size) {
+            return new ContentClassification[size];
+        }
+    };
+}
diff --git a/core/java/android/app/contentsuggestions/ContentSelection.aidl b/core/java/android/app/contentsuggestions/ContentSelection.aidl
new file mode 100644
index 0000000..e626d69
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentSelection.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app.contentsuggestions;
+
+parcelable ContentSelection;
diff --git a/core/java/android/app/contentsuggestions/ContentSelection.java b/core/java/android/app/contentsuggestions/ContentSelection.java
new file mode 100644
index 0000000..a8917f7
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentSelection.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ContentSelection implements Parcelable {
+    @NonNull
+    private final String mSelectionId;
+    @NonNull
+    private final Bundle mExtras;
+
+    public ContentSelection(@NonNull String selectionId, @NonNull Bundle extras) {
+        mSelectionId = selectionId;
+        mExtras = extras;
+    }
+
+    /**
+     * Return the selection id.
+     */
+    public String getId() {
+        return mSelectionId;
+    }
+
+    /**
+     * Return the selection extras.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mSelectionId);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final Creator<ContentSelection> CREATOR =
+            new Creator<ContentSelection>() {
+        @Override
+        public ContentSelection createFromParcel(Parcel source) {
+            return new ContentSelection(
+                    source.readString(),
+                    source.readBundle());
+        }
+
+        @Override
+        public ContentSelection[] newArray(int size) {
+            return new ContentSelection[size];
+        }
+    };
+}
diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
new file mode 100644
index 0000000..b4d8977
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java
@@ -0,0 +1,230 @@
+/*
+ * 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 android.app.contentsuggestions;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * When provided with content from an app, can suggest selections and classifications of that
+ * content.
+ *
+ * <p>The content is mainly a snapshot of a running task, the selections will be text and image
+ * selections with that image content. These mSelections can then be classified to find actions and
+ * entities on those selections.
+ *
+ * <p>Only accessible to blessed components such as Overview.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ContentSuggestionsManager {
+    private static final String TAG = ContentSuggestionsManager.class.getSimpleName();
+
+    @Nullable
+    private final IContentSuggestionsManager mService;
+
+    /** @hide */
+    public ContentSuggestionsManager(@Nullable IContentSuggestionsManager service) {
+        mService = service;
+    }
+
+    /**
+     * Hints to the system that a new context image for the provided task should be sent to the
+     * system content suggestions service.
+     *
+     * @param taskId of the task to snapshot.
+     * @param imageContextRequestExtras sent with with request to provide implementation specific
+     *                                  extra information.
+     */
+    public void provideContextImage(int taskId, @NonNull Bundle imageContextRequestExtras) {
+        if (mService == null) {
+            Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured");
+            return;
+        }
+
+        try {
+            mService.provideContextImage(taskId, imageContextRequestExtras);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Suggest content selections, based on the provided task id and optional
+     * location on screen provided in the request. Called after provideContextImage().
+     * The result can be passed to
+     * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)}
+     *  to classify actions and entities on these selections.
+     *
+     * @param request containing the task and point location.
+     * @param callbackExecutor to execute the provided callback on.
+     * @param callback to receive the selections.
+     */
+    public void suggestContentSelections(
+            @NonNull SelectionsRequest request,
+            @NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull SelectionsCallback callback) {
+        if (mService == null) {
+            Log.e(TAG,
+                    "suggestContentSelections called, but no ContentSuggestionsManager configured");
+            return;
+        }
+
+        try {
+            mService.suggestContentSelections(
+                    request, new SelectionsCallbackWrapper(callback, callbackExecutor));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Classify actions and entities in content selections, as returned from
+     * suggestContentSelections. Note these selections may be modified by the
+     * caller before being passed here.
+     *
+     * @param request containing the selections to classify.
+     * @param callbackExecutor to execute the provided callback on.
+     * @param callback to receive the classifications.
+     */
+    public void classifyContentSelections(
+            @NonNull ClassificationsRequest request,
+            @NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull ClassificationsCallback callback) {
+        if (mService == null) {
+            Log.e(TAG, "classifyContentSelections called, "
+                    + "but no ContentSuggestionsManager configured");
+            return;
+        }
+
+        try {
+            mService.classifyContentSelections(
+                    request, new ClassificationsCallbackWrapper(callback, callbackExecutor));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Report telemetry for interaction with suggestions / classifications.
+     *
+     * @param requestId the id for the associated interaction
+     * @param interaction to report back to the system content suggestions service.
+     */
+    public void notifyInteraction(@NonNull String requestId, @NonNull Bundle interaction) {
+        if (mService == null) {
+            Log.e(TAG, "notifyInteraction called, but no ContentSuggestionsManager configured");
+            return;
+        }
+
+        try {
+            mService.notifyInteraction(requestId, interaction);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Callback to receive content selections from
+     *  {@link #suggestContentSelections(SelectionsRequest, Executor, SelectionsCallback)}.
+     */
+    public interface SelectionsCallback {
+        /**
+         * Async callback called when the content suggestions service has selections available.
+         * These can be modified and sent back to the manager for classification. The contents of
+         * the selection is implementation dependent.
+         *
+         * @param statusCode as defined by the implementation of content suggestions service.
+         * @param selections not {@code null}, but can be size {@code 0}.
+         */
+        void onContentSelectionsAvailable(
+                int statusCode, @NonNull List<ContentSelection> selections);
+    }
+
+    /**
+     * Callback to receive classifications from
+     * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)}
+     */
+    public interface ClassificationsCallback {
+        /**
+         * Async callback called when the content suggestions service has classified selections. The
+         * contents of the classification is implementation dependent.
+         *
+         * @param statusCode as defined by the implementation of content suggestions service.
+         * @param classifications not {@code null}, but can be size {@code 0}.
+         */
+        void onContentClassificationsAvailable(int statusCode,
+                @NonNull List<ContentClassification> classifications);
+    }
+
+    private static class SelectionsCallbackWrapper extends ISelectionsCallback.Stub {
+        private final SelectionsCallback mCallback;
+        private final Executor mExecutor;
+
+        SelectionsCallbackWrapper(
+                @NonNull SelectionsCallback callback, @NonNull Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onContentSelectionsAvailable(
+                int statusCode, List<ContentSelection> selections) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() ->
+                        mCallback.onContentSelectionsAvailable(statusCode, selections));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private static final class ClassificationsCallbackWrapper extends
+            IClassificationsCallback.Stub {
+        private final ClassificationsCallback mCallback;
+        private final Executor mExecutor;
+
+        ClassificationsCallbackWrapper(@NonNull ClassificationsCallback callback,
+                @NonNull Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onContentClassificationsAvailable(
+                int statusCode, List<ContentClassification> classifications) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() ->
+                        mCallback.onContentClassificationsAvailable(statusCode, classifications));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl b/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl
new file mode 100644
index 0000000..69f5d55
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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 android.app.contentsuggestions;
+
+import android.app.contentsuggestions.ContentClassification;
+
+/** @hide */
+oneway interface IClassificationsCallback {
+    void onContentClassificationsAvailable(
+            int statusCode, in List<ContentClassification> classifications);
+}
diff --git a/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl
new file mode 100644
index 0000000..24f5ad8
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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 android.app.contentsuggestions;
+
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IContentSuggestionsManager {
+    void provideContextImage(
+            int taskId,
+            in Bundle imageContextRequestExtras);
+    void suggestContentSelections(
+            in SelectionsRequest request,
+            in ISelectionsCallback callback);
+    void classifyContentSelections(
+            in ClassificationsRequest request,
+            in IClassificationsCallback callback);
+    void notifyInteraction(in String requestId, in Bundle interaction);
+}
diff --git a/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl b/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl
new file mode 100644
index 0000000..1952583
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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 android.app.contentsuggestions;
+
+import android.app.contentsuggestions.ContentSelection;
+import android.os.Bundle;
+
+/** @hide */
+oneway interface ISelectionsCallback {
+    void onContentSelectionsAvailable(int statusCode, in List<ContentSelection> selections);
+}
diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.aidl b/core/java/android/app/contentsuggestions/SelectionsRequest.aidl
new file mode 100644
index 0000000..f5ce7c3
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/SelectionsRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app.contentsuggestions;
+
+parcelable SelectionsRequest;
diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.java b/core/java/android/app/contentsuggestions/SelectionsRequest.java
new file mode 100644
index 0000000..16f3e6b
--- /dev/null
+++ b/core/java/android/app/contentsuggestions/SelectionsRequest.java
@@ -0,0 +1,129 @@
+/*
+ * 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 android.app.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class SelectionsRequest implements Parcelable {
+    private final int mTaskId;
+    @Nullable
+    private final Point mInterestPoint;
+    @Nullable
+    private final Bundle mExtras;
+
+    private SelectionsRequest(int taskId, @Nullable Point interestPoint, @Nullable Bundle extras) {
+        mTaskId = taskId;
+        mInterestPoint = interestPoint;
+        mExtras = extras;
+    }
+
+    /**
+     * Return the request task id.
+     */
+    public int getTaskId() {
+        return mTaskId;
+    }
+
+    /**
+     * Return the request point of interest.
+     */
+    public Point getInterestPoint() {
+        return mInterestPoint;
+    }
+
+    /**
+     * Return the request extras.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mTaskId);
+        dest.writeTypedObject(mInterestPoint, flags);
+        dest.writeBundle(mExtras);
+    }
+
+    public static final Creator<SelectionsRequest> CREATOR =
+            new Creator<SelectionsRequest>() {
+        @Override
+        public SelectionsRequest createFromParcel(Parcel source) {
+            return new SelectionsRequest(
+                    source.readInt(), source.readTypedObject(Point.CREATOR), source.readBundle());
+        }
+
+        @Override
+        public SelectionsRequest[] newArray(int size) {
+            return new SelectionsRequest[size];
+        }
+    };
+
+    /**
+     * A builder for selections requests events.
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+
+        private final int mTaskId;
+        private Point mInterestPoint;
+        private Bundle mExtras;
+
+        public Builder(int taskId) {
+            mTaskId = taskId;
+        }
+
+        /**
+         * Sets the request extras.
+         */
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Sets the request interest point.
+         */
+        public Builder setInterestPoint(@NonNull Point interestPoint) {
+            mInterestPoint = interestPoint;
+            return this;
+        }
+
+        /**
+         * Builds a new request instance.
+         */
+        public SelectionsRequest build() {
+            return new SelectionsRequest(mTaskId, mInterestPoint, mExtras);
+        }
+    }
+}
diff --git a/core/java/android/app/prediction/AppPredictionContext.aidl b/core/java/android/app/prediction/AppPredictionContext.aidl
new file mode 100644
index 0000000..5767bf4
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionContext.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app.prediction;
+
+parcelable AppPredictionContext;
diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java
new file mode 100644
index 0000000..87ccb66
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionContext.java
@@ -0,0 +1,158 @@
+/*
+ * 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 android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * TODO(b/111701043): Add java docs
+ *
+ * @hide
+ */
+@SystemApi
+public final class AppPredictionContext implements Parcelable {
+
+    private final int mPredictedTargetCount;
+    @NonNull
+    private final String mUiSurface;
+    @NonNull
+    private final String mPackageName;
+    @Nullable
+    private final Bundle mExtras;
+
+    private AppPredictionContext(@NonNull String uiSurface, int numPredictedTargets,
+            @NonNull String packageName, @Nullable Bundle extras) {
+        mUiSurface = uiSurface;
+        mPredictedTargetCount = numPredictedTargets;
+        mPackageName = packageName;
+        mExtras = extras;
+    }
+
+    private AppPredictionContext(Parcel parcel) {
+        mUiSurface = parcel.readString();
+        mPredictedTargetCount = parcel.readInt();
+        mPackageName = parcel.readString();
+        mExtras = parcel.readBundle();
+    }
+
+    public String getUiSurface() {
+        return mUiSurface;
+    }
+
+    public int getPredictedTargetCount() {
+        return mPredictedTargetCount;
+    }
+
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    @Nullable
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mUiSurface);
+        dest.writeInt(mPredictedTargetCount);
+        dest.writeString(mPackageName);
+        dest.writeBundle(mExtras);
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    public static final Parcelable.Creator<AppPredictionContext> CREATOR =
+            new Parcelable.Creator<AppPredictionContext>() {
+                public AppPredictionContext createFromParcel(Parcel parcel) {
+                    return new AppPredictionContext(parcel);
+                }
+
+                public AppPredictionContext[] newArray(int size) {
+                    return new AppPredictionContext[size];
+                }
+            };
+
+    /**
+     * A builder for app prediction contexts.
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+
+        @NonNull
+        private final String mPackageName;
+
+        private int mPredictedTargetCount;
+        @Nullable
+        private String mUiSurface;
+        @Nullable
+        private Bundle mExtras;
+
+        /**
+         * @hide
+         */
+        public Builder(@NonNull Context context) {
+            mPackageName = context.getPackageName();
+        }
+
+
+        /**
+         * Sets the number of prediction targets as a hint.
+         */
+        public Builder setPredictedTargetCount(int predictedTargetCount) {
+            mPredictedTargetCount = predictedTargetCount;
+            return this;
+        }
+
+        /**
+         * Sets the UI surface.
+         */
+        public Builder setUiSurface(@Nullable String uiSurface) {
+            mUiSurface = uiSurface;
+            return this;
+        }
+
+        /**
+         * Sets the extras.
+         */
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds a new context instance.
+         */
+        public AppPredictionContext build() {
+            return new AppPredictionContext(mUiSurface, mPredictedTargetCount, mPackageName,
+                    mExtras);
+        }
+    }
+}
diff --git a/core/java/android/app/prediction/AppPredictionManager.java b/core/java/android/app/prediction/AppPredictionManager.java
new file mode 100644
index 0000000..f8578d4
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionManager.java
@@ -0,0 +1,47 @@
+/*
+ * 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 android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * TODO (b/111701043) : Add java doc
+ * @hide
+ */
+@SystemApi
+public final class AppPredictionManager {
+
+    private final Context mContext;
+
+    /**
+     * @hide
+     */
+    public AppPredictionManager(Context context) {
+        mContext = Preconditions.checkNotNull(context);
+    }
+
+    /**
+     * Creates a new app prediction session.
+     */
+    public AppPredictor createAppPredictionSession(
+            @NonNull AppPredictionContext predictionContext) {
+        return new AppPredictor(mContext, predictionContext);
+    }
+}
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.aidl b/core/java/android/app/prediction/AppPredictionSessionId.aidl
new file mode 100644
index 0000000..e829526
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionSessionId.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app.prediction;
+
+parcelable AppPredictionSessionId;
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java
new file mode 100644
index 0000000..1d7308e
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictionSessionId.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * TODO (b/111701043) : Add java doc
+ *
+ * @hide
+ */
+@SystemApi
+public final class AppPredictionSessionId implements Parcelable {
+
+    private final String mId;
+
+    /**
+     * @hide
+     */
+    public AppPredictionSessionId(@NonNull String id) {
+        mId = id;
+    }
+
+    private AppPredictionSessionId(Parcel p) {
+        mId = p.readString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+        AppPredictionSessionId other = (AppPredictionSessionId) o;
+        return mId.equals(other.mId);
+    }
+
+    @Override
+    public @NonNull String toString() {
+        return mId;
+    }
+
+    @Override
+    public int hashCode() {
+        // Ensure that the id has a consistent hash
+        return mId.hashCode();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    public static final Parcelable.Creator<AppPredictionSessionId> CREATOR =
+            new Parcelable.Creator<AppPredictionSessionId>() {
+                public AppPredictionSessionId createFromParcel(Parcel parcel) {
+                    return new AppPredictionSessionId(parcel);
+                }
+
+                public AppPredictionSessionId[] newArray(int size) {
+                    return new AppPredictionSessionId[size];
+                }
+            };
+}
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
new file mode 100644
index 0000000..2ddbd08
--- /dev/null
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -0,0 +1,257 @@
+/*
+ * 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 android.app.prediction;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.prediction.IPredictionCallback.Stub;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * TODO (b/111701043) : Add java doc
+ *
+ * <p>
+ * Usage: <pre> {@code
+ *
+ * class MyActivity {
+ *    private AppPredictor mClient
+ *
+ *    void onCreate() {
+ *         mClient = new AppPredictor(...)
+ *    }
+ *
+ *    void onStart() {
+ *        mClient.requestPredictionUpdate();
+ *    }
+ *
+ *    void onDestroy() {
+ *        mClient.close();
+ *    }
+ *
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class AppPredictor {
+
+    private static final String TAG = AppPredictor.class.getSimpleName();
+
+
+    private final IPredictionManager mPredictionManager;
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+    private final AppPredictionSessionId mSessionId;
+    private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
+
+    /**
+     * Creates a new Prediction client.
+     * <p>
+     * The caller should call {@link AppPredictor#destroy()} to dispose the client once it
+     * no longer used.
+     *
+     * @param predictionContext The prediction context
+     */
+    AppPredictor(@NonNull Context context, @NonNull AppPredictionContext predictionContext) {
+        IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE);
+        mPredictionManager = IPredictionManager.Stub.asInterface(b);
+        mSessionId = new AppPredictionSessionId(
+                context.getPackageName() + ":" + UUID.randomUUID().toString());
+        try {
+            mPredictionManager.createPredictionSession(predictionContext, mSessionId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to create predictor", e);
+            return;
+        }
+
+        mCloseGuard.open("close");
+    }
+
+    /**
+     * Notifies the prediction service of an app target event.
+     */
+    public void notifyAppTargetEvent(@NonNull AppTargetEvent event) {
+        try {
+            mPredictionManager.notifyAppTargetEvent(mSessionId, event);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to notify app target event", e);
+        }
+    }
+
+    /**
+     * Notifies the prediction service when the targets in a launch location are shown to the user.
+     */
+    public void notifyLocationShown(@NonNull String launchLocation,
+            @NonNull List<AppTargetId> targetIds) {
+        try {
+            mPredictionManager.notifyLocationShown(mSessionId, launchLocation,
+                    new ParceledListSlice<>(targetIds));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to notify location shown event", e);
+        }
+    }
+
+    /**
+     * Requests the prediction service provide continuous updates of App predictions via the
+     * provided callback, until the given callback is unregistered.
+     *
+     * @see Callback#onTargetsAvailable(List)
+     */
+    public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull AppPredictor.Callback callback) {
+        if (mRegisteredCallbacks.containsKey(callback)) {
+            // Skip if this callback is already registered
+            return;
+        }
+        try {
+            final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+                    callback::onTargetsAvailable);
+            mPredictionManager.registerPredictionUpdates(mSessionId, callbackWrapper);
+            mRegisteredCallbacks.put(callback, callbackWrapper);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register for prediction updates", e);
+        }
+    }
+
+    /**
+     * Requests the prediction service to stop providing continuous updates to the provided
+     * callback until the callback is re-registered.
+     */
+    public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) {
+        if (!mRegisteredCallbacks.containsKey(callback)) {
+            // Skip if this callback was never registered
+            return;
+        }
+        try {
+            final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+            mPredictionManager.unregisterPredictionUpdates(mSessionId, callbackWrapper);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unregister for prediction updates", e);
+        }
+    }
+
+    /**
+     * Requests the prediction service to dispatch a new set of App predictions via the provided
+     * callback.
+     *
+     * @see Callback#onTargetsAvailable(List)
+     */
+    public void requestPredictionUpdate() {
+        try {
+            mPredictionManager.requestPredictionUpdate(mSessionId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to request prediction update", e);
+        }
+    }
+
+    /**
+     * Returns a new list of AppTargets sorted based on prediction rank or {@code null} if the
+     * ranker is not available.
+     */
+    @Nullable
+    public void sortTargets(@NonNull List<AppTarget> targets,
+            @NonNull Executor callbackExecutor, @NonNull Consumer<List<AppTarget>> callback) {
+        try {
+            mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets),
+                    new CallbackWrapper(callbackExecutor, callback));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to sort targets", e);
+        }
+    }
+
+    /**
+     * Destroys the client and unregisters the callback. Any method on this class after this call
+     * with throw {@link IllegalStateException}.
+     *
+     * TODO(b/111701043): Add state check in other methods.
+     */
+    public void destroy() {
+        if (!mIsClosed.getAndSet(true)) {
+            mCloseGuard.close();
+
+            // Do destroy;
+            try {
+                mPredictionManager.onDestroyPredictionSession(mSessionId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to notify app target event", e);
+            }
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            destroy();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Callback for receiving prediction updates.
+     */
+    public interface Callback {
+
+        /**
+         * Called when a new set of predicted app targets are available.
+         * @param targets Sorted list of predicted targets
+         */
+        void onTargetsAvailable(@NonNull List<AppTarget> targets);
+    }
+
+    static class CallbackWrapper extends Stub {
+
+        private final Consumer<List<AppTarget>> mCallback;
+        private final Executor mExecutor;
+
+        CallbackWrapper(@NonNull Executor callbackExecutor,
+                @NonNull Consumer<List<AppTarget>> callback) {
+            mCallback = callback;
+            mExecutor = callbackExecutor;
+        }
+
+        @Override
+        public void onResult(ParceledListSlice result) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.accept(result.getList()));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/prediction/AppTarget.aidl b/core/java/android/app/prediction/AppTarget.aidl
new file mode 100644
index 0000000..e4e2bc2
--- /dev/null
+++ b/core/java/android/app/prediction/AppTarget.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app.prediction;
+
+parcelable AppTarget;
diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java
new file mode 100644
index 0000000..99c1c44
--- /dev/null
+++ b/core/java/android/app/prediction/AppTarget.java
@@ -0,0 +1,166 @@
+/*
+ * 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 android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.pm.ShortcutInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A representation of a launchable target.
+ * @hide
+ */
+@SystemApi
+public final class AppTarget implements Parcelable {
+
+    private final AppTargetId mId;
+    private final String mPackageName;
+    private final String mClassName;
+    private final UserHandle mUser;
+
+    private final ShortcutInfo mShortcutInfo;
+
+    private int mRank;
+
+    /**
+     * @hide
+     */
+    public AppTarget(@NonNull AppTargetId id, @NonNull String packageName,
+            @Nullable String className, @NonNull UserHandle user) {
+        mId = id;
+        mShortcutInfo = null;
+
+        mPackageName = Preconditions.checkNotNull(packageName);
+        mClassName = className;
+        mUser = Preconditions.checkNotNull(user);
+    }
+
+    /**
+     * @hide
+     */
+    public AppTarget(@NonNull AppTargetId id, @NonNull ShortcutInfo shortcutInfo) {
+        mId = id;
+        mShortcutInfo = Preconditions.checkNotNull(shortcutInfo);
+
+        mPackageName = mShortcutInfo.getPackage();
+        mUser = mShortcutInfo.getUserHandle();
+        mClassName = null;
+    }
+
+    private AppTarget(Parcel parcel) {
+        mId = parcel.readTypedObject(AppTargetId.CREATOR);
+        mShortcutInfo = parcel.readTypedObject(ShortcutInfo.CREATOR);
+        if (mShortcutInfo == null) {
+            mPackageName = parcel.readString();
+            mClassName = parcel.readString();
+            mUser = UserHandle.of(parcel.readInt());
+        } else {
+            mPackageName = mShortcutInfo.getPackage();
+            mUser = mShortcutInfo.getUserHandle();
+            mClassName = null;
+        }
+        mRank = parcel.readInt();
+    }
+
+    /**
+     * Returns the target id.
+     */
+    @NonNull
+    public AppTargetId getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the class name for the app target.
+     */
+    @Nullable
+    public String getClassName() {
+        return mClassName;
+    }
+
+    /**
+     * Returns the package name for the app target.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Returns the user for the app target.
+     */
+    @NonNull
+    public UserHandle getUser() {
+        return mUser;
+    }
+
+    /**
+     * Returns the shortcut info for the target.
+     */
+    @Nullable
+    public ShortcutInfo getShortcutInfo() {
+        return mShortcutInfo;
+    }
+
+    /**
+     * Sets the rank of the for the target.
+     * @hide
+     */
+    public void setRank(int rank) {
+        mRank = rank;
+    }
+
+    public int getRank() {
+        return mRank;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedObject(mId, flags);
+        dest.writeTypedObject(mShortcutInfo, flags);
+        if (mShortcutInfo == null) {
+            dest.writeString(mPackageName);
+            dest.writeString(mClassName);
+            dest.writeInt(mUser.getIdentifier());
+        }
+        dest.writeInt(mRank);
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    public static final Parcelable.Creator<AppTarget> CREATOR =
+            new Parcelable.Creator<AppTarget>() {
+                public AppTarget createFromParcel(Parcel parcel) {
+                    return new AppTarget(parcel);
+                }
+
+                public AppTarget[] newArray(int size) {
+                    return new AppTarget[size];
+                }
+            };
+}
diff --git a/core/java/android/app/prediction/AppTargetEvent.aidl b/core/java/android/app/prediction/AppTargetEvent.aidl
new file mode 100644
index 0000000..ebed2da
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetEvent.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app.prediction;
+
+parcelable AppTargetEvent;
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
new file mode 100644
index 0000000..18317e1
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -0,0 +1,154 @@
+/*
+ * 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 android.app.prediction;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A representation of an app target event.
+ * @hide
+ */
+@SystemApi
+public final class AppTargetEvent implements Parcelable {
+
+    /**
+     * @hide
+     */
+    @IntDef({ACTION_LAUNCH, ACTION_DISMISS, ACTION_PIN})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ActionType {}
+
+    /**
+     * Event type constant indicating an app target has been launched.
+     */
+    public static final int ACTION_LAUNCH = 1;
+
+    /**
+     * Event type constant indicating an app target has been dismissed.
+     */
+    public static final int ACTION_DISMISS = 2;
+
+    /**
+     * Event type constant indicating an app target has been pinned.
+     */
+    public static final int ACTION_PIN = 3;
+
+    private final AppTarget mTarget;
+    private final String mLocation;
+    private final int mAction;
+
+    private AppTargetEvent(@Nullable AppTarget target, @Nullable String location,
+            @ActionType int actionType) {
+        mTarget = target;
+        mLocation = location;
+        mAction = actionType;
+    }
+
+    private AppTargetEvent(Parcel parcel) {
+        mTarget = parcel.readParcelable(null);
+        mLocation = parcel.readString();
+        mAction = parcel.readInt();
+    }
+
+    /**
+     * Returns the app target.
+     */
+    @Nullable
+    public AppTarget getTarget() {
+        return mTarget;
+    }
+
+    /**
+     * Returns the launch location.
+     */
+    @NonNull
+    public String getLaunchLocation() {
+        return mLocation;
+    }
+
+    /**
+     * Returns the action type.
+     */
+    @NonNull
+    public int getAction() {
+        return mAction;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mTarget, 0);
+        dest.writeString(mLocation);
+        dest.writeInt(mAction);
+    }
+
+    /**
+     * @see Creator
+     */
+    public static final Creator<AppTargetEvent> CREATOR =
+            new Creator<AppTargetEvent>() {
+                public AppTargetEvent createFromParcel(Parcel parcel) {
+                    return new AppTargetEvent(parcel);
+                }
+
+                public AppTargetEvent[] newArray(int size) {
+                    return new AppTargetEvent[size];
+                }
+            };
+
+    /**
+     * A builder for app target events.
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        private AppTarget mTarget;
+        private String mLocation;
+        private @ActionType int mAction;
+
+        public Builder(@Nullable AppTarget target, @ActionType int actionType) {
+            mTarget = target;
+            mAction = actionType;
+        }
+
+        /**
+         * Sets the launch location.
+         */
+        public Builder setLaunchLocation(String location) {
+            mLocation = location;
+            return this;
+        }
+
+        /**
+         * Builds a new event instance.
+         */
+        public AppTargetEvent build() {
+            return new AppTargetEvent(mTarget, mLocation, mAction);
+        }
+    }
+}
diff --git a/core/java/android/app/prediction/AppTargetId.aidl b/core/java/android/app/prediction/AppTargetId.aidl
new file mode 100644
index 0000000..bf69eea
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetId.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app.prediction;
+
+parcelable AppTargetId;
diff --git a/core/java/android/app/prediction/AppTargetId.java b/core/java/android/app/prediction/AppTargetId.java
new file mode 100644
index 0000000..0b8fb47
--- /dev/null
+++ b/core/java/android/app/prediction/AppTargetId.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.app.prediction;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The id for a prediction target.
+ * @hide
+ */
+@SystemApi
+public final class AppTargetId implements Parcelable {
+
+    @NonNull
+    private final String mId;
+
+    /**
+     * @hide
+     */
+    public AppTargetId(@NonNull String id) {
+        mId = id;
+    }
+
+    private AppTargetId(Parcel parcel) {
+        mId = parcel.readString();
+    }
+
+    /**
+     * Returns the id.
+     * @hide
+     */
+    @NonNull
+    public String getId() {
+        return mId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+        AppTargetId other = (AppTargetId) o;
+        return mId.equals(other.mId);
+    }
+
+    @Override
+    public int hashCode() {
+        // Ensure that the id has a consistent hash
+        return mId.hashCode();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+    }
+
+    /**
+     * @see Creator
+     */
+    public static final Creator<AppTargetId> CREATOR =
+            new Creator<AppTargetId>() {
+                public AppTargetId createFromParcel(Parcel parcel) {
+                    return new AppTargetId(parcel);
+                }
+
+                public AppTargetId[] newArray(int size) {
+                    return new AppTargetId[size];
+                }
+            };
+}
diff --git a/core/java/android/app/prediction/IPredictionCallback.aidl b/core/java/android/app/prediction/IPredictionCallback.aidl
new file mode 100644
index 0000000..f6f241e
--- /dev/null
+++ b/core/java/android/app/prediction/IPredictionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.app.prediction;
+
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+oneway interface IPredictionCallback {
+
+    void onResult(in ParceledListSlice result);
+}
diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl
new file mode 100644
index 0000000..114a1ff
--- /dev/null
+++ b/core/java/android/app/prediction/IPredictionManager.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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 android.app.prediction;
+
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.IPredictionCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+interface IPredictionManager {
+
+    void createPredictionSession(in AppPredictionContext context,
+            in AppPredictionSessionId sessionId);
+
+    void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event);
+
+    void notifyLocationShown(in AppPredictionSessionId sessionId, in String launchLocation,
+            in ParceledListSlice targetIds);
+
+    void sortAppTargets(in AppPredictionSessionId sessionId, in ParceledListSlice targets,
+            in IPredictionCallback callback);
+
+    void registerPredictionUpdates(in AppPredictionSessionId sessionId,
+            in IPredictionCallback callback);
+
+    void unregisterPredictionUpdates(in AppPredictionSessionId sessionId,
+            in IPredictionCallback callback);
+
+    void requestPredictionUpdate(in AppPredictionSessionId sessionId);
+
+    void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+}
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 2d630a6..d73e73f 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -67,6 +67,56 @@
     private static final String LOG_TAG = RoleManager.class.getSimpleName();
 
     /**
+     * The name of the proxy calling role.
+     * <p>
+     * A proxy calling app implements the {@link android.telecom.CallRedirectionService} API and
+     * provides a means to re-write the phone number for an outgoing call to place the call through
+     * a proxy calling service.
+     * <p>
+     * A single app may fill this role at any one time.
+     * @hide
+     */
+    public static final String ROLE_PROXY_CALLING_APP = "android.app.role.PROXY_CALLING_APP";
+
+    /**
+     * The name of the call screening and caller id role.
+     * <p>
+     * A call screening and caller id app implements the
+     * {@link android.telecom.CallScreeningService} API.
+     * <p>
+     * A single app may fill this role at any one time.
+     * @hide
+     */
+    public static final String ROLE_CALL_SCREENING_APP = "android.app.role.CALL_SCREENING_APP";
+
+    /**
+     * The name of the call companion app role.
+     * <p>
+     * A call companion app provides no user interface for calls, but will be bound to by Telecom
+     * when there are active calls on the device.  Companion apps for wearable devices are an
+     * acceptable use-case.  A call companion app implements the
+     * {@link android.telecom.InCallService} API.
+     * <p>
+     * Multiple apps app may fill this role at any one time.
+     * @hide
+     */
+    public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
+
+    /**
+     * The name of the car mode dialer app role.
+     * <p>
+     * Similar to the {@link #ROLE_DIALER} role, this role determines which app is responsible for
+     * showing the user interface for ongoing calls on the device.  This app filling this role is
+     * only used when the device is in car mode (see
+     * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information).  An app
+     * filling this role must implement the {@link android.telecom.InCallService} API.
+     * <p>
+     * A single app may fill this role at any one time.
+     * @hide
+     */
+    public static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP";
+
+    /**
      * The name of the dialer role.
      */
     public static final String ROLE_DIALER = "android.app.role.DIALER";
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index a2b7d58..8ee9e53 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -36,6 +36,11 @@
     private Configuration mConfiguration;
 
     @Override
+    public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
+        client.updatePendingActivityConfiguration(token, mConfiguration);
+    }
+
+    @Override
     public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index eb7be6f..cefc700 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -337,6 +337,15 @@
     public static final int BIND_ADJUST_BELOW_PERCEPTIBLE = 0x0100;
 
     /**
+     * @hide Flag for {@link #bindService}: the service being bound to represents a
+     * protected system component, so must have association restrictions applied to it.
+     * That is, a system config must have one or more allow-association tags limiting
+     * which packages it can interact with.  If it does not have any such association
+     * restrictions, a default empty set will be created.
+     */
+    public static final int BIND_RESTRICT_ASSOCIATIONS = 0x00200000;
+
+    /**
      * @hide Flag for {@link #bindService}: allows binding to a service provided
      * by an instant app. Note that the caller may not have access to the instant
      * app providing the service which is a violation of the instant app sandbox.
@@ -3093,6 +3102,7 @@
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
             CONNECTIVITY_SERVICE,
+            //@hide: IP_MEMORY_STORE_SERVICE,
             IPSEC_SERVICE,
             //@hide: UPDATE_LOCK_SERVICE,
             //@hide: NETWORKMANAGEMENT_SERVICE,
@@ -3630,6 +3640,14 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.net.IpMemoryStore} to store and read information about
+     * known networks.
+     * @hide
+     */
+    public static final String IP_MEMORY_STORE_SERVICE = "ipmemorystore";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
      * IPSec.
      *
@@ -3975,6 +3993,24 @@
     public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
 
     /**
+     * Used for getting content selections and classifications for task snapshots.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    @SystemApi
+    public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
+
+    /**
+     * Official published name of the app prediction service.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    @SystemApi
+    public static final String APP_PREDICTION_SERVICE = "app_prediction";
+
+    /**
      * Use with {@link #getSystemService(String)} to access the
      * {@link com.android.server.voiceinteraction.SoundTriggerService}.
      *
@@ -4427,6 +4463,16 @@
     public static final String STATS_MANAGER = "stats";
 
     /**
+     * Service to capture a bugreport.
+     * @see #getSystemService(String)
+     * @see android.os.BugreportManager
+     * @hide
+     */
+    // TODO: Expose API when the implementation is more complete.
+    // @SystemApi
+    public static final String BUGREPORT_SERVICE = "bugreport";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve a {@link
      * android.content.om.OverlayManager} for managing overlay packages.
      *
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2978058..576466f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1866,7 +1866,15 @@
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0;
     }
 
-    /** @hide */
+    /**
+     * Check whether the application is encryption aware.
+     *
+     * @see #isDirectBootAware()
+     * @see #isPartiallyDirectBootAware()
+     *
+     * @hide
+     */
+    @SystemApi
     public boolean isEncryptionAware() {
         return isDirectBootAware() || isPartiallyDirectBootAware();
     }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2b266b7..c984651 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1621,7 +1621,7 @@
             }
 
             final AttributeSet attrs = parser;
-            return parseApkLite(apkPath, parser, attrs, signingDetails);
+            return parseApkLite(apkPath, parser, attrs, signingDetails, flags);
 
         } catch (XmlPullParserException | IOException | RuntimeException e) {
             Slog.w(TAG, "Failed to parse " + apkPath, e);
@@ -1708,7 +1708,7 @@
     }
 
     private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
-            SigningDetails signingDetails)
+            SigningDetails signingDetails, int flags)
             throws IOException, XmlPullParserException, PackageParserException {
         final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
 
@@ -1716,11 +1716,12 @@
         int versionCode = 0;
         int versionCodeMajor = 0;
         int revisionCode = 0;
+        int targetSdkVersion = 0;
         boolean coreApp = false;
         boolean debuggable = false;
         boolean multiArch = false;
         boolean use32bitAbi = false;
-        boolean extractNativeLibs = true;
+        Boolean extractNativeLibsProvided = null;
         boolean isolatedSplits = false;
         boolean isFeatureSplit = false;
         boolean isSplitRequired = false;
@@ -1785,7 +1786,8 @@
                         use32bitAbi = attrs.getAttributeBooleanValue(i, false);
                     }
                     if ("extractNativeLibs".equals(attr)) {
-                        extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
+                        extractNativeLibsProvided = Boolean.valueOf(
+                                attrs.getAttributeBooleanValue(i, true));
                     }
                     if ("preferCodeIntegrity".equals(attr)) {
                         preferCodeIntegrity = attrs.getAttributeBooleanValue(i, false);
@@ -1803,9 +1805,51 @@
                             PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                             "<uses-split> tag requires 'android:name' attribute");
                 }
+            } else if (TAG_USES_SDK.equals(parser.getName())) {
+                final String[] errorMsg = new String[1];
+                Pair<Integer, Integer> versions = deriveSdkVersions(new AbstractVersionsAccessor() {
+                    @Override public String getMinSdkVersionCode() {
+                        return getAttributeAsString("minSdkVersion");
+                    }
+
+                    @Override public int getMinSdkVersion() {
+                        return getAttributeAsInt("minSdkVersion");
+                    }
+
+                    @Override public String getTargetSdkVersionCode() {
+                        return getAttributeAsString("targetSdkVersion");
+                    }
+
+                    @Override public int getTargetSdkVersion() {
+                        return getAttributeAsInt("targetSdkVersion");
+                    }
+
+                    private String getAttributeAsString(String name) {
+                        return attrs.getAttributeValue(ANDROID_RESOURCES, name);
+                    }
+
+                    private int getAttributeAsInt(String name) {
+                        try {
+                            return attrs.getAttributeIntValue(ANDROID_RESOURCES, name, -1);
+                        } catch (NumberFormatException e) {
+                            return -1;
+                        }
+                    }
+                }, flags, errorMsg);
+
+                if (versions == null) {
+                    throw new PackageParserException(
+                            PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, errorMsg[0]);
+                }
+
+                targetSdkVersion = versions.second;
             }
         }
 
+        final boolean extractNativeLibsDefault = targetSdkVersion < Build.VERSION_CODES.Q;
+        final boolean extractNativeLibs = (extractNativeLibsProvided != null)
+                ? extractNativeLibsProvided : extractNativeLibsDefault;
+
         if (preferCodeIntegrity && extractNativeLibs) {
             throw new PackageParserException(
                     PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
@@ -2215,65 +2259,60 @@
 
             } else if (tagName.equals(TAG_USES_SDK)) {
                 if (SDK_VERSION > 0) {
-                    sa = res.obtainAttributes(parser,
-                            com.android.internal.R.styleable.AndroidManifestUsesSdk);
+                    sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
+                    final TypedArray saFinal = sa;
+                    Pair<Integer, Integer> versions = deriveSdkVersions(
+                            new AbstractVersionsAccessor() {
+                                @Override public String getMinSdkVersionCode() {
+                                    return getAttributeAsString(
+                                            R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+                                }
 
-                    int minVers = 1;
-                    String minCode = null;
-                    int targetVers = 0;
-                    String targetCode = null;
+                                @Override public int getMinSdkVersion() {
+                                    return getAttributeAsInt(
+                                            R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+                                }
 
-                    TypedValue val = sa.peekValue(
-                            com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion);
-                    if (val != null) {
-                        if (val.type == TypedValue.TYPE_STRING && val.string != null) {
-                            minCode = val.string.toString();
-                        } else {
-                            // If it's not a string, it's an integer.
-                            minVers = val.data;
-                        }
+                                @Override public String getTargetSdkVersionCode() {
+                                    return getAttributeAsString(
+                                            R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+                                }
+
+                                @Override public int getTargetSdkVersion() {
+                                    return getAttributeAsInt(
+                                            R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+                                }
+
+                                private String getAttributeAsString(int index) {
+                                    TypedValue val = saFinal.peekValue(index);
+                                    if (val != null && val.type == TypedValue.TYPE_STRING
+                                            && val.string != null) {
+                                        return val.string.toString();
+                                    }
+                                    return null;
+                                }
+
+                                private int getAttributeAsInt(int index) {
+                                    TypedValue val = saFinal.peekValue(index);
+                                    if (val != null && val.type != TypedValue.TYPE_STRING) {
+                                        // If it's not a string, it's an integer.
+                                        return val.data;
+                                    }
+                                    return -1;
+                                }
+                            }, flags, outError);
+
+                    if (versions == null) {
+                        mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+                        return null;
                     }
 
-                    val = sa.peekValue(
-                            com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
-                    if (val != null) {
-                        if (val.type == TypedValue.TYPE_STRING && val.string != null) {
-                            targetCode = val.string.toString();
-                            if (minCode == null) {
-                                minCode = targetCode;
-                            }
-                        } else {
-                            // If it's not a string, it's an integer.
-                            targetVers = val.data;
-                        }
-                    } else {
-                        targetVers = minVers;
-                        targetCode = minCode;
-                    }
+                    pkg.applicationInfo.minSdkVersion = versions.first;
+                    pkg.applicationInfo.targetSdkVersion = versions.second;
 
                     sa.recycle();
-
-                    final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode,
-                            SDK_VERSION, SDK_CODENAMES, outError);
-                    if (minSdkVersion < 0) {
-                        mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
-                        return null;
-                    }
-
-                    boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
-                    final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers,
-                            targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
-                    if (targetSdkVersion < 0) {
-                        mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
-                        return null;
-                    }
-
-                    pkg.applicationInfo.minSdkVersion = minSdkVersion;
-                    pkg.applicationInfo.targetSdkVersion = targetSdkVersion;
                 }
-
                 XmlUtils.skipCurrentTag(parser);
-
             } else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
                 sa = res.obtainAttributes(parser,
                         com.android.internal.R.styleable.AndroidManifestSupportsScreens);
@@ -2675,6 +2714,67 @@
         return -1;
     }
 
+    private interface AbstractVersionsAccessor {
+        /** Returns minimum SDK version code string, or null if absent. */
+        String getMinSdkVersionCode();
+
+        /** Returns minimum SDK version code, or -1 if absent. */
+        int getMinSdkVersion();
+
+        /** Returns target SDK version code string, or null if absent. */
+        String getTargetSdkVersionCode();
+
+        /** Returns target SDK version code, or -1 if absent. */
+        int getTargetSdkVersion();
+    }
+
+    private static @Nullable Pair<Integer, Integer> deriveSdkVersions(
+            @NonNull AbstractVersionsAccessor accessor, int flags, String[] outError) {
+        int minVers = 1;
+        String minCode = null;
+        int targetVers = 0;
+        String targetCode = null;
+
+        String code = accessor.getMinSdkVersionCode();
+        int version = accessor.getMinSdkVersion();
+        // Check integer first since code is almost never a null string (e.g. "28").
+        if (version >= 0) {
+            minVers = version;
+        } else if (code != null) {
+            minCode = code;
+        }
+
+        code = accessor.getTargetSdkVersionCode();
+        version = accessor.getTargetSdkVersion();
+        // Check integer first since code is almost never a null string (e.g. "28").
+        if (version >= 0) {
+            targetVers = version;
+        } else if (code != null) {
+            targetCode = code;
+            if (minCode == null) {
+                minCode = targetCode;
+            }
+        } else {
+            targetVers = minVers;
+            targetCode = minCode;
+        }
+
+        final int minSdkVersion = computeMinSdkVersion(minVers, minCode,
+                SDK_VERSION, SDK_CODENAMES, outError);
+        if (minSdkVersion < 0) {
+            return null;
+        }
+
+        boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
+        final int targetSdkVersion = computeTargetSdkVersion(targetVers,
+                targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
+        if (targetSdkVersion < 0) {
+            return null;
+        }
+
+        return Pair.create(minSdkVersion, targetSdkVersion);
+    }
+
     /**
      * Computes the minSdkVersion to use at runtime. If the package is not
      * compatible with this platform, populates {@code outError[0]} with an
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index a8c3b88..6e519c1 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -475,6 +475,14 @@
         final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
         for (ResolveInfo resolveInfo : resolveInfos) {
             try {
+                // if this package is not one of those changedUids, we don't need to scan it,
+                // since nothing in it changed, so save a call to parseServiceInfo, which
+                // can cause a large amount of the package apk to be loaded into memory.
+                // if this is the initial scan, changedUids will be null, and containsUid will
+                // trivially return true, and will call parseServiceInfo
+                if (!containsUid(changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) {
+                    continue;
+                }
                 ServiceInfo<V> info = parseServiceInfo(resolveInfo);
                 if (info == null) {
                     Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index b238d77..f652f85 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -73,6 +73,10 @@
      * @hide
      */
     public static final String KEY_NEGATIVE_TEXT = "negative_text";
+    /**
+     * @hide
+     */
+    public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
 
     /**
      * Error/help message will show for this amount of time.
@@ -215,6 +219,30 @@
         }
 
         /**
+         * Optional: A hint to the system to require user confirmation after a biometric has been
+         * authenticated. For example, implicit modalities like Face and Iris authentication are
+         * passive, meaning they don't require an explicit user action to complete. When set to
+         * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt
+         * will require confirmation by default.
+         *
+         * A typical use case for not requiring confirmation would be for low-risk transactions,
+         * such as re-authenticating a recently authenticated application. A typical use case for
+         * requiring confirmation would be for authorizing a purchase.
+         *
+         * Note that this is a hint to the system. The system may choose to ignore the flag. For
+         * example, if the user disables implicit authentication in Settings, or if it does not
+         * apply to a modality (e.g. Fingerprint). When ignored, the system will default to
+         * requiring confirmation.
+         *
+         * @param requireConfirmation
+         * @hide
+         */
+        public Builder setRequireConfirmation(boolean requireConfirmation) {
+            mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
+            return this;
+        }
+
+        /**
          * Creates a {@link BiometricPrompt}.
          * @return a {@link BiometricPrompt}
          * @throws IllegalArgumentException if any of the required fields are not set.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 436b4a1..abc00fe 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2051,6 +2051,16 @@
         return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
+    /** @hide */
+    public NetworkRequest getDefaultRequest() {
+        try {
+            // This is not racy as the default request is final in ConnectivityService.
+            return mService.getDefaultRequest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /* TODO: These permissions checks don't belong in client-side code. Move them to
      * services.jar, possibly in com.android.server.net. */
 
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e7d441d..da5d96e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -167,6 +167,8 @@
 
     int getMultipathPreference(in Network Network);
 
+    NetworkRequest getDefaultRequest();
+
     int getRestoreDefaultNetworkDelay(int networkType);
 
     boolean addVpnAddress(String address, int prefixLength);
diff --git a/core/java/android/net/IIpMemoryStore.aidl b/core/java/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..6f88dec
--- /dev/null
+++ b/core/java/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,113 @@
+/*
+ * 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 android.net;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+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;
+
+/** {@hide} */
+oneway interface IIpMemoryStore {
+    /**
+     * Store network attributes for a given L2 key.
+     * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+     * calling findL2Key with the attributes and storing in the returned value.
+     *
+     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+     *              key and only care about grouping can pass a unique ID here like the ones
+     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+     *              relevance of such a network will lead to it being evicted soon if it's not
+     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+     * @param attributes The attributes for this network.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * @return (through the listener) The L2 key. This is useful if the L2 key was not specified.
+     *         If the call failed, the L2 key will be null.
+     */
+    void storeNetworkAttributes(String l2Key, in NetworkAttributesParcelable attributes,
+            IOnStatusListener listener);
+
+    /**
+     * Store a binary blob associated with an L2 key and a name.
+     *
+     * @param l2Key The L2 key for this network.
+     * @param clientId The ID of the client.
+     * @param name The name of this data.
+     * @param data The data to store.
+     * @param listener A listener to inform of the completion of this call, or null if the client
+     *        is not interested in learning about success/failure.
+     * @return (through the listener) A status to indicate success or failure.
+     */
+    void storeBlob(String l2Key, String clientId, String name, in Blob data,
+            IOnStatusListener listener);
+
+    /**
+     * Returns the best L2 key associated with the attributes.
+     *
+     * This will find a record that would be in the same group as the passed attributes. This is
+     * useful to choose the key for storing a sample or private data when the L2 key is not known.
+     * If multiple records are group-close to these attributes, the closest match is returned.
+     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+     * order) L2 key is returned.
+     * If no record matches these attributes, null is returned.
+     *
+     * @param attributes The attributes of the network to find.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) The L2 key if one matched, or null.
+     */
+    void findL2Key(in NetworkAttributesParcelable attributes, IOnL2KeyResponseListener listener);
+
+    /**
+     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+     * to the same L3 network. Group-closeness is used to determine this.
+     *
+     * @param l2Key1 The key for the first network.
+     * @param l2Key2 The key for the second network.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) A SameL3NetworkResponse containing the answer and confidence.
+     */
+    void isSameNetwork(String l2Key1, String l2Key2, IOnSameNetworkResponseListener listener);
+
+    /**
+     * Retrieve the network attributes for a key.
+     * If no record is present for this key, this will return null attributes.
+     *
+     * @param l2Key The key of the network to query.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) The network attributes and the L2 key associated with
+     *         the query.
+     */
+    void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrieved listener);
+
+    /**
+     * Retrieve previously stored private data.
+     * If no data was stored for this L2 key and name this will return null.
+     *
+     * @param l2Key The L2 key.
+     * @param clientId The id of the client that stored this data.
+     * @param name The name of the data.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) The private data (or null), with the L2 key
+     *         and the name of the data associated with the query.
+     */
+    void retrieveBlob(String l2Key, String clientId, String name,
+            IOnBlobRetrievedListener listener);
+}
diff --git a/core/java/android/net/INetworkMonitor.aidl b/core/java/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..41f969a
--- /dev/null
+++ b/core/java/android/net/INetworkMonitor.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitor {
+    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+    // The network should be used as a default internet connection.  It was found to be:
+    // 1. a functioning network providing internet access, or
+    // 2. a captive portal and the user decided to use it as is.
+    const int NETWORK_TEST_RESULT_VALID = 0;
+
+    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+    // The network should not be used as a default internet connection.  It was found to be:
+    // 1. a captive portal and the user is prompted to sign-in, or
+    // 2. a captive portal and the user did not want to use it, or
+    // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
+    const int NETWORK_TEST_RESULT_INVALID = 1;
+
+    void start();
+    void launchCaptivePortalApp();
+    void forceReevaluation(int uid);
+    void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config);
+    void notifyDnsResponse(int returnCode);
+    void notifySystemReady();
+    void notifyNetworkConnected();
+    void notifyNetworkDisconnected();
+    void notifyLinkPropertiesChanged();
+    void notifyNetworkCapabilitiesChanged();
+}
\ No newline at end of file
diff --git a/core/java/android/net/INetworkMonitorCallbacks.aidl b/core/java/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..0bc2575
--- /dev/null
+++ b/core/java/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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 android.net;
+
+import android.net.INetworkMonitor;
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitorCallbacks {
+    void onNetworkMonitorCreated(in INetworkMonitor networkMonitor);
+    void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+    void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config);
+    void showProvisioningNotification(String action);
+    void hideProvisioningNotification();
+}
\ No newline at end of file
diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl
index be0dc07..2df8ab7 100644
--- a/core/java/android/net/INetworkStackConnector.aidl
+++ b/core/java/android/net/INetworkStackConnector.aidl
@@ -15,6 +15,7 @@
  */
 package android.net;
 
+import android.net.INetworkMonitorCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
 
@@ -22,4 +23,5 @@
 oneway interface INetworkStackConnector {
     void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
         in IDhcpServerCallbacks cb);
+    void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb);
 }
\ No newline at end of file
diff --git a/core/java/android/net/IpMemoryStore.java b/core/java/android/net/IpMemoryStore.java
new file mode 100644
index 0000000..b35f097
--- /dev/null
+++ b/core/java/android/net/IpMemoryStore.java
@@ -0,0 +1,174 @@
+/*
+ * 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 android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+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;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * The interface for system components to access the IP memory store.
+ * @see com.android.server.net.ipmemorystore.IpMemoryStoreService
+ * @hide
+ */
+@SystemService(Context.IP_MEMORY_STORE_SERVICE)
+public class IpMemoryStore {
+    @NonNull final Context mContext;
+    @NonNull final IIpMemoryStore mService;
+
+    public IpMemoryStore(@NonNull final Context context, @NonNull final IIpMemoryStore service) {
+        mContext = Preconditions.checkNotNull(context, "missing context");
+        mService = Preconditions.checkNotNull(service, "missing IIpMemoryStore");
+    }
+
+    /**
+     * Store network attributes for a given L2 key.
+     * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+     * calling findL2Key with the attributes and storing in the returned value.
+     *
+     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+     *              key and only care about grouping can pass a unique ID here like the ones
+     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+     *              relevance of such a network will lead to it being evicted soon if it's not
+     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+     * @param attributes The attributes for this network.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+     * If the call failed, the L2 key will be null.
+     */
+    public void storeNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final NetworkAttributes attributes,
+            @Nullable final IOnStatusListener listener) {
+        try {
+            mService.storeNetworkAttributes(l2Key, attributes.toParcelable(), listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Store a binary blob associated with an L2 key and a name.
+     *
+     * @param l2Key The L2 key for this network.
+     * @param clientId The ID of the client.
+     * @param name The name of this data.
+     * @param data The data to store.
+     * @param listener A listener to inform of the completion of this call, or null if the client
+     *        is not interested in learning about success/failure.
+     * Through the listener, returns a status to indicate success or failure.
+     */
+    public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final Blob data,
+            @Nullable final IOnStatusListener listener) {
+        try {
+            mService.storeBlob(l2Key, clientId, name, data, listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the best L2 key associated with the attributes.
+     *
+     * This will find a record that would be in the same group as the passed attributes. This is
+     * useful to choose the key for storing a sample or private data when the L2 key is not known.
+     * If multiple records are group-close to these attributes, the closest match is returned.
+     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+     * order) L2 key is returned.
+     * If no record matches these attributes, null is returned.
+     *
+     * @param attributes The attributes of the network to find.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the L2 key if one matched, or null.
+     */
+    public void findL2Key(@NonNull final NetworkAttributes attributes,
+            @NonNull final IOnL2KeyResponseListener listener) {
+        try {
+            mService.findL2Key(attributes.toParcelable(), listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+     * to the same L3 network. Group-closeness is used to determine this.
+     *
+     * @param l2Key1 The key for the first network.
+     * @param l2Key2 The key for the second network.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+     */
+    public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+            @NonNull final IOnSameNetworkResponseListener listener) {
+        try {
+            mService.isSameNetwork(l2Key1, l2Key2, listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieve the network attributes for a key.
+     * If no record is present for this key, this will return null attributes.
+     *
+     * @param l2Key The key of the network to query.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the network attributes and the L2 key associated with
+     *         the query.
+     */
+    public void retrieveNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final IOnNetworkAttributesRetrieved listener) {
+        try {
+            mService.retrieveNetworkAttributes(l2Key, listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieve previously stored private data.
+     * If no data was stored for this L2 key and name this will return null.
+     *
+     * @param l2Key The L2 key.
+     * @param clientId The id of the client that stored this data.
+     * @param name The name of the data.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the private data (or null), with the L2 key
+     *         and the name of the data associated with the query.
+     */
+    public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
+        try {
+            mService.retrieveBlob(l2Key, clientId, name, listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 3a79206..4a466f3 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -55,6 +55,8 @@
     private String mIfaceName;
     private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
     private ArrayList<InetAddress> mDnses = new ArrayList<>();
+    // PCSCF addresses are addresses of SIP proxies that only exist for the IMS core service.
+    private ArrayList<InetAddress> mPcscfs = new ArrayList<InetAddress>();
     private ArrayList<InetAddress> mValidatedPrivateDnses = new ArrayList<>();
     private boolean mUsePrivateDns;
     private String mPrivateDnsServerName;
@@ -179,6 +181,7 @@
             mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
             mUsePrivateDns = source.mUsePrivateDns;
             mPrivateDnsServerName = source.mPrivateDnsServerName;
+            mPcscfs.addAll(source.mPcscfs);
             mDomains = source.mDomains;
             mRoutes.addAll(source.mRoutes);
             mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
@@ -525,6 +528,60 @@
     }
 
     /**
+     * Adds the given {@link InetAddress} to the list of PCSCF servers, if not present.
+     *
+     * @param pcscfServer The {@link InetAddress} to add to the list of PCSCF servers.
+     * @return true if the PCSCF server was added, false otherwise.
+     * @hide
+     */
+    public boolean addPcscfServer(InetAddress pcscfServer) {
+        if (pcscfServer != null && !mPcscfs.contains(pcscfServer)) {
+            mPcscfs.add(pcscfServer);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Removes the given {@link InetAddress} from the list of PCSCF servers.
+     *
+     * @param pcscf Server The {@link InetAddress} to remove from the list of PCSCF servers.
+     * @return true if the PCSCF server was removed, false otherwise.
+     * @hide
+     */
+    public boolean removePcscfServer(InetAddress pcscfServer) {
+        if (pcscfServer != null) {
+            return mPcscfs.remove(pcscfServer);
+        }
+        return false;
+    }
+
+    /**
+     * Replaces the PCSCF servers in this {@code LinkProperties} with
+     * the given {@link Collection} of {@link InetAddress} objects.
+     *
+     * @param addresses The {@link Collection} of PCSCF servers to set in this object.
+     * @hide
+     */
+    public void setPcscfServers(Collection<InetAddress> pcscfServers) {
+        mPcscfs.clear();
+        for (InetAddress pcscfServer: pcscfServers) {
+            addPcscfServer(pcscfServer);
+        }
+    }
+
+    /**
+     * Returns all the {@link InetAddress} for PCSCF servers on this link.
+     *
+     * @return An unmodifiable {@link List} of {@link InetAddress} for PCSCF servers on
+     *         this link.
+     * @hide
+     */
+    public List<InetAddress> getPcscfServers() {
+        return Collections.unmodifiableList(mPcscfs);
+    }
+
+    /**
      * Sets the DNS domain search path used on this link.
      *
      * @param domains A {@link String} listing in priority order the comma separated
@@ -767,6 +824,7 @@
         mDnses.clear();
         mUsePrivateDns = false;
         mPrivateDnsServerName = null;
+        mPcscfs.clear();
         mDomains = null;
         mRoutes.clear();
         mHttpProxy = null;
@@ -813,6 +871,12 @@
             resultJoiner.add(mPrivateDnsServerName);
         }
 
+        if (!mPcscfs.isEmpty()) {
+            resultJoiner.add("PcscfAddresses: [");
+            resultJoiner.add(TextUtils.join(",", mPcscfs));
+            resultJoiner.add("]");
+        }
+
         if (!mValidatedPrivateDnses.isEmpty()) {
             final StringJoiner validatedPrivateDnsesJoiner =
                     new StringJoiner(",", "ValidatedPrivateDnsAddresses: [", "]");
@@ -965,6 +1029,36 @@
     }
 
     /**
+     * Returns true if this link has an IPv4 PCSCF server.
+     *
+     * @return {@code true} if there is an IPv4 PCSCF server, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv4PcscfServer() {
+        for (InetAddress ia : mPcscfs) {
+          if (ia instanceof Inet4Address) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link has an IPv6 PCSCF server.
+     *
+     * @return {@code true} if there is an IPv6 PCSCF server, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv6PcscfServer() {
+        for (InetAddress ia : mPcscfs) {
+          if (ia instanceof Inet6Address) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
      * Returns true if this link is provisioned for global IPv4 connectivity.
      * This requires an IP address, default route, and DNS server.
      *
@@ -1117,6 +1211,19 @@
     }
 
     /**
+     * Compares this {@code LinkProperties} PCSCF addresses against the target
+     *
+     * @param target LinkProperties to compare.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isIdenticalPcscfs(LinkProperties target) {
+        Collection<InetAddress> targetPcscfs = target.getPcscfServers();
+        return (mPcscfs.size() == targetPcscfs.size()) ?
+                    mPcscfs.containsAll(targetPcscfs) : false;
+    }
+
+    /**
      * Compares this {@code LinkProperties} Routes against the target
      *
      * @param target LinkProperties to compare.
@@ -1218,6 +1325,7 @@
                 && isIdenticalDnses(target)
                 && isIdenticalPrivateDns(target)
                 && isIdenticalValidatedPrivateDnses(target)
+                && isIdenticalPcscfs(target)
                 && isIdenticalRoutes(target)
                 && isIdenticalHttpProxy(target)
                 && isIdenticalStackedLinks(target)
@@ -1334,6 +1442,7 @@
                 + mMtu * 51
                 + ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode())
                 + (mUsePrivateDns ? 57 : 0)
+                + mPcscfs.size() * 67
                 + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode());
     }
 
@@ -1357,6 +1466,10 @@
         }
         dest.writeBoolean(mUsePrivateDns);
         dest.writeString(mPrivateDnsServerName);
+        dest.writeInt(mPcscfs.size());
+        for (InetAddress d : mPcscfs) {
+            dest.writeByteArray(d.getAddress());
+        }
         dest.writeString(mDomains);
         dest.writeInt(mMtu);
         dest.writeString(mTcpBufferSizes);
@@ -1406,6 +1519,12 @@
                 }
                 netProp.setUsePrivateDns(in.readBoolean());
                 netProp.setPrivateDnsServerName(in.readString());
+                addressCount = in.readInt();
+                for (int i = 0; i < addressCount; i++) {
+                    try {
+                        netProp.addPcscfServer(InetAddress.getByAddress(in.createByteArray()));
+                    } catch (UnknownHostException e) { }
+                }
                 netProp.setDomains(in.readString());
                 netProp.setMtu(in.readInt());
                 netProp.setTcpBufferSizes(in.readString());
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index d4a0ec63..2eac6de 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -48,14 +48,16 @@
 public class NetworkStack {
     private static final String TAG = NetworkStack.class.getSimpleName();
 
+    public static final String NETWORKSTACK_PACKAGE_NAME = "com.android.mainline.networkstack";
+
     @NonNull
     @GuardedBy("mPendingNetStackRequests")
-    private final ArrayList<NetworkStackRequest> mPendingNetStackRequests = new ArrayList<>();
+    private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
     @Nullable
     @GuardedBy("mPendingNetStackRequests")
     private INetworkStackConnector mConnector;
 
-    private interface NetworkStackRequest {
+    private interface NetworkStackCallback {
         void onNetworkStackConnected(INetworkStackConnector connector);
     }
 
@@ -77,6 +79,21 @@
         });
     }
 
+    /**
+     * Create a NetworkMonitor.
+     *
+     * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
+     */
+    public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) {
+        requestConnector(connector -> {
+            try {
+                connector.makeNetworkMonitor(network.netId, name, cb);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        });
+    }
+
     private class NetworkStackConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
@@ -96,14 +113,14 @@
         ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */,
                 DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
 
-        final ArrayList<NetworkStackRequest> requests;
+        final ArrayList<NetworkStackCallback> requests;
         synchronized (mPendingNetStackRequests) {
             requests = new ArrayList<>(mPendingNetStackRequests);
             mPendingNetStackRequests.clear();
             mConnector = connector;
         }
 
-        for (NetworkStackRequest r : requests) {
+        for (NetworkStackCallback r : requests) {
             r.onNetworkStackConnected(connector);
         }
     }
@@ -124,7 +141,8 @@
                     "com.android.server.NetworkStackService",
                     true /* initialize */,
                     context.getClassLoader());
-            connector = (IBinder) service.getMethod("makeConnector").invoke(null);
+            connector = (IBinder) service.getMethod("makeConnector", Context.class)
+                    .invoke(null, context);
         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
             Slog.wtf(TAG, "Could not create network stack connector from NetworkStackService");
             // TODO: crash/reboot system here ?
@@ -153,7 +171,7 @@
     }
 
     // TODO: use this method to obtain the connector when implementing network stack operations
-    private void requestConnector(@NonNull NetworkStackRequest request) {
+    private void requestConnector(@NonNull NetworkStackCallback request) {
         // TODO: PID check.
         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
             // Don't even attempt to obtain the connector and give a nice error message
diff --git a/core/java/android/net/PrivateDnsConfigParcel.aidl b/core/java/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..b52fce6
--- /dev/null
+++ b/core/java/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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 android.net;
+
+parcelable PrivateDnsConfigParcel {
+    String hostname;
+    String[] ips;
+}
diff --git a/core/java/android/net/ipmemorystore/Blob.aidl b/core/java/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..9dbef11
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+/**
+ * A blob of data opaque to the memory store. The client mutates this at its own risk,
+ * and it is strongly suggested to never do it at all and treat this as immutable.
+ * {@hide}
+ */
+parcelable Blob {
+    byte[] data;
+}
diff --git a/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..4926feb
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnBlobRetrievedListener {
+    /**
+     * Private data was retrieved for the L2 key and name specified.
+     * Note this does not return the client ID, as clients are expected to only ever use one ID.
+     */
+     void onBlobRetrieved(in StatusParcelable status, in String l2Key, in String name,
+             in Blob data);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..dea0cc4
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnL2KeyResponseListener {
+    /**
+     * The operation completed with the specified L2 key.
+     */
+     void onL2KeyResponse(in StatusParcelable status, in String l2Key);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
new file mode 100644
index 0000000..57f59a1
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnNetworkAttributesRetrieved {
+    /**
+     * Network attributes were fetched for the specified L2 key. While the L2 key will never
+     * be null, the attributes may be if no data is stored about this L2 key.
+     */
+     void onL2KeyResponse(in StatusParcelable status, in String l2Key,
+             in NetworkAttributesParcelable attributes);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
new file mode 100644
index 0000000..294bd3b
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnSameNetworkResponseListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+     void onSameNetworkResponse(in StatusParcelable status,
+             in SameL3NetworkResponseParcelable response);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnStatusListener.aidl b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..5d07504
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnStatusListener {
+    /**
+     * The operation has completed with the specified status.
+     */
+     void onComplete(in StatusParcelable status);
+}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
new file mode 100644
index 0000000..d7e5b27
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -0,0 +1,210 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A POD object to represent attributes of a single L2 network entry.
+ * @hide
+ */
+public class NetworkAttributes {
+    private static final boolean DBG = true;
+
+    // The v4 address that was assigned to this device the last time it joined this network.
+    // This typically comes from DHCP but could be something else like static configuration.
+    // This does not apply to IPv6.
+    // TODO : add a list of v6 prefixes for the v6 case.
+    @Nullable
+    public final Inet4Address assignedV4Address;
+
+    // Optionally supplied by the client if it has an opinion on L3 network. For example, this
+    // could be a hash of the SSID + security type on WiFi.
+    @Nullable
+    public final String groupHint;
+
+    // The list of DNS server addresses.
+    @Nullable
+    public final List<InetAddress> dnsAddresses;
+
+    // The mtu on this network.
+    @Nullable
+    public final Integer mtu;
+
+    NetworkAttributes(
+            @Nullable final Inet4Address assignedV4Address,
+            @Nullable final String groupHint,
+            @Nullable final List<InetAddress> dnsAddresses,
+            @Nullable final Integer mtu) {
+        if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+        this.assignedV4Address = assignedV4Address;
+        this.groupHint = groupHint;
+        this.dnsAddresses = null == dnsAddresses ? null :
+                Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
+        this.mtu = mtu;
+    }
+
+    @VisibleForTesting
+    public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) {
+        // The call to the other constructor must be the first statement of this constructor,
+        // so everything has to be inline
+        this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
+                parcelable.groupHint,
+                blobArrayToInetAddressList(parcelable.dnsAddresses),
+                parcelable.mtu >= 0 ? parcelable.mtu : null);
+    }
+
+    @Nullable
+    private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
+        try {
+            return InetAddress.getByAddress(address);
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+
+    @Nullable
+    private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) {
+        if (null == blobs) return null;
+        final ArrayList<InetAddress> list = new ArrayList<>(blobs.length);
+        for (final Blob b : blobs) {
+            final InetAddress addr = getByAddressOrNull(b.data);
+            if (null != addr) list.add(addr);
+        }
+        return list;
+    }
+
+    @Nullable
+    private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) {
+        if (null == addresses) return null;
+        final ArrayList<Blob> blobs = new ArrayList<>();
+        for (int i = 0; i < addresses.size(); ++i) {
+            final InetAddress addr = addresses.get(i);
+            if (null == addr) continue;
+            final Blob b = new Blob();
+            b.data = addr.getAddress();
+            blobs.add(b);
+        }
+        return blobs.toArray(new Blob[0]);
+    }
+
+    /** Converts this NetworkAttributes to a parcelable object */
+    @NonNull
+    public NetworkAttributesParcelable toParcelable() {
+        final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
+        parcelable.assignedV4Address =
+                (null == assignedV4Address) ? null : assignedV4Address.getAddress();
+        parcelable.groupHint = groupHint;
+        parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
+        parcelable.mtu = (null == mtu) ? -1 : mtu;
+        return parcelable;
+    }
+
+    /** @hide */
+    public static class Builder {
+        @Nullable
+        private Inet4Address mAssignedAddress;
+        @Nullable
+        private String mGroupHint;
+        @Nullable
+        private List<InetAddress> mDnsAddresses;
+        @Nullable
+        private Integer mMtu;
+
+        /**
+         * Set the assigned address.
+         * @param assignedV4Address The assigned address.
+         * @return This builder.
+         */
+        public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) {
+            mAssignedAddress = assignedV4Address;
+            return this;
+        }
+
+        /**
+         * Set the group hint.
+         * @param groupHint The group hint.
+         * @return This builder.
+         */
+        public Builder setGroupHint(@Nullable final String groupHint) {
+            mGroupHint = groupHint;
+            return this;
+        }
+
+        /**
+         * Set the DNS addresses.
+         * @param dnsAddresses The DNS addresses.
+         * @return This builder.
+         */
+        public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) {
+            if (DBG && null != dnsAddresses) {
+                // Parceling code crashes if one of the addresses is null, therefore validate
+                // them when running in debug.
+                for (final InetAddress address : dnsAddresses) {
+                    if (null == address) throw new IllegalArgumentException("Null DNS address");
+                }
+            }
+            this.mDnsAddresses = dnsAddresses;
+            return this;
+        }
+
+        /**
+         * Set the MTU.
+         * @param mtu The MTU.
+         * @return This builder.
+         */
+        public Builder setMtu(@Nullable final Integer mtu) {
+            if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+            mMtu = mtu;
+            return this;
+        }
+
+        /**
+         * Return the built NetworkAttributes object.
+         * @return The built NetworkAttributes object.
+         */
+        public NetworkAttributes build() {
+            return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu);
+        }
+    }
+
+    @Override
+    public boolean equals(@Nullable final Object o) {
+        if (!(o instanceof NetworkAttributes)) return false;
+        final NetworkAttributes other = (NetworkAttributes) o;
+        return Objects.equals(assignedV4Address, other.assignedV4Address)
+                && Objects.equals(groupHint, other.groupHint)
+                && Objects.equals(dnsAddresses, other.dnsAddresses)
+                && Objects.equals(mtu, other.mtu);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
+    }
+}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..0894d72
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+// Blob[] is used to represent an array of byte[], as structured AIDL does not support arrays
+// of arrays.
+import android.net.ipmemorystore.Blob;
+
+/**
+ * An object to represent attributes of a single L2 network entry.
+ * See NetworkAttributes.java for a description of each field. The types used in this class
+ * are structured parcelable types instead of the richer types of the NetworkAttributes object,
+ * but they have the same purpose. The NetworkAttributes.java file also contains the code
+ * to convert the richer types to the parcelable types and back.
+ * @hide
+ */
+parcelable NetworkAttributesParcelable {
+    byte[] assignedV4Address;
+    String groupHint;
+    Blob[] dnsAddresses;
+    int mtu;
+}
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
new file mode 100644
index 0000000..0cb37e9
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -0,0 +1,131 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An object representing the answer to a query whether two given L2 networks represent the
+ * same L3 network. Parcels as a SameL3NetworkResponseParceled object.
+ * @hide
+ */
+public class SameL3NetworkResponse {
+    @IntDef(prefix = "NETWORK_",
+            value = {NETWORK_SAME, NETWORK_DIFFERENT, NETWORK_NEVER_CONNECTED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NetworkSameness {}
+
+    /**
+     * Both L2 networks represent the same L3 network.
+     */
+    public static final int NETWORK_SAME = 1;
+
+    /**
+     * The two L2 networks represent a different L3 network.
+     */
+    public static final int NETWORK_DIFFERENT = 2;
+
+    /**
+     * The device has never connected to at least one of these two L2 networks, or data
+     * has been wiped. Therefore the device has never seen the L3 network behind at least
+     * one of these two L2 networks, and can't evaluate whether it's the same as the other.
+     */
+    public static final int NETWORK_NEVER_CONNECTED = 3;
+
+    /**
+     * The first L2 key specified in the query.
+     */
+    @NonNull
+    public final String l2Key1;
+
+    /**
+     * The second L2 key specified in the query.
+     */
+    @NonNull
+    public final String l2Key2;
+
+    /**
+     * A confidence value indicating whether the two L2 networks represent the same L3 network.
+     *
+     * If both L2 networks were known, this value will be between 0.0 and 1.0, with 0.0
+     * representing complete confidence that the given L2 networks represent a different
+     * L3 network, and 1.0 representing complete confidence that the given L2 networks
+     * represent the same L3 network.
+     * If at least one of the L2 networks was not known, this value will be outside of the
+     * 0.0~1.0 range.
+     *
+     * Most apps should not be interested in this, and are encouraged to use the collapsing
+     * {@link #getNetworkSameness()} function below.
+     */
+    public final float confidence;
+
+    /**
+     * @return whether the two L2 networks represent the same L3 network. Either
+     *     {@code NETWORK_SAME}, {@code NETWORK_DIFFERENT} or {@code NETWORK_NEVER_CONNECTED}.
+     */
+    @NetworkSameness
+    public final int getNetworkSameness() {
+        if (confidence > 1.0 || confidence < 0.0) return NETWORK_NEVER_CONNECTED;
+        return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
+    }
+
+    SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+            final float confidence) {
+        this.l2Key1 = l2Key1;
+        this.l2Key2 = l2Key2;
+        this.confidence = confidence;
+    }
+
+    /** Builds a SameL3NetworkResponse from a parcelable object */
+    @VisibleForTesting
+    public SameL3NetworkResponse(@NonNull final SameL3NetworkResponseParcelable parceled) {
+        this(parceled.l2Key1, parceled.l2Key2, parceled.confidence);
+    }
+
+    /** Converts this SameL3NetworkResponse to a parcelable object */
+    @NonNull
+    public SameL3NetworkResponseParcelable toParcelable() {
+        final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+        parcelable.l2Key1 = l2Key1;
+        parcelable.l2Key2 = l2Key2;
+        parcelable.confidence = confidence;
+        return parcelable;
+    }
+
+    // Note key1 and key2 have to match each other for this to return true. If
+    // key1 matches o.key2 and the other way around this returns false.
+    @Override
+    public boolean equals(@Nullable final Object o) {
+        if (!(o instanceof SameL3NetworkResponse)) return false;
+        final SameL3NetworkResponse other = (SameL3NetworkResponse) o;
+        return l2Key1.equals(other.l2Key1) && l2Key2.equals(other.l2Key2)
+                && confidence == other.confidence;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(l2Key1, l2Key2, confidence);
+    }
+}
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..7196699
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+/** {@hide} */
+parcelable SameL3NetworkResponseParcelable {
+    String l2Key1;
+    String l2Key2;
+    float confidence;
+}
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
new file mode 100644
index 0000000..5b016ec
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -0,0 +1,50 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A parcelable status representing the result of an operation.
+ * Parcels as StatusParceled.
+ * @hide
+ */
+public class Status {
+    public static final int SUCCESS = 0;
+
+    public final int resultCode;
+
+    public Status(final int resultCode) {
+        this.resultCode = resultCode;
+    }
+
+    Status(@NonNull final StatusParcelable parcelable) {
+        this(parcelable.resultCode);
+    }
+
+    /** Converts this Status to a parcelable object */
+    @NonNull
+    public StatusParcelable toParcelable() {
+        final StatusParcelable parcelable = new StatusParcelable();
+        parcelable.resultCode = resultCode;
+        return parcelable;
+    }
+
+    public boolean isSuccess() {
+        return SUCCESS == resultCode;
+    }
+}
diff --git a/core/java/android/net/ipmemorystore/StatusParcelable.aidl b/core/java/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..fb36ef4
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+/** {@hide} */
+parcelable StatusParcelable {
+  int resultCode;
+}
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
new file mode 100644
index 0000000..1343d24
--- /dev/null
+++ b/core/java/android/os/BugreportManager.java
@@ -0,0 +1,148 @@
+/*
+ * 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.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.IBinder.DeathRecipient;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that provides a privileged API to capture and consume bugreports.
+ *
+ * @hide
+ */
+// TODO: Expose API when the implementation is more complete.
+// @SystemApi
+@SystemService(Context.BUGREPORT_SERVICE)
+public class BugreportManager {
+    private final Context mContext;
+    private final IDumpstate mBinder;
+
+    /** @hide */
+    public BugreportManager(@NonNull Context context, IDumpstate binder) {
+        mContext = context;
+        mBinder = binder;
+    }
+
+    /**
+     * An interface describing the listener for bugreport progress and status.
+     */
+    public interface BugreportListener {
+        /**
+         * Called when there is a progress update.
+         * @param progress the progress in [0.0, 100.0]
+         */
+        void onProgress(float progress);
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
+                BUGREPORT_ERROR_INVALID_INPUT,
+                BUGREPORT_ERROR_RUNTIME
+        })
+
+        /** Possible error codes taking a bugreport can encounter */
+        @interface BugreportErrorCode {}
+
+        /** The input options were invalid */
+        int BUGREPORT_ERROR_INVALID_INPUT = 1;
+
+        /** A runtime error occured */
+        int BUGREPORT_ERROR_RUNTIME = 2;
+
+        /**
+         * 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}.
+         */
+        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
+         */
+        void onFinished(long durationMs, @NonNull String title,
+                @NonNull String description);
+    }
+
+    /**
+     * Starts a bugreport asynchronously.
+     *
+     * @param bugreportFd file to write the bugreport. This should be opened in write-only,
+     *     append mode.
+     * @param screenshotFd file to write the screenshot, if necessary. This should be opened
+     *     in write-only, append mode.
+     * @param params options that specify what kind of a bugreport should be taken
+     * @param listener callback for progress and status updates
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void startBugreport(@NonNull FileDescriptor bugreportFd,
+            @Nullable FileDescriptor screenshotFd,
+            @NonNull BugreportParams params, @Nullable BugreportListener listener) {
+        // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary.
+        DumpstateListener dsListener = new DumpstateListener(listener);
+
+        try {
+            mBinder.startBugreport(bugreportFd, screenshotFd, params.getMode(), dsListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    // TODO(b/111441001) Connect up with BugreportListener methods.
+    private final class DumpstateListener extends IDumpstateListener.Stub
+            implements DeathRecipient {
+        private final BugreportListener mListener;
+
+        DumpstateListener(@Nullable BugreportListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            // TODO(b/111441001): implement
+        }
+
+        @Override
+        public void onProgressUpdated(int progress) throws RemoteException {
+            // TODO(b/111441001): implement
+        }
+
+        @Override
+        public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
+            // TODO(b/111441001): implement
+        }
+
+        @Override
+        public void onSectionComplete(String title, int status, int size, int durationMs)
+                throws RemoteException {
+            // TODO(b/111441001): implement
+        }
+    }
+}
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
new file mode 100644
index 0000000..4e696ae
--- /dev/null
+++ b/core/java/android/os/BugreportParams.java
@@ -0,0 +1,90 @@
+/*
+ * 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.os;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Parameters that specify what kind of bugreport should be taken.
+ *
+ * @hide
+ */
+// TODO: Expose API when the implementation is more complete.
+// @SystemApi
+public final class BugreportParams {
+    private final int mMode;
+
+    public BugreportParams(@BugreportMode int mode) {
+        mMode = mode;
+    }
+
+    public int getMode() {
+        return mMode;
+    }
+
+    /**
+     * Defines acceptable types of bugreports.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "BUGREPORT_MODE_" }, value = {
+            BUGREPORT_MODE_FULL,
+            BUGREPORT_MODE_INTERACTIVE,
+            BUGREPORT_MODE_REMOTE,
+            BUGREPORT_MODE_WEAR,
+            BUGREPORT_MODE_TELEPHONY,
+            BUGREPORT_MODE_WIFI
+    })
+    public @interface BugreportMode {}
+
+    /**
+     * Options for a bugreport without user interference (and hence causing less
+     * interference to the system), but includes all sections.
+     */
+    public static final int BUGREPORT_MODE_FULL = IDumpstate.BUGREPORT_MODE_FULL;
+
+    /**
+     * Options that allow user to monitor progress and enter additional data; might not
+     * include all sections.
+     */
+    public static final int BUGREPORT_MODE_INTERACTIVE = IDumpstate.BUGREPORT_MODE_INTERACTIVE;
+
+    /**
+     * Options for a bugreport requested remotely by administrator of the Device Owner app,
+     * not the device's user.
+     */
+    public static final int BUGREPORT_MODE_REMOTE = IDumpstate.BUGREPORT_MODE_REMOTE;
+
+    /**
+     * Options for a bugreport on a wearable device.
+     */
+    public static final int BUGREPORT_MODE_WEAR = IDumpstate.BUGREPORT_MODE_WEAR;
+
+    /**
+     * Options for a lightweight version of bugreport that only includes a few, urgent
+     * sections used to report telephony bugs.
+     */
+    public static final int BUGREPORT_MODE_TELEPHONY = IDumpstate.BUGREPORT_MODE_TELEPHONY;
+
+    /**
+     * Options for a lightweight bugreport that only includes a few sections related to
+     * Wifi.
+     */
+    public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI;
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index cc6bb12..b9cdcc0 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -899,6 +899,33 @@
     }
 
     /**
+     * Flatten an {@link ArrayMap} with string keys containing a particular object
+     * type into the parcel at the current dataPosition() and growing dataCapacity()
+     * if needed. The type of the objects in the array must be one that implements
+     * Parcelable. Only the raw data of the objects is written and not their type,
+     * so you must use the corresponding {@link #createTypedArrayMap(Parcelable.Creator)}
+     *
+     * @param val The map of objects to be written.
+     * @param parcelableFlags The parcelable flags to use.
+     *
+     * @see #createTypedArrayMap(Parcelable.Creator)
+     * @see Parcelable
+     */
+    public <T extends Parcelable> void writeTypedArrayMap(@Nullable ArrayMap<String, T> val,
+            int parcelableFlags) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        final int count = val.size();
+        writeInt(count);
+        for (int i = 0; i < count; i++) {
+            writeString(val.keyAt(i));
+            writeTypedObject(val.valueAt(i), parcelableFlags);
+        }
+    }
+
+    /**
      * Write an array set to the parcel.
      *
      * @param val The array set to write.
@@ -1001,7 +1028,7 @@
      * values are written using {@link #writeValue} and must follow the
      * specification there.
      */
-    public final void writeSparseArray(@Nullable SparseArray<Object> val) {
+    public final <T> void writeSparseArray(@Nullable SparseArray<T> val) {
         if (val == null) {
             writeInt(-1);
             return;
@@ -1400,6 +1427,34 @@
     }
 
     /**
+     * Flatten a {@link SparseArray} containing a particular object type into the parcel
+     * at the current dataPosition() and growing dataCapacity() if needed. The
+     * type of the objects in the array must be one that implements Parcelable.
+     * Unlike the generic {@link #writeSparseArray(SparseArray)} method, however, only
+     * the raw data of the objects is written and not their type, so you must use the
+     * corresponding {@link #createTypedSparseArray(Parcelable.Creator)}.
+     *
+     * @param val The list of objects to be written.
+     * @param parcelableFlags The parcelable flags to use.
+     *
+     * @see #createTypedSparseArray(Parcelable.Creator)
+     * @see Parcelable
+     */
+    public final <T extends Parcelable> void writeTypedSparseArray(@Nullable SparseArray<T> val,
+            int parcelableFlags) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        final int count = val.size();
+        writeInt(count);
+        for (int i = 0; i < count; i++) {
+            writeInt(val.keyAt(i));
+            writeTypedObject(val.valueAt(i), parcelableFlags);
+        }
+    }
+
+    /**
      * @hide
      */
     public <T extends Parcelable> void writeTypedList(@Nullable List<T> val, int parcelableFlags) {
@@ -2369,7 +2424,7 @@
      * Parcelables.
      */
     @Nullable
-    public final SparseArray readSparseArray(@Nullable ClassLoader loader) {
+    public final <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) {
         int N = readInt();
         if (N < 0) {
             return null;
@@ -2466,6 +2521,62 @@
     }
 
     /**
+     * Read into a new {@link SparseArray} items containing a particular object type
+     * that were written with {@link #writeTypedSparseArray(SparseArray, int)} at the
+     * current dataPosition().  The list <em>must</em> have previously been written
+     * via {@link #writeTypedSparseArray(SparseArray, int)} with the same object type.
+     *
+     * @param creator The creator to use when for instantiation.
+     *
+     * @return A newly created {@link SparseArray} containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedSparseArray(SparseArray, int)
+     */
+    public final @Nullable <T extends Parcelable> SparseArray<T> createTypedSparseArray(
+            @NonNull Parcelable.Creator<T> creator) {
+        final int count = readInt();
+        if (count < 0) {
+            return null;
+        }
+        final SparseArray<T> array = new SparseArray<>(count);
+        for (int i = 0; i < count; i++) {
+            final int index = readInt();
+            final T value = readTypedObject(creator);
+            array.append(index, value);
+        }
+        return array;
+    }
+
+    /**
+     * Read into a new {@link ArrayMap} with string keys items containing a particular
+     * object type that were written with {@link #writeTypedArrayMap(ArrayMap, int)} at the
+     * current dataPosition().  The list <em>must</em> have previously been written
+     * via {@link #writeTypedArrayMap(ArrayMap, int)} with the same object type.
+     *
+     * @param creator The creator to use when for instantiation.
+     *
+     * @return A newly created {@link ArrayMap} containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedArrayMap(ArrayMap, int)
+     */
+    public final @Nullable <T extends Parcelable> ArrayMap<String, T> createTypedArrayMap(
+            @NonNull Parcelable.Creator<T> creator) {
+        final int count = readInt();
+        if (count < 0) {
+            return null;
+        }
+        final ArrayMap<String, T> map = new ArrayMap<>(count);
+        for (int i = 0; i < count; i++) {
+            final String key = readString();
+            final T value = readTypedObject(creator);
+            map.append(key, value);
+        }
+        return map;
+    }
+
+    /**
      * Read and return a new ArrayList containing String objects from
      * the parcel that was written with {@link #writeStringList} at the
      * current dataPosition().  Returns null if the
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index c3e0489..0942d97 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1326,7 +1326,7 @@
      * @return Battery saver state data.
      *
      * @hide
-     * @see com.android.server.power.BatterySaverPolicy
+     * @see com.android.server.power.batterysaver.BatterySaverPolicy
      * @see PowerSaveState
      */
     public PowerSaveState getPowerSaveState(@ServiceType int serviceType) {
diff --git a/core/java/android/os/PowerSaveState.java b/core/java/android/os/PowerSaveState.java
index de1128df..4918ad1 100644
--- a/core/java/android/os/PowerSaveState.java
+++ b/core/java/android/os/PowerSaveState.java
@@ -27,7 +27,7 @@
     /**
      * Whether we should enable battery saver for this service.
      *
-     * @see com.android.server.power.BatterySaverPolicy
+     * @see com.android.server.power.batterysaver.BatterySaverPolicy
      */
     public final boolean batterySaverEnabled;
     /**
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 9801406..68d6d85 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+
 import com.android.internal.os.Zygote;
 
 import dalvik.annotation.optimization.FastNative;
@@ -313,7 +315,7 @@
      * @param sectionName The name of the code section to appear in the trace.  This may be at
      * most 127 Unicode code units long.
      */
-    public static void beginSection(String sectionName) {
+    public static void beginSection(@NonNull String sectionName) {
         if (isTagEnabled(TRACE_TAG_APP)) {
             if (sectionName.length() > MAX_SECTION_NAME_LEN) {
                 throw new IllegalArgumentException("sectionName is too long");
@@ -345,7 +347,7 @@
      * @param methodName The method name to appear in the trace.
      * @param cookie Unique identifier for distinguishing simultaneous events
      */
-    public static void beginAsyncSection(String methodName, int cookie) {
+    public static void beginAsyncSection(@NonNull String methodName, int cookie) {
         asyncTraceBegin(TRACE_TAG_APP, methodName, cookie);
     }
 
@@ -357,7 +359,7 @@
      * @param methodName The method name to appear in the trace.
      * @param cookie Unique identifier for distinguishing simultaneous events
      */
-    public static void endAsyncSection(String methodName, int cookie) {
+    public static void endAsyncSection(@NonNull String methodName, int cookie) {
         asyncTraceEnd(TRACE_TAG_APP, methodName, cookie);
     }
 
@@ -367,7 +369,7 @@
      * @param counterName The counter name to appear in the trace.
      * @param counterValue The counter value.
      */
-    public static void setCounter(String counterName, long counterValue) {
+    public static void setCounter(@NonNull String counterName, long counterValue) {
         if (isTagEnabled(TRACE_TAG_APP)) {
             nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue);
         }
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index b5aeba0..226d1d0 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -74,10 +74,13 @@
     private final String mPackageName;
     // The default vibration intensity level for haptic feedback.
     @VibrationIntensity
-    private final int mDefaultHapticFeedbackIntensity;
+    private int mDefaultHapticFeedbackIntensity;
     // The default vibration intensity level for notifications.
     @VibrationIntensity
-    private final int mDefaultNotificationVibrationIntensity;
+    private int mDefaultNotificationVibrationIntensity;
+    // The default vibration intensity level for ringtones.
+    @VibrationIntensity
+    private int mDefaultRingVibrationIntensity;
 
     /**
      * @hide to prevent subclassing from outside of the framework
@@ -85,10 +88,7 @@
     public Vibrator() {
         mPackageName = ActivityThread.currentPackageName();
         final Context ctx = ActivityThread.currentActivityThread().getSystemContext();
-        mDefaultHapticFeedbackIntensity = loadDefaultIntensity(ctx,
-                com.android.internal.R.integer.config_defaultHapticFeedbackIntensity);
-        mDefaultNotificationVibrationIntensity = loadDefaultIntensity(ctx,
-                com.android.internal.R.integer.config_defaultNotificationVibrationIntensity);
+        loadVibrationIntensities(ctx);
     }
 
     /**
@@ -96,10 +96,16 @@
      */
     protected Vibrator(Context context) {
         mPackageName = context.getOpPackageName();
+        loadVibrationIntensities(context);
+    }
+
+    private void loadVibrationIntensities(Context context) {
         mDefaultHapticFeedbackIntensity = loadDefaultIntensity(context,
                 com.android.internal.R.integer.config_defaultHapticFeedbackIntensity);
         mDefaultNotificationVibrationIntensity = loadDefaultIntensity(context,
                 com.android.internal.R.integer.config_defaultNotificationVibrationIntensity);
+        mDefaultRingVibrationIntensity = loadDefaultIntensity(context,
+                com.android.internal.R.integer.config_defaultRingVibrationIntensity);
     }
 
     private int loadDefaultIntensity(Context ctx, int resId) {
@@ -115,13 +121,20 @@
     }
 
     /**
-     * Get the default vibration intensity for notifications and ringtones.
+     * Get the default vibration intensity for notifications.
      * @hide
      */
     public int getDefaultNotificationVibrationIntensity() {
         return mDefaultNotificationVibrationIntensity;
     }
 
+    /** Get the default vibration intensity for ringtones.
+     * @hide
+     */
+    public int getDefaultRingVibrationIntensity() {
+        return mDefaultRingVibrationIntensity;
+    }
+
     /**
      * Check whether the hardware has a vibrator.
      *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 16f9a43..4649776 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -86,7 +86,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.MemoryIntArray;
-import android.view.textservice.TextServicesManager;
+import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ColorDisplayController;
@@ -3377,6 +3377,22 @@
          */
         public static final String NOTIFICATION_VIBRATION_INTENSITY =
                 "notification_vibration_intensity";
+        /**
+         * The intensity of ringtone vibrations, if configurable.
+         *
+         * Not all devices are capable of changing their vibration intensity; on these devices
+         * there will likely be no difference between the various vibration intensities except for
+         * intensity 0 (off) and the rest.
+         *
+         * <b>Values:</b><br/>
+         * 0 - Vibration is disabled<br/>
+         * 1 - Weak vibrations<br/>
+         * 2 - Medium vibrations<br/>
+         * 3 - Strong vibrations
+         * @hide
+         */
+        public static final String RING_VIBRATION_INTENSITY =
+                "ring_vibration_intensity";
 
         /**
          * The intensity of haptic feedback vibrations, if configurable.
@@ -4246,6 +4262,7 @@
             ACCELEROMETER_ROTATION,
             SHOW_BATTERY_PERCENT,
             NOTIFICATION_VIBRATION_INTENSITY,
+            RING_VIBRATION_INTENSITY,
             HAPTIC_FEEDBACK_INTENSITY,
             DISPLAY_COLOR_MODE,
             ALARM_ALERT,
@@ -4397,6 +4414,7 @@
             VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
             VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
             VALIDATORS.put(NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+            VALIDATORS.put(RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
             VALIDATORS.put(HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
             VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR);
             VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR);
@@ -5030,10 +5048,6 @@
         public static boolean putStringForUser(@NonNull ContentResolver resolver,
                 @NonNull String name, @Nullable String value, @Nullable String tag,
                 boolean makeDefault, @UserIdInt int userHandle) {
-            if (LOCATION_MODE.equals(name)) {
-                // Map LOCATION_MODE to underlying location provider storage API
-                return setLocationModeForUser(resolver, Integer.parseInt(value), userHandle);
-            }
             if (MOVED_TO_GLOBAL.contains(name)) {
                 Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
                         + " to android.provider.Settings.Global");
@@ -5186,10 +5200,6 @@
         /** @hide */
         @UnsupportedAppUsage
         public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
-            if (LOCATION_MODE.equals(name)) {
-                // Map from to underlying location provider storage API to location mode
-                return getLocationModeForUser(cr, userHandle);
-            }
             String v = getStringForUser(cr, name, userHandle);
             try {
                 return v != null ? Integer.parseInt(v) : def;
@@ -5224,10 +5234,6 @@
         /** @hide */
         public static int getIntForUser(ContentResolver cr, String name, int userHandle)
                 throws SettingNotFoundException {
-            if (LOCATION_MODE.equals(name)) {
-                // Map from to underlying location provider storage API to location mode
-                return getLocationModeForUser(cr, userHandle);
-            }
             String v = getStringForUser(cr, name, userHandle);
             try {
                 return Integer.parseInt(v);
@@ -5810,9 +5816,8 @@
          * this value being present in settings.db or on ContentObserver notifications on the
          * corresponding Uri.
          *
-         * @deprecated use {@link #LOCATION_MODE} and
-         * {@link LocationManager#MODE_CHANGED_ACTION} (or
-         * {@link LocationManager#PROVIDERS_CHANGED_ACTION})
+         * @deprecated Providers should not be controlled individually. See {@link #LOCATION_MODE}
+          * documentation for information on reading/writing location information.
          */
         @Deprecated
         public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
@@ -5830,9 +5835,7 @@
          * notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION}
          * to receive changes in this value.
          *
-         * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
-         *             get the status of a location provider, use
-         *             {@link LocationManager#isProviderEnabled(String)}.
+         * @deprecated To check location mode, use {@link LocationManager#isLocationEnabled()}.
          */
         @Deprecated
         public static final String LOCATION_MODE = "location_mode";
@@ -5861,9 +5864,7 @@
         /**
          * Location access disabled.
          *
-         * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
-         *             get the status of a location provider, use
-         *             {@link LocationManager#isProviderEnabled(String)}.
+         * @deprecated See {@link #LOCATION_MODE}.
          */
         @Deprecated
         public static final int LOCATION_MODE_OFF = 0;
@@ -5883,9 +5884,7 @@
          * with {@link android.location.Criteria#POWER_HIGH} may be downgraded to
          * {@link android.location.Criteria#POWER_MEDIUM}.
          *
-         * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
-         *             get the status of a location provider, use
-         *             {@link LocationManager#isProviderEnabled(String)}.
+         * @deprecated See {@link #LOCATION_MODE}.
          */
         @Deprecated
         public static final int LOCATION_MODE_BATTERY_SAVING = 2;
@@ -5893,9 +5892,7 @@
         /**
          * Best-effort location computation allowed.
          *
-         * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
-         *             get the status of a location provider, use
-         *             {@link LocationManager#isProviderEnabled(String)}.
+         * @deprecated See {@link #LOCATION_MODE}.
          */
         @Deprecated
         public static final int LOCATION_MODE_HIGH_ACCURACY = 3;
@@ -6019,6 +6016,15 @@
                 "lock_screen_allow_remote_input";
 
         /**
+         * Indicates which clock face to show on lock screen and AOD.
+         * @hide
+         */
+        public static final String LOCK_SCREEN_CUSTOM_CLOCK_FACE = "lock_screen_custom_clock_face";
+
+        private static final Validator LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR =
+                ANY_STRING_VALIDATOR;
+
+        /**
          * Set by the system to track if the user needs to see the call to action for
          * the lockscreen notification policy.
          * @hide
@@ -7835,6 +7841,19 @@
                 BOOLEAN_VALIDATOR;
 
         /**
+         * Whether or not face unlock always requires user confirmation, meaning {@link
+         * android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(boolean)}
+         * is always 'true'. This overrides the behavior that apps choose in the
+         * setRequireConfirmation API.
+         * @hide
+         */
+        public static final String FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
+                "face_unlock_always_require_confirmation";
+
+        private static final Validator FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR =
+                BOOLEAN_VALIDATOR;
+
+        /**
          * Whether the assist gesture should be enabled.
          *
          * @hide
@@ -8431,6 +8450,7 @@
             AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
             FACE_UNLOCK_KEYGUARD_ENABLED,
             FACE_UNLOCK_APP_ENABLED,
+            FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
             ASSIST_GESTURE_ENABLED,
             ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
             ASSIST_GESTURE_WAKE_ENABLED,
@@ -8448,6 +8468,7 @@
             HUSH_GESTURE_USED,
             IN_CALL_NOTIFICATION_ENABLED,
             LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+            LOCK_SCREEN_CUSTOM_CLOCK_FACE,
             LOCK_SCREEN_SHOW_NOTIFICATIONS,
             ZEN_DURATION,
             SHOW_ZEN_UPGRADE_NOTIFICATION,
@@ -8584,6 +8605,8 @@
                     AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_VALIDATOR);
             VALIDATORS.put(FACE_UNLOCK_KEYGUARD_ENABLED, FACE_UNLOCK_KEYGUARD_ENABLED_VALIDATOR);
             VALIDATORS.put(FACE_UNLOCK_APP_ENABLED, FACE_UNLOCK_APP_ENABLED_VALIDATOR);
+            VALIDATORS.put(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+                    FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR);
             VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR);
             VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
                     ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR);
@@ -8624,6 +8647,7 @@
             VALIDATORS.put(ASSIST_GESTURE_SETUP_COMPLETE, BOOLEAN_VALIDATOR);
             VALIDATORS.put(NOTIFICATION_NEW_INTERRUPTION_MODEL, BOOLEAN_VALIDATOR);
             VALIDATORS.put(TRUST_AGENTS_EXTEND_UNLOCK, TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR);
+            VALIDATORS.put(LOCK_SCREEN_CUSTOM_CLOCK_FACE, LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR);
             VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR);
         }
 
@@ -8652,14 +8676,14 @@
             CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED);
             CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION);
             CLONE_TO_MANAGED_PROFILE.add(ALLOWED_GEOLOCATION_ORIGINS);
-            CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
             CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
-            CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
             CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
-            if (TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER) {
+            if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
+                CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
+                CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
                 CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER);
                 CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER_SUBTYPE);
             }
@@ -8778,84 +8802,6 @@
                         userId);
             }
         }
-
-        /**
-         * Thread-safe method for setting the location mode to one of
-         * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
-         * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
-         * Necessary because the mode is a composite of the underlying location provider
-         * settings.
-         *
-         * @param cr the content resolver to use
-         * @param mode such as {@link #LOCATION_MODE_HIGH_ACCURACY}
-         * @param userId the userId for which to change mode
-         * @return true if the value was set, false on database errors
-         *
-         * @throws IllegalArgumentException if mode is not one of the supported values
-         *
-         * @deprecated To enable/disable location, use
-         *             {@link LocationManager#setLocationEnabledForUser(boolean, int)}.
-         *             To enable/disable a specific location provider, use
-         *             {@link LocationManager#setProviderEnabledForUser(String, boolean, int)}.
-         */
-        @Deprecated
-        private static boolean setLocationModeForUser(
-                ContentResolver cr, int mode, int userId) {
-            synchronized (mLocationSettingsLock) {
-                boolean gps = false;
-                boolean network = false;
-                switch (mode) {
-                    case LOCATION_MODE_OFF:
-                        break;
-                    case LOCATION_MODE_SENSORS_ONLY:
-                        gps = true;
-                        break;
-                    case LOCATION_MODE_BATTERY_SAVING:
-                        network = true;
-                        break;
-                    case LOCATION_MODE_HIGH_ACCURACY:
-                        gps = true;
-                        network = true;
-                        break;
-                    default:
-                        throw new IllegalArgumentException("Invalid location mode: " + mode);
-                }
-
-                boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser(
-                        cr, LocationManager.NETWORK_PROVIDER, network, userId);
-                boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser(
-                        cr, LocationManager.GPS_PROVIDER, gps, userId);
-                return gpsSuccess && nlpSuccess;
-            }
-        }
-
-        /**
-         * Thread-safe method for reading the location mode, returns one of
-         * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
-         * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. Necessary
-         * because the mode is a composite of the underlying location provider settings.
-         *
-         * @param cr the content resolver to use
-         * @param userId the userId for which to read the mode
-         * @return the location mode
-         */
-        private static final int getLocationModeForUser(ContentResolver cr, int userId) {
-            synchronized (mLocationSettingsLock) {
-                boolean gpsEnabled = Settings.Secure.isLocationProviderEnabledForUser(
-                        cr, LocationManager.GPS_PROVIDER, userId);
-                boolean networkEnabled = Settings.Secure.isLocationProviderEnabledForUser(
-                        cr, LocationManager.NETWORK_PROVIDER, userId);
-                if (gpsEnabled && networkEnabled) {
-                    return LOCATION_MODE_HIGH_ACCURACY;
-                } else if (gpsEnabled) {
-                    return LOCATION_MODE_SENSORS_ONLY;
-                } else if (networkEnabled) {
-                    return LOCATION_MODE_BATTERY_SAVING;
-                } else {
-                    return LOCATION_MODE_OFF;
-                }
-            }
-        }
     }
 
     /**
@@ -11272,14 +11218,14 @@
          * quick_doze_enabled                (boolean)
          * </pre>
          * @hide
-         * @see com.android.server.power.BatterySaverPolicy
+         * @see com.android.server.power.batterysaver.BatterySaverPolicy
          */
         public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants";
 
         /**
          * Battery Saver device specific settings
          * This is encoded as a key=value list, separated by commas.
-         * See {@link com.android.server.power.BatterySaverPolicy} for the details.
+         * See {@link com.android.server.power.batterysaver.BatterySaverPolicy} for the details.
          *
          * @hide
          */
@@ -13835,6 +13781,7 @@
          * enabled                         (boolean)
          * requires_targeting_p            (boolean)
          * max_squeeze_remeasure_attempts  (int)
+         * edit_choices_before_sending     (boolean)
          * </pre>
          * @see com.android.systemui.statusbar.policy.SmartReplyConstants
          * @hide
@@ -14019,6 +13966,53 @@
          */
         public static final String NATIVE_FLAGS_HEALTH_CHECK_ENABLED =
                 "native_flags_health_check_enabled";
+
+        /**
+         * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls the mode
+         * in which the historical registry operates.
+         *
+         * @hide
+         */
+        public static final String APPOP_HISTORY_MODE = "mode";
+
+        /**
+         * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls how long
+         * is the interval between snapshots in the base case i.e. the most recent
+         * part of the history.
+         *
+         * @hide
+         */
+        public static final String APPOP_HISTORY_BASE_INTERVAL_MILLIS = "baseIntervalMillis";
+
+        /**
+         * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls the base
+         * for the logarithmic step when building app op history.
+         *
+         * @hide
+         */
+        public static final String APPOP_HISTORY_INTERVAL_MULTIPLIER = "intervalMultiplier";
+
+        /**
+         * Appop history parameters. These parameters are represented by
+         * a comma-delimited key-value list.
+         *
+         * The following strings are supported as keys:
+         * <pre>
+         *     mode                  (int)
+         *     baseIntervalMillis    (long)
+         *     intervalMultiplier    (int)
+         * </pre>
+         *
+         * Ex: "enabled=true,baseIntervalMillis=1000,intervalMultiplier=10"
+         *
+         * @see #APPOP_HISTORY_MODE
+         * @see #APPOP_HISTORY_BASE_INTERVAL_MILLIS
+         * @see #APPOP_HISTORY_INTERVAL_MULTIPLIER
+         *
+         * @hide
+         */
+        public static final String APPOP_HISTORY_PARAMETERS =
+                "appop_history_parameters";
     }
 
     /**
diff --git a/core/java/android/service/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java
new file mode 100644
index 0000000..b77405a
--- /dev/null
+++ b/core/java/android/service/appprediction/AppPredictionService.java
@@ -0,0 +1,322 @@
+/*
+ * 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 android.service.appprediction;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.app.prediction.IPredictionCallback;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.appprediction.IPredictionService.Stub;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * TODO(b/111701043): Add java docs
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class AppPredictionService extends Service {
+
+    private static final String TAG = "AppPredictionService";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     * TODO(b/111701043): Add any docs about permissions the service must hold
+     *
+     * @hide
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.appprediction.AppPredictionService";
+
+    private final ArrayMap<AppPredictionSessionId, ArrayList<CallbackWrapper>> mSessionCallbacks =
+            new ArrayMap<>();
+    private Handler mHandler;
+
+    private final IPredictionService mInterface = new Stub() {
+
+        @Override
+        public void onCreatePredictionSession(AppPredictionContext context,
+                AppPredictionSessionId sessionId) {
+            mHandler.sendMessage(
+                    obtainMessage(AppPredictionService::doCreatePredictionSession,
+                            AppPredictionService.this, context, sessionId));
+        }
+
+        @Override
+        public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
+            mHandler.sendMessage(
+                    obtainMessage(AppPredictionService::onAppTargetEvent,
+                            AppPredictionService.this, sessionId, event));
+        }
+
+        @Override
+        public void notifyLocationShown(AppPredictionSessionId sessionId, String launchLocation,
+                ParceledListSlice targetIds) {
+            mHandler.sendMessage(
+                    obtainMessage(AppPredictionService::onLocationShown, AppPredictionService.this,
+                            sessionId, launchLocation, targetIds.getList()));
+        }
+
+        @Override
+        public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets,
+                IPredictionCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(AppPredictionService::onSortAppTargets,
+                            AppPredictionService.this, sessionId, targets.getList(), null,
+                            new CallbackWrapper(callback)));
+        }
+
+        @Override
+        public void registerPredictionUpdates(AppPredictionSessionId sessionId,
+                IPredictionCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(AppPredictionService::doRegisterPredictionUpdates,
+                            AppPredictionService.this, sessionId, callback));
+        }
+
+        @Override
+        public void unregisterPredictionUpdates(AppPredictionSessionId sessionId,
+                IPredictionCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(AppPredictionService::doUnregisterPredictionUpdates,
+                            AppPredictionService.this, sessionId, callback));
+        }
+
+        @Override
+        public void requestPredictionUpdate(AppPredictionSessionId sessionId) {
+            mHandler.sendMessage(
+                    obtainMessage(AppPredictionService::doRequestPredictionUpdate,
+                            AppPredictionService.this, sessionId));
+        }
+
+        @Override
+        public void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
+            mHandler.sendMessage(
+                    obtainMessage(AppPredictionService::doDestroyPredictionSession,
+                            AppPredictionService.this, sessionId));
+        }
+    };
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+    }
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return mInterface.asBinder();
+    }
+
+    /**
+     * Called by a client app to indicate a target launch
+     */
+    @MainThread
+    public abstract void onAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
+            @NonNull AppTargetEvent event);
+
+    /**
+     * Called by a client app to indication a particular location has been shown to the user.
+     */
+    @MainThread
+    public abstract void onLocationShown(@NonNull AppPredictionSessionId sessionId,
+            @NonNull String launchLocation, @NonNull List<AppTargetId> targetIds);
+
+    private void doCreatePredictionSession(@NonNull AppPredictionContext context,
+            @NonNull AppPredictionSessionId sessionId) {
+        mSessionCallbacks.put(sessionId, new ArrayList<>());
+        onCreatePredictionSession(context, sessionId);
+    }
+
+    /**
+     * Creates a new interaction session.
+     *
+     * @param context interaction context
+     * @param sessionId the session's Id
+     */
+    public void onCreatePredictionSession(@NonNull AppPredictionContext context,
+            @NonNull AppPredictionSessionId sessionId) {}
+
+    /**
+     * Called by the client app to request sorting of targets based on prediction rank.
+     * TODO(b/111701043): Implement CancellationSignal so caller can cancel a long running request
+     */
+    @MainThread
+    public abstract void onSortAppTargets(@NonNull AppPredictionSessionId sessionId,
+            @NonNull List<AppTarget> targets, @NonNull CancellationSignal cancellationSignal,
+            @NonNull Consumer<List<AppTarget>> callback);
+
+    private void doRegisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+            @NonNull IPredictionCallback callback) {
+        final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks == null) {
+            Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+            return;
+        }
+
+        final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+        if (wrapper == null) {
+            callbacks.add(new CallbackWrapper(callback));
+            if (callbacks.size() == 1) {
+                onStartPredictionUpdates();
+            }
+        }
+    }
+
+    /**
+     * Called when any continuous prediction callback is registered.
+     */
+    @MainThread
+    public void onStartPredictionUpdates() {}
+
+    private void doUnregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+            @NonNull IPredictionCallback callback) {
+        final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks == null) {
+            Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+            return;
+        }
+
+        final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+        if (wrapper != null) {
+            callbacks.remove(wrapper);
+            if (callbacks.isEmpty()) {
+                onStopPredictionUpdates();
+            }
+        }
+    }
+
+    /**
+     * Called when there are no longer any continuous prediction callbacks registered.
+     */
+    @MainThread
+    public void onStopPredictionUpdates() {}
+
+    private void doRequestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+        final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks != null && !callbacks.isEmpty()) {
+            onRequestPredictionUpdate(sessionId);
+        }
+    }
+
+    /**
+     * Called by the client app to request target predictions. This method is only called if there
+     * are one or more prediction callbacks registered.
+     * TODO(b/111701043): Add java docs
+     *
+     * @see #updatePredictions(AppPredictionSessionId, List)
+     */
+    @MainThread
+    public abstract void onRequestPredictionUpdate(@NonNull AppPredictionSessionId sessionId);
+
+    private void doDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
+        mSessionCallbacks.remove(sessionId);
+        onDestroyPredictionSession(sessionId);
+    }
+
+    /**
+     * Destroys the interaction session.
+     *
+     * @param sessionId the id of the session to destroy
+     */
+    @MainThread
+    public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {}
+
+    /**
+     * Used by the prediction factory to send back results the client app. The can be called
+     * in response to {@link #onRequestPredictionUpdate(AppPredictionSessionId)} or proactively as
+     * a result of changes in predictions.
+     */
+    public final void updatePredictions(@NonNull AppPredictionSessionId sessionId,
+            @NonNull List<AppTarget> targets) {
+        List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks != null) {
+            for (CallbackWrapper callback : callbacks) {
+                callback.accept(targets);
+            }
+        }
+    }
+
+    /**
+     * Finds the callback wrapper for the given callback.
+     */
+    private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+            IPredictionCallback callback) {
+        for (int i = callbacks.size() - 1; i >= 0; i--) {
+            if (callbacks.get(i).isCallback(callback)) {
+                return callbacks.get(i);
+            }
+        }
+        return null;
+    }
+
+    private static final class CallbackWrapper implements Consumer<List<AppTarget>>,
+            IBinder.DeathRecipient {
+
+        private IPredictionCallback mCallback;
+
+        CallbackWrapper(IPredictionCallback callback) {
+            mCallback = callback;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to link to death: " + e);
+            }
+        }
+
+        public boolean isCallback(@NonNull IPredictionCallback callback) {
+            return mCallback.equals(callback);
+        }
+
+        @Override
+        public void accept(List<AppTarget> ts) {
+            try {
+                if (mCallback != null) {
+                    mCallback.onResult(new ParceledListSlice(ts));
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error sending result:" + e);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            mCallback = null;
+        }
+    }
+}
diff --git a/core/java/android/service/appprediction/IPredictionService.aidl b/core/java/android/service/appprediction/IPredictionService.aidl
new file mode 100644
index 0000000..3a6d166
--- /dev/null
+++ b/core/java/android/service/appprediction/IPredictionService.aidl
@@ -0,0 +1,53 @@
+/*
+ * 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 android.service.appprediction;
+
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.IPredictionCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * Interface from the system to a prediction service.
+ *
+ * @hide
+ */
+oneway interface IPredictionService {
+
+    void onCreatePredictionSession(in AppPredictionContext context,
+            in AppPredictionSessionId sessionId);
+
+    void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event);
+
+    void notifyLocationShown(in AppPredictionSessionId sessionId,  in String launchLocation,
+            in ParceledListSlice targetIds);
+
+    void sortAppTargets(in AppPredictionSessionId sessionId, in ParceledListSlice targets,
+            in IPredictionCallback callback);
+
+    void registerPredictionUpdates(in AppPredictionSessionId sessionId,
+            in IPredictionCallback callback);
+
+    void unregisterPredictionUpdates(in AppPredictionSessionId sessionId,
+            in IPredictionCallback callback);
+
+    void requestPredictionUpdate(in AppPredictionSessionId sessionId);
+
+    void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+}
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 26bf361..e5e028d 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -42,6 +42,7 @@
 import android.view.contentcapture.ContentCaptureSessionId;
 import android.view.contentcapture.IContentCaptureDirectManager;
 import android.view.contentcapture.MainContentCaptureSession;
+import android.view.contentcapture.UserDataRemovalRequest;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -165,7 +166,7 @@
      */
     public final void setContentCaptureWhitelist(@Nullable List<String> packages,
             @Nullable List<ComponentName> activities) {
-        //TODO(b/111276913): implement
+        //TODO(b/122595322): implement
     }
 
     /**
@@ -176,7 +177,7 @@
      */
     public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
             boolean enabled) {
-        //TODO(b/111276913): implement
+        //TODO(b/122595322): implement
     }
 
     /**
@@ -187,7 +188,7 @@
      */
     public final void setPackageContentCaptureEnabled(@NonNull String packageName,
             boolean enabled) {
-        //TODO(b/111276913): implement
+        //TODO(b/122595322): implement
     }
 
     /**
@@ -196,7 +197,7 @@
      */
     @NonNull
     public final Set<ComponentName> getContentCaptureDisabledActivities() {
-        //TODO(b/111276913): implement
+        //TODO(b/122595322): implement
         return null;
     }
 
@@ -206,7 +207,7 @@
      */
     @NonNull
     public final Set<String> getContentCaptureDisabledPackages() {
-        //TODO(b/111276913): implement
+        //TODO(b/122595322): implement
         return null;
     }
 
@@ -255,6 +256,16 @@
         if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
         onContentCaptureEventsRequest(sessionId, new ContentCaptureEventsRequest(event));
     }
+
+    /**
+     * Notifies the service that the app requested to remove data associated with the user.
+     *
+     * @param request the user data requested to be removed
+     */
+    public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+        if (VERBOSE) Log.v(TAG, "onUserDataRemovalRequest()");
+    }
+
     /**
      * Notifies the service of {@link SnapshotData snapshot data} associated with a session.
      *
@@ -311,6 +322,14 @@
             @NonNull String sessionId, int uid, IResultReceiver clientReceiver) {
         mSessionUids.put(sessionId, uid);
         onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
+
+        final int flags = context.getFlags();
+        if ((flags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
+            setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_BY_FLAG_SECURE,
+                    mClientInterface.asBinder());
+            return;
+        }
+
         setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE,
                 mClientInterface.asBinder());
     }
@@ -392,15 +411,16 @@
     }
 
     /**
-     * Sends the state of the {@link ContentCaptureManager} in the cleint app.
+     * Sends the state of the {@link ContentCaptureManager} in the client app.
      *
      * @param clientReceiver receiver in the client app.
+     * @param sessionState state of the session
      * @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the
      * service.
      * @hide
      */
     public static void setClientState(@NonNull IResultReceiver clientReceiver,
-            int sessionStatus, @Nullable IBinder binder) {
+            int sessionState, @Nullable IBinder binder) {
         try {
             final Bundle extras;
             if (binder != null) {
@@ -409,7 +429,7 @@
             } else {
                 extras = null;
             }
-            clientReceiver.send(sessionStatus, extras);
+            clientReceiver.send(sessionState, extras);
         } catch (RemoteException e) {
             Slog.w(TAG, "Error async reporting result to client: " + e);
         }
diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
new file mode 100644
index 0000000..0da8039
--- /dev/null
+++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
@@ -0,0 +1,154 @@
+/*
+ * 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 android.service.contentsuggestions;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.ContentSuggestionsManager;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * @hide
+ */
+@SystemApi
+public abstract class ContentSuggestionsService extends Service {
+
+    private static final String TAG = ContentSuggestionsService.class.getSimpleName();
+
+    private Handler mHandler;
+
+    /**
+     * The action for the intent used to define the content suggestions service.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.contentsuggestions.ContentSuggestionsService";
+
+    private final IContentSuggestionsService mInterface = new IContentSuggestionsService.Stub() {
+        @Override
+        public void provideContextImage(int taskId, GraphicBuffer contextImage,
+                Bundle imageContextRequestExtras) {
+            mHandler.sendMessage(
+                    obtainMessage(ContentSuggestionsService::processContextImage,
+                            ContentSuggestionsService.this, taskId,
+                            Bitmap.createHardwareBitmap(contextImage),
+                            imageContextRequestExtras));
+        }
+
+        @Override
+        public void suggestContentSelections(SelectionsRequest request,
+                ISelectionsCallback callback) {
+            mHandler.sendMessage(obtainMessage(ContentSuggestionsService::suggestContentSelections,
+                    ContentSuggestionsService.this, request, wrapSelectionsCallback(callback)));
+
+        }
+
+        @Override
+        public void classifyContentSelections(ClassificationsRequest request,
+                IClassificationsCallback callback) {
+            mHandler.sendMessage(obtainMessage(ContentSuggestionsService::classifyContentSelections,
+                    ContentSuggestionsService.this, request, wrapClassificationCallback(callback)));
+        }
+
+        @Override
+        public void notifyInteraction(String requestId, Bundle interaction) {
+            mHandler.sendMessage(
+                    obtainMessage(ContentSuggestionsService::notifyInteraction,
+                            ContentSuggestionsService.this, requestId, interaction));
+        }
+    };
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+    }
+
+    /** @hide */
+    @Override
+    public final IBinder onBind(Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+    /**
+     * Called by the system to provide the snapshot for the task associated with the given
+     * {@param taskId}.
+     */
+    public abstract void processContextImage(
+            int taskId, @Nullable Bitmap contextImage, @NonNull Bundle extras);
+
+    /**
+     * Called by a client app to make a request for content selections.
+     */
+    public abstract void suggestContentSelections(@NonNull SelectionsRequest request,
+            @NonNull ContentSuggestionsManager.SelectionsCallback callback);
+
+    /**
+     * Called by a client app to classify the provided content selections.
+     */
+    public abstract void classifyContentSelections(@NonNull ClassificationsRequest request,
+            @NonNull ContentSuggestionsManager.ClassificationsCallback callback);
+
+    /**
+     * Called by a client app to report an interaction.
+     */
+    public abstract void notifyInteraction(@NonNull String requestId, @NonNull Bundle interaction);
+
+    private ContentSuggestionsManager.SelectionsCallback wrapSelectionsCallback(
+            ISelectionsCallback callback) {
+        return (statusCode, selections) -> {
+            try {
+                callback.onContentSelectionsAvailable(statusCode, selections);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error sending result: " + e);
+            }
+        };
+    }
+
+    private ContentSuggestionsManager.ClassificationsCallback wrapClassificationCallback(
+            IClassificationsCallback callback) {
+        return ((statusCode, classifications) -> {
+            try {
+                callback.onContentClassificationsAvailable(statusCode, classifications);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error sending result: " + e);
+            }
+        });
+    }
+}
diff --git a/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl b/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl
new file mode 100644
index 0000000..1926478
--- /dev/null
+++ b/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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 android.service.contentsuggestions;
+
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+
+/**
+ * Interface from the system to an implementation of a content suggestions service.
+ *
+ * @hide
+ */
+oneway interface IContentSuggestionsService {
+    void provideContextImage(
+            int taskId,
+            in GraphicBuffer contextImage,
+            in Bundle imageContextRequestExtras);
+    void suggestContentSelections(
+            in SelectionsRequest request,
+            in ISelectionsCallback callback);
+    void classifyContentSelections(
+            in ClassificationsRequest request,
+            in IClassificationsCallback callback);
+    void notifyInteraction(in String requestId, in Bundle interaction);
+}
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 433483f7..7a58681 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -16,6 +16,7 @@
 
 package android.text.style;
 
+import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
@@ -370,6 +371,7 @@
     /**
      * @return The color of the underline for that span, or 0 if there is no underline
      */
+    @ColorInt
     public int getUnderlineColor() {
         // The order here should match what is used in updateDrawState
         final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 8b97e0e..0edcb3d 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -22,7 +22,9 @@
 import android.text.TextUtils;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Util class to get feature flag information.
@@ -37,8 +39,11 @@
     public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
     public static final String SAFETY_HUB = "settings_safety_hub";
     public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
+    public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled";
 
     private static final Map<String, String> DEFAULT_FLAGS;
+    private static final Set<String> OBSERVABLE_FLAGS;
+
     static {
         DEFAULT_FLAGS = new HashMap<>();
         DEFAULT_FLAGS.put("settings_audio_switcher", "true");
@@ -54,6 +59,10 @@
         DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
         DEFAULT_FLAGS.put(SAFETY_HUB, "false");
         DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
+        DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false");
+
+        OBSERVABLE_FLAGS = new HashSet<>();
+        OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED);
     }
 
     /**
@@ -90,6 +99,16 @@
      */
     public static void setEnabled(Context context, String feature, boolean enabled) {
         SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
+
+        // Also update Settings.Global if needed so that we can observe it via observer.
+        if (OBSERVABLE_FLAGS.contains(feature)) {
+            setObservableFlag(context, feature, enabled);
+        }
+    }
+
+    private static void setObservableFlag(Context context, String feature, boolean enabled) {
+        Settings.Global.putString(
+                context.getContentResolver(), feature, enabled ? "true" : "false");
     }
 
     /**
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a86abe5..9d3552f 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -18,6 +18,9 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.graphics.Matrix;
@@ -30,6 +33,7 @@
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
 
+import java.lang.annotation.Retention;
 import java.util.Objects;
 
 /**
@@ -462,7 +466,7 @@
 
     /**
      * This flag indicates that the event has been generated by a gesture generator. It
-     * provides a hint to the GestureDector to not apply any touch slop.
+     * provides a hint to the GestureDetector to not apply any touch slop.
      *
      * @hide
      */
@@ -1391,6 +1395,42 @@
     };
 
     /**
+     * Classification constant: None.
+     *
+     * No additional information is available about the current motion event stream.
+     *
+     * @see #getClassification
+     */
+    public static final int CLASSIFICATION_NONE = 0;
+
+    /**
+     * Classification constant: Ambiguous gesture.
+     *
+     * The user's intent with respect to the current event stream is not yet determined.
+     * Gestural actions, such as scrolling, should be inhibited until the classification resolves
+     * to another value or the event stream ends.
+     *
+     * @see #getClassification
+     */
+    public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1;
+
+    /**
+     * Classification constant: Deep press.
+     *
+     * The current event stream represents the user intentionally pressing harder on the screen.
+     * This classification type should be used to accelerate the long press behaviour.
+     *
+     * @see #getClassification
+     */
+    public static final int CLASSIFICATION_DEEP_PRESS = 2;
+
+    /** @hide */
+    @Retention(SOURCE)
+    @IntDef(prefix = { "CLASSIFICATION" }, value = {
+            CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS})
+    public @interface Classification {};
+
+    /**
      * Tool type constant: Unknown tool type.
      * This constant is used when the tool type is not known or is not relevant,
      * such as for a trackball or other non-pointing device.
@@ -1478,7 +1518,7 @@
 
     private static native long nativeInitialize(long nativePtr,
             int deviceId, int source, int displayId, int action, int flags, int edgeFlags,
-            int metaState, int buttonState,
+            int metaState, int buttonState, @Classification int classification,
             float xOffset, float yOffset, float xPrecision, float yPrecision,
             long downTimeNanos, long eventTimeNanos,
             int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords);
@@ -1548,6 +1588,8 @@
     @CriticalNative
     private static native void nativeSetButtonState(long nativePtr, int buttonState);
     @CriticalNative
+    private static native int nativeGetClassification(long nativePtr);
+    @CriticalNative
     private static native int nativeGetActionButton(long nativePtr);
     @CriticalNative
     private static native void nativeSetActionButton(long nativePtr, int actionButton);
@@ -1648,7 +1690,7 @@
         MotionEvent ev = obtain();
         ev.mNativePtr = nativeInitialize(ev.mNativePtr,
                 deviceId, source, displayId, action, flags, edgeFlags, metaState, buttonState,
-                0, 0, xPrecision, yPrecision,
+                CLASSIFICATION_NONE, 0, 0, xPrecision, yPrecision,
                 downTime * NS_PER_MS, eventTime * NS_PER_MS,
                 pointerCount, pointerProperties, pointerCoords);
         if (ev.mNativePtr == 0) {
@@ -1833,7 +1875,7 @@
 
             ev.mNativePtr = nativeInitialize(ev.mNativePtr,
                     deviceId, source, displayId,
-                    action, 0, edgeFlags, metaState, 0,
+                    action, 0, edgeFlags, metaState, 0 /*buttonState*/, CLASSIFICATION_NONE,
                     0, 0, xPrecision, yPrecision,
                     downTime * NS_PER_MS, eventTime * NS_PER_MS,
                     1, pp, pc);
@@ -2539,6 +2581,18 @@
     }
 
     /**
+     * Returns the classification for the current gesture.
+     * The classification may change as more events become available for the same gesture.
+     *
+     * @see #CLASSIFICATION_NONE
+     * @see #CLASSIFICATION_AMBIGUOUS_GESTURE
+     * @see #CLASSIFICATION_DEEP_PRESS
+     */
+    public @Classification int getClassification() {
+        return nativeGetClassification(mNativePtr);
+    }
+
+    /**
      * Gets which button has been modified during a press or release action.
      *
      * For actions other than {@link #ACTION_BUTTON_PRESS} and {@link #ACTION_BUTTON_RELEASE}
@@ -3172,7 +3226,7 @@
     /**
      * Adds all of the movement samples of the specified event to this one if
      * it is compatible.  To be compatible, the event must have the same device id,
-     * source, display id, action, flags, pointer count, pointer properties.
+     * source, display id, action, flags, classification, pointer count, pointer properties.
      *
      * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
      *
@@ -3194,7 +3248,9 @@
         if (nativeGetDeviceId(mNativePtr) != nativeGetDeviceId(event.mNativePtr)
                 || nativeGetSource(mNativePtr) != nativeGetSource(event.mNativePtr)
                 || nativeGetDisplayId(mNativePtr) != nativeGetDisplayId(event.mNativePtr)
-                || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)) {
+                || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)
+                || nativeGetClassification(mNativePtr)
+                        != nativeGetClassification(event.mNativePtr)) {
             return false;
         }
 
@@ -3282,7 +3338,7 @@
                     nativeGetDisplayId(mNativePtr),
                     nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
                     nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
-                    nativeGetButtonState(mNativePtr),
+                    nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
                     nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
                     nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
                     nativeGetDownTimeNanos(mNativePtr),
@@ -3376,7 +3432,7 @@
                             nativeGetDisplayId(mNativePtr),
                             newAction, nativeGetFlags(mNativePtr),
                             nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
-                            nativeGetButtonState(mNativePtr),
+                            nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
                             nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
                             nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
                             nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
@@ -3409,6 +3465,8 @@
         }
 
         appendUnless("0", msg, ", buttonState=", MotionEvent.buttonStateToString(getButtonState()));
+        appendUnless(classificationToString(CLASSIFICATION_NONE), msg, ", classification=",
+                classificationToString(getClassification()));
         appendUnless("0", msg, ", metaState=", KeyEvent.metaStateToString(getMetaState()));
         appendUnless("0", msg, ", flags=0x", Integer.toHexString(getFlags()));
         appendUnless("0", msg, ", edgeFlags=0x", Integer.toHexString(getEdgeFlags()));
@@ -3547,6 +3605,26 @@
     }
 
     /**
+     * Returns a string that represents the symbolic name of the specified classification.
+     *
+     * @param classification The classification type.
+     * @return The symbolic name of this classification.
+     * @hide
+     */
+    public static String classificationToString(@Classification int classification) {
+        switch (classification) {
+            case CLASSIFICATION_NONE:
+                return "NONE";
+            case CLASSIFICATION_AMBIGUOUS_GESTURE:
+                return "AMBIGUOUS_GESTURE";
+            case CLASSIFICATION_DEEP_PRESS:
+                return "DEEP_PRESS";
+
+        }
+        return "NONE";
+    }
+
+    /**
      * Returns a string that represents the symbolic name of the specified tool type
      * such as "TOOL_TYPE_FINGER" or an equivalent numeric constant such as "42" if unknown.
      *
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cb2c40e..cd0e579 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8199,6 +8199,19 @@
      * <p>The populated structure is then passed to the service through
      * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}.
      *
+     * <p><b>Note: </b>views that manage a virtual structure under this view must populate just
+     * the node representing this view and return right away, then asynchronously report (not
+     * necessarily in the UI thread) when the children nodes appear, disappear or have their text
+     * changed by calling
+     * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)},
+     * {@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
+     * {@code autofillId} for a child can be obtained either through
+     * {@code childStructure.getAutofillId()} or
+     * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}.
+     *
      * <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
      * <ul>
      *   <li>{@link ViewStructure#setChildCount(int)}
@@ -8235,10 +8248,6 @@
         } else {
             structure.setId(id, null, null, null);
         }
-        if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
-            //TODO(b/111276913): STOPSHIP - don't set it if not needed
-            structure.setDataIsSensitive(false);
-        }
 
         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL
                 || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) {
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 95a346f..c630177 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -794,7 +794,7 @@
      * @deprecated All window animations are running with detached wallpaper.
      */
     public boolean getDetachWallpaper() {
-        return false;
+        return true;
     }
 
     /**
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index e962e7c..ff45efd 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 com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
@@ -24,9 +26,12 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
 
 import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -48,6 +53,11 @@
 
     private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
 
+    /**
+     * Timeout for calls to system_server.
+     */
+    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
@@ -116,8 +126,8 @@
 
     /** @hide */
     public void onActivityStarted(@NonNull IBinder applicationToken,
-            @NonNull ComponentName activityComponent) {
-        getMainContentCaptureSession().start(applicationToken, activityComponent);
+            @NonNull ComponentName activityComponent, int flags) {
+        getMainContentCaptureSession().start(applicationToken, activityComponent, flags);
     }
 
     /** @hide */
@@ -142,8 +152,21 @@
      */
     @Nullable
     public ComponentName getServiceComponentName() {
-        //TODO(b/121047489): implement
-        return null;
+        if (!isContentCaptureEnabled()) {
+            return null;
+        }
+        // Wait for system server to return the component name.
+        final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+        mHandler.sendMessage(obtainMessage(
+                ContentCaptureManager::handleReceiverServiceComponentName,
+                this, mContext.getUserId(), resultReceiver));
+
+        try {
+            return resultReceiver.getParcelableResult();
+        } catch (RemoteException e) {
+            // Unable to retrieve component name in a reasonable amount of time.
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -191,4 +214,14 @@
             pw.print(prefix); pw.println("No sessions");
         }
     }
+
+
+    /** Retrieves the component name of the target content capture service through system_server. */
+    private void handleReceiverServiceComponentName(int userId, IResultReceiver resultReceiver) {
+        try {
+            mService.getReceiverServiceComponentName(userId, resultReceiver);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Unable to retrieve service component name: " + e);
+        }
+    }
 }
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 344b9973..6890beaf 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -46,8 +46,7 @@
 
     /**
      * Used on {@link #notifyViewTextChanged(AutofillId, CharSequence, int)} to indicate that the
-     *
-     * thext change was caused by user input (for example, through IME).
+     * text change was caused by user input (for example, through IME).
      */
     public static final int FLAG_USER_INPUT = 0x1;
 
@@ -86,6 +85,13 @@
      */
     public static final int STATE_DISABLED_DUPLICATED_ID = 4;
 
+    /**
+     * Session is disabled by FLAG_SECURE
+     *
+     * @hide
+     */
+    public static final int STATE_DISABLED_BY_FLAG_SECURE = 5;
+
     private static final int INITIAL_CHILDREN_CAPACITY = 5;
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -178,7 +184,7 @@
 
             mCloseGuard.close();
 
-            //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
+            // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
             // id) and send it to the cache of batched commands
             if (VERBOSE) {
                 Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
@@ -295,6 +301,26 @@
     }
 
     /**
+     * Creates a new {@link AutofillId} for a virtual child, so it can be used to uniquely identify
+     * the children in the session.
+     *
+     * @param parentId id of the virtual view parent (it can be obtained by calling
+     * {@link ViewStructure#getAutofillId()} on the parent).
+     * @param virtualChildId id of the virtual child, relative to the parent.
+     *
+     * @return if for the virtual child
+     *
+     * @throws IllegalArgumentException if the {@code parentId} is a virtual child id.
+     */
+    public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) {
+        Preconditions.checkNotNull(parentId);
+        Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children");
+        // TODO(b/121197119): we need to add the session id to the AutofillId to make them unique
+        // per session
+        return new AutofillId(parentId, virtualChildId);
+    }
+
+    /**
      * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
      * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
      *
@@ -303,12 +329,11 @@
      * @param virtualId id of the virtual child, relative to the parent.
      *
      * @return a new {@link ViewStructure} that can be used for Content Capture purposes.
-     *
-     * @hide
      */
     @NonNull
     public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
             int virtualId) {
+        // TODO(b/121197119): use the constructor that takes a session id / assert on unit test.
         return new ViewNode.ViewStructureImpl(parentId, virtualId);
     }
 
@@ -356,6 +381,8 @@
                 return "DISABLED_NO_SERVICE";
             case STATE_DISABLED_DUPLICATED_ID:
                 return "DISABLED_DUPLICATED_ID";
+            case STATE_DISABLED_BY_FLAG_SECURE:
+                return "DISABLED_FLAG_SECURE";
             default:
                 return "INVALID:" + state;
         }
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index 01776f8..be9c00f 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -32,7 +32,28 @@
   * @hide
   */
 oneway interface IContentCaptureManager {
+    /**
+     * Starts a new session for the provided {@code userId} running as part of the
+     * app's activity identified by {@code activityToken}/{@code componentName}.
+     *
+     * @param sessionId Unique session id as provided by the app.
+     * @param flags Meta flags that enable or disable content capture (see
+     *     {@link IContentCaptureContext#flags}).
+     */
     void startSession(int userId, IBinder activityToken, in ComponentName componentName,
                       String sessionId, int flags, in IResultReceiver result);
+
+    /**
+     * Marks the end of a session for the provided {@code userId} identified by
+     * the corresponding {@code startSession}'s {@code sessionId}.
+     */
     void finishSession(int userId, String sessionId);
+
+    /**
+     * Returns the content capture service's component name (if enabled and
+     * connected).
+     * @param Receiver of the content capture service's @{code ComponentName}
+     *     provided {@code Bundle} with key "{@code EXTRA}".
+     */
+    void getReceiverServiceComponentName(int userId, in IResultReceiver result);
 }
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 8be887b..a29aaf0 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -157,7 +157,8 @@
      *
      * @hide
      */
-    void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent) {
+    void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent,
+            int flags) {
         if (!isContentCaptureEnabled()) return;
 
         if (VERBOSE) {
@@ -166,7 +167,7 @@
         }
 
         mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
-                applicationToken, activityComponent));
+                applicationToken, activityComponent, flags));
     }
 
     @Override
@@ -181,7 +182,8 @@
                 obtainMessage(MainContentCaptureSession::handleDestroySession, this));
     }
 
-    private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
+    private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
+            int flags) {
         if (mState != STATE_UNKNOWN) {
             // TODO(b/111276913): revisit this scenario
             Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
@@ -196,7 +198,6 @@
             Log.v(TAG, "handleStartSession(): token=" + token + ", act="
                     + getActivityDebugName() + ", id=" + mId);
         }
-        final int flags = 0; // TODO(b/111276913): get proper flags
 
         try {
             if (mSystemServerInterface == null) return;
@@ -245,7 +246,11 @@
                 Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
             }
         }
-        if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID) {
+
+        // TODO(b/111276913): change the resultCode to use flags so there's just one flag for
+        // disabled stuff
+        if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID
+                || resultCode == STATE_DISABLED_BY_FLAG_SECURE) {
             mDisabled.set(true);
             handleResetSession(/* resetState= */ false);
         } else {
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 86b89adb..b7a486a 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -24,6 +24,7 @@
 import android.os.Bundle;
 import android.os.LocaleList;
 import android.os.Parcel;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewParent;
@@ -32,30 +33,188 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
-//TODO(b/111276913): add javadocs / implement Parcelable / implement
-//TODO(b/111276913): for now it's extending ViewNode directly as it needs most of its properties,
+//TODO(b/122484602): add javadocs / implement Parcelable / implement
+//TODO(b/122484602): for now it's extending ViewNode directly as it needs most of its properties,
 // but it might be better to create a common, abstract android.view.ViewNode class that both extend
 // instead
 /** @hide */
 @SystemApi
 public final class ViewNode extends AssistStructure.ViewNode {
 
-    private static final String TAG = "ViewNode";
+    private static final String TAG = ViewNode.class.getSimpleName();
+    private static final boolean VERBOSE = false;
+
+    private static final long FLAGS_HAS_TEXT = 1L << 0;
+    private static final long FLAGS_HAS_COMPLEX_TEXT = 1L << 1;
+    private static final long FLAGS_VISIBILITY_MASK = View.VISIBLE | View.INVISIBLE | View.GONE;
+    private static final long FLAGS_HAS_CLASSNAME = 1L << 4;
+    private static final long FLAGS_HAS_AUTOFILL_ID = 1L << 5;
+    private static final long FLAGS_HAS_AUTOFILL_PARENT_ID = 1L << 6;
+    private static final long FLAGS_HAS_ID = 1L << 7;
+    private static final long FLAGS_HAS_LARGE_COORDS = 1L << 8;
+    private static final long FLAGS_HAS_SCROLL = 1L << 9;
+    private static final long FLAGS_ASSIST_BLOCKED = 1L << 10;
+    private static final long FLAGS_DISABLED = 1L << 11;
+    private static final long FLAGS_CLICKABLE = 1L << 12;
+    private static final long FLAGS_LONG_CLICKABLE = 1L << 13;
+    private static final long FLAGS_CONTEXT_CLICKABLE = 1L << 14;
+    private static final long FLAGS_FOCUSABLE = 1L << 15;
+    private static final long FLAGS_FOCUSED = 1L << 16;
+    private static final long FLAGS_ACCESSIBILITY_FOCUSED = 1L << 17;
+    private static final long FLAGS_CHECKABLE = 1L << 18;
+    private static final long FLAGS_CHECKED = 1L << 19;
+    private static final long FLAGS_SELECTED = 1L << 20;
+    private static final long FLAGS_ACTIVATED = 1L << 21;
+    private static final long FLAGS_OPAQUE = 1L << 22;
+    private static final long FLAGS_HAS_MATRIX = 1L << 23;
+    private static final long FLAGS_HAS_ELEVATION = 1L << 24;
+    private static final long FLAGS_HAS_ALPHA = 1L << 25;
+    private static final long FLAGS_HAS_CONTENT_DESCRIPTION = 1L << 26;
+    private static final long FLAGS_HAS_EXTRAS = 1L << 27;
+    private static final long FLAGS_HAS_LOCALE_LIST = 1L << 28;
+    private static final long FLAGS_HAS_INPUT_TYPE = 1L << 29;
+    private static final long FLAGS_HAS_MIN_TEXT_EMS = 1L << 30;
+    private static final long FLAGS_HAS_MAX_TEXT_EMS = 1L << 31;
+    private static final long FLAGS_HAS_MAX_TEXT_LENGTH = 1L << 32;
+    private static final long FLAGS_HAS_TEXT_ID_ENTRY = 1L << 33;
+    private static final long FLAGS_HAS_AUTOFILL_TYPE = 1L << 34;
+    private static final long FLAGS_HAS_AUTOFILL_VALUE = 1L << 35;
+    private static final long FLAGS_HAS_AUTOFILL_HINTS = 1L << 36;
+    private static final long FLAGS_HAS_AUTOFILL_OPTIONS = 1L << 37;
+
+    /** Flags used to optimize what's written to the parcel */
+    private long mFlags;
 
     private AutofillId mParentAutofillId;
 
-    // TODO(b/111276913): temporarily setting some fields here while they're not accessible from the
-    // superclass
     private AutofillId mAutofillId;
-    private CharSequence mText;
+    private ViewNodeText mText;
     private String mClassName;
+    private int mId = View.NO_ID;
+    private String mIdPackage;
+    private String mIdType;
+    private String mIdEntry;
+    private int mX;
+    private int mY;
+    private int mScrollX;
+    private int mScrollY;
+    private int mWidth;
+    private int mHeight;
+    private Matrix mMatrix;
+    private float mElevation;
+    private float mAlpha = 1.0f;
+    private CharSequence mContentDescription;
+    private Bundle mExtras;
+    private LocaleList mLocaleList;
+    private int mInputType;
+    private int mMinEms = -1;
+    private int mMaxEms = -1;
+    private int mMaxLength = -1;
+    private String mTextIdEntry;
+    private @View.AutofillType int mAutofillType = View.AUTOFILL_TYPE_NONE;
+    private String[] mAutofillHints;
+    private AutofillValue mAutofillValue;
+    private CharSequence[] mAutofillOptions;
 
     /** @hide */
     public ViewNode() {
     }
 
+    private ViewNode(long nodeFlags, @NonNull Parcel parcel) {
+        mFlags = nodeFlags;
+
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
+            mAutofillId = parcel.readParcelable(null);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
+            mParentAutofillId = parcel.readParcelable(null);
+        }
+        if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
+            mText = new ViewNodeText(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) {
+            mClassName = parcel.readString();
+        }
+        if ((nodeFlags & FLAGS_HAS_ID) != 0) {
+            mId = parcel.readInt();
+            if (mId != View.NO_ID) {
+                mIdEntry = parcel.readString();
+                if (mIdEntry != null) {
+                    mIdType = parcel.readString();
+                    mIdPackage = parcel.readString();
+                }
+            }
+        }
+        if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) {
+            mX = parcel.readInt();
+            mY = parcel.readInt();
+            mWidth = parcel.readInt();
+            mHeight = parcel.readInt();
+        } else {
+            int val = parcel.readInt();
+            mX = val & 0x7fff;
+            mY = (val >> 16) & 0x7fff;
+            val = parcel.readInt();
+            mWidth = val & 0x7fff;
+            mHeight = (val >> 16) & 0x7fff;
+        }
+        if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) {
+            mScrollX = parcel.readInt();
+            mScrollY = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_MATRIX) != 0) {
+            mMatrix = new Matrix();
+            final float[] tmpMatrix = new float[9];
+            parcel.readFloatArray(tmpMatrix);
+            mMatrix.setValues(tmpMatrix);
+        }
+        if ((nodeFlags & FLAGS_HAS_ELEVATION) != 0) {
+            mElevation = parcel.readFloat();
+        }
+        if ((nodeFlags & FLAGS_HAS_ALPHA) != 0) {
+            mAlpha = parcel.readFloat();
+        }
+        if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) {
+            mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        }
+        if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) {
+            mExtras = parcel.readBundle();
+        }
+        if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
+            mLocaleList = parcel.readParcelable(null);
+        }
+        if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) {
+            mInputType = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) {
+            mMinEms = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) {
+            mMaxEms = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) {
+            mMaxLength = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) {
+            mTextIdEntry = parcel.readString();
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) {
+            mAutofillType = parcel.readInt();
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) {
+            mAutofillHints = parcel.readStringArray();
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
+            mAutofillValue = parcel.readParcelable(null);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
+            mAutofillOptions = parcel.readCharSequenceArray();
+        }
+    }
+
     /**
      * Returns the {@link AutofillId} of this view's parent, if the parent is also part of the
      * screen observation tree.
@@ -65,53 +224,446 @@
         return mParentAutofillId;
     }
 
-    // TODO(b/111276913): temporarily overwriting some methods
     @Override
     public AutofillId getAutofillId() {
         return mAutofillId;
     }
+
     @Override
     public CharSequence getText() {
-        return mText;
+        return mText != null ? mText.mText : null;
     }
+
     @Override
     public String getClassName() {
         return mClassName;
     }
 
+    @Override
+    public int getId() {
+        return mId;
+    }
+
+    @Override
+    public String getIdPackage() {
+        return mIdPackage;
+    }
+
+    @Override
+    public String getIdType() {
+        return mIdType;
+    }
+
+    @Override
+    public String getIdEntry() {
+        return mIdEntry;
+    }
+
+    @Override
+    public int getLeft() {
+        return mX;
+    }
+
+    @Override
+    public int getTop() {
+        return mY;
+    }
+
+    @Override
+    public int getScrollX() {
+        return mScrollX;
+    }
+
+    @Override
+    public int getScrollY() {
+        return mScrollY;
+    }
+
+    @Override
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public boolean isAssistBlocked() {
+        return (mFlags & FLAGS_ASSIST_BLOCKED) != 0;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return (mFlags & FLAGS_DISABLED) == 0;
+    }
+
+    @Override
+    public boolean isClickable() {
+        return (mFlags & FLAGS_CLICKABLE) != 0;
+    }
+
+    @Override
+    public boolean isLongClickable() {
+        return (mFlags & FLAGS_LONG_CLICKABLE) != 0;
+    }
+
+    @Override
+    public boolean isContextClickable() {
+        return (mFlags & FLAGS_CONTEXT_CLICKABLE) != 0;
+    }
+
+    @Override
+    public boolean isFocusable() {
+        return (mFlags & FLAGS_FOCUSABLE) != 0;
+    }
+
+    @Override
+    public boolean isFocused() {
+        return (mFlags & FLAGS_FOCUSED) != 0;
+    }
+
+    @Override
+    public boolean isAccessibilityFocused() {
+        return (mFlags & FLAGS_ACCESSIBILITY_FOCUSED) != 0;
+    }
+
+    @Override
+    public boolean isCheckable() {
+        return (mFlags & FLAGS_CHECKABLE) != 0;
+    }
+
+    @Override
+    public boolean isChecked() {
+        return (mFlags & FLAGS_CHECKED) != 0;
+    }
+
+    @Override
+    public boolean isSelected() {
+        return (mFlags & FLAGS_SELECTED) != 0;
+    }
+
+    @Override
+    public boolean isActivated() {
+        return (mFlags & FLAGS_ACTIVATED) != 0;
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return (mFlags & FLAGS_OPAQUE) != 0;
+    }
+
+    @Override
+    public Matrix getTransformation() {
+        return mMatrix;
+    }
+
+    @Override
+    public float getElevation() {
+        return mElevation;
+    }
+
+    @Override
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    @Override
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public String getHint() {
+        return mText != null ? mText.mHint : null;
+    }
+
+    @Override
+    public int getTextSelectionStart() {
+        return mText != null ? mText.mTextSelectionStart : -1;
+    }
+
+    @Override
+    public int getTextSelectionEnd() {
+        return mText != null ? mText.mTextSelectionEnd : -1;
+    }
+
+    @Override
+    public int getTextColor() {
+        return mText != null ? mText.mTextColor : TEXT_COLOR_UNDEFINED;
+    }
+
+    @Override
+    public int getTextBackgroundColor() {
+        return mText != null ? mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED;
+    }
+
+    @Override
+    public float getTextSize() {
+        return mText != null ? mText.mTextSize : 0;
+    }
+
+    @Override
+    public int getTextStyle() {
+        return mText != null ? mText.mTextStyle : 0;
+    }
+
+    @Override
+    public int[] getTextLineCharOffsets() {
+        return mText != null ? mText.mLineCharOffsets : null;
+    }
+
+    @Override
+    public int[] getTextLineBaselines() {
+        return mText != null ? mText.mLineBaselines : null;
+    }
+
+    @Override
+    public int getVisibility() {
+        return (int) (mFlags & FLAGS_VISIBILITY_MASK);
+    }
+
+    @Override
+    public int getInputType() {
+        return mInputType;
+    }
+
+    @Override
+    public int getMinTextEms() {
+        return mMinEms;
+    }
+
+    @Override
+    public int getMaxTextEms() {
+        return mMaxEms;
+    }
+
+    @Override
+    public int getMaxTextLength() {
+        return mMaxLength;
+    }
+
+    @Override
+    public String getTextIdEntry() {
+        return mTextIdEntry;
+    }
+
+    @Override
+    public @View.AutofillType int getAutofillType() {
+        return mAutofillType;
+    }
+
+    @Override
+    @Nullable public String[] getAutofillHints() {
+        return mAutofillHints;
+    }
+
+    @Override
+    @Nullable public AutofillValue getAutofillValue() {
+        return mAutofillValue;
+    }
+
+    @Override
+    @Nullable public CharSequence[] getAutofillOptions() {
+        return mAutofillOptions;
+    }
+
+    @Override
+    public LocaleList getLocaleList() {
+        return mLocaleList;
+    }
+
+    private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) {
+        long nodeFlags = mFlags;
+
+        if (mAutofillId != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_ID;
+        }
+
+        if (mParentAutofillId != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_PARENT_ID;
+        }
+
+        if (mText != null) {
+            nodeFlags |= FLAGS_HAS_TEXT;
+            if (!mText.isSimple()) {
+                nodeFlags |= FLAGS_HAS_COMPLEX_TEXT;
+            }
+        }
+        if (mClassName != null) {
+            nodeFlags |= FLAGS_HAS_CLASSNAME;
+        }
+        if (mId != View.NO_ID) {
+            nodeFlags |= FLAGS_HAS_ID;
+        }
+        if ((mX & ~0x7fff) != 0 || (mY & ~0x7fff) != 0
+                || (mWidth & ~0x7fff) != 0 | (mHeight & ~0x7fff) != 0) {
+            nodeFlags |= FLAGS_HAS_LARGE_COORDS;
+        }
+        if (mScrollX != 0 || mScrollY != 0) {
+            nodeFlags |= FLAGS_HAS_SCROLL;
+        }
+        if (mMatrix != null) {
+            nodeFlags |= FLAGS_HAS_MATRIX;
+        }
+        if (mElevation != 0) {
+            nodeFlags |= FLAGS_HAS_ELEVATION;
+        }
+        if (mAlpha != 1.0f) {
+            nodeFlags |= FLAGS_HAS_ALPHA;
+        }
+        if (mContentDescription != null) {
+            nodeFlags |= FLAGS_HAS_CONTENT_DESCRIPTION;
+        }
+        if (mExtras != null) {
+            nodeFlags |= FLAGS_HAS_EXTRAS;
+        }
+        if (mLocaleList != null) {
+            nodeFlags |= FLAGS_HAS_LOCALE_LIST;
+        }
+        if (mInputType != 0) {
+            nodeFlags |= FLAGS_HAS_INPUT_TYPE;
+        }
+        if (mMinEms > -1) {
+            nodeFlags |= FLAGS_HAS_MIN_TEXT_EMS;
+        }
+        if (mMaxEms > -1) {
+            nodeFlags |= FLAGS_HAS_MAX_TEXT_EMS;
+        }
+        if (mMaxLength > -1) {
+            nodeFlags |= FLAGS_HAS_MAX_TEXT_LENGTH;
+        }
+        if (mTextIdEntry != null) {
+            nodeFlags |= FLAGS_HAS_TEXT_ID_ENTRY;
+        }
+        if (mAutofillValue != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_VALUE;
+        }
+        if (mAutofillType != View.AUTOFILL_TYPE_NONE) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_TYPE;
+        }
+        if (mAutofillHints != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_HINTS;
+        }
+        if (mAutofillOptions != null) {
+            nodeFlags |= FLAGS_HAS_AUTOFILL_OPTIONS;
+        }
+        parcel.writeLong(nodeFlags);
+
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
+            parcel.writeParcelable(mAutofillId, parcelFlags);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
+            parcel.writeParcelable(mParentAutofillId, parcelFlags);
+        }
+        if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
+            mText.writeToParcel(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) {
+            parcel.writeString(mClassName);
+        }
+        if ((nodeFlags & FLAGS_HAS_ID) != 0) {
+            parcel.writeInt(mId);
+            if (mId != View.NO_ID) {
+                parcel.writeString(mIdEntry);
+                if (mIdEntry != null) {
+                    parcel.writeString(mIdType);
+                    parcel.writeString(mIdPackage);
+                }
+            }
+        }
+        if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) {
+            parcel.writeInt(mX);
+            parcel.writeInt(mY);
+            parcel.writeInt(mWidth);
+            parcel.writeInt(mHeight);
+        } else {
+            parcel.writeInt((mY << 16) | mX);
+            parcel.writeInt((mHeight << 16) | mWidth);
+        }
+        if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) {
+            parcel.writeInt(mScrollX);
+            parcel.writeInt(mScrollY);
+        }
+        if ((nodeFlags & FLAGS_HAS_MATRIX) != 0) {
+            //TODO(b/122484602): use a singleton tmpMatrix (if logic is not moved to superclass)
+            final float[] tmpMatrix = new float[9];
+            mMatrix.getValues(tmpMatrix);
+            parcel.writeFloatArray(tmpMatrix);
+        }
+        if ((nodeFlags & FLAGS_HAS_ELEVATION) != 0) {
+            parcel.writeFloat(mElevation);
+        }
+        if ((nodeFlags & FLAGS_HAS_ALPHA) != 0) {
+            parcel.writeFloat(mAlpha);
+        }
+        if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) {
+            TextUtils.writeToParcel(mContentDescription, parcel, 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) {
+            parcel.writeBundle(mExtras);
+        }
+        if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
+            parcel.writeParcelable(mLocaleList, 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) {
+            parcel.writeInt(mInputType);
+        }
+        if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) {
+            parcel.writeInt(mMinEms);
+        }
+        if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) {
+            parcel.writeInt(mMaxEms);
+        }
+        if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) {
+            parcel.writeInt(mMaxLength);
+        }
+        if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) {
+            parcel.writeString(mTextIdEntry);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) {
+            parcel.writeInt(mAutofillType);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) {
+            parcel.writeStringArray(mAutofillHints);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
+            parcel.writeParcelable(mAutofillValue, 0);
+        }
+        if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
+            parcel.writeCharSequenceArray(mAutofillOptions);
+        }
+    }
+
     /** @hide */
     public static void writeToParcel(@NonNull Parcel parcel, @Nullable ViewNode node, int flags) {
         if (node == null) {
-            parcel.writeParcelable(null, flags);
-            return;
+            parcel.writeLong(0);
+        } else {
+            node.writeSelfToParcel(parcel, flags);
         }
-        parcel.writeParcelable(node.mAutofillId, flags);
-        parcel.writeParcelable(node.mParentAutofillId, flags);
-        parcel.writeCharSequence(node.mText);
-        parcel.writeString(node.mClassName);
     }
 
     /** @hide */
     public static @Nullable ViewNode readFromParcel(@NonNull Parcel parcel) {
-        final AutofillId id = parcel.readParcelable(null);
-        if (id == null) return null;
-
-        final ViewNode node = new ViewNode();
-
-        node.mAutofillId = id;
-        node.mParentAutofillId = parcel.readParcelable(null);
-        node.mText = parcel.readCharSequence();
-        node.mClassName = parcel.readString();
-
-        return node;
+        final long nodeFlags = parcel.readLong();
+        return nodeFlags == 0 ? new ViewNode() : new ViewNode(nodeFlags, parcel);
     }
 
     /** @hide */
-    static final class ViewStructureImpl extends ViewStructure {
+    @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+    public static final class ViewStructureImpl extends ViewStructure {
 
         final ViewNode mNode = new ViewNode();
 
-        ViewStructureImpl(@NonNull View view) {
+        @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+        public ViewStructureImpl(@NonNull View view) {
             mNode.mAutofillId = Preconditions.checkNotNull(view).getAutofillId();
             final ViewParent parent = view.getParent();
             if (parent instanceof View) {
@@ -119,179 +671,212 @@
             }
         }
 
-        ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) {
+        @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+        public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) {
             mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
             mNode.mAutofillId = new AutofillId(parentId, virtualId);
         }
 
+        @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
+        public ViewNode getNode() {
+            return mNode;
+        }
+
         @Override
         public void setId(int id, String packageName, String typeName, String entryName) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mId = id;
+            mNode.mIdPackage = packageName;
+            mNode.mIdType = typeName;
+            mNode.mIdEntry = entryName;
         }
 
         @Override
         public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mX = left;
+            mNode.mY = top;
+            mNode.mScrollX = scrollX;
+            mNode.mScrollY = scrollY;
+            mNode.mWidth = width;
+            mNode.mHeight = height;
         }
 
         @Override
         public void setTransformation(Matrix matrix) {
-            // TODO(b/111276913): implement or move to superclass
+            if (matrix == null) {
+                mNode.mMatrix = null;
+            } else {
+                mNode.mMatrix = new Matrix(matrix);
+            }
         }
 
         @Override
         public void setElevation(float elevation) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mElevation = elevation;
         }
 
         @Override
         public void setAlpha(float alpha) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mAlpha = alpha;
         }
 
         @Override
         public void setVisibility(int visibility) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_VISIBILITY_MASK)
+                    | (visibility & FLAGS_VISIBILITY_MASK);
         }
 
         @Override
         public void setAssistBlocked(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_ASSIST_BLOCKED)
+                    | (state ? FLAGS_ASSIST_BLOCKED : 0);
         }
 
         @Override
         public void setEnabled(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_DISABLED) | (state ? 0 : FLAGS_DISABLED);
         }
 
         @Override
         public void setClickable(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_CLICKABLE) | (state ? FLAGS_CLICKABLE : 0);
         }
 
         @Override
         public void setLongClickable(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_LONG_CLICKABLE)
+                    | (state ? FLAGS_LONG_CLICKABLE : 0);
         }
 
         @Override
         public void setContextClickable(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_CONTEXT_CLICKABLE)
+                    | (state ? FLAGS_CONTEXT_CLICKABLE : 0);
         }
 
         @Override
         public void setFocusable(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSABLE) | (state ? FLAGS_FOCUSABLE : 0);
         }
 
         @Override
         public void setFocused(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSED) | (state ? FLAGS_FOCUSED : 0);
         }
 
         @Override
         public void setAccessibilityFocused(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_ACCESSIBILITY_FOCUSED)
+                    | (state ? FLAGS_ACCESSIBILITY_FOCUSED : 0);
         }
 
         @Override
         public void setCheckable(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKABLE) | (state ? FLAGS_CHECKABLE : 0);
         }
 
         @Override
         public void setChecked(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKED) | (state ? FLAGS_CHECKED : 0);
         }
 
         @Override
         public void setSelected(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_SELECTED) | (state ? FLAGS_SELECTED : 0);
         }
 
         @Override
         public void setActivated(boolean state) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_ACTIVATED) | (state ? FLAGS_ACTIVATED : 0);
         }
 
         @Override
         public void setOpaque(boolean opaque) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mFlags = (mNode.mFlags & ~FLAGS_OPAQUE) | (opaque ? FLAGS_OPAQUE : 0);
         }
 
         @Override
         public void setClassName(String className) {
-            // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
             mNode.mClassName = className;
         }
 
         @Override
         public void setContentDescription(CharSequence contentDescription) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mContentDescription = contentDescription;
         }
 
         @Override
         public void setText(CharSequence text) {
-            // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
-            mNode.mText = text;
+            final ViewNodeText t = getNodeText();
+            t.mText = TextUtils.trimNoCopySpans(text);
+            t.mTextSelectionStart = t.mTextSelectionEnd = -1;
         }
 
         @Override
         public void setText(CharSequence text, int selectionStart, int selectionEnd) {
-            // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
-            mNode.mText = text;
-            // TODO(b/111276913): implement or move to superclass
+            final ViewNodeText t = getNodeText();
+            t.mText = TextUtils.trimNoCopySpans(text);
+            t.mTextSelectionStart = selectionStart;
+            t.mTextSelectionEnd = selectionEnd;
         }
 
         @Override
         public void setTextStyle(float size, int fgColor, int bgColor, int style) {
-            // TODO(b/111276913): implement or move to superclass
+            final ViewNodeText t = getNodeText();
+            t.mTextColor = fgColor;
+            t.mTextBackgroundColor = bgColor;
+            t.mTextSize = size;
+            t.mTextStyle = style;
         }
 
         @Override
         public void setTextLines(int[] charOffsets, int[] baselines) {
-            // TODO(b/111276913): implement or move to superclass
+            final ViewNodeText t = getNodeText();
+            t.mLineCharOffsets = charOffsets;
+            t.mLineBaselines = baselines;
+        }
+
+        @Override
+        public void setTextIdEntry(String entryName) {
+            mNode.mTextIdEntry = Preconditions.checkNotNull(entryName);
         }
 
         @Override
         public void setHint(CharSequence hint) {
-            // TODO(b/111276913): implement or move to superclass
+            getNodeText().mHint = hint != null ? hint.toString() : null;
         }
 
         @Override
         public CharSequence getText() {
-            // TODO(b/111276913): temporarily getting directly; should be done on superclass instead
-            return mNode.mText;
+            return mNode.getText();
         }
 
         @Override
         public int getTextSelectionStart() {
-            // TODO(b/111276913): implement or move to superclass
-            return 0;
+            return mNode.getTextSelectionStart();
         }
 
         @Override
         public int getTextSelectionEnd() {
-            // TODO(b/111276913): implement or move to superclass
-            return 0;
+            return mNode.getTextSelectionEnd();
         }
 
         @Override
         public CharSequence getHint() {
-            // TODO(b/111276913): implement or move to superclass
-            return null;
+            return mNode.getHint();
         }
 
         @Override
         public Bundle getExtras() {
-            // TODO(b/111276913): implement or move to superclass
-            return null;
+            if (mNode.mExtras != null) {
+                return mNode.mExtras;
+            }
+            mNode.mExtras = new Bundle();
+            return mNode.mExtras;
         }
 
         @Override
         public boolean hasExtras() {
-            // TODO(b/111276913): implement or move to superclass
-            return false;
+            return mNode.mExtras != null;
         }
 
         @Override
@@ -325,50 +910,64 @@
 
         @Override
         public AutofillId getAutofillId() {
-            // TODO(b/111276913): temporarily getting directly; should be done on superclass instead
             return mNode.mAutofillId;
         }
 
         @Override
         public void setAutofillId(AutofillId id) {
-            // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
-            mNode.mAutofillId = id;
+            mNode.mAutofillId = Preconditions.checkNotNull(id);
         }
 
+
         @Override
         public void setAutofillId(AutofillId parentId, int virtualId) {
-            // TODO(b/111276913): temporarily setting directly; should be done on superclass instead
+            mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
             mNode.mAutofillId = new AutofillId(parentId, virtualId);
         }
 
         @Override
-        public void setAutofillType(int type) {
-            // TODO(b/111276913): implement or move to superclass
+        public void setAutofillType(@View.AutofillType int type) {
+            mNode.mAutofillType = type;
         }
 
         @Override
-        public void setAutofillHints(String[] hint) {
-            // TODO(b/111276913): implement or move to superclass
+        public void setAutofillHints(String[] hints) {
+            mNode.mAutofillHints = hints;
         }
 
         @Override
         public void setAutofillValue(AutofillValue value) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mAutofillValue = value;
         }
 
         @Override
         public void setAutofillOptions(CharSequence[] options) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mAutofillOptions = options;
         }
 
         @Override
         public void setInputType(int inputType) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mInputType = inputType;
+        }
+
+        @Override
+        public void setMinTextEms(int minEms) {
+            mNode.mMinEms = minEms;
+        }
+
+        @Override
+        public void setMaxTextEms(int maxEms) {
+            mNode.mMaxEms = maxEms;
+        }
+
+        @Override
+        public void setMaxTextLength(int maxLength) {
+            mNode.mMaxLength = maxLength;
         }
 
         @Override
         public void setDataIsSensitive(boolean sensitive) {
-            // TODO(b/111276913): implement or move to superclass
+            Log.w(TAG, "setDataIsSensitive() is not supported");
         }
 
         @Override
@@ -378,7 +977,7 @@
 
         @Override
         public Rect getTempRect() {
-            // TODO(b/111276913): implement or move to superclass
+            Log.w(TAG, "getTempRect() is not supported");
             return null;
         }
 
@@ -389,7 +988,7 @@
 
         @Override
         public void setLocaleList(LocaleList localeList) {
-            // TODO(b/111276913): implement or move to superclass
+            mNode.mLocaleList = localeList;
         }
 
         @Override
@@ -402,5 +1001,66 @@
         public void setHtmlInfo(HtmlInfo htmlInfo) {
             Log.w(TAG, "setHtmlInfo() is not supported");
         }
+
+        private ViewNodeText getNodeText() {
+            if (mNode.mText != null) {
+                return mNode.mText;
+            }
+            mNode.mText = new ViewNodeText();
+            return mNode.mText;
+        }
+    }
+
+    //TODO(b/122484602): copied 'as-is' from AssistStructure, except for writeSensitive
+    static final class ViewNodeText {
+        CharSequence mText;
+        float mTextSize;
+        int mTextStyle;
+        int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED;
+        int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED;
+        int mTextSelectionStart;
+        int mTextSelectionEnd;
+        int[] mLineCharOffsets;
+        int[] mLineBaselines;
+        String mHint;
+
+        ViewNodeText() {
+        }
+
+        boolean isSimple() {
+            return mTextBackgroundColor == ViewNode.TEXT_COLOR_UNDEFINED
+                    && mTextSelectionStart == 0 && mTextSelectionEnd == 0
+                    && mLineCharOffsets == null && mLineBaselines == null && mHint == null;
+        }
+
+        ViewNodeText(Parcel in, boolean simple) {
+            mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            mTextSize = in.readFloat();
+            mTextStyle = in.readInt();
+            mTextColor = in.readInt();
+            if (!simple) {
+                mTextBackgroundColor = in.readInt();
+                mTextSelectionStart = in.readInt();
+                mTextSelectionEnd = in.readInt();
+                mLineCharOffsets = in.createIntArray();
+                mLineBaselines = in.createIntArray();
+                mHint = in.readString();
+            }
+        }
+
+        void writeToParcel(Parcel out, boolean simple) {
+            TextUtils.writeToParcel(mText, out, 0);
+            out.writeFloat(mTextSize);
+            out.writeInt(mTextStyle);
+            out.writeInt(mTextColor);
+            if (!simple) {
+                out.writeInt(mTextBackgroundColor);
+                out.writeInt(mTextSelectionStart);
+                out.writeInt(mTextSelectionEnd);
+                out.writeIntArray(mLineCharOffsets);
+                out.writeIntArray(mLineBaselines);
+                out.writeString(mHint);
+            }
+        }
     }
 }
diff --git a/core/java/android/view/inputmethod/InputMethodSystemProperty.java b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
index b233b75..57ed7f9 100644
--- a/core/java/android/view/inputmethod/InputMethodSystemProperty.java
+++ b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
@@ -41,6 +41,17 @@
      */
     private static final String PROP_DEBUG_MULTI_CLIENT_IME = "persist.debug.multi_client_ime";
 
+    /**
+     * System property key for debugging purpose. The value must be empty, "1", or "0".
+     *
+     * <p>Values 'y', 'yes', '1', 'true' or 'on' are considered true.</p>
+     *
+     * <p>To set, run "adb root && adb shell setprop persist.debug.per_profile_ime 1".</p>
+     *
+     * <p>This value will be ignored when {@link Build#IS_DEBUGGABLE} returns {@code false}.</p>
+     */
+    private static final String PROP_DEBUG_PER_PROFILE_IME = "persist.debug.per_profile_ime";
+
     @Nullable
     private static ComponentName getMultiClientImeComponentName() {
         if (Build.IS_DEBUGGABLE) {
@@ -59,6 +70,9 @@
     /**
      * {@link ComponentName} of multi-client IME to be used.
      *
+     * <p>TODO: Move this back to MultiClientInputMethodManagerService once
+     * {@link #PER_PROFILE_IME_ENABLED} always becomes {@code true}.</p>
+     *
      * @hide
      */
     @Nullable
@@ -68,7 +82,19 @@
     /**
      * {@code true} when multi-client IME is enabled.
      *
+     * <p>TODO: Move this back to MultiClientInputMethodManagerService once
+     * {@link #PER_PROFILE_IME_ENABLED} always becomes {@code true}.</p>
+     *
      * @hide
      */
     public static final boolean MULTI_CLIENT_IME_ENABLED = (sMultiClientImeComponentName != null);
+
+    /**
+     * {@code true} when per-profile IME is enabled.
+     * @hide
+     */
+    public static final boolean PER_PROFILE_IME_ENABLED = MULTI_CLIENT_IME_ENABLED
+            || Build.IS_DEBUGGABLE && SystemProperties.getBoolean(
+                    PROP_DEBUG_PER_PROFILE_IME, false);
+
 }
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 4c6862c..5dc8b19 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -67,12 +67,6 @@
     private static final String TAG = TextServicesManager.class.getSimpleName();
     private static final boolean DBG = false;
 
-    /**
-     * A compile time switch to control per-profile spell checker, which is not yet ready.
-     * @hide
-     */
-    public static final boolean DISABLE_PER_PROFILE_SPELL_CHECKER = true;
-
     private static TextServicesManager sInstance;
 
     private final ITextServicesManager mService;
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 20febc2..afe46701 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -837,8 +837,8 @@
 
             mContentWidth = width;
             mContentHeight = height;
-            mOffsetX = (int) (0.1f * width);
-            mOffsetY = (int) (0.1f * height);
+            mOffsetX = (int) (1.05f * elevation);
+            mOffsetY = (int) (1.05f * elevation);
             // Setup the surface we will use for drawing the content and shadow.
             mSurfaceWidth = mContentWidth + 2 * mOffsetX;
             mSurfaceHeight = mContentHeight + 2 * mOffsetY;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0ea8579..51b8734 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3520,7 +3520,7 @@
      */
     @android.view.RemotableViewMethod
     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
-        Preconditions.checkArgumentPositive(textSelectHandle,
+        Preconditions.checkArgument(textSelectHandle != 0,
                 "The text select handle should be a valid drawable resource id.");
         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
     }
@@ -3577,7 +3577,7 @@
      */
     @android.view.RemotableViewMethod
     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
-        Preconditions.checkArgumentPositive(textSelectHandleLeft,
+        Preconditions.checkArgument(textSelectHandleLeft != 0,
                 "The text select left handle should be a valid drawable resource id.");
         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
     }
@@ -3634,7 +3634,7 @@
      */
     @android.view.RemotableViewMethod
     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
-        Preconditions.checkArgumentPositive(textSelectHandleRight,
+        Preconditions.checkArgument(textSelectHandleRight != 0,
                 "The text select right handle should be a valid drawable resource id.");
         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
     }
@@ -3667,9 +3667,7 @@
      * @see #setTextCursorDrawable(int)
      * @attr ref android.R.styleable#TextView_textCursorDrawable
      */
-    public void setTextCursorDrawable(@NonNull Drawable textCursorDrawable) {
-        Preconditions.checkNotNull(textCursorDrawable,
-                "The cursor drawable should not be null.");
+    public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
         mCursorDrawable = textCursorDrawable;
         mCursorDrawableRes = 0;
         if (mEditor != null) {
@@ -3687,8 +3685,6 @@
      * @attr ref android.R.styleable#TextView_textCursorDrawable
      */
     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
-        Preconditions.checkArgumentPositive(textCursorDrawable,
-                "The cursor drawable should be a valid drawable resource id.");
         setTextCursorDrawable(mContext.getDrawable(textCursorDrawable));
     }
 
@@ -8061,6 +8057,26 @@
                     }
                 }
                 break;
+
+            case KeyEvent.KEYCODE_FORWARD_DEL:
+                if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
+                    if (onTextContextMenuItem(ID_CUT)) {
+                        return KEY_EVENT_HANDLED;
+                    }
+                }
+                break;
+
+            case KeyEvent.KEYCODE_INSERT:
+                if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
+                    if (onTextContextMenuItem(ID_COPY)) {
+                        return KEY_EVENT_HANDLED;
+                    }
+                } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
+                    if (onTextContextMenuItem(ID_PASTE)) {
+                        return KEY_EVENT_HANDLED;
+                    }
+                }
+                break;
         }
 
         if (mEditor != null && mEditor.mKeyListener != null) {
@@ -10831,25 +10847,6 @@
                         return onTextContextMenuItem(ID_PASTE);
                     }
                     break;
-                case KeyEvent.KEYCODE_INSERT:
-                    if (canCopy()) {
-                        return onTextContextMenuItem(ID_COPY);
-                    }
-                    break;
-            }
-        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
-            // Handle Shift-only shortcuts.
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_FORWARD_DEL:
-                    if (canCut()) {
-                        return onTextContextMenuItem(ID_CUT);
-                    }
-                    break;
-                case KeyEvent.KEYCODE_INSERT:
-                    if (canPaste()) {
-                        return onTextContextMenuItem(ID_PASTE);
-                    }
-                    break;
             }
         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
             // Handle Ctrl-Shift shortcuts.
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index e59bee4..c4ab91f 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -17,8 +17,10 @@
 package com.android.internal.app;
 
 import android.app.AppOpsManager;
+import android.app.AppOpsManager;
 import android.content.pm.ParceledListSlice;
 import android.os.Bundle;
+import android.os.RemoteCallback;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsActiveCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
@@ -42,10 +44,15 @@
     int checkPackage(int uid, String packageName);
     List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
     List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
-    ParceledListSlice getAllHistoricalPackagesOps(in String[] ops,
-            long beginTimeMillis, long endTimeMillis);
-    AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int uid, String packageName,
-            in String[] ops, long beginTimeMillis, long endTimeMillis);
+    void getHistoricalOps(int uid, String packageName, in String[] ops, long beginTimeMillis,
+            long endTimeMillis, in RemoteCallback callback);
+    void getHistoricalOpsFromDiskRaw(int uid, String packageName, in String[] ops,
+            long beginTimeMillis, long endTimeMillis, in RemoteCallback callback);
+    void offsetHistory(long duration);
+    void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
+    void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
+    void resetHistoryParameters();
+    void clearHistory();
     List<AppOpsManager.PackageOps> getUidOps(int uid, in int[] ops);
     void setUidMode(int code, int uid, int mode);
     void setMode(int code, int uid, String packageName, int mode);
diff --git a/core/java/com/android/internal/os/AtomicDirectory.java b/core/java/com/android/internal/os/AtomicDirectory.java
new file mode 100644
index 0000000..f24d12e
--- /dev/null
+++ b/core/java/com/android/internal/os/AtomicDirectory.java
@@ -0,0 +1,294 @@
+/*
+ * 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FileUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Helper class for performing atomic operations on a directory, by creating a
+ * backup directory until a write has successfully completed.
+ * <p>
+ * Atomic directory guarantees directory integrity by ensuring that a directory has
+ * been completely written and sync'd to disk before removing its backup.
+ * As long as the backup directory exists, the original directory is considered
+ * to be invalid (leftover from a previous attempt to write).
+ * <p>
+ * Atomic directory does not confer any file locking semantics. Do not use this
+ * class when the directory may be accessed or modified concurrently
+ * by multiple threads or processes. The caller is responsible for ensuring
+ * appropriate mutual exclusion invariants whenever it accesses the directory.
+ * <p>
+ * To ensure atomicity you must always use this class to interact with the
+ * backing directory when checking existence, making changes, and deleting.
+ */
+public final class AtomicDirectory {
+    private final @NonNull ArrayMap<File, FileOutputStream> mOpenFiles = new ArrayMap<>();
+    private final @NonNull File mBaseDirectory;
+    private final @NonNull File mBackupDirectory;
+
+    private int mBaseDirectoryFd = -1;
+    private int mBackupDirectoryFd = -1;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param baseDirectory The base directory to treat atomically.
+     */
+    public AtomicDirectory(@NonNull File baseDirectory) {
+        Preconditions.checkNotNull(baseDirectory, "baseDirectory cannot be null");
+        mBaseDirectory = baseDirectory;
+        mBackupDirectory = new File(baseDirectory.getPath() + "_bak");
+    }
+
+    /**
+     * Gets the backup directory if present. This could be useful if you are
+     * writing new state to the dir but need to access the last persisted state
+     * at the same time. This means that this call is useful in between
+     * {@link #startWrite()} and {@link #finishWrite()} or {@link #failWrite()}.
+     * You should not modify the content returned by this method.
+     *
+     * @see #startRead()
+     */
+    public @Nullable File getBackupDirectory() {
+        return mBackupDirectory;
+    }
+
+    /**
+     * Starts reading this directory. After calling this method you should
+     * not make any changes to its contents.
+     *
+     * @throws IOException If an error occurs.
+     *
+     * @see #finishRead()
+     * @see #startWrite()
+     */
+    public @NonNull File startRead() throws IOException {
+        restore();
+        return getOrCreateBaseDirectory();
+    }
+
+    /**
+     * Finishes reading this directory.
+     *
+     * @see #startRead()
+     * @see #startWrite()
+     */
+    public void finishRead() {
+        mBaseDirectoryFd = -1;
+        mBackupDirectoryFd = -1;
+    }
+
+    /**
+     * Starts editing this directory. After calling this method you should
+     * add content to the directory only via the APIs on this class. To open a
+     * file for writing in this directory you should use {@link #openWrite(File)}
+     * and to close the file {@link #closeWrite(FileOutputStream)}. Once all
+     * content has been written and all files closed you should commit via a
+     * call to {@link #finishWrite()} or discard via a call to {@link #failWrite()}.
+     *
+     * @throws IOException If an error occurs.
+     *
+     * @see #startRead()
+     * @see #openWrite(File)
+     * @see #finishWrite()
+     * @see #failWrite()
+     */
+    public @NonNull File startWrite() throws IOException {
+        backup();
+        return getOrCreateBaseDirectory();
+    }
+
+    /**
+     * Opens a file in this directory for writing.
+     *
+     * @param file The file to open. Must be a file in the base directory.
+     * @return An input stream for reading.
+     *
+     * @throws IOException If an I/O error occurs.
+     *
+     * @see #closeWrite(FileOutputStream)
+     */
+    public @NonNull FileOutputStream openWrite(@NonNull File file) throws IOException {
+        if (file.isDirectory() || !file.getParentFile().equals(getOrCreateBaseDirectory())) {
+            throw new IllegalArgumentException("Must be a file in " + getOrCreateBaseDirectory());
+        }
+        final FileOutputStream destination = new FileOutputStream(file);
+        if (mOpenFiles.put(file, destination) != null) {
+            throw new IllegalArgumentException("Already open file" + file.getCanonicalPath());
+        }
+        return destination;
+    }
+
+    /**
+     * Closes a previously opened file.
+     *
+     * @param destination The stream to the file returned by {@link #openWrite(File)}.
+     *
+     * @see #openWrite(File)
+     */
+    public void closeWrite(@NonNull FileOutputStream destination) {
+        final int indexOfValue = mOpenFiles.indexOfValue(destination);
+        if (mOpenFiles.removeAt(indexOfValue) == null) {
+            throw new IllegalArgumentException("Unknown file stream " + destination);
+        }
+        FileUtils.sync(destination);
+        try {
+            destination.close();
+        } catch (IOException ignored) {}
+    }
+
+    public void failWrite(@NonNull FileOutputStream destination) {
+        final int indexOfValue = mOpenFiles.indexOfValue(destination);
+        if (indexOfValue >= 0) {
+            mOpenFiles.removeAt(indexOfValue);
+        }
+    }
+
+    /**
+     * Finishes the edit and commits all changes.
+     *
+     * @see #startWrite()
+     *
+     * @throws IllegalStateException is some files are not closed.
+     */
+    public void finishWrite() {
+        throwIfSomeFilesOpen();
+        fsyncDirectoryFd(mBaseDirectoryFd);
+        deleteDirectory(mBackupDirectory);
+        fsyncDirectoryFd(mBackupDirectoryFd);
+        mBaseDirectoryFd = -1;
+        mBackupDirectoryFd = -1;
+    }
+
+    /**
+     * Finishes the edit and discards all changes.
+     *
+     * @see #startWrite()
+     */
+    public void failWrite() {
+        throwIfSomeFilesOpen();
+        try{
+            restore();
+        } catch (IOException ignored) {}
+        mBaseDirectoryFd = -1;
+        mBackupDirectoryFd = -1;
+    }
+
+    /**
+     * @return Whether this directory exists.
+     */
+    public boolean exists() {
+        return mBaseDirectory.exists() || mBackupDirectory.exists();
+    }
+
+    /**
+     * Deletes this directory.
+     */
+    public void delete() {
+        if (mBaseDirectory.exists()) {
+            deleteDirectory(mBaseDirectory);
+            fsyncDirectoryFd(mBaseDirectoryFd);
+        }
+        if (mBackupDirectory.exists()) {
+            deleteDirectory(mBackupDirectory);
+            fsyncDirectoryFd(mBackupDirectoryFd);
+        }
+    }
+
+    private @NonNull File getOrCreateBaseDirectory() throws IOException {
+        if (!mBaseDirectory.exists()) {
+            if (!mBaseDirectory.mkdirs()) {
+                throw new IOException("Couldn't create directory " + mBaseDirectory);
+            }
+            FileUtils.setPermissions(mBaseDirectory.getPath(),
+                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH,
+                    -1, -1);
+        }
+        if (mBaseDirectoryFd < 0) {
+            mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath());
+        }
+        return mBaseDirectory;
+    }
+
+    private void throwIfSomeFilesOpen() {
+        if (!mOpenFiles.isEmpty()) {
+            throw new IllegalStateException("Unclosed files: "
+                    + Arrays.toString(mOpenFiles.keySet().toArray()));
+        }
+    }
+
+    private void backup() throws IOException {
+        if (!mBaseDirectory.exists()) {
+            return;
+        }
+        if (mBaseDirectoryFd < 0) {
+            mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath());
+        }
+        if (mBackupDirectory.exists()) {
+            deleteDirectory(mBackupDirectory);
+        }
+        if (!mBaseDirectory.renameTo(mBackupDirectory)) {
+            throw new IOException("Couldn't backup " + mBaseDirectory
+                    + " to " + mBackupDirectory);
+        }
+        mBackupDirectoryFd = mBaseDirectoryFd;
+        mBaseDirectoryFd = -1;
+        fsyncDirectoryFd(mBackupDirectoryFd);
+    }
+
+    private void restore() throws IOException {
+        if (!mBackupDirectory.exists()) {
+            return;
+        }
+        if (mBackupDirectoryFd == -1) {
+            mBackupDirectoryFd = getDirectoryFd(mBackupDirectory.getCanonicalPath());
+        }
+        if (mBaseDirectory.exists()) {
+            deleteDirectory(mBaseDirectory);
+        }
+        if (!mBackupDirectory.renameTo(mBaseDirectory)) {
+            throw new IOException("Couldn't restore " + mBackupDirectory
+                    + " to " + mBaseDirectory);
+        }
+        mBaseDirectoryFd = mBackupDirectoryFd;
+        mBackupDirectoryFd = -1;
+        fsyncDirectoryFd(mBaseDirectoryFd);
+    }
+
+    private static void deleteDirectory(@NonNull File file) {
+        final File[] children = file.listFiles();
+        if (children != null) {
+            for (File child : children) {
+                deleteDirectory(child);
+            }
+        }
+        file.delete();
+    }
+
+    private static native int getDirectoryFd(String path);
+    private static native void fsyncDirectoryFd(int fd);
+}
diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
index 100c6ee..adf7692 100644
--- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
+++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
@@ -16,10 +16,10 @@
 
 package com.android.internal.policy;
 
-import com.android.internal.R;
-
 import android.content.res.Resources;
 
+import com.android.internal.R;
+
 /**
  * Utility functions for screen decorations used by both window manager and System UI.
  */
@@ -31,15 +31,19 @@
      * scaling, this means that we don't have to reload them on config changes.
      */
     public static float getWindowCornerRadius(Resources resources) {
+        if (!supportsRoundedCornersOnWindows(resources)) {
+            return 0f;
+        }
+
         // Radius that should be used in case top or bottom aren't defined.
         float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius);
 
         float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top);
-        if (topRadius == 0) {
+        if (topRadius == 0f) {
             topRadius = defaultRadius;
         }
         float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom);
-        if (bottomRadius == 0) {
+        if (bottomRadius == 0f) {
             bottomRadius = defaultRadius;
         }
 
@@ -47,4 +51,11 @@
         // completely cover the display.
         return Math.min(topRadius, bottomRadius);
     }
+
+    /**
+     * If live rounded corners are supported on windows.
+     */
+    public static boolean supportsRoundedCornersOnWindows(Resources resources) {
+        return resources.getBoolean(R.bool.config_supportsRoundedCornersOnWindows);
+    }
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 53b56f2..6a28059 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -153,13 +153,11 @@
     void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
             boolean requireConfirmation, int userId);
     // Used to hide the dialog when a biometric is authenticated
-    void onBiometricAuthenticated();
+    void onBiometricAuthenticated(boolean authenticated);
     // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
     void onBiometricHelp(String message);
     // Used to set a message - the dialog will dismiss after a certain amount of time
     void onBiometricError(String error);
     // Used to hide the biometric dialog when the AuthenticationClient is stopped
     void hideBiometricDialog();
-    // Used to request the "try again" button for authentications which requireConfirmation=true
-    void showBiometricTryAgain();
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 9087dd2..197e873 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -97,13 +97,11 @@
     void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
             boolean requireConfirmation, int userId);
     // Used to hide the dialog when a biometric is authenticated
-    void onBiometricAuthenticated();
+    void onBiometricAuthenticated(boolean authenticated);
     // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
     void onBiometricHelp(String message);
     // Used to set a message - the dialog will dismiss after a certain amount of time
     void onBiometricError(String error);
     // Used to hide the biometric dialog when the AuthenticationClient is stopped
     void hideBiometricDialog();
-    // Used to request the "try again" button for authentications which requireConfirmation=true
-    void showBiometricTryAgain();
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index dc6a73a..088e13f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -41,7 +41,7 @@
         "com_google_android_gles_jni_EGLImpl.cpp",
         "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm
         "android_app_Activity.cpp",
-	"android_app_ActivityThread.cpp",
+        "android_app_ActivityThread.cpp",
         "android_app_NativeActivity.cpp",
         "android_app_admin_SecurityLog.cpp",
         "android_opengl_EGL14.cpp",
@@ -202,6 +202,7 @@
         "android_animation_PropertyValuesHolder.cpp",
         "android_security_Scrypt.cpp",
         "com_android_internal_net_NetworkStatsFactory.cpp",
+        "com_android_internal_os_AtomicDirectory.cpp",
         "com_android_internal_os_ClassLoaderFactory.cpp",
         "com_android_internal_os_FuseAppLoop.cpp",
         "com_android_internal_os_Zygote.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 687b105..1092222 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -218,6 +218,7 @@
 extern int register_android_security_Scrypt(JNIEnv *env);
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env);
+extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
 extern int register_com_android_internal_os_Zygote(JNIEnv *env);
@@ -1495,6 +1496,7 @@
     REG_JNI(register_android_security_Scrypt),
     REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
     REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
+    REG_JNI(register_com_android_internal_os_AtomicDirectory),
     REG_JNI(register_com_android_internal_os_FuseAppLoop),
 };
 
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index ecf8119..50cff5c 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -334,7 +334,7 @@
 static jlong android_view_MotionEvent_nativeInitialize(JNIEnv* env, jclass clazz,
         jlong nativePtr,
         jint deviceId, jint source, jint displayId, jint action, jint flags, jint edgeFlags,
-        jint metaState, jint buttonState,
+        jint metaState, jint buttonState, jint classification,
         jfloat xOffset, jfloat yOffset, jfloat xPrecision, jfloat yPrecision,
         jlong downTimeNanos, jlong eventTimeNanos,
         jint pointerCount, jobjectArray pointerPropertiesObjArray,
@@ -373,7 +373,8 @@
     }
 
     event->initialize(deviceId, source, displayId, action, 0, flags, edgeFlags, metaState,
-            buttonState, xOffset, yOffset, xPrecision, yPrecision,
+            buttonState, static_cast<MotionClassification>(classification),
+            xOffset, yOffset, xPrecision, yPrecision,
             downTimeNanos, eventTimeNanos, pointerCount, pointerProperties, rawPointerCoords);
 
     return reinterpret_cast<jlong>(event);
@@ -668,6 +669,11 @@
     event->setButtonState(buttonState);
 }
 
+static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) {
+    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+    return static_cast<jint>(event->getClassification());
+}
+
 static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
         jfloat deltaY) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
@@ -747,7 +753,7 @@
 static const JNINativeMethod gMotionEventMethods[] = {
     /* name, signature, funcPtr */
     { "nativeInitialize",
-            "(JIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;"
+            "(JIIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;"
                     "[Landroid/view/MotionEvent$PointerCoords;)J",
             (void*)android_view_MotionEvent_nativeInitialize },
     { "nativeDispose",
@@ -846,6 +852,9 @@
     { "nativeSetButtonState",
             "(JI)V",
             (void*)android_view_MotionEvent_nativeSetButtonState },
+    { "nativeGetClassification",
+            "(J)I",
+            (void*)android_view_MotionEvent_nativeGetClassification },
     { "nativeOffsetLocation",
             "(JFF)V",
             (void*)android_view_MotionEvent_nativeOffsetLocation },
diff --git a/core/jni/com_android_internal_os_AtomicDirectory.cpp b/core/jni/com_android_internal_os_AtomicDirectory.cpp
new file mode 100644
index 0000000..50b2288
--- /dev/null
+++ b/core/jni/com_android_internal_os_AtomicDirectory.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include <nativehelper/ScopedUtfChars.h>
+#include "jni.h"
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static jint com_android_internal_os_AtomicDirectory_getDirectoryFd(JNIEnv* env,
+        jobject /*clazz*/, jstring path) {
+    ScopedUtfChars path8(env, path);
+    if (path8.c_str() == NULL) {
+        ALOGE("Invalid path: %s", path8.c_str());
+        return -1;
+    }
+    int fd;
+    if ((fd = TEMP_FAILURE_RETRY(open(path8.c_str(), O_DIRECTORY | O_RDONLY))) == -1) {
+        ALOGE("Cannot open directory %s, error: %s\n", path8.c_str(), strerror(errno));
+        return -1;
+    }
+    return fd;
+}
+
+static void com_android_internal_os_AtomicDirectory_fsyncDirectoryFd(JNIEnv* env,
+        jobject /*clazz*/, jint fd) {
+    if (TEMP_FAILURE_RETRY(fsync(fd)) == -1) {
+        ALOGE("Cannot fsync directory %d, error: %s\n", fd, strerror(errno));
+    }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gRegisterMethods[] = {
+    /* name, signature, funcPtr */
+    { "fsyncDirectoryFd",
+      "(I)V",
+       (void*) com_android_internal_os_AtomicDirectory_fsyncDirectoryFd
+    },
+    { "getDirectoryFd",
+      "(Ljava/lang/String;)I",
+       (void*) com_android_internal_os_AtomicDirectory_getDirectoryFd
+    },
+};
+
+int register_com_android_internal_os_AtomicDirectory(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "com/android/internal/os/AtomicDirectory",
+            gRegisterMethods, NELEM(gRegisterMethods));
+}
+
+}; // namespace android
diff --git a/core/proto/android/app/window_configuration.proto b/core/proto/android/app/window_configuration.proto
index 2d15552..6cc1a40 100644
--- a/core/proto/android/app/window_configuration.proto
+++ b/core/proto/android/app/window_configuration.proto
@@ -29,4 +29,5 @@
     optional .android.graphics.RectProto app_bounds = 1;
     optional int32 windowing_mode = 2;
     optional int32 activity_type = 3;
+    optional .android.graphics.RectProto bounds = 4;
 }
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a00fde9..cc5aa20 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -1018,7 +1018,9 @@
 
     optional SettingProto zram_enabled = 139 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
+    optional SettingProto app_ops_constants = 148 [ (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 = 148;
+    // Next tag = 149;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 449a7b3..8b85a28 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4254,6 +4254,16 @@
     <permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to manage the content suggestions service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
+         android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows an application to manage the app predictions service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_APP_PREDICTIONS"
+         android:protectionLevel="signature" />
+
     <!-- Allows an app to set the theme overlay in /vendor/overlay
          being used.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -4334,6 +4344,11 @@
     <permission android:name="android.permission.BIND_SMS_APP_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- @hide Permission that allows configuring appops.
+     <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_APPOPS"
+                android:protectionLevel="signature" />
+
     <!-- @hide Permission that allows background clipboard access.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1c98c66..1feb59a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1152,6 +1152,10 @@
          Settings.System.NOTIFICATION_VIBRATION_INTENSITY more details on the constant values and
          meanings. -->
     <integer name="config_defaultNotificationVibrationIntensity">2</integer>
+    <!-- The default intensity level for ring vibrations. See
+         Settings.System.RING_VIBRATION_INTENSITY more details on the constant values and
+         meanings. -->
+    <integer name="config_defaultRingVibrationIntensity">2</integer>
 
     <bool name="config_use_strict_phone_number_comparation">false</bool>
 
@@ -3419,6 +3423,23 @@
     -->
     <string name="config_defaultAugmentedAutofillService" translatable="false"></string>
 
+    <!-- The package name for the system's app prediction service.
+         This service must be trusted, as it can be activated without explicit consent of the user.
+         Example: "com.android.intelligence/.AppPredictionService"
+    -->
+    <string name="config_defaultAppPredictionService" translatable="false"></string>
+
+    <!-- The package name for the system's content suggestions service.
+         Provides suggestions for text and image selection regions in snapshots of apps and should
+         be able to classify the type of entities in those selections.
+
+         This service must be trusted, as it can be activated without explicit consent of the user.
+         If no service with the specified name exists on the device, content suggestions wil be
+         disabled.
+         Example: "com.android.contentsuggestions/.ContentSuggestionsService"
+    -->
+    <string name="config_defaultContentSuggestionsService" translatable="false"></string>
+
     <!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
     <bool name="config_useDefaultFocusHighlight">true</bool>
 
@@ -3648,4 +3669,8 @@
          set in AndroidManifest.
          {@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} -->
     <string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string>
+
+    <!-- If device supports corner radius on windows.
+         This should be turned off on low-end devices to improve animation performance. -->
+    <bool name="config_supportsRoundedCornersOnWindows">true</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 01fbf80..010accf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3275,6 +3275,8 @@
   <java-symbol type="string" name="config_defaultWellbeingPackage" />
   <java-symbol type="string" name="config_defaultContentCaptureService" />
   <java-symbol type="string" name="config_defaultAugmentedAutofillService" />
+  <java-symbol type="string" name="config_defaultAppPredictionService" />
+  <java-symbol type="string" name="config_defaultContentSuggestionsService" />
 
   <java-symbol type="string" name="notification_channel_foreground_service" />
   <java-symbol type="string" name="foreground_service_app_in_background" />
@@ -3489,6 +3491,7 @@
 
   <java-symbol type="integer" name="config_defaultHapticFeedbackIntensity" />
   <java-symbol type="integer" name="config_defaultNotificationVibrationIntensity" />
+  <java-symbol type="integer" name="config_defaultRingVibrationIntensity" />
 
   <java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" />
 
@@ -3515,6 +3518,7 @@
   <java-symbol type="dimen" name="rounded_corner_radius" />
   <java-symbol type="dimen" name="rounded_corner_radius_top" />
   <java-symbol type="dimen" name="rounded_corner_radius_bottom" />
+  <java-symbol type="bool" name="config_supportsRoundedCornersOnWindows" />
 
   <java-symbol type="string" name="config_defaultModuleMetadataProvider" />
 
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 041fb7e..74943c7 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -43,7 +43,8 @@
     platform-test-annotations \
     compatibility-device-util \
     truth-prebuilt \
-    print-test-util-lib
+    print-test-util-lib \
+    testng # TODO: remove once Android migrates to JUnit 4.12, which provide assertThrows
 
 LOCAL_JAVA_LIBRARIES := \
     android.test.runner \
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 1750dac..0f83a29 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -19,28 +19,35 @@
 import static android.content.Intent.ACTION_EDIT;
 import static android.content.Intent.ACTION_VIEW;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.IApplicationThread;
+import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.StopActivityItem;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.os.IBinder;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.MergedConfiguration;
+import android.view.Display;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+
 /**
  * Test for verifying {@link android.app.ActivityThread} class.
  * Build/Install/Run:
@@ -50,8 +57,12 @@
 @MediumTest
 public class ActivityThreadTest {
 
-    private final ActivityTestRule mActivityTestRule =
-            new ActivityTestRule(TestActivity.class, true /* initialTouchMode */,
+    // The first sequence number to try with. Use a large number to avoid conflicts with the first a
+    // few sequence numbers the framework used to launch the test activity.
+    private static final int BASE_SEQ = 10000;
+
+    private final ActivityTestRule<TestActivity> mActivityTestRule =
+            new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
                     false /* launchActivity */);
 
     @Test
@@ -129,6 +140,179 @@
         });
     }
 
+    @Test
+    public void testHandleActivityConfigurationChanged() {
+        final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            final int numOfConfig = activity.mNumOfConfigChanges;
+            applyConfigurationChange(activity, BASE_SEQ);
+            assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+        });
+    }
+
+    @Test
+    public void testHandleActivityConfigurationChanged_DropStaleConfigurations() {
+        final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // Set the sequence number to BASE_SEQ.
+            applyConfigurationChange(activity, BASE_SEQ);
+
+            final int orientation = activity.mConfig.orientation;
+            final int numOfConfig = activity.mNumOfConfigChanges;
+
+            // Try to apply an old configuration change.
+            applyConfigurationChange(activity, BASE_SEQ - 1);
+            assertEquals(numOfConfig, activity.mNumOfConfigChanges);
+            assertEquals(orientation, activity.mConfig.orientation);
+        });
+    }
+
+    @Test
+    public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() {
+        final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // Set the sequence number to BASE_SEQ and record the final sequence number it used.
+            final int seq = applyConfigurationChange(activity, BASE_SEQ);
+
+            final int orientation = activity.mConfig.orientation;
+            final int numOfConfig = activity.mNumOfConfigChanges;
+
+            // Try to apply an new configuration change.
+            applyConfigurationChange(activity, seq + 1);
+            assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+            assertNotEquals(orientation, activity.mConfig.orientation);
+        });
+    }
+
+    @Test
+    public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() {
+        final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // Set the sequence number to BASE_SEQ and record the final sequence number it used.
+            final int seq = applyConfigurationChange(activity, BASE_SEQ);
+
+            final int orientation = activity.mConfig.orientation;
+            final int numOfConfig = activity.mNumOfConfigChanges;
+
+            final ActivityThread activityThread = activity.getActivityThread();
+
+            final Configuration pendingConfig = new Configuration();
+            pendingConfig.orientation = orientation == Configuration.ORIENTATION_LANDSCAPE
+                    ? Configuration.ORIENTATION_PORTRAIT
+                    : Configuration.ORIENTATION_LANDSCAPE;
+            pendingConfig.seq = seq + 2;
+            activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
+                    pendingConfig);
+
+            final Configuration newConfig = new Configuration();
+            newConfig.orientation = orientation;
+            newConfig.seq = seq + 1;
+
+            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+                    newConfig, Display.INVALID_DISPLAY);
+            assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+            assertEquals(pendingConfig.orientation, activity.mConfig.orientation);
+        });
+    }
+
+    @Test
+    public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()
+            throws Exception {
+        final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+        final ActivityThread activityThread = activity.getActivityThread();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            final Configuration config = new Configuration();
+            config.seq = BASE_SEQ;
+            config.orientation = Configuration.ORIENTATION_PORTRAIT;
+
+            activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+                    config, Display.INVALID_DISPLAY);
+        });
+
+        final int numOfConfig = activity.mNumOfConfigChanges;
+        final IApplicationThread appThread = activityThread.getApplicationThread();
+
+        activity.mConfigLatch = new CountDownLatch(1);
+        activity.mTestLatch = new CountDownLatch(1);
+
+        Configuration config = new Configuration();
+        config.seq = BASE_SEQ + 1;
+        config.smallestScreenWidthDp = 100;
+        appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+        // Wait until the main thread is performing the configuration change for the configuration
+        // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where
+        // the activity takes very long time to process configuration changes.
+        activity.mTestLatch.await();
+
+        config = new Configuration();
+        config.seq = BASE_SEQ + 2;
+        config.smallestScreenWidthDp = 200;
+        appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+        config = new Configuration();
+        config.seq = BASE_SEQ + 3;
+        config.smallestScreenWidthDp = 300;
+        appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+        config = new Configuration();
+        config.seq = BASE_SEQ + 4;
+        config.smallestScreenWidthDp = 400;
+        appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
+
+        activity.mConfigLatch.countDown();
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        activity.mConfigLatch = null;
+        activity.mTestLatch = null;
+
+        // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq
+        // BASE_SEQ + 4. Configurations scheduled in between should be dropped.
+        assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges);
+        assertEquals(400, activity.mConfig.smallestScreenWidthDp);
+    }
+
+    /**
+     * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)}
+     * to try to push activity configuration to the activity for the given sequence number.
+     * <p>
+     * It uses orientation to push the configuration and it tries a different orientation if the
+     * first attempt doesn't make through, to rule out the possibility that the previous
+     * configuration already has the same orientation.
+     *
+     * @param activity the test target activity
+     * @param seq the specified sequence number
+     * @return the sequence number this method tried with the last time, so that the caller can use
+     * the next sequence number for next configuration update.
+     */
+    private int applyConfigurationChange(TestActivity activity, int seq) {
+        final ActivityThread activityThread = activity.getActivityThread();
+
+        final int numOfConfig = activity.mNumOfConfigChanges;
+        Configuration config = new Configuration();
+        config.orientation = Configuration.ORIENTATION_PORTRAIT;
+        config.seq = seq;
+        activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
+                Display.INVALID_DISPLAY);
+
+        if (activity.mNumOfConfigChanges > numOfConfig) {
+            return config.seq;
+        }
+
+        config = new Configuration();
+        config.orientation = Configuration.ORIENTATION_LANDSCAPE;
+        config.seq = seq + 1;
+        activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
+                Display.INVALID_DISPLAY);
+
+        return config.seq;
+    }
+
     private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
         final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
                 null, 0, new MergedConfiguration(), false /* preserveWindow */);
@@ -162,6 +346,16 @@
         return transaction;
     }
 
+    private static ClientTransaction newActivityConfigTransaction(Activity activity,
+            Configuration config) {
+        final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config);
+
+        final ClientTransaction transaction = newTransaction(activity);
+        transaction.addCallback(item);
+
+        return transaction;
+    }
+
     private static ClientTransaction newTransaction(Activity activity) {
         final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
         return ClientTransaction.obtain(appThread, activity.getActivityToken());
@@ -169,5 +363,37 @@
 
     // Test activity
     public static class TestActivity extends Activity {
+        int mNumOfConfigChanges;
+        final Configuration mConfig = new Configuration();
+
+        /**
+         * A latch used to notify tests that we're about to wait for configuration latch. This
+         * is used to notify test code that preExecute phase for activity configuration change
+         * transaction has passed.
+         */
+        volatile CountDownLatch mTestLatch;
+        /**
+         * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the
+         * latch reaches 0.
+         */
+        volatile CountDownLatch mConfigLatch;
+
+        @Override
+        public void onConfigurationChanged(Configuration config) {
+            super.onConfigurationChanged(config);
+            mConfig.setTo(config);
+            ++mNumOfConfigChanges;
+
+            if (mConfigLatch != null) {
+                if (mTestLatch != null) {
+                    mTestLatch.countDown();
+                }
+                try {
+                    mConfigLatch.await();
+                } catch (InterruptedException e) {
+                    throw new IllegalStateException(e);
+                }
+            }
+        }
     }
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index f863356..4e405ca 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -27,6 +27,7 @@
 import static java.lang.reflect.Modifier.isStatic;
 
 import android.platform.test.annotations.Presubmit;
+import android.provider.Settings.Global;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -550,7 +551,12 @@
                     Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
                     Settings.Global.BACKUP_MULTI_USER_ENABLED,
                     Settings.Global.ISOLATED_STORAGE_LOCAL,
-                    Settings.Global.ISOLATED_STORAGE_REMOTE);
+                    Settings.Global.ISOLATED_STORAGE_REMOTE,
+                    Settings.Global.APPOP_HISTORY_PARAMETERS,
+                    Settings.Global.APPOP_HISTORY_MODE,
+                    Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER,
+                    Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS);
+
     private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
              newHashSet(
                  Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
new file mode 100644
index 0000000..59f3a4c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link ContentCaptureSessionTest}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ContentCaptureSessionTest {
+
+    /**
+     * Uses a spy as ContentCaptureSession is abstract but (so far) we're testing its concrete
+     * methods.
+     */
+    @Spy
+    private ContentCaptureSession mMockSession;
+
+    @Test
+    public void testNewAutofillId_invalid() {
+        assertThrows(NullPointerException.class, () -> mMockSession.newAutofillId(null, 42));
+        assertThrows(IllegalArgumentException.class,
+                () -> mMockSession.newAutofillId(new AutofillId(42, 42), 42));
+    }
+
+    @Test
+    public void testNewAutofillId_valid() {
+        final AutofillId parentId = new AutofillId(42);
+        final AutofillId childId = mMockSession.newAutofillId(parentId, 108);
+        assertThat(childId.getViewId()).isEqualTo(42);
+        assertThat(childId.getVirtualChildId()).isEqualTo(108);
+        // TODO(b/121197119): assert session id
+    }
+
+    @Test
+    public void testNotifyXXX_null() {
+        assertThrows(NullPointerException.class, () -> mMockSession.notifyViewAppeared(null));
+        assertThrows(NullPointerException.class, () -> mMockSession.notifyViewDisappeared(null));
+        assertThrows(NullPointerException.class,
+                () -> mMockSession.notifyViewTextChanged(null, "whatever", 0));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
new file mode 100644
index 0000000..995946b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -0,0 +1,462 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.widget.FrameLayout;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Locale;
+
+/**
+ * Unit test for {@link ViewNode}.
+ *
+ * <p>To run it: {@code atest FrameworksCoreTests:android.view.contentcapture.ViewNodeTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ViewNodeTest {
+
+    private final Context mContext = InstrumentationRegistry.getTargetContext();
+
+    @Mock
+    private HtmlInfo mHtmlInfoMock;
+
+    @Test
+    public void testAutofillIdMethods_orphanView() {
+        View view = new View(mContext);
+        AutofillId initialId = new AutofillId(42);
+        view.setAutofillId(initialId);
+
+        ViewStructureImpl structure = new ViewStructureImpl(view);
+        ViewNode node = structure.getNode();
+
+        assertThat(node.getAutofillId()).isEqualTo(initialId);
+        assertThat(node.getParentAutofillId()).isNull();
+
+        AutofillId newId = new AutofillId(108);
+        structure.setAutofillId(newId);
+        assertThat(node.getAutofillId()).isEqualTo(newId);
+        assertThat(node.getParentAutofillId()).isNull();
+
+        structure.setAutofillId(new AutofillId(66), 6);
+        assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6));
+        assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66));
+    }
+
+    @Test
+    public void testAutofillIdMethods_parentedView() {
+        FrameLayout parent = new FrameLayout(mContext);
+        AutofillId initialParentId = new AutofillId(48);
+        parent.setAutofillId(initialParentId);
+
+        View child = new View(mContext);
+        AutofillId initialChildId = new AutofillId(42);
+        child.setAutofillId(initialChildId);
+
+        parent.addView(child);
+
+        ViewStructureImpl structure = new ViewStructureImpl(child);
+        ViewNode node = structure.getNode();
+
+        assertThat(node.getAutofillId()).isEqualTo(initialChildId);
+        assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+        AutofillId newChildId = new AutofillId(108);
+        structure.setAutofillId(newChildId);
+        assertThat(node.getAutofillId()).isEqualTo(newChildId);
+        assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+        AutofillId newParentId = new AutofillId(15162342);
+        parent.setAutofillId(newParentId);
+        assertThat(node.getAutofillId()).isEqualTo(newChildId);
+        assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+        structure.setAutofillId(new AutofillId(66), 6);
+        assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6));
+        assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66));
+    }
+
+    @Test
+    public void testAutofillIdMethods_explicitIdsConstructor() {
+        AutofillId initialParentId = new AutofillId(42);
+        ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108);
+        ViewNode node = structure.getNode();
+
+        assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108));
+        assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+        AutofillId newChildId = new AutofillId(108);
+        structure.setAutofillId(newChildId);
+        assertThat(node.getAutofillId()).isEqualTo(newChildId);
+        assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
+
+        structure.setAutofillId(new AutofillId(66), 6);
+        assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6));
+        assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66));
+    }
+
+    @Test
+    public void testInvalidSetters() {
+        View view = new View(mContext);
+        AutofillId initialId = new AutofillId(42);
+        view.setAutofillId(initialId);
+
+        ViewStructureImpl structure = new ViewStructureImpl(view);
+        ViewNode node = structure.getNode();
+        assertThat(node.getAutofillId()).isEqualTo(initialId); // sanity check
+
+        assertThrows(NullPointerException.class, () -> structure.setAutofillId(null));
+        assertThat(node.getAutofillId()).isEqualTo(initialId); // invariant
+
+        assertThrows(NullPointerException.class, () -> structure.setAutofillId(null, 666));
+        assertThat(node.getAutofillId()).isEqualTo(initialId); // invariant
+
+        assertThrows(NullPointerException.class, () -> structure.setTextIdEntry(null));
+        assertThat(node.getTextIdEntry()).isNull();
+    }
+
+    @Test
+    public void testUnsupportedProperties() {
+        View view = new View(mContext);
+
+        ViewStructureImpl structure = new ViewStructureImpl(view);
+        ViewNode node = structure.getNode();
+
+        structure.setChildCount(1);
+        assertThat(node.getChildCount()).isEqualTo(0);
+
+        structure.addChildCount(1);
+        assertThat(node.getChildCount()).isEqualTo(0);
+
+        assertThat(structure.newChild(0)).isNull();
+        assertThat(node.getChildCount()).isEqualTo(0);
+
+        assertThat(structure.asyncNewChild(0)).isNull();
+        assertThat(node.getChildCount()).isEqualTo(0);
+
+        structure.asyncCommit();
+        assertThat(node.getChildCount()).isEqualTo(0);
+
+        structure.setWebDomain("Y U NO SET?");
+        assertThat(node.getWebDomain()).isNull();
+
+        assertThat(structure.newHtmlInfoBuilder("WHATEVER")).isNull();
+
+        structure.setHtmlInfo(mHtmlInfoMock);
+        assertThat(node.getHtmlInfo()).isNull();
+
+        structure.setDataIsSensitive(true);
+
+        assertThat(structure.getTempRect()).isNull();
+    }
+
+    @Test
+    public void testValidProperties_directly() {
+        ViewStructureImpl structure = newSimpleStructure();
+        assertSimpleStructure(structure);
+        assertSimpleNode(structure.getNode());
+    }
+
+    @Test
+    public void testValidProperties_throughParcel() {
+        ViewStructureImpl structure = newSimpleStructure();
+        final ViewNode node = structure.getNode();
+        assertSimpleNode(node); // sanity check
+
+        final ViewNode clone = cloneThroughParcel(node);
+        assertSimpleNode(clone);
+    }
+
+    @Test
+    public void testComplexText_directly() {
+        ViewStructureImpl structure = newStructureWithComplexText();
+        assertStructureWithComplexText(structure);
+        assertNodeWithComplexText(structure.getNode());
+    }
+
+    @Test
+    public void testComplexText_throughParcel() {
+        ViewStructureImpl structure = newStructureWithComplexText();
+        final ViewNode node = structure.getNode();
+        assertNodeWithComplexText(node); // sanity check
+
+        ViewNode clone = cloneThroughParcel(node);
+        assertNodeWithComplexText(clone);
+    }
+
+    @Test
+    public void testVisibility() {
+        // Visibility is a special case becase it use flag masks, so we want to make sure it works
+        // fine
+        View view = new View(mContext);
+        ViewStructureImpl structure = new ViewStructureImpl(view);
+        ViewNode node = structure.getNode();
+
+        structure.setVisibility(View.VISIBLE);
+        assertThat(node.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE);
+
+        structure.setVisibility(View.GONE);
+        assertThat(node.getVisibility()).isEqualTo(View.GONE);
+        assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.GONE);
+
+        structure.setVisibility(View.VISIBLE);
+        assertThat(node.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE);
+
+        structure.setVisibility(View.INVISIBLE);
+        assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE);
+        assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.INVISIBLE);
+
+        structure.setVisibility(View.INVISIBLE | View.GONE);
+        assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE | View.GONE);
+        assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.INVISIBLE | View.GONE);
+
+
+        final int invalidValue = Math.max(Math.max(View.VISIBLE, View.INVISIBLE), View.GONE) * 2;
+        structure.setVisibility(View.VISIBLE);
+        structure.setVisibility(invalidValue); // should be ignored
+        assertThat(node.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE);
+
+        structure.setVisibility(View.GONE | invalidValue);
+        assertThat(node.getVisibility()).isEqualTo(View.GONE);
+        assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.GONE);
+    }
+
+    /**
+     * Creates a {@link ViewStructureImpl} that can be asserted through
+     * {@link #assertSimpleNode(ViewNode)}.
+     */
+    private ViewStructureImpl newSimpleStructure() {
+        View view = new View(mContext);
+        view.setAutofillId(new AutofillId(42));
+
+        ViewStructureImpl structure = new ViewStructureImpl(view);
+
+        // Basic properties
+        structure.setText("Text is set!");
+        structure.setClassName("Classy!");
+        structure.setContentDescription("Described I am!");
+        structure.setVisibility(View.INVISIBLE);
+
+        // Autofill properties
+        structure.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+        structure.setAutofillHints(new String[] { "Auto", "Man" });
+        structure.setAutofillOptions(new String[] { "Maybe" });
+        structure.setAutofillValue(AutofillValue.forText("Malkovich"));
+
+        // Graphic properties
+        structure.setElevation(6.66f);
+        structure.setAlpha(66.6f);
+        structure.setTransformation(Matrix.IDENTITY_MATRIX);
+
+        // Extra text properties
+        structure.setMinTextEms(6);
+        structure.setMaxTextLength(66);
+        structure.setMaxTextEms(666);
+        structure.setInputType(42);
+        structure.setTextIdEntry("TEXT, Y U NO ENTRY?");
+        structure.setLocaleList(new LocaleList(Locale.US, Locale.ENGLISH));
+
+        // Resource id
+        structure.setId(16, "package.name", "type.name", "entry.name");
+
+        // Dimensions
+        structure.setDimens(4, 8, 15, 16, 23, 42);
+
+        // Boolean properties
+        structure.setAssistBlocked(true);
+        structure.setEnabled(true);
+        structure.setClickable(true);
+        structure.setLongClickable(true);
+        structure.setContextClickable(true);
+        structure.setFocusable(true);
+        structure.setFocused(true);
+        structure.setAccessibilityFocused(true);
+        structure.setChecked(true);
+        structure.setActivated(true);
+        structure.setOpaque(true);
+
+        // Bundle
+        assertThat(structure.hasExtras()).isFalse();
+        final Bundle bundle = structure.getExtras();
+        assertThat(bundle).isNotNull();
+        bundle.putString("Marlon", "Bundle");
+        assertThat(structure.hasExtras()).isTrue();
+        return structure;
+    }
+
+    /**
+     * Asserts the properties of a {@link ViewNode} that was created by
+     * {@link #newSimpleStructure()}.
+     */
+    private void assertSimpleNode(ViewNode node) {
+
+        // Basic properties
+        assertThat(node.getAutofillId()).isEqualTo(new AutofillId(42));
+        assertThat(node.getParentAutofillId()).isNull();
+        assertThat(node.getText()).isEqualTo("Text is set!");
+        assertThat(node.getClassName()).isEqualTo("Classy!");
+        assertThat(node.getContentDescription().toString()).isEqualTo("Described I am!");
+        assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE);
+
+        // Autofill properties
+        assertThat(node.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
+        assertThat(node.getAutofillHints()).asList().containsExactly("Auto", "Man").inOrder();
+        assertThat(node.getAutofillOptions()).asList().containsExactly("Maybe").inOrder();
+        assertThat(node.getAutofillValue().getTextValue()).isEqualTo("Malkovich");
+
+        // Graphic properties
+        assertThat(node.getElevation()).isWithin(1.0e-10f).of(6.66f);
+        assertThat(node.getAlpha()).isWithin(1.0e-10f).of(66.6f);
+        assertThat(node.getTransformation()).isEqualTo(Matrix.IDENTITY_MATRIX);
+
+        // Extra text properties
+        assertThat(node.getMinTextEms()).isEqualTo(6);
+        assertThat(node.getMaxTextLength()).isEqualTo(66);
+        assertThat(node.getMaxTextEms()).isEqualTo(666);
+        assertThat(node.getInputType()).isEqualTo(42);
+        assertThat(node.getTextIdEntry()).isEqualTo("TEXT, Y U NO ENTRY?");
+        assertThat(node.getLocaleList()).isEqualTo(new LocaleList(Locale.US, Locale.ENGLISH));
+
+        // Resource id
+        assertThat(node.getId()).isEqualTo(16);
+        assertThat(node.getIdPackage()).isEqualTo("package.name");
+        assertThat(node.getIdType()).isEqualTo("type.name");
+        assertThat(node.getIdEntry()).isEqualTo("entry.name");
+
+        // Dimensions
+        assertThat(node.getLeft()).isEqualTo(4);
+        assertThat(node.getTop()).isEqualTo(8);
+        assertThat(node.getScrollX()).isEqualTo(15);
+        assertThat(node.getScrollY()).isEqualTo(16);
+        assertThat(node.getWidth()).isEqualTo(23);
+        assertThat(node.getHeight()).isEqualTo(42);
+
+        // Boolean properties
+        assertThat(node.isAssistBlocked()).isTrue();
+        assertThat(node.isEnabled()).isTrue();
+        assertThat(node.isClickable()).isTrue();
+        assertThat(node.isLongClickable()).isTrue();
+        assertThat(node.isContextClickable()).isTrue();
+        assertThat(node.isFocusable()).isTrue();
+        assertThat(node.isFocused()).isTrue();
+        assertThat(node.isAccessibilityFocused()).isTrue();
+        assertThat(node.isChecked()).isTrue();
+        assertThat(node.isActivated()).isTrue();
+        assertThat(node.isOpaque()).isTrue();
+
+        // Bundle
+        final Bundle bundle = node.getExtras();
+        assertThat(bundle).isNotNull();
+        assertThat(bundle.size()).isEqualTo(1);
+        assertThat(bundle.getString("Marlon")).isEqualTo("Bundle");
+    }
+
+    /**
+     * Asserts the properties of a {@link ViewStructureImpl} that was created by
+     * {@link #newSimpleStructure()}.
+     */
+    private void assertSimpleStructure(ViewStructureImpl structure) {
+        assertThat(structure.getAutofillId()).isEqualTo(new AutofillId(42));
+        assertThat(structure.getText()).isEqualTo("Text is set!");
+
+        // Bundle
+        final Bundle bundle = structure.getExtras();
+        assertThat(bundle.size()).isEqualTo(1);
+        assertThat(bundle.getString("Marlon")).isEqualTo("Bundle");
+    }
+
+    /**
+     * Creates a {@link ViewStructureImpl} with "complex" text properties (such as selection); it
+     * can be asserted through {@link #assertNodeWithComplexText(ViewNode)}.
+     */
+    private ViewStructureImpl newStructureWithComplexText() {
+        View view = new View(mContext);
+        ViewStructureImpl structure = new ViewStructureImpl(view);
+        structure.setText("IGNORE ME!");
+        structure.setText("Now we're talking!", 4, 8);
+        structure.setHint("Soylent Green is SPOILER ALERT");
+        structure.setTextStyle(15.0f, 16, 23, 42);
+        structure.setTextLines(new int[] {4,  8, 15} , new int[] {16, 23, 42});
+        return structure;
+    }
+
+    /**
+     * Asserts the properties of a {@link ViewNode} that was created by
+     * {@link #newStructureWithComplexText()}.
+     */
+    private void assertNodeWithComplexText(ViewNode node) {
+        assertThat(node.getText()).isEqualTo("Now we're talking!");
+        assertThat(node.getTextSelectionStart()).isEqualTo(4);
+        assertThat(node.getTextSelectionEnd()).isEqualTo(8);
+        assertThat(node.getHint()).isEqualTo("Soylent Green is SPOILER ALERT");
+        assertThat(node.getTextSize()).isWithin(1.0e-10f).of(15.0f);
+        assertThat(node.getTextColor()).isEqualTo(16);
+        assertThat(node.getTextBackgroundColor()).isEqualTo(23);
+        assertThat(node.getTextStyle()).isEqualTo(42);
+        assertThat(node.getTextLineCharOffsets()).asList().containsExactly(4, 8, 15).inOrder();
+        assertThat(node.getTextLineBaselines()).asList().containsExactly(16, 23, 42).inOrder();
+    }
+
+    /**
+     * Asserts the properties of a {@link ViewStructureImpl} that was created by
+     * {@link #newStructureWithComplexText()}.
+     */
+    private void assertStructureWithComplexText(ViewStructureImpl structure) {
+        assertThat(structure.getText()).isEqualTo("Now we're talking!");
+        assertThat(structure.getTextSelectionStart()).isEqualTo(4);
+        assertThat(structure.getTextSelectionEnd()).isEqualTo(8);
+        assertThat(structure.getHint()).isEqualTo("Soylent Green is SPOILER ALERT");
+    }
+
+    private ViewNode cloneThroughParcel(ViewNode node) {
+        Parcel parcel = Parcel.obtain();
+
+        try {
+            // Write to parcel
+            parcel.setDataPosition(0); // Sanity / paranoid check
+            ViewNode.writeToParcel(parcel, node, 0);
+
+            // Read from parcel
+            parcel.setDataPosition(0);
+            ViewNode clone = ViewNode.readFromParcel(parcel);
+            assertThat(clone).isNotNull();
+            return clone;
+        } finally {
+            parcel.recycle();
+        }
+    }
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
index f2bd353..2dc30ea 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
@@ -18,7 +18,7 @@
 ## The tests with only one dex
 include $(CLEAR_VARS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -41,7 +41,7 @@
 ## The tests with a minimal main dex
 include $(CLEAR_VARS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
index 4e6ec14..7812c58 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
@@ -15,10 +15,13 @@
  */
 package com.android.multidexlegacytestapp.test2;
 
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.multidexlegacytestapp.manymethods.Big001;
 import com.android.multidexlegacytestapp.manymethods.Big079;
+
 import junit.framework.Assert;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
index 9e41a92..b3044dc 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
@@ -1,8 +1,9 @@
 package com.android.multidexlegacytestapp.test2;
 
 import android.os.Bundle;
+
 import androidx.multidex.MultiDex;
-import android.support.test.runner.AndroidJUnitRunner;
+import androidx.test.runner.AndroidJUnitRunner;
 
 public class MultiDexAndroidJUnitRunner extends AndroidJUnitRunner {
 
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 04bf804..21531ab 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -99,6 +99,46 @@
         <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
     </family>
 
+    <family name="arbutus-slab">
+        <font weight="400" style="normal">ArbutusSlab-Regular.ttf</font>
+    </family>
+
+    <family name="arvo">
+        <font weight="400" style="normal">Arvo-Regular.ttf</font>
+        <font weight="400" style="italic">Arvo-Italic.ttf</font>
+        <font weight="700" style="normal">Arvo-Bold.ttf</font>
+        <font weight="700" style="italic">Arvo-BoldItalic.ttf</font>
+    </family>
+    <alias name="arvo-bold" to="arvo" weight="700" />
+
+    <family name="lato">
+        <font weight="400" style="normal">Lato-Regular.ttf</font>
+        <font weight="400" style="italic">Lato-Italic.ttf</font>
+        <font weight="700" style="normal">Lato-Bold.ttf</font>
+        <font weight="700" style="italic">Lato-BoldItalic.ttf</font>
+    </family>
+    <alias name="lato-bold" to="lato" weight="700" />
+
+    <family name="rubik">
+        <font weight="400" style="normal">Rubik-Regular.ttf</font>
+        <font weight="400" style="italic">Rubik-Italic.ttf</font>
+        <font weight="500" style="normal">Rubik-Medium.ttf</font>
+        <font weight="500" style="italic">Rubik-MediumItalic.ttf</font>
+        <font weight="700" style="normal">Rubik-Bold.ttf</font>
+        <font weight="700" style="italic">Rubik-BoldItalic.ttf</font>
+    </family>
+    <alias name="rubik-medium" to="rubik" weight="500" />
+
+    <family name="source-sans-pro">
+        <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+        <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+        <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+        <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+        <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+        <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+    </family>
+    <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600" />
+
     <!-- fallback fonts -->
     <family lang="und-Arab" variant="elegant">
         <font weight="400" style="normal">NotoNaskhArabic-Regular.ttf</font>
diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java
index 76eab4a..fcebad3 100644
--- a/drm/java/android/drm/DrmManagerClient.java
+++ b/drm/java/android/drm/DrmManagerClient.java
@@ -831,6 +831,7 @@
      *     content://media/<table_name>/<row_index> (or)
      *     file://sdcard/test.mp4
      *     http://test.com/test.mp4
+     *     https://test.com/test.mp4
      *
      * Here <table_name> shall be "video" or "audio" or "images"
      * <row_index> the index of the content in given table
@@ -843,7 +844,7 @@
                     scheme.equals(ContentResolver.SCHEME_FILE)) {
                 path = uri.getPath();
 
-            } else if (scheme.equals("http")) {
+            } else if (scheme.equals("http") || scheme.equals("https")) {
                 path = uri.toString();
 
             } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 9b86b77..25f6775 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1075,6 +1075,11 @@
                 continue; // If alias and named family are conflict, use named family.
             }
             final Typeface base = systemFontMap.get(alias.getToName());
+            if (base == null) {
+                // The missing target is a valid thing, some configuration don't have font files,
+                // e.g. wear devices. Just skip this alias.
+                continue;
+            }
             final int weight = alias.getWeight();
             final Typeface newFace = weight == 400 ? base :
                     new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index b9860ad..635d0ec 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -34,7 +34,7 @@
 namespace android::uirenderer {
 
 static std::mutex sLock{};
-static ThreadBase* sUploadThread = nullptr;
+static sp<ThreadBase> sUploadThread = nullptr;
 static renderthread::EglManager sEglManager;
 static int sPendingUploads = 0;
 static nsecs_t sLastUpload = 0;
@@ -257,4 +257,15 @@
                               Bitmap::computePalette(bitmap));
 }
 
+void HardwareBitmapUploader::terminate() {
+    std::lock_guard _lock{sLock};
+    LOG_ALWAYS_FATAL_IF(sPendingUploads, "terminate called while uploads in progress");
+    if (sUploadThread) {
+        sUploadThread->requestExit();
+        sUploadThread->join();
+        sUploadThread = nullptr;
+    }
+    sEglManager.destroy();
+}
+
 }  // namespace android::uirenderer
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index 6298013..40f2b0c 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -23,6 +23,7 @@
 class HardwareBitmapUploader {
 public:
     static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap);
+    static void terminate();
 };
 
 }  // namespace android::uirenderer
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index 019950f..b2351fc 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -53,16 +53,16 @@
     VkFormat format;
 
     // Input: Color space transfer params
-    float G;
-    float A;
-    float B;
-    float C;
-    float D;
-    float E;
-    float F;
+    float g;
+    float a;
+    float b;
+    float c;
+    float d;
+    float e;
+    float f;
 
     // Input: Color space transformation from linear RGB to D50-adapted XYZ
-    float matrix[9];
+    float colorSpaceTransform[9];
 
     // Input: current clip rect
     int clipLeft;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 56eedff..8cd97ed 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -93,7 +93,9 @@
         , mHasWideColorGamutSupport(false) {}
 
 EglManager::~EglManager() {
-    destroy();
+    if (hasEglContext()) {
+        ALOGW("~EglManager() leaked an EGL context");
+    }
 }
 
 void EglManager::initialize() {
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 524dfb0..174a140 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -19,6 +19,8 @@
 
 #include "Properties.h"
 #include "hwui/Typeface.h"
+#include "HardwareBitmapUploader.h"
+#include "renderthread/RenderProxy.h"
 
 #include <benchmark/benchmark.h>
 #include <getopt.h>
@@ -353,6 +355,9 @@
         gBenchmarkReporter->Finalize();
     }
 
+    renderthread::RenderProxy::trimMemory(100);
+    HardwareBitmapUploader::terminate();
+
     LeakChecker::checkForLeaks();
     return 0;
 }
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/thread/ThreadBase.h
index f9de8a5..8cdcc46 100644
--- a/libs/hwui/thread/ThreadBase.h
+++ b/libs/hwui/thread/ThreadBase.h
@@ -27,7 +27,7 @@
 
 namespace android::uirenderer {
 
-class ThreadBase : protected Thread {
+class ThreadBase : public Thread {
     PREVENT_COPY_AND_ASSIGN(ThreadBase);
 
 public:
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 0a90f85..b4f19c9 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -39,7 +39,7 @@
     virtual ~WeakLooperCallback() { }
 
 public:
-    WeakLooperCallback(const wp<LooperCallback>& callback) :
+    explicit WeakLooperCallback(const wp<LooperCallback>& callback) :
         mCallback(callback) {
     }
 
@@ -89,10 +89,6 @@
 
     mLocked.animationPending = false;
 
-    mLocked.displayWidth = -1;
-    mLocked.displayHeight = -1;
-    mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
-
     mLocked.presentation = PRESENTATION_POINTER;
     mLocked.presentationChanged = false;
 
@@ -110,15 +106,6 @@
     mLocked.lastFrameUpdatedTime = 0;
 
     mLocked.buttonState = 0;
-
-    mPolicy->loadPointerIcon(&mLocked.pointerIcon);
-
-    loadResources();
-
-    if (mLocked.pointerIcon.isValid()) {
-        mLocked.pointerIconChanged = true;
-        updatePointerLocked();
-    }
 }
 
 PointerController::~PointerController() {
@@ -144,23 +131,15 @@
 
 bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
         float* outMaxX, float* outMaxY) const {
-    if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
+
+    if (!mLocked.viewport.isValid()) {
         return false;
     }
 
-    *outMinX = 0;
-    *outMinY = 0;
-    switch (mLocked.displayOrientation) {
-    case DISPLAY_ORIENTATION_90:
-    case DISPLAY_ORIENTATION_270:
-        *outMaxX = mLocked.displayHeight - 1;
-        *outMaxY = mLocked.displayWidth - 1;
-        break;
-    default:
-        *outMaxX = mLocked.displayWidth - 1;
-        *outMaxY = mLocked.displayHeight - 1;
-        break;
-    }
+    *outMinX = mLocked.viewport.logicalLeft;
+    *outMinY = mLocked.viewport.logicalTop;
+    *outMaxX = mLocked.viewport.logicalRight - 1;
+    *outMaxY = mLocked.viewport.logicalBottom - 1;
     return true;
 }
 
@@ -231,6 +210,12 @@
     *outY = mLocked.pointerY;
 }
 
+int32_t PointerController::getDisplayId() const {
+    AutoMutex _l(mLock);
+
+    return mLocked.viewport.displayId;
+}
+
 void PointerController::fade(Transition transition) {
     AutoMutex _l(mLock);
 
@@ -355,48 +340,56 @@
 void PointerController::reloadPointerResources() {
     AutoMutex _l(mLock);
 
-    loadResources();
-
-    if (mLocked.presentation == PRESENTATION_POINTER) {
-        mLocked.additionalMouseResources.clear();
-        mLocked.animationResources.clear();
-        mPolicy->loadPointerIcon(&mLocked.pointerIcon);
-        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
-                                              &mLocked.animationResources);
-    }
-
-    mLocked.presentationChanged = true;
+    loadResourcesLocked();
     updatePointerLocked();
 }
 
-void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
-    AutoMutex _l(mLock);
+/**
+ * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation,
+ * so here we are getting the dimensions in the original, unrotated orientation (orientation 0).
+ */
+static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) {
+    width = viewport.deviceWidth;
+    height = viewport.deviceHeight;
 
-    // Adjust to use the display's unrotated coordinate frame.
-    if (orientation == DISPLAY_ORIENTATION_90
-            || orientation == DISPLAY_ORIENTATION_270) {
-        int32_t temp = height;
-        height = width;
-        width = temp;
+    if (viewport.orientation == DISPLAY_ORIENTATION_90
+            || viewport.orientation == DISPLAY_ORIENTATION_270) {
+        std::swap(width, height);
+    }
+}
+
+void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
+    AutoMutex _l(mLock);
+    if (viewport == mLocked.viewport) {
+        return;
     }
 
-    if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
-        mLocked.displayWidth = width;
-        mLocked.displayHeight = height;
+    const DisplayViewport oldViewport = mLocked.viewport;
+    mLocked.viewport = viewport;
+
+    int32_t oldDisplayWidth, oldDisplayHeight;
+    getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
+    int32_t newDisplayWidth, newDisplayHeight;
+    getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);
+
+    // Reset cursor position to center if size or display changed.
+    if (oldViewport.displayId != viewport.displayId
+            || oldDisplayWidth != newDisplayWidth
+            || oldDisplayHeight != newDisplayHeight) {
 
         float minX, minY, maxX, maxY;
         if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
             mLocked.pointerX = (minX + maxX) * 0.5f;
             mLocked.pointerY = (minY + maxY) * 0.5f;
+            // Reload icon resources for density may be changed.
+            loadResourcesLocked();
         } else {
             mLocked.pointerX = 0;
             mLocked.pointerY = 0;
         }
 
         fadeOutAndReleaseAllSpotsLocked();
-    }
-
-    if (mLocked.displayOrientation != orientation) {
+    } else if (oldViewport.orientation != viewport.orientation) {
         // Apply offsets to convert from the pixel top-left corner position to the pixel center.
         // This creates an invariant frame of reference that we can easily rotate when
         // taking into account that the pointer may be located at fractional pixel offsets.
@@ -405,37 +398,37 @@
         float temp;
 
         // Undo the previous rotation.
-        switch (mLocked.displayOrientation) {
+        switch (oldViewport.orientation) {
         case DISPLAY_ORIENTATION_90:
             temp = x;
-            x = mLocked.displayWidth - y;
+            x =  oldViewport.deviceHeight - y;
             y = temp;
             break;
         case DISPLAY_ORIENTATION_180:
-            x = mLocked.displayWidth - x;
-            y = mLocked.displayHeight - y;
+            x = oldViewport.deviceWidth - x;
+            y = oldViewport.deviceHeight - y;
             break;
         case DISPLAY_ORIENTATION_270:
             temp = x;
             x = y;
-            y = mLocked.displayHeight - temp;
+            y = oldViewport.deviceWidth - temp;
             break;
         }
 
         // Perform the new rotation.
-        switch (orientation) {
+        switch (viewport.orientation) {
         case DISPLAY_ORIENTATION_90:
             temp = x;
             x = y;
-            y = mLocked.displayWidth - temp;
+            y = viewport.deviceHeight - temp;
             break;
         case DISPLAY_ORIENTATION_180:
-            x = mLocked.displayWidth - x;
-            y = mLocked.displayHeight - y;
+            x = viewport.deviceWidth - x;
+            y = viewport.deviceHeight - y;
             break;
         case DISPLAY_ORIENTATION_270:
             temp = x;
-            x = mLocked.displayHeight - y;
+            x = viewport.deviceWidth - y;
             y = temp;
             break;
         }
@@ -444,7 +437,6 @@
         // and save the results.
         mLocked.pointerX = x - 0.5f;
         mLocked.pointerY = y - 0.5f;
-        mLocked.displayOrientation = orientation;
     }
 
     updatePointerLocked();
@@ -614,11 +606,16 @@
     mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
 }
 
-void PointerController::updatePointerLocked() {
+void PointerController::updatePointerLocked() REQUIRES(mLock) {
+    if (!mLocked.viewport.isValid()) {
+        return;
+    }
+
     mSpriteController->openTransaction();
 
     mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
     mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+    mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
 
     if (mLocked.pointerAlpha > 0) {
         mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
@@ -729,8 +726,18 @@
     }
 }
 
-void PointerController::loadResources() {
+void PointerController::loadResourcesLocked() REQUIRES(mLock) {
     mPolicy->loadPointerResources(&mResources);
+
+    if (mLocked.presentation == PRESENTATION_POINTER) {
+        mLocked.additionalMouseResources.clear();
+        mLocked.animationResources.clear();
+        mPolicy->loadPointerIcon(&mLocked.pointerIcon);
+        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+                                              &mLocked.animationResources);
+    }
+
+    mLocked.pointerIconChanged = true;
 }
 
 
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 7f4e5a5..a32cc42 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -23,6 +23,7 @@
 #include <vector>
 
 #include <ui/DisplayInfo.h>
+#include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <PointerControllerInterface.h>
 #include <utils/BitSet.h>
@@ -96,6 +97,7 @@
     virtual int32_t getButtonState() const;
     virtual void setPosition(float x, float y);
     virtual void getPosition(float* outX, float* outY) const;
+    virtual int32_t getDisplayId() const;
     virtual void fade(Transition transition);
     virtual void unfade(Transition transition);
 
@@ -106,7 +108,7 @@
 
     void updatePointerIcon(int32_t iconId);
     void setCustomPointerIcon(const SpriteIcon& icon);
-    void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
+    void setDisplayViewport(const DisplayViewport& viewport);
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void reloadPointerResources();
 
@@ -156,9 +158,7 @@
         size_t animationFrameIndex;
         nsecs_t lastFrameUpdatedTime;
 
-        int32_t displayWidth;
-        int32_t displayHeight;
-        int32_t displayOrientation;
+        DisplayViewport viewport;
 
         InactivityTimeout inactivityTimeout;
 
@@ -182,7 +182,7 @@
 
         Vector<Spot*> spots;
         Vector<sp<Sprite> > recycledSprites;
-    } mLocked;
+    } mLocked GUARDED_BY(mLock);
 
     bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
     void setPositionLocked(float x, float y);
@@ -207,7 +207,7 @@
     void fadeOutAndReleaseSpotLocked(Spot* spot);
     void fadeOutAndReleaseAllSpotsLocked();
 
-    void loadResources();
+    void loadResourcesLocked();
 };
 
 } // namespace android
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index eb2bc98..c1868d3 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -144,13 +144,16 @@
         }
     }
 
-    // Resize sprites if needed.
+    // Resize and/or reparent sprites if needed.
     SurfaceComposerClient::Transaction t;
     bool needApplyTransaction = false;
     for (size_t i = 0; i < numSprites; i++) {
         SpriteUpdate& update = updates.editItemAt(i);
+        if (update.state.surfaceControl == nullptr) {
+            continue;
+        }
 
-        if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
+        if (update.state.wantSurfaceVisible()) {
             int32_t desiredWidth = update.state.icon.bitmap.width();
             int32_t desiredHeight = update.state.icon.bitmap.height();
             if (update.state.surfaceWidth < desiredWidth
@@ -170,6 +173,12 @@
                 }
             }
         }
+
+        // If surface is a new one, we have to set right layer stack.
+        if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) {
+            t.setLayerStack(update.state.surfaceControl, update.state.displayId);
+            needApplyTransaction = true;
+        }
     }
     if (needApplyTransaction) {
         t.apply();
@@ -236,7 +245,7 @@
         if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
                 || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
                         | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
-                        | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
+                        | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) {
             needApplyTransaction = true;
 
             if (wantSurfaceVisibleAndDrawn
@@ -445,6 +454,15 @@
     }
 }
 
+void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
+    AutoMutex _l(mController->mLock);
+
+    if (mLocked.state.displayId != displayId) {
+        mLocked.state.displayId = displayId;
+        invalidateLocked(DIRTY_DISPLAY_ID);
+    }
+}
+
 void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
     bool wasDirty = mLocked.state.dirty;
     mLocked.state.dirty |= dirty;
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 31e43e9..5b216f5 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -125,6 +125,9 @@
 
     /* Sets the sprite transformation matrix. */
     virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
+
+    /* Sets the id of the display where the sprite should be shown. */
+    virtual void setDisplayId(int32_t displayId) = 0;
 };
 
 /*
@@ -170,6 +173,7 @@
         DIRTY_LAYER = 1 << 4,
         DIRTY_VISIBILITY = 1 << 5,
         DIRTY_HOTSPOT = 1 << 6,
+        DIRTY_DISPLAY_ID = 1 << 7,
     };
 
     /* Describes the state of a sprite.
@@ -180,7 +184,7 @@
     struct SpriteState {
         inline SpriteState() :
                 dirty(0), visible(false),
-                positionX(0), positionY(0), layer(0), alpha(1.0f),
+                positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT),
                 surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
         }
 
@@ -193,6 +197,7 @@
         int32_t layer;
         float alpha;
         SpriteTransformationMatrix transformationMatrix;
+        int32_t displayId;
 
         sp<SurfaceControl> surfaceControl;
         int32_t surfaceWidth;
@@ -225,6 +230,7 @@
         virtual void setLayer(int32_t layer);
         virtual void setAlpha(float alpha);
         virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
+        virtual void setDisplayId(int32_t displayId);
 
         inline const SpriteState& getStateLocked() const {
             return mLocked.state;
diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h
index c84de4c..0b7f6e46 100644
--- a/libs/protoutil/include/android/util/EncodedBuffer.h
+++ b/libs/protoutil/include/android/util/EncodedBuffer.h
@@ -38,13 +38,13 @@
 {
 public:
     EncodedBuffer();
-    EncodedBuffer(size_t chunkSize);
+    explicit EncodedBuffer(size_t chunkSize);
     ~EncodedBuffer();
 
     class Pointer {
     public:
         Pointer();
-        Pointer(size_t chunkSize);
+        explicit Pointer(size_t chunkSize);
 
         size_t pos() const;
         size_t index() const;
@@ -161,7 +161,7 @@
     friend class iterator;
     class iterator {
     public:
-        iterator(const EncodedBuffer& buffer);
+        explicit iterator(const EncodedBuffer& buffer);
 
         /**
          * Returns the number of bytes written in the buffer
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index bdc84da..e795b81 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -89,7 +89,6 @@
     List<String> getAllProviders();
     List<String> getProviders(in Criteria criteria, boolean enabledOnly);
     String getBestProvider(in Criteria criteria, boolean enabledOnly);
-    boolean providerMeetsCriteria(String provider, in Criteria criteria);
     ProviderProperties getProviderProperties(String provider);
     String getNetworkProviderPackage();
     void setLocationControllerExtraPackage(String packageName);
@@ -98,9 +97,7 @@
     boolean isLocationControllerExtraPackageEnabled();
 
     boolean isProviderEnabledForUser(String provider, int userId);
-    boolean setProviderEnabledForUser(String provider, boolean enabled, int userId);
     boolean isLocationEnabledForUser(int userId);
-    void setLocationEnabledForUser(boolean enabled, int userId);
     void addTestProvider(String name, in ProviderProperties properties, String opPackageName);
     void removeTestProvider(String provider, String opPackageName);
     void setTestProviderLocation(String provider, in Location loc, String opPackageName);
@@ -114,10 +111,6 @@
 
     // --- internal ---
 
-    // --- deprecated ---
-    void reportLocation(in Location location, boolean passive);
-    void reportLocationBatch(in List<Location> locations);
-
     // for reporting callback completion
     void locationCallbackFinished(ILocationListener listener);
 
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d96597b..59c6a0a 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -43,6 +43,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.location.ProviderProperties;
@@ -1282,11 +1283,13 @@
     @SystemApi
     @RequiresPermission(WRITE_SECURE_SETTINGS)
     public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
-        try {
-            mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        Settings.Secure.putIntForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.LOCATION_MODE,
+                enabled
+                        ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
+                        : Settings.Secure.LOCATION_MODE_OFF,
+                userHandle.getIdentifier());
     }
 
     /**
@@ -1372,20 +1375,22 @@
      * @return true if the value was set, false on database errors
      *
      * @throws IllegalArgumentException if provider is null
+     * @deprecated Do not manipulate providers individually, use
+     * {@link #setLocationEnabledForUser(boolean, UserHandle)} instead.
      * @hide
      */
+    @Deprecated
     @SystemApi
     @RequiresPermission(WRITE_SECURE_SETTINGS)
     public boolean setProviderEnabledForUser(
             String provider, boolean enabled, UserHandle userHandle) {
         checkProvider(provider);
 
-        try {
-            return mService.setProviderEnabledForUser(
-                    provider, enabled, userHandle.getIdentifier());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return Settings.Secure.setLocationProviderEnabledForUser(
+                mContext.getContentResolver(),
+                provider,
+                enabled,
+                userHandle.getIdentifier());
     }
 
     /**
@@ -1578,7 +1583,7 @@
      */
     @Deprecated
     public void clearTestProviderEnabled(String provider) {
-        setTestProviderEnabled(provider, true);
+        setTestProviderEnabled(provider, false);
     }
 
     /**
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index b531325..866634e 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -16,9 +16,6 @@
 
 package com.android.internal.location;
 
-import java.io.UnsupportedEncodingException;
-import java.util.concurrent.TimeUnit;
-
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -26,20 +23,23 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.location.LocationManager;
 import android.location.INetInitiatedListener;
+import android.location.LocationManager;
+import android.os.RemoteException;
 import android.os.SystemClock;
-import android.telephony.TelephonyManager;
+import android.os.UserHandle;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
-import android.os.RemoteException;
-import android.os.UserHandle;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
-import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.telephony.GsmAlphabet;
 
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.TimeUnit;
+
 /**
  * A GPS Network-initiated Handler class used by LocationManager.
  *
@@ -92,9 +92,6 @@
     public static final int GPS_ENC_SUPL_UCS2 = 3;
     public static final int GPS_ENC_UNKNOWN = -1;
 
-    // Limit on SUPL NI emergency mode time extension after emergency sessions ends
-    private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300;  // 5 minute maximum
-
     private final Context mContext;
     private final TelephonyManager mTelephonyManager;
     private final PhoneStateListener mPhoneStateListener;
@@ -252,19 +249,9 @@
     }
 
     public void setEmergencyExtensionSeconds(int emergencyExtensionSeconds) {
-        if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
-            Log.w(TAG, "emergencyExtensionSeconds " + emergencyExtensionSeconds
-                    + " too high, reset to " + MAX_EMERGENCY_MODE_EXTENSION_SECONDS);
-            emergencyExtensionSeconds = MAX_EMERGENCY_MODE_EXTENSION_SECONDS;
-        } else if (emergencyExtensionSeconds < 0) {
-            Log.w(TAG, "emergencyExtensionSeconds " + emergencyExtensionSeconds
-                    + " is negative, reset to zero.");
-            emergencyExtensionSeconds = 0;
-        }
         mEmergencyExtensionMillis = TimeUnit.SECONDS.toMillis(emergencyExtensionSeconds);
     }
 
-
     // Handles NI events from HAL
     public void handleNiNotification(GpsNiNotification notif) {
         if (DEBUG) Log.d(TAG, "in handleNiNotification () :"
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index b09335c..35f2877 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -18,5 +18,7 @@
     name: "com.android.location.provider",
     srcs: ["java/**/*.java"],
     api_packages: ["com.android.location.provider"],
-    metalava_enabled: false,
+    srcs_lib: "framework",
+    srcs_lib_whitelist_dirs: ["location/java"],
+    srcs_lib_whitelist_pkgs: ["com.android.internal.location"],
 }
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index f482f64..df96994 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -1334,11 +1334,11 @@
         return addTask(new Task(CALL_COMPLETED_SET_WAKE_LOCK, false) {
             @Override
             void process() {
-                boolean washeld = false;
+                boolean wasHeld = false;
 
                 if (mWakeLock != null) {
                     if (mWakeLock.isHeld()) {
-                        washeld = true;
+                        wasHeld = true;
                         mWakeLock.release();
                     }
                 }
@@ -1346,7 +1346,7 @@
                 mWakeLock = wakeLock;
                 if (mWakeLock != null) {
                     mWakeLock.setReferenceCounted(false);
-                    if (washeld) {
+                    if (wasHeld) {
                         mWakeLock.acquire();
                     }
                 }
diff --git a/media/java/android/media/Session2Command.java b/media/java/android/media/Session2Command.java
index d2a5aae..8b285f2 100644
--- a/media/java/android/media/Session2Command.java
+++ b/media/java/android/media/Session2Command.java
@@ -36,8 +36,6 @@
  * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
  * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
  * for consistent behavior across all devices.
- * </p>
- * @hide
  */
 public final class Session2Command implements Parcelable {
     /**
@@ -151,14 +149,17 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        if (dest == null) {
+            throw new IllegalArgumentException("parcel shouldn't be null");
+        }
         dest.writeInt(mCommandCode);
         dest.writeString(mCustomCommand);
         dest.writeBundle(mExtras);
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (!(obj instanceof Session2Command)) {
             return false;
         }
@@ -185,7 +186,7 @@
          * @param resultCode result code
          * @param resultData result data
          */
-        public Result(int resultCode, Bundle resultData) {
+        public Result(int resultCode, @Nullable Bundle resultData) {
             mResultCode = resultCode;
             mResultData = resultData;
         }
@@ -200,6 +201,7 @@
         /**
          * Returns the result data.
          */
+        @Nullable
         public Bundle getResultData() {
             return mResultData;
         }
diff --git a/media/java/android/media/Session2CommandGroup.java b/media/java/android/media/Session2CommandGroup.java
index 122dfb1..4668ec4 100644
--- a/media/java/android/media/Session2CommandGroup.java
+++ b/media/java/android/media/Session2CommandGroup.java
@@ -130,7 +130,7 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeParcelableArray((Session2Command[]) mCommands.toArray(), 0);
+        dest.writeParcelableArray(mCommands.toArray(new Session2Command[0]), 0);
     }
 
     /**
diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
index 4634c69..e1fff38 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -171,7 +171,7 @@
         dest.writeString(mPackageName);
         dest.writeString(mServiceName);
         // TODO: Uncomment below
-        //dest.writeStrongBinder(mSessionLink.asBinder());
+        //dest.writeStrongBinder(mSessionLink.getBinder());
         dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString());
     }
 
diff --git a/media/java/android/media/session/ControllerCallbackLink.aidl b/media/java/android/media/session/ControllerCallbackLink.aidl
new file mode 100644
index 0000000..8ee8c7d
--- /dev/null
+++ b/media/java/android/media/session/ControllerCallbackLink.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+package android.media.session;
+
+parcelable ControllerCallbackLink;
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java
new file mode 100644
index 0000000..a143c9b
--- /dev/null
+++ b/media/java/android/media/session/ControllerCallbackLink.java
@@ -0,0 +1,272 @@
+/*
+ * 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.
+ */
+
+package android.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.media.MediaMetadata;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession.QueueItem;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Handles incoming commands to {@link MediaController.Callback}.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ */
+public final class ControllerCallbackLink implements Parcelable {
+    final CallbackStub mCallbackStub;
+    final ISessionControllerCallback mIControllerCallback;
+
+    /**
+     * Constructor for stub (Callee)
+     * @hide
+     */
+    @SystemApi
+    public ControllerCallbackLink(@NonNull CallbackStub callbackStub) {
+        mCallbackStub = callbackStub;
+        mIControllerCallback = new CallbackStubProxy();
+    }
+
+    /**
+     * Constructor for interface (Caller)
+     */
+    ControllerCallbackLink(Parcel in) {
+        mCallbackStub = null;
+        mIControllerCallback = ISessionControllerCallback.Stub.asInterface(in.readStrongBinder());
+    }
+
+    /**
+     * Notify controller that the connected session is destroyed.
+     */
+    public void notifySessionDestroyed() {
+        try {
+            mIControllerCallback.notifySessionDestroyed();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify controller that the connected session sends an event.
+     *
+     * @param event the name of the event
+     * @param extras the extras included with the event
+     */
+    public void notifyEvent(@NonNull String event, @Nullable Bundle extras) {
+        try {
+            mIControllerCallback.notifyEvent(event, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify controller that the current playback state is changed.
+     *
+     * @param state the new playback state
+     */
+    public void notifyPlaybackStateChanged(@Nullable PlaybackState state) {
+        try {
+            mIControllerCallback.notifyPlaybackStateChanged(state);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify controller that the current metadata is changed.
+     *
+     * @param metadata the new metadata
+     */
+    public void notifyMetadataChanged(@Nullable MediaMetadata metadata) {
+        try {
+            mIControllerCallback.notifyMetadataChanged(metadata);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify controller that the current queue is changed.
+     *
+     * @param queue the new queue
+     */
+    public void notifyQueueChanged(@Nullable List<QueueItem> queue) {
+        try {
+            mIControllerCallback.notifyQueueChanged(queue);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify controller that the current queue title is changed.
+     *
+     * @param title the new queue title
+     */
+    public void notifyQueueTitleChanged(@Nullable CharSequence title) {
+        try {
+            mIControllerCallback.notifyQueueTitleChanged(title);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify controller that the extras are changed.
+     *
+     * @param extras the new extras
+     */
+    public void notifyExtrasChanged(@Nullable Bundle extras) {
+        try {
+            mIControllerCallback.notifyExtrasChanged(extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify controller that the playback info is changed.
+     *
+     * @param info the new playback info
+     */
+    public void notifyVolumeInfoChanged(@NonNull PlaybackInfo info) {
+        try {
+            mIControllerCallback.notifyVolumeInfoChanged(info);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Gets the binder */
+    @NonNull
+    public IBinder getBinder() {
+        return mIControllerCallback.asBinder();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mIControllerCallback.asBinder());
+    }
+
+    public static final Parcelable.Creator<ControllerCallbackLink> CREATOR =
+            new Parcelable.Creator<ControllerCallbackLink>() {
+        @Override
+        public ControllerCallbackLink createFromParcel(Parcel in) {
+            return new ControllerCallbackLink(in);
+        }
+
+        @Override
+        public ControllerCallbackLink[] newArray(int size) {
+            return new ControllerCallbackLink[size];
+        }
+    };
+
+    /**
+     * Class for Stub implementation
+     * @hide
+     */
+    @SystemApi
+    public abstract static class CallbackStub {
+        /** Stub method for ISessionControllerCallback.notifySessionDestroyed */
+        public void onSessionDestroyed() {
+        }
+
+        /** Stub method for ISessionControllerCallback.notifyEvent */
+        public void onEvent(@NonNull String event, @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionControllerCallback.notifyPlaybackStateChanged */
+        public void onPlaybackStateChanged(@Nullable PlaybackState state) {
+        }
+
+        /** Stub method for ISessionControllerCallback.notifyMetadataChanged */
+        public void onMetadataChanged(@Nullable MediaMetadata metadata) {
+        }
+
+        /** Stub method for ISessionControllerCallback.notifyQueueChanged */
+        public void onQueueChanged(@Nullable List<QueueItem> queue) {
+        }
+
+        /** Stub method for ISessionControllerCallback.notifyQueueTitleChanged */
+        public void onQueueTitleChanged(@Nullable CharSequence title) {
+        }
+
+        /** Stub method for ISessionControllerCallback.notifyExtrasChanged */
+        public void onExtrasChanged(@Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionControllerCallback.notifyVolumeInfoChanged */
+        public void onVolumeInfoChanged(@NonNull PlaybackInfo info) {
+        }
+    }
+
+    private class CallbackStubProxy extends ISessionControllerCallback.Stub {
+        @Override
+        public void notifyEvent(String event, Bundle extras) {
+            mCallbackStub.onEvent(event, extras);
+        }
+
+        @Override
+        public void notifySessionDestroyed() {
+            mCallbackStub.onSessionDestroyed();
+        }
+
+        @Override
+        public void notifyPlaybackStateChanged(PlaybackState state) {
+            mCallbackStub.onPlaybackStateChanged(state);
+        }
+
+        @Override
+        public void notifyMetadataChanged(MediaMetadata metadata) {
+            mCallbackStub.onMetadataChanged(metadata);
+        }
+
+        @Override
+        public void notifyQueueChanged(List<QueueItem> queue) {
+            mCallbackStub.onQueueChanged(queue);
+        }
+
+        @Override
+        public void notifyQueueTitleChanged(CharSequence title) {
+            mCallbackStub.onQueueTitleChanged(title);
+        }
+
+        @Override
+        public void notifyExtrasChanged(Bundle extras) {
+            mCallbackStub.onExtrasChanged(extras);
+        }
+
+        @Override
+        public void notifyVolumeInfoChanged(PlaybackInfo info) {
+            mCallbackStub.onVolumeInfoChanged(info);
+        }
+    }
+}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index bfc05fa..1524ad9 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -16,7 +16,6 @@
 package android.media.session;
 
 import android.app.PendingIntent;
-import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
 import android.media.session.ISessionController;
@@ -41,7 +40,8 @@
     // These commands are for the TransportPerformer
     void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription);
     void setPlaybackState(in PlaybackState state);
-    void setQueue(in ParceledListSlice queue);
+    // TODO(b/122432476): Replace List with MediaParceledListSlice
+    void setQueue(in List<MediaSession.QueueItem> queue);
     void setQueueTitle(CharSequence title);
     void setExtras(in Bundle extras);
     void setRatingType(int type);
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 626338d..9b86bfc 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -17,7 +17,7 @@
 
 import android.content.Intent;
 import android.media.Rating;
-import android.media.session.ISessionControllerCallback;
+import android.media.session.ControllerCallbackLink;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.ResultReceiver;
@@ -26,46 +26,46 @@
  * @hide
  */
 oneway interface ISessionCallback {
-    void onCommand(String packageName, int pid, int uid, ISessionControllerCallback caller,
+    void notifyCommand(String packageName, int pid, int uid, in ControllerCallbackLink caller,
             String command, in Bundle args, in ResultReceiver cb);
-    void onMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent,
+    void notifyMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent,
             int sequenceNumber, in ResultReceiver cb);
-    void onMediaButtonFromController(String packageName, int pid, int uid,
-            ISessionControllerCallback caller, in Intent mediaButtonIntent);
+    void notifyMediaButtonFromController(String packageName, int pid, int uid,
+            in ControllerCallbackLink caller, in Intent mediaButtonIntent);
 
     // These callbacks are for the TransportPerformer
-    void onPrepare(String packageName, int pid, int uid, ISessionControllerCallback caller);
-    void onPrepareFromMediaId(String packageName, int pid, int uid,
-            ISessionControllerCallback caller, String mediaId, in Bundle extras);
-    void onPrepareFromSearch(String packageName, int pid, int uid,
-            ISessionControllerCallback caller, String query, in Bundle extras);
-    void onPrepareFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller,
+    void notifyPrepare(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+    void notifyPrepareFromMediaId(String packageName, int pid, int uid,
+            in ControllerCallbackLink caller, String mediaId, in Bundle extras);
+    void notifyPrepareFromSearch(String packageName, int pid, int uid,
+            in ControllerCallbackLink caller, String query, in Bundle extras);
+    void notifyPrepareFromUri(String packageName, int pid, int uid,
+            in ControllerCallbackLink caller, in Uri uri, in Bundle extras);
+    void notifyPlay(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+    void notifyPlayFromMediaId(String packageName, int pid, int uid,
+            in ControllerCallbackLink caller, String mediaId, in Bundle extras);
+    void notifyPlayFromSearch(String packageName, int pid, int uid,
+            in ControllerCallbackLink caller, String query, in Bundle extras);
+    void notifyPlayFromUri(String packageName, int pid, int uid, in ControllerCallbackLink caller,
             in Uri uri, in Bundle extras);
-    void onPlay(String packageName, int pid, int uid, ISessionControllerCallback caller);
-    void onPlayFromMediaId(String packageName, int pid, int uid, ISessionControllerCallback caller,
-            String mediaId, in Bundle extras);
-    void onPlayFromSearch(String packageName, int pid, int uid, ISessionControllerCallback caller,
-            String query, in Bundle extras);
-    void onPlayFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller,
-            in Uri uri, in Bundle extras);
-    void onSkipToTrack(String packageName, int pid, int uid, ISessionControllerCallback caller,
+    void notifySkipToTrack(String packageName, int pid, int uid, in ControllerCallbackLink caller,
             long id);
-    void onPause(String packageName, int pid, int uid, ISessionControllerCallback caller);
-    void onStop(String packageName, int pid, int uid, ISessionControllerCallback caller);
-    void onNext(String packageName, int pid, int uid, ISessionControllerCallback caller);
-    void onPrevious(String packageName, int pid, int uid, ISessionControllerCallback caller);
-    void onFastForward(String packageName, int pid, int uid, ISessionControllerCallback caller);
-    void onRewind(String packageName, int pid, int uid, ISessionControllerCallback caller);
-    void onSeekTo(String packageName, int pid, int uid, ISessionControllerCallback caller,
+    void notifyPause(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+    void notifyStop(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+    void notifyNext(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+    void notifyPrevious(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+    void notifyFastForward(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+    void notifyRewind(String packageName, int pid, int uid, in ControllerCallbackLink caller);
+    void notifySeekTo(String packageName, int pid, int uid, in ControllerCallbackLink caller,
             long pos);
-    void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+    void notifyRate(String packageName, int pid, int uid, in ControllerCallbackLink caller,
             in Rating rating);
-    void onCustomAction(String packageName, int pid, int uid, ISessionControllerCallback caller,
+    void notifyCustomAction(String packageName, int pid, int uid, in ControllerCallbackLink caller,
             String action, in Bundle args);
 
     // These callbacks are for volume handling
-    void onAdjustVolume(String packageName, int pid, int uid, ISessionControllerCallback caller,
+    void notifyAdjustVolume(String packageName, int pid, int uid, in ControllerCallbackLink caller,
             int direction);
-    void onSetVolumeTo(String packageName, int pid, int uid,
-            ISessionControllerCallback caller, int value);
+    void notifySetVolumeTo(String packageName, int pid, int uid,
+            in ControllerCallbackLink caller, int value);
 }
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index a843881..2ba09fd 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -17,10 +17,9 @@
 
 import android.app.PendingIntent;
 import android.content.Intent;
-import android.content.pm.ParceledListSlice;
 import android.media.MediaMetadata;
 import android.media.Rating;
-import android.media.session.ISessionControllerCallback;
+import android.media.session.ControllerCallbackLink;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
@@ -36,12 +35,12 @@
  * @hide
  */
 interface ISessionController {
-    void sendCommand(String packageName, ISessionControllerCallback caller,
+    void sendCommand(String packageName, in ControllerCallbackLink caller,
             String command, in Bundle args, in ResultReceiver cb);
-    boolean sendMediaButton(String packageName, ISessionControllerCallback caller,
+    boolean sendMediaButton(String packageName, in ControllerCallbackLink caller,
             boolean asSystemService, in KeyEvent mediaButton);
-    void registerCallbackListener(String packageName, ISessionControllerCallback cb);
-    void unregisterCallbackListener(ISessionControllerCallback cb);
+    void registerCallbackListener(String packageName, in ControllerCallbackLink cb);
+    void unregisterCallbackListener(in ControllerCallbackLink cb);
     boolean isTransportControlEnabled();
     String getPackageName();
     String getTag();
@@ -49,40 +48,40 @@
     long getFlags();
     MediaController.PlaybackInfo getVolumeAttributes();
     void adjustVolume(String packageName, String opPackageName,
-            in ISessionControllerCallback caller, boolean asSystemService, int direction,
+            in ControllerCallbackLink caller, boolean asSystemService, int direction,
             int flags);
-    void setVolumeTo(String packageName, String opPackageName, in ISessionControllerCallback caller,
+    void setVolumeTo(String packageName, String opPackageName, in ControllerCallbackLink caller,
             int value, int flags);
 
     // These commands are for the TransportControls
-    void prepare(String packageName, ISessionControllerCallback caller);
-    void prepareFromMediaId(String packageName, ISessionControllerCallback caller,
+    void prepare(String packageName, in ControllerCallbackLink caller);
+    void prepareFromMediaId(String packageName, in ControllerCallbackLink caller,
             String mediaId, in Bundle extras);
-    void prepareFromSearch(String packageName, ISessionControllerCallback caller,
+    void prepareFromSearch(String packageName, in ControllerCallbackLink caller,
             String string, in Bundle extras);
-    void prepareFromUri(String packageName, ISessionControllerCallback caller,
+    void prepareFromUri(String packageName, in ControllerCallbackLink caller,
             in Uri uri, in Bundle extras);
-    void play(String packageName, ISessionControllerCallback caller);
-    void playFromMediaId(String packageName, ISessionControllerCallback caller,
+    void play(String packageName, in ControllerCallbackLink caller);
+    void playFromMediaId(String packageName, in ControllerCallbackLink caller,
             String mediaId, in Bundle extras);
-    void playFromSearch(String packageName, ISessionControllerCallback caller,
+    void playFromSearch(String packageName, in ControllerCallbackLink caller,
             String string, in Bundle extras);
-    void playFromUri(String packageName, ISessionControllerCallback caller,
+    void playFromUri(String packageName, in ControllerCallbackLink caller,
             in Uri uri, in Bundle extras);
-    void skipToQueueItem(String packageName, ISessionControllerCallback caller, long id);
-    void pause(String packageName, ISessionControllerCallback caller);
-    void stop(String packageName, ISessionControllerCallback caller);
-    void next(String packageName, ISessionControllerCallback caller);
-    void previous(String packageName, ISessionControllerCallback caller);
-    void fastForward(String packageName, ISessionControllerCallback caller);
-    void rewind(String packageName, ISessionControllerCallback caller);
-    void seekTo(String packageName, ISessionControllerCallback caller, long pos);
-    void rate(String packageName, ISessionControllerCallback caller, in Rating rating);
-    void sendCustomAction(String packageName, ISessionControllerCallback caller,
+    void skipToQueueItem(String packageName, in ControllerCallbackLink caller, long id);
+    void pause(String packageName, in ControllerCallbackLink caller);
+    void stop(String packageName, in ControllerCallbackLink caller);
+    void next(String packageName, in ControllerCallbackLink caller);
+    void previous(String packageName, in ControllerCallbackLink caller);
+    void fastForward(String packageName, in ControllerCallbackLink caller);
+    void rewind(String packageName, in ControllerCallbackLink caller);
+    void seekTo(String packageName, in ControllerCallbackLink caller, long pos);
+    void rate(String packageName, in ControllerCallbackLink caller, in Rating rating);
+    void sendCustomAction(String packageName, in ControllerCallbackLink caller,
             String action, in Bundle args);
     MediaMetadata getMetadata();
     PlaybackState getPlaybackState();
-    ParceledListSlice getQueue();
+    List<MediaSession.QueueItem> getQueue();
     CharSequence getQueueTitle();
     Bundle getExtras();
     int getRatingType();
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index fac8897..5c02e7c 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -15,25 +15,24 @@
 
 package android.media.session;
 
-import android.content.pm.ParceledListSlice;
 import android.media.MediaMetadata;
-import android.media.session.PlaybackState;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
 import android.os.Bundle;
 
 /**
  * @hide
  */
 oneway interface ISessionControllerCallback {
-    void onEvent(String event, in Bundle extras);
-    void onSessionDestroyed();
+    void notifyEvent(String event, in Bundle extras);
+    void notifySessionDestroyed();
 
     // These callbacks are for the TransportController
-    void onPlaybackStateChanged(in PlaybackState state);
-    void onMetadataChanged(in MediaMetadata metadata);
-    void onQueueChanged(in ParceledListSlice queue);
-    void onQueueTitleChanged(CharSequence title);
-    void onExtrasChanged(in Bundle extras);
-    void onVolumeInfoChanged(in MediaController.PlaybackInfo info);
+    void notifyPlaybackStateChanged(in PlaybackState state);
+    void notifyMetadataChanged(in MediaMetadata metadata);
+    void notifyQueueChanged(in List<MediaSession.QueueItem> queue);
+    void notifyQueueTitleChanged(CharSequence title);
+    void notifyExtrasChanged(in Bundle extras);
+    void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info);
 }
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 51148e2..7ac3ef2 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -23,7 +23,7 @@
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession;
-import android.media.session.ISessionCallback;
+import android.media.session.SessionCallbackLink;
 import android.os.Bundle;
 import android.view.KeyEvent;
 
@@ -32,9 +32,10 @@
  * @hide
  */
 interface ISessionManager {
-    ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
+    ISession createSession(String packageName, in SessionCallbackLink cb, String tag, int userId);
     void notifySession2Created(in Session2Token sessionToken);
     List<IBinder> getSessions(in ComponentName compName, int userId);
+    List<Session2Token> getSession2Tokens(int userId);
     void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
             boolean needWakeLock);
     void dispatchVolumeKeyEvent(String packageName, String opPackageName, boolean asSystemService,
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index ef2df15..a1b8170 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -21,12 +21,12 @@
 import android.annotation.UnsupportedAppUsage;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
+import android.media.session.MediaSession.QueueItem;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -72,7 +72,8 @@
 
     private final MediaSession.Token mToken;
     private final Context mContext;
-    private final CallbackStub mCbStub = new CallbackStub(this);
+    private final ControllerCallbackLink mCbStub =
+            new ControllerCallbackLink(new CallbackStub(this));
     private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
     private final Object mLock = new Object();
 
@@ -251,10 +252,7 @@
      */
     public @Nullable List<MediaSession.QueueItem> getQueue() {
         try {
-            ParceledListSlice queue = mSessionBinder.getQueue();
-            if (queue != null) {
-                return queue.getList();
-            }
+            return mSessionBinder.getQueue();
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error calling getQueue.", e);
         }
@@ -1113,10 +1111,10 @@
         };
     }
 
-    private final static class CallbackStub extends ISessionControllerCallback.Stub {
+    private static final class CallbackStub extends ControllerCallbackLink.CallbackStub {
         private final WeakReference<MediaController> mController;
 
-        public CallbackStub(MediaController controller) {
+        CallbackStub(MediaController controller) {
             mController = new WeakReference<MediaController>(controller);
         }
 
@@ -1153,9 +1151,7 @@
         }
 
         @Override
-        public void onQueueChanged(ParceledListSlice parceledQueue) {
-            List<MediaSession.QueueItem> queue = parceledQueue == null ? null : parceledQueue
-                    .getList();
+        public void onQueueChanged(List<QueueItem> queue) {
             MediaController controller = mController.get();
             if (controller != null) {
                 controller.postMessage(MSG_UPDATE_QUEUE, queue, null);
@@ -1185,7 +1181,6 @@
                 controller.postMessage(MSG_UPDATE_VOLUME, info, null);
             }
         }
-
     }
 
     private final static class MessageHandler extends Handler {
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index df8cc35..e07cf15 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -24,7 +24,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
@@ -130,7 +129,7 @@
     private final MediaSession.Token mSessionToken;
     private final MediaController mController;
     private final ISession mBinder;
-    private final CallbackStub mCbStub;
+    private final SessionCallbackLink mCbStub;
 
     // Do not change the name of mCallback. Support lib accesses this by using reflection.
     @UnsupportedAppUsage
@@ -173,7 +172,7 @@
         }
         mMaxBitmapSize = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
-        mCbStub = new CallbackStub(this);
+        mCbStub = new SessionCallbackLink(new CallbackStub(this));
         MediaSessionManager manager = (MediaSessionManager) context
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         try {
@@ -467,7 +466,7 @@
      */
     public void setQueue(@Nullable List<QueueItem> queue) {
         try {
-            mBinder.setQueue(queue == null ? null : new ParceledListSlice<QueueItem>(queue));
+            mBinder.setQueue(queue);
         } catch (RemoteException e) {
             Log.wtf("Dead object in setQueue.", e);
         }
@@ -1063,7 +1062,7 @@
     /**
      * @hide
      */
-    public static class CallbackStub extends ISessionCallback.Stub {
+    public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
         private WeakReference<MediaSession> mMediaSession;
 
         public CallbackStub(MediaSession session) {
@@ -1071,14 +1070,14 @@
         }
 
         private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             return new RemoteUserInfo(packageName, pid, uid,
-                    caller != null ? caller.asBinder() : null);
+                    caller != null ? caller.getBinder() : null);
         }
 
         @Override
         public void onCommand(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) {
+                ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1104,7 +1103,7 @@
 
         @Override
         public void onMediaButtonFromController(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, Intent mediaButtonIntent) {
+                ControllerCallbackLink caller, Intent mediaButtonIntent) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1114,7 +1113,7 @@
 
         @Override
         public void onPrepare(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1123,7 +1122,7 @@
 
         @Override
         public void onPrepareFromMediaId(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String mediaId,
+                ControllerCallbackLink caller, String mediaId,
                 Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
@@ -1134,7 +1133,7 @@
 
         @Override
         public void onPrepareFromSearch(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String query,
+                ControllerCallbackLink caller, String query,
                 Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
@@ -1145,7 +1144,7 @@
 
         @Override
         public void onPrepareFromUri(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, Uri uri, Bundle extras) {
+                ControllerCallbackLink caller, Uri uri, Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1155,7 +1154,7 @@
 
         @Override
         public void onPlay(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1164,7 +1163,7 @@
 
         @Override
         public void onPlayFromMediaId(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String mediaId,
+                ControllerCallbackLink caller, String mediaId,
                 Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
@@ -1175,7 +1174,7 @@
 
         @Override
         public void onPlayFromSearch(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String query,
+                ControllerCallbackLink caller, String query,
                 Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
@@ -1186,7 +1185,7 @@
 
         @Override
         public void onPlayFromUri(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, Uri uri, Bundle extras) {
+                ControllerCallbackLink caller, Uri uri, Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1196,7 +1195,7 @@
 
         @Override
         public void onSkipToTrack(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, long id) {
+                ControllerCallbackLink caller, long id) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id);
@@ -1205,7 +1204,7 @@
 
         @Override
         public void onPause(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1214,7 +1213,7 @@
 
         @Override
         public void onStop(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1223,7 +1222,7 @@
 
         @Override
         public void onNext(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1232,7 +1231,7 @@
 
         @Override
         public void onPrevious(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1241,7 +1240,7 @@
 
         @Override
         public void onFastForward(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1250,7 +1249,7 @@
 
         @Override
         public void onRewind(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
@@ -1259,7 +1258,7 @@
 
         @Override
         public void onSeekTo(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, long pos) {
+                ControllerCallbackLink caller, long pos) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos);
@@ -1267,7 +1266,7 @@
         }
 
         @Override
-        public void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+        public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
                 Rating rating) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
@@ -1277,7 +1276,7 @@
 
         @Override
         public void onCustomAction(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String action, Bundle args) {
+                ControllerCallbackLink caller, String action, Bundle args) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1287,7 +1286,7 @@
 
         @Override
         public void onAdjustVolume(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, int direction) {
+                ControllerCallbackLink caller, int direction) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller),
@@ -1297,7 +1296,7 @@
 
         @Override
         public void onSetVolumeTo(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, int value) {
+                ControllerCallbackLink caller, int value) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller),
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index ef5cf00..56ea484 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -98,7 +98,7 @@
      * @return The binder object from the system
      * @hide
      */
-    public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
+    public @NonNull ISession createSession(@NonNull SessionCallbackLink cbStub,
             @NonNull String tag, int userId) throws RemoteException {
         return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
     }
@@ -179,6 +179,44 @@
     }
 
     /**
+     * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
+     * current user.
+     * <p>
+     * Although this API can be used without any restriction, each session owners can accept or
+     * reject your uses of {@link MediaSession2}.
+     *
+     * @return A list of {@link Session2Token}.
+     * @hide
+     */
+    // TODO: unhide
+    @NonNull
+    public List<Session2Token> getSession2Tokens() {
+        return getSession2Tokens(UserHandle.myUserId());
+    }
+
+    /**
+     * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
+     * given user.
+     * <p>
+     * If you want to get tokens for another user, you must hold the
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+     *
+     * @param userId The user id to fetch sessions for.
+     * @return A list of {@link Session2Token}
+     * @hide
+     */
+    // TODO: unhide
+    @NonNull
+    public List<Session2Token> getSession2Tokens(int userId) {
+        try {
+            return mService.getSession2Tokens(userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get session tokens", e);
+        }
+        return new ArrayList<>();
+    }
+
+    /**
      * Add a listener to be notified when the list of active sessions
      * changes.This requires the
      * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
diff --git a/media/java/android/media/session/SessionCallbackLink.aidl b/media/java/android/media/session/SessionCallbackLink.aidl
new file mode 100644
index 0000000..c489e5b
--- /dev/null
+++ b/media/java/android/media/session/SessionCallbackLink.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+package android.media.session;
+
+parcelable SessionCallbackLink;
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java
new file mode 100644
index 0000000..7547bff
--- /dev/null
+++ b/media/java/android/media/session/SessionCallbackLink.java
@@ -0,0 +1,776 @@
+/*
+ * 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.
+ */
+
+package android.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.media.Rating;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+
+/**
+ * Handles incoming commands to {@link MediaSession.Callback}.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ */
+public final class SessionCallbackLink implements Parcelable {
+    final CallbackStub mCallbackStub;
+    final ISessionCallback mISessionCallback;
+
+    /**
+     * Constructor for stub (Callee)
+     */
+    SessionCallbackLink(@NonNull CallbackStub callbackStub) {
+        mCallbackStub = callbackStub;
+        mISessionCallback = new CallbackStubProxy();
+    }
+
+    /**
+     * Constructor for interface (Caller)
+     */
+    SessionCallbackLink(Parcel in) {
+        mCallbackStub = null;
+        mISessionCallback = ISessionCallback.Stub.asInterface(in.readStrongBinder());
+    }
+
+    /**
+     * Notify session that a controller sends a command.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param command the name of the command
+     * @param args the arguments included with the command
+     * @param cb the result receiver for getting the result of the command
+     */
+    public void notifyCommand(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull String command,
+            @Nullable Bundle args, @Nullable ResultReceiver cb) {
+        try {
+            mISessionCallback.notifyCommand(packageName, pid, uid, caller, command, args, cb);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that the android system sends a media button event.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param mediaButtonIntent the media button intent
+     * @param sequenceNumber the sequence number of this call
+     * @param cb the result receiver for getting the result of the command
+     */
+    public void notifyMediaButton(@NonNull String packageName, int pid, int uid,
+            @NonNull Intent mediaButtonIntent, int sequenceNumber,
+            @Nullable ResultReceiver cb) {
+        try {
+            mISessionCallback.notifyMediaButton(packageName, pid, uid, mediaButtonIntent,
+                    sequenceNumber, cb);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller sends a media button event.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param mediaButtonIntent the media button intent
+     */
+    public void notifyMediaButtonFromController(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) {
+        try {
+            mISessionCallback.notifyMediaButtonFromController(packageName, pid, uid, caller,
+                    mediaButtonIntent);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests preparing media.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    public void notifyPrepare(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionCallback.notifyPrepare(packageName, pid, uid, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests preparing media from given media ID.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param mediaId the ID of the media
+     * @param extras the extras included with this request.
+     */
+    public void notifyPrepareFromMediaId(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+            @Nullable Bundle extras) {
+        try {
+            mISessionCallback.notifyPrepareFromMediaId(packageName, pid, uid, caller, mediaId,
+                    extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests preparing media from given search query.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param query the search query
+     * @param extras the extras included with this request.
+     */
+    public void notifyPrepareFromSearch(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull String query,
+            @Nullable Bundle extras) {
+        try {
+            mISessionCallback.notifyPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests preparing media from given uri.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param uri the uri of the media
+     * @param extras the extras included with this request.
+     */
+    public void notifyPrepareFromUri(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+        try {
+            mISessionCallback.notifyPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests playing media.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    public void notifyPlay(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionCallback.notifyPlay(packageName, pid, uid, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests playing media from given media ID.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param mediaId the ID of the media
+     * @param extras the extras included with this request.
+     */
+    public void notifyPlayFromMediaId(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+            @Nullable Bundle extras) {
+        try {
+            mISessionCallback.notifyPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests playing media from given search query.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param query the search query
+     * @param extras the extras included with this request.
+     */
+    public void notifyPlayFromSearch(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull String query,
+            @Nullable Bundle extras) {
+        try {
+            mISessionCallback.notifyPlayFromSearch(packageName, pid, uid, caller, query, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests playing media from given uri.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param uri the uri of the media
+     * @param extras the extras included with this request.
+     */
+    public void notifyPlayFromUri(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+        try {
+            mISessionCallback.notifyPlayFromUri(packageName, pid, uid, caller, uri, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests skipping to the queue item with given ID.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param id the queue id of the item
+     */
+    public void notifySkipToTrack(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, long id) {
+        try {
+            mISessionCallback.notifySkipToTrack(packageName, pid, uid, caller, id);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests pausing media.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    public void notifyPause(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionCallback.notifyPause(packageName, pid, uid, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests stopping media.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    public void notifyStop(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionCallback.notifyStop(packageName, pid, uid, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests skipping to the next queue item.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    public void notifyNext(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionCallback.notifyNext(packageName, pid, uid, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests skipping to the previous queue item.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    public void notifyPrevious(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionCallback.notifyPrevious(packageName, pid, uid, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests fast-forwarding.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    public void notifyFastForward(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionCallback.notifyFastForward(packageName, pid, uid, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests rewinding.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     */
+    public void notifyRewind(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller) {
+        try {
+            mISessionCallback.notifyRewind(packageName, pid, uid, caller);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests seeking to the specific position.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param pos the position to move to, in milliseconds
+     */
+    public void notifySeekTo(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, long pos) {
+        try {
+            mISessionCallback.notifySeekTo(packageName, pid, uid, caller, pos);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests rating of the current media.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param rating the rating of the current media
+     */
+    public void notifyRate(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull Rating rating) {
+        try {
+            mISessionCallback.notifyRate(packageName, pid, uid, caller, rating);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller sends a custom action.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param action the name of the action
+     * @param args the arguments included with this action
+     */
+    public void notifyCustomAction(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, @NonNull String action, @Nullable Bundle args) {
+        try {
+            mISessionCallback.notifyCustomAction(packageName, pid, uid, caller, action, args);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests adjusting volume.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param direction the direction of the volume change.
+     */
+    public void notifyAdjustVolume(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, int direction) {
+        try {
+            mISessionCallback.notifyAdjustVolume(packageName, pid, uid, caller, direction);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Notify session that a controller requests setting volume.
+     *
+     * @param packageName the package name of the controller
+     * @param pid the pid of the controller
+     * @param uid the uid of the controller
+     * @param caller the {@link ControllerCallbackLink} of the controller
+     * @param value the volume value to set
+     */
+    public void notifySetVolumeTo(@NonNull String packageName, int pid, int uid,
+            @NonNull ControllerCallbackLink caller, int value) {
+        try {
+            mISessionCallback.notifySetVolumeTo(packageName, pid, uid, caller, value);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Gets the binder */
+    @NonNull
+    public IBinder getBinder() {
+        return mISessionCallback.asBinder();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mISessionCallback.asBinder());
+    }
+
+    public static final Parcelable.Creator<SessionCallbackLink> CREATOR =
+            new Parcelable.Creator<SessionCallbackLink>() {
+                @Override
+                public SessionCallbackLink createFromParcel(Parcel in) {
+                    return new SessionCallbackLink(in);
+                }
+
+                @Override
+                public SessionCallbackLink[] newArray(int size) {
+                    return new SessionCallbackLink[size];
+                }
+            };
+
+    /**
+     * Class for Stub implementation
+     */
+    abstract static class CallbackStub {
+        /** Stub method for ISessionCallback.notifyCommand */
+        public void onCommand(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, @NonNull String command,
+                @Nullable Bundle args, @Nullable ResultReceiver cb) {
+        }
+
+        /** Stub method for ISessionCallback.notifyMediaButton */
+        public void onMediaButton(@NonNull String packageName, int pid, int uid,
+                @NonNull Intent mediaButtonIntent, int sequenceNumber,
+                @Nullable ResultReceiver cb) {
+        }
+
+        /** Stub method for ISessionCallback.notifyMediaButtonFromController */
+        public void onMediaButtonFromController(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPrepare */
+        public void onPrepare(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPrepareFromMediaId */
+        public void onPrepareFromMediaId(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+                @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPrepareFromSearch */
+        public void onPrepareFromSearch(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, String query, @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPrepareFromUri */
+        public void onPrepareFromUri(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPlay */
+        public void onPlay(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPlayFromMediaId */
+        public void onPlayFromMediaId(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, @NonNull String mediaId,
+                @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPlayFromSearch */
+        public void onPlayFromSearch(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, String query, @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPlayFromUri */
+        public void onPlayFromUri(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) {
+        }
+
+        /** Stub method for ISessionCallback.notifySkipToTrack */
+        public void onSkipToTrack(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, long id) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPause */
+        public void onPause(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionCallback.notifyStop */
+        public void onStop(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionCallback.notifyNext */
+        public void onNext(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionCallback.notifyPrevious */
+        public void onPrevious(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionCallback.notifyFastForward */
+        public void onFastForward(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionCallback.notifyRewind */
+        public void onRewind(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller) {
+        }
+
+        /** Stub method for ISessionCallback.notifySeekTo */
+        public void onSeekTo(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, long pos) {
+        }
+
+        /** Stub method for ISessionCallback.notifyRate */
+        public void onRate(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, @NonNull Rating rating) {
+        }
+
+        /** Stub method for ISessionCallback.notifyCustomAction */
+        public void onCustomAction(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, @NonNull String action,
+                @Nullable Bundle args) {
+        }
+
+        /** Stub method for ISessionCallback.notifyAdjustVolume */
+        public void onAdjustVolume(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, int direction) {
+        }
+
+        /** Stub method for ISessionCallback.notifySetVolumeTo */
+        public void onSetVolumeTo(@NonNull String packageName, int pid, int uid,
+                @NonNull ControllerCallbackLink caller, int value) {
+        }
+    }
+
+    private class CallbackStubProxy extends ISessionCallback.Stub {
+        @Override
+        public void notifyCommand(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
+            mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
+        }
+
+        @Override
+        public void notifyMediaButton(String packageName, int pid, int uid,
+                Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) {
+            mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent, sequenceNumber,
+                    cb);
+        }
+
+        @Override
+        public void notifyMediaButtonFromController(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, Intent mediaButtonIntent) {
+            mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
+                    mediaButtonIntent);
+        }
+
+        @Override
+        public void notifyPrepare(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            mCallbackStub.onPrepare(packageName, pid, uid, caller);
+        }
+
+        @Override
+        public void notifyPrepareFromMediaId(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String mediaId, Bundle extras) {
+            mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+        }
+
+        @Override
+        public void notifyPrepareFromSearch(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String query, Bundle extras) {
+            mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+        }
+
+        @Override
+        public void notifyPrepareFromUri(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, Uri uri, Bundle extras) {
+            mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+        }
+
+        @Override
+        public void notifyPlay(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            mCallbackStub.onPlay(packageName, pid, uid, caller);
+        }
+
+        @Override
+        public void notifyPlayFromMediaId(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String mediaId, Bundle extras) {
+            mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+        }
+
+        @Override
+        public void notifyPlayFromSearch(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String query, Bundle extras) {
+            mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
+        }
+
+        @Override
+        public void notifyPlayFromUri(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, Uri uri, Bundle extras) {
+            mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
+        }
+
+        @Override
+        public void notifySkipToTrack(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, long id) {
+            mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
+        }
+
+        @Override
+        public void notifyPause(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            mCallbackStub.onPause(packageName, pid, uid, caller);
+        }
+
+        @Override
+        public void notifyStop(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            mCallbackStub.onStop(packageName, pid, uid, caller);
+        }
+
+        @Override
+        public void notifyNext(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            mCallbackStub.onNext(packageName, pid, uid, caller);
+        }
+
+        @Override
+        public void notifyPrevious(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            mCallbackStub.onPrevious(packageName, pid, uid, caller);
+        }
+
+        @Override
+        public void notifyFastForward(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            mCallbackStub.onFastForward(packageName, pid, uid, caller);
+        }
+
+        @Override
+        public void notifyRewind(String packageName, int pid, int uid,
+                ControllerCallbackLink caller) {
+            mCallbackStub.onRewind(packageName, pid, uid, caller);
+        }
+
+        @Override
+        public void notifySeekTo(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, long pos) {
+            mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
+        }
+
+        @Override
+        public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
+                Rating rating) {
+            mCallbackStub.onRate(packageName, pid, uid, caller, rating);
+        }
+
+        @Override
+        public void notifyCustomAction(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, String action, Bundle args) {
+            mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
+        }
+
+        @Override
+        public void notifyAdjustVolume(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, int direction) {
+            mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
+        }
+
+        @Override
+        public void notifySetVolumeTo(String packageName, int pid, int uid,
+                ControllerCallbackLink caller, int value) {
+            mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
+        }
+    }
+}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index fda0fc4..d6b6339 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -102,7 +102,6 @@
         "libhidlbase",
         "libhidlmemory",
 
-        "libpowermanager",  // Used by JWakeLock. Will be replace with public SDK API.
         "libmediametrics",  // Used by MediaMetrics. Will be replaced with stable C API.
         "libbinder",  // Used by JWakeLock and MediaMetrics.
 
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index 7e6a8ab..9b4e730 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -86,7 +86,8 @@
 // ----------------------------------------------------------------------------
 
 struct fields_t {
-    jfieldID    context;
+    jfieldID    context;               // passed from Java to native, used for creating JWakeLock
+    jfieldID    nativeContext;         // mNativeContext in MediaPlayer2.java
     jfieldID    surface_texture;
 
     jmethodID   post_event;
@@ -225,21 +226,21 @@
 static sp<MediaPlayer2> getMediaPlayer(JNIEnv* env, jobject thiz)
 {
     Mutex::Autolock l(sLock);
-    MediaPlayer2* const p = (MediaPlayer2*)env->GetLongField(thiz, fields.context);
+    MediaPlayer2* const p = (MediaPlayer2*)env->GetLongField(thiz, fields.nativeContext);
     return sp<MediaPlayer2>(p);
 }
 
 static sp<MediaPlayer2> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer2>& player)
 {
     Mutex::Autolock l(sLock);
-    sp<MediaPlayer2> old = (MediaPlayer2*)env->GetLongField(thiz, fields.context);
+    sp<MediaPlayer2> old = (MediaPlayer2*)env->GetLongField(thiz, fields.nativeContext);
     if (player.get()) {
         player->incStrong((void*)setMediaPlayer);
     }
     if (old != 0) {
         old->decStrong((void*)setMediaPlayer);
     }
-    env->SetLongField(thiz, fields.context, (jlong)player.get());
+    env->SetLongField(thiz, fields.nativeContext, (jlong)player.get());
     return old;
 }
 
@@ -955,11 +956,16 @@
         return;
     }
 
-    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
+    fields.context = env->GetFieldID(clazz, "mContext", "Landroid/content/Context;");
     if (fields.context == NULL) {
         return;
     }
 
+    fields.nativeContext = env->GetFieldID(clazz, "mNativeContext", "J");
+    if (fields.nativeContext == NULL) {
+        return;
+    }
+
     fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                                "(Ljava/lang/Object;JIII[B)V");
     if (fields.post_event == NULL) {
@@ -1013,7 +1019,8 @@
         jint sessionId, jobject weak_this)
 {
     ALOGV("native_setup");
-    sp<MediaPlayer2> mp = MediaPlayer2::Create(sessionId);
+    jobject context = env->GetObjectField(thiz, fields.context);
+    sp<MediaPlayer2> mp = MediaPlayer2::Create(sessionId, context);
     if (mp == NULL) {
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
         return;
diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp
index 8c43683..44f8725 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -18,5 +18,7 @@
     name: "com.android.mediadrm.signer",
     srcs: ["java/**/*.java"],
     api_packages: ["com.android.mediadrm.signer"],
-    metalava_enabled: false,
+    srcs_lib: "framework",
+    srcs_lib_whitelist_dirs: ["media/java"],
+    srcs_lib_whitelist_pkgs: ["android.media"],
 }
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 5cfb09b..fdcfc44 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -83,6 +83,10 @@
     include_dirs: ["bionic/libc/dns/include"],
 
     version_script: "libandroid.map.txt",
+    stubs: {
+        symbol_file: "libandroid.map.txt",
+        versions: ["29"],
+    },
 }
 
 // Network library.
diff --git a/native/webview/plat_support/Android.bp b/native/webview/plat_support/Android.bp
index 0936256..88decc8 100644
--- a/native/webview/plat_support/Android.bp
+++ b/native/webview/plat_support/Android.bp
@@ -24,7 +24,6 @@
     srcs: [
         "draw_functor.cpp",
         "draw_gl_functor.cpp",
-        "draw_vk_functor.cpp",
         "functor_utils.cpp",
         "jni_entry_point.cpp",
         "graphics_utils.cpp",
diff --git a/native/webview/plat_support/draw_vk.h b/native/webview/plat_support/draw_vk.h
deleted file mode 100644
index 6b7d8d0..0000000
--- a/native/webview/plat_support/draw_vk.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-//******************************************************************************
-// This is a copy of the coresponding android_webview/public/browser header.
-// Any changes to the interface should be made there.
-//
-// The purpose of having the copy is twofold:
-//  - it removes the need to have Chromium sources present in the tree in order
-//    to build the plat_support library,
-//  - it captures API that the corresponding Android release supports.
-//******************************************************************************
-
-#ifndef ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_
-#define ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_
-
-#include <vulkan/vulkan.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-static const int kAwDrawVKInfoVersion = 1;
-
-// Holds the information required to trigger initialization of the Vulkan
-// functor.
-struct InitParams {
-  // All params are input
-  VkInstance instance;
-  VkPhysicalDevice physical_device;
-  VkDevice device;
-  VkQueue queue;
-  uint32_t graphics_queue_index;
-  uint32_t instance_version;
-  const char* const* enabled_extension_names;
-  // Only one of device_features and device_features_2 should be non-null.
-  // If both are null then no features are enabled.
-  VkPhysicalDeviceFeatures* device_features;
-  VkPhysicalDeviceFeatures2* device_features_2;
-};
-
-// Holds the information required to trigger an Vulkan composite operation.
-struct CompositeParams {
-  // Input: current width/height of destination surface.
-  int width;
-  int height;
-
-  // Input: is the render target a FBO
-  bool is_layer;
-
-  // Input: current transform matrix
-  float transform[16];
-
-  // Input WebView should do its main compositing draws into this. It cannot do
-  // anything that would require stopping the render pass.
-  VkCommandBuffer secondary_command_buffer;
-
-  // Input: The main color attachment index where secondary_command_buffer will
-  // eventually be submitted.
-  uint32_t color_attachment_index;
-
-  // Input: A render pass which will be compatible to the one which the
-  // secondary_command_buffer will be submitted into.
-  VkRenderPass compatible_render_pass;
-
-  // Input: Format of the destination surface.
-  VkFormat format;
-
-  // Input: Color space transfer params
-  float G;
-  float A;
-  float B;
-  float C;
-  float D;
-  float E;
-  float F;
-
-  // Input: Color space transformation from linear RGB to D50-adapted XYZ
-  float matrix[9];
-
-  // Input: current clip rect
-  int clip_left;
-  int clip_top;
-  int clip_right;
-  int clip_bottom;
-};
-
-// Holds the information for the post-submission callback of main composite
-// draw.
-struct PostCompositeParams {
-  // Input: Fence for the composite command buffer to signal it has finished its
-  // work on the GPU.
-  int fd;
-};
-
-// Holds the information required to trigger an Vulkan operation.
-struct AwDrawVKInfo {
-  int version;  // The AwDrawVKInfo this struct was built with.
-
-  // Input: tells the draw function what action to perform.
-  enum Mode {
-    kModeInit = 0,
-    kModeReInit = 1,
-    kModePreComposite = 2,
-    kModeComposite = 3,
-    kModePostComposite = 4,
-    kModeSync = 5,
-  } mode;
-
-  // Input: The parameters for the functor being called
-  union ParamUnion {
-    struct InitParams init_params;
-    struct CompositeParams composite_params;
-    struct PostCompositeParams post_composite_params;
-  } info;
-};
-
-typedef void(AwDrawVKFunction)(long view_context, AwDrawVKInfo* draw_info);
-
-#ifdef __cplusplus
-}  // extern "C"
-#endif
-
-#endif  // ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_
diff --git a/native/webview/plat_support/draw_vk_functor.cpp b/native/webview/plat_support/draw_vk_functor.cpp
deleted file mode 100644
index eab1340..0000000
--- a/native/webview/plat_support/draw_vk_functor.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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.
- */
-
-// Provides a webviewchromium glue layer adapter from the internal Android
-// Vulkan Functor data types into the types the chromium stack expects, and
-// back.
-
-#define LOG_TAG "webviewchromium_plat_support"
-
-#include "draw_fn.h"
-#include "draw_vk.h"
-
-#include <jni.h>
-#include <private/hwui/DrawVkInfo.h>
-#include <utils/Functor.h>
-#include <utils/Log.h>
-
-#include "functor_utils.h"
-
-#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
-
-namespace android {
-namespace {
-
-AwDrawVKFunction* g_aw_drawvk_function = NULL;
-
-class DrawVKFunctor : public Functor {
- public:
-  explicit DrawVKFunctor(jlong view_context) : view_context_(view_context) {}
-  ~DrawVKFunctor() override {}
-
-  // Functor
-  status_t operator ()(int what, void* data) override {
-    using uirenderer::DrawVkInfo;
-    if (!g_aw_drawvk_function) {
-      ALOGE("Cannot draw: no DrawVK Function installed");
-      return DrawVkInfo::kStatusDone;
-    }
-
-    AwDrawVKInfo aw_info;
-    aw_info.version = kAwDrawVKInfoVersion;
-    switch (what) {
-      case DrawVkInfo::kModeComposite: {
-        aw_info.mode = AwDrawVKInfo::kModeComposite;
-        DrawVkInfo* vk_info = reinterpret_cast<DrawVkInfo*>(data);
-
-        // Map across the input values.
-        CompositeParams& params = aw_info.info.composite_params;
-        params.width = vk_info->width;
-        params.height = vk_info->height;
-        params.is_layer = vk_info->isLayer;
-        for (size_t i = 0; i < 16; i++) {
-            params.transform[i] = vk_info->transform[i];
-        }
-        params.secondary_command_buffer = vk_info->secondaryCommandBuffer;
-        params.color_attachment_index = vk_info->colorAttachmentIndex;
-        params.compatible_render_pass = vk_info->compatibleRenderPass;
-        params.format = vk_info->format;
-        params.G = vk_info->G;
-        params.A = vk_info->A;
-        params.B = vk_info->B;
-        params.C = vk_info->C;
-        params.D = vk_info->D;
-        params.E = vk_info->E;
-        params.F = vk_info->F;
-        for (size_t i = 0; i < 9; i++) {
-            params.matrix[i] = vk_info->matrix[i];
-        }
-        params.clip_left = vk_info->clipLeft;
-        params.clip_top = vk_info->clipTop;
-        params.clip_right = vk_info->clipRight;
-        params.clip_bottom = vk_info->clipBottom;
-
-        break;
-      }
-      case DrawVkInfo::kModePostComposite:
-        break;
-      case DrawVkInfo::kModeSync:
-        aw_info.mode = AwDrawVKInfo::kModeSync;
-        break;
-      default:
-        ALOGE("Unexpected DrawVKInfo type %d", what);
-        return DrawVkInfo::kStatusDone;
-    }
-
-    // Invoke the DrawVK method.
-    g_aw_drawvk_function(view_context_, &aw_info);
-
-    return DrawVkInfo::kStatusDone;
-  }
-
- private:
-  intptr_t view_context_;
-};
-
-jlong CreateVKFunctor(JNIEnv*, jclass, jlong view_context) {
-  RaiseFileNumberLimit();
-  return reinterpret_cast<jlong>(new DrawVKFunctor(view_context));
-}
-
-void DestroyVKFunctor(JNIEnv*, jclass, jlong functor) {
-  delete reinterpret_cast<DrawVKFunctor*>(functor);
-}
-
-void SetChromiumAwDrawVKFunction(JNIEnv*, jclass, jlong draw_function) {
-  g_aw_drawvk_function = reinterpret_cast<AwDrawVKFunction*>(draw_function);
-}
-
-const char kClassName[] = "com/android/webview/chromium/DrawVKFunctor";
-const JNINativeMethod kJniMethods[] = {
-    { "nativeCreateVKFunctor", "(J)J",
-        reinterpret_cast<void*>(CreateVKFunctor) },
-    { "nativeDestroyVKFunctor", "(J)V",
-        reinterpret_cast<void*>(DestroyVKFunctor) },
-    { "nativeSetChromiumAwDrawVKFunction", "(J)V",
-        reinterpret_cast<void*>(SetChromiumAwDrawVKFunction) },
-};
-
-}  // namespace
-
-void RegisterDrawVKFunctor(JNIEnv* env) {
-  jclass clazz = env->FindClass(kClassName);
-  LOG_ALWAYS_FATAL_IF(!clazz, "Unable to find class '%s'", kClassName);
-
-  int res = env->RegisterNatives(clazz, kJniMethods, NELEM(kJniMethods));
-  LOG_ALWAYS_FATAL_IF(res < 0, "register native methods failed: res=%d", res);
-}
-
-}  // namespace android
diff --git a/packages/AppPredictionLib/Android.bp b/packages/AppPredictionLib/Android.bp
new file mode 100644
index 0000000..e0f4ded
--- /dev/null
+++ b/packages/AppPredictionLib/Android.bp
@@ -0,0 +1,24 @@
+// 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.
+
+android_library {
+    name: "app_prediction",
+
+    sdk_version: "system_current",
+    min_sdk_version: "system_current",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+}
diff --git a/packages/AppPredictionLib/AndroidManifest.xml b/packages/AppPredictionLib/AndroidManifest.xml
new file mode 100644
index 0000000..b992788
--- /dev/null
+++ b/packages/AppPredictionLib/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.app.prediction">
+</manifest>
diff --git a/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java b/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java
new file mode 100644
index 0000000..0993c9a
--- /dev/null
+++ b/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java
@@ -0,0 +1,71 @@
+/*
+ * 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.app.prediction;
+
+/**
+ * Constants to be used with {@link android.app.prediction.AppPredictor}.
+ */
+public class Constants {
+
+    /**
+     * UI surface for predictions displayed on the user's home screen
+     */
+    public static final String UI_SURFACE_HOME = "home";
+
+    /**
+     * UI surface for predictions displayed on the recents/task switcher view
+     */
+    public static final String UI_SURFACE_RECENTS = "recents";
+
+    /**
+     * UI surface for predictions displayed on the share sheet.
+     */
+    public static final String UI_SURFACE_SHARE = "share";
+
+    /**
+     * Location constant when an app target or shortcut is started from the apps list
+     */
+    public static final String LAUNCH_LOCATION_APPS_LIST = "apps_list";
+
+    /**
+     * Location constant when an app target or shortcut is started from the user's home screen
+     */
+    public static final String LAUNCH_LOCATION_APPS_HOME = "home";
+
+    /**
+     * Location constant when an app target or shortcut is started from task switcher
+     */
+    public static final String LAUNCH_LOCATION_APPS_RECENTS = "recents";
+
+    /**
+     * Location constant when an app target or shortcut is started in the share sheet while it is
+     * in collapsed state (showing a limited set of result).
+     */
+    public static final String LAUNCH_LOCATION_APPS_SHARE_COLLAPSED = "share_collapsed";
+
+    /**
+     * Location constant when an app target or shortcut is started in the share sheet while it is
+     * in expended state and showing all the results.
+     */
+    public static final String LAUNCH_LOCATION_APPS_SHARE_EXPANDED = "shared_expanded";
+
+    /**
+     * Location constant when an app target or shortcut is started in the share sheet when the
+     * target is displayed as a placeholder for an deprecated object.
+     */
+    public static final String LAUNCH_LOCATION_APPS_SHARE_LEGACY = "share_legacy";
+}
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.mk b/packages/CarrierDefaultApp/tests/unit/Android.mk
index 8e3785e..4c638811 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.mk
+++ b/packages/CarrierDefaultApp/tests/unit/Android.mk
@@ -21,7 +21,7 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.test.base
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target-minus-junit4
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules mockito-target-minus-junit4
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
index 3a06a09..7a26d95 100644
--- a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
@@ -21,7 +21,7 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.carrierdefaultapp"
         android:label="CarrierDefaultApp Unit Test Cases">
     </instrumentation>
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 4688848..2f7d599 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -21,10 +21,10 @@
     installable: true,
     srcs: [
         "src/**/*.java",
+        ":services-networkstack-shared-srcs",
     ],
     static_libs: [
         "dhcp-packet-lib",
-        "frameworks-net-shared-utils",
     ]
 }
 
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index 8516d94..0b0f1ec 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -22,8 +22,11 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
     <!-- 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" />
     <application
         android:label="NetworkStack"
         android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/NetworkStack/src/android/net/util/SharedLog.java b/packages/NetworkStack/src/android/net/util/SharedLog.java
index 74bc147..4fabf10 100644
--- a/packages/NetworkStack/src/android/net/util/SharedLog.java
+++ b/packages/NetworkStack/src/android/net/util/SharedLog.java
@@ -69,6 +69,10 @@
         mComponent = component;
     }
 
+    public String getTag() {
+        return mTag;
+    }
+
     /**
      * Create a SharedLog based on this log with an additional component prefix on each logged line.
      */
diff --git a/services/net/java/android/net/util/Stopwatch.java b/packages/NetworkStack/src/android/net/util/Stopwatch.java
similarity index 78%
rename from services/net/java/android/net/util/Stopwatch.java
rename to packages/NetworkStack/src/android/net/util/Stopwatch.java
index cb15ee5..c316699 100644
--- a/services/net/java/android/net/util/Stopwatch.java
+++ b/packages/NetworkStack/src/android/net/util/Stopwatch.java
@@ -38,9 +38,9 @@
         return (isStarted() && !isStopped());
     }
 
-    // Returning |this| makes possible the following usage pattern:
-    //
-    //     Stopwatch s = new Stopwatch().start();
+    /**
+     * Start the Stopwatch.
+     */
     public Stopwatch start() {
         if (!isStarted()) {
             mStartTimeMs = SystemClock.elapsedRealtime();
@@ -48,7 +48,10 @@
         return this;
     }
 
-    // Returns the total time recorded, in milliseconds, or 0 if not started.
+    /**
+     * Stop the Stopwatch.
+     * @return the total time recorded, in milliseconds, or 0 if not started.
+     */
     public long stop() {
         if (isRunning()) {
             mStopTimeMs = SystemClock.elapsedRealtime();
@@ -57,9 +60,11 @@
         return (mStopTimeMs - mStartTimeMs);
     }
 
-    // Returns the total time recorded to date, in milliseconds.
-    // If the Stopwatch is not running, returns the same value as stop(),
-    // i.e. either the total time recorded before stopping or 0.
+    /**
+     * Return the total time recorded to date, in milliseconds.
+     * If the Stopwatch is not running, returns the same value as stop(),
+     * i.e. either the total time recorded before stopping or 0.
+     */
     public long lap() {
         if (isRunning()) {
             return (SystemClock.elapsedRealtime() - mStartTimeMs);
@@ -68,6 +73,9 @@
         }
     }
 
+    /**
+     * Reset the Stopwatch. It will be stopped when this method returns.
+     */
     public void reset() {
         mStartTimeMs = 0;
         mStopTimeMs = 0;
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index 7fea1e0..057012d 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -25,18 +25,31 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkStackConnector;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.PrivateDnsConfigParcel;
 import android.net.dhcp.DhcpServer;
 import android.net.dhcp.DhcpServingParams;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.shared.PrivateDnsConfig;
 import android.net.util.SharedLog;
 import android.os.IBinder;
 import android.os.RemoteException;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.connectivity.NetworkMonitor;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
 
 /**
  * Android service used to start the network stack when bound to via an intent.
@@ -52,17 +65,41 @@
      * <p>On platforms where the network stack runs in the system server process, this method may
      * be called directly instead of obtaining the connector by binding to the service.
      */
-    public static IBinder makeConnector() {
-        return new NetworkStackConnector();
+    public static IBinder makeConnector(Context context) {
+        return new NetworkStackConnector(context);
     }
 
     @NonNull
     @Override
     public IBinder onBind(Intent intent) {
-        return makeConnector();
+        return makeConnector(this);
     }
 
     private static class NetworkStackConnector extends INetworkStackConnector.Stub {
+        private static final int NUM_VALIDATION_LOG_LINES = 20;
+        private final Context mContext;
+        private final ConnectivityManager mCm;
+
+        private static final int MAX_VALIDATION_LOGS = 10;
+        @GuardedBy("mValidationLogs")
+        private final ArrayDeque<SharedLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
+
+        private SharedLog addValidationLogs(Network network, String name) {
+            final SharedLog log = new SharedLog(NUM_VALIDATION_LOG_LINES, network + " - " + name);
+            synchronized (mValidationLogs) {
+                while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
+                    mValidationLogs.removeLast();
+                }
+                mValidationLogs.addFirst(log);
+            }
+            return log;
+        }
+
+        NetworkStackConnector(Context context) {
+            mContext = context;
+            mCm = context.getSystemService(ConnectivityManager.class);
+        }
+
         @NonNull
         private final SharedLog mLog = new SharedLog(TAG);
 
@@ -89,11 +126,102 @@
         }
 
         @Override
+        public void makeNetworkMonitor(int netId, String name, INetworkMonitorCallbacks cb)
+                throws RemoteException {
+            final Network network = new Network(netId, false /* privateDnsBypass */);
+            final NetworkRequest defaultRequest = mCm.getDefaultRequest();
+            final SharedLog log = addValidationLogs(network, name);
+            final NetworkMonitor nm = new NetworkMonitor(
+                    mContext, cb, network, defaultRequest, log);
+            cb.onNetworkMonitorCreated(new NetworkMonitorImpl(nm));
+        }
+
+        @Override
         protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
                 @Nullable String[] args) {
             checkNetworkStackCallingPermission();
-            fout.println("NetworkStack logs:");
-            mLog.dump(fd, fout, args);
+            final IndentingPrintWriter pw = new IndentingPrintWriter(fout, "  ");
+            pw.println("NetworkStack logs:");
+            mLog.dump(fd, pw, args);
+
+            pw.println();
+            pw.println("Validation logs (most recent first):");
+            synchronized (mValidationLogs) {
+                for (SharedLog p : mValidationLogs) {
+                    pw.println(p.getTag());
+                    pw.increaseIndent();
+                    p.dump(fd, pw, args);
+                    pw.decreaseIndent();
+                }
+            }
+        }
+    }
+
+    private static class NetworkMonitorImpl extends INetworkMonitor.Stub {
+        private final NetworkMonitor mNm;
+
+        NetworkMonitorImpl(NetworkMonitor nm) {
+            mNm = nm;
+        }
+
+        @Override
+        public void start() {
+            checkNetworkStackCallingPermission();
+            mNm.start();
+        }
+
+        @Override
+        public void launchCaptivePortalApp() {
+            checkNetworkStackCallingPermission();
+            mNm.launchCaptivePortalApp();
+        }
+
+        @Override
+        public void forceReevaluation(int uid) {
+            checkNetworkStackCallingPermission();
+            mNm.forceReevaluation(uid);
+        }
+
+        @Override
+        public void notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
+            checkNetworkStackCallingPermission();
+            mNm.notifyPrivateDnsSettingsChanged(PrivateDnsConfig.fromParcel(config));
+        }
+
+        @Override
+        public void notifyDnsResponse(int returnCode) {
+            checkNetworkStackCallingPermission();
+            mNm.notifyDnsResponse(returnCode);
+        }
+
+        @Override
+        public void notifySystemReady() {
+            checkNetworkStackCallingPermission();
+            mNm.notifySystemReady();
+        }
+
+        @Override
+        public void notifyNetworkConnected() {
+            checkNetworkStackCallingPermission();
+            mNm.notifyNetworkConnected();
+        }
+
+        @Override
+        public void notifyNetworkDisconnected() {
+            checkNetworkStackCallingPermission();
+            mNm.notifyNetworkDisconnected();
+        }
+
+        @Override
+        public void notifyLinkPropertiesChanged() {
+            checkNetworkStackCallingPermission();
+            mNm.notifyLinkPropertiesChanged();
+        }
+
+        @Override
+        public void notifyNetworkCapabilitiesChanged() {
+            checkNetworkStackCallingPermission();
+            mNm.notifyNetworkCapabilitiesChanged();
         }
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
similarity index 84%
rename from services/core/java/com/android/server/connectivity/NetworkMonitor.java
rename to packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 2a00025..94ea1b9 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -21,6 +21,11 @@
 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
 import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
@@ -35,6 +40,9 @@
 import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
 import android.net.ICaptivePortal;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
@@ -46,11 +54,14 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.metrics.ValidationProbeEvent;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
+import android.net.util.SharedLog;
 import android.net.util.Stopwatch;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.os.Handler;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -65,8 +76,6 @@
 import android.telephony.CellInfoWcdma;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -75,7 +84,6 @@
 import com.android.internal.util.RingBufferIndices;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
 
 import java.io.IOException;
 import java.net.HttpURLConnection;
@@ -104,9 +112,7 @@
     // Default configuration values for captive portal detection probes.
     // TODO: append a random length parameter to the default HTTPS url.
     // TODO: randomize browser version ids in the default User-Agent String.
-    private static final String DEFAULT_HTTPS_URL     = "https://www.google.com/generate_204";
-    private static final String DEFAULT_HTTP_URL      =
-            "http://connectivitycheck.gstatic.com/generate_204";
+    private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
     private static final String DEFAULT_FALLBACK_URL  = "http://www.google.com/gen_204";
     private static final String DEFAULT_OTHER_FALLBACK_URLS =
             "http://play.googleapis.com/generate_204";
@@ -144,33 +150,12 @@
         }
     }
 
-    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
-    // The network should be used as a default internet connection.  It was found to be:
-    // 1. a functioning network providing internet access, or
-    // 2. a captive portal and the user decided to use it as is.
-    public static final int NETWORK_TEST_RESULT_VALID = 0;
-    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
-    // The network should not be used as a default internet connection.  It was found to be:
-    // 1. a captive portal and the user is prompted to sign-in, or
-    // 2. a captive portal and the user did not want to use it, or
-    // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
-    public static final int NETWORK_TEST_RESULT_INVALID = 1;
-
     private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
-
     /**
-     * Inform NetworkMonitor that their network is connected.
+     * ConnectivityService has sent a notification to indicate that network has connected.
      * Initiates Network Validation.
      */
-    public static final int CMD_NETWORK_CONNECTED = BASE + 1;
-
-    /**
-     * Inform ConnectivityService that the network has been tested.
-     * obj = String representing URL that Internet probe was redirect to, if it was redirected.
-     * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
-     * arg2 = NetID.
-     */
-    public static final int EVENT_NETWORK_TESTED = BASE + 2;
+    private static final int CMD_NETWORK_CONNECTED = BASE + 1;
 
     /**
      * Message to self indicating it's time to evaluate a network's connectivity.
@@ -179,9 +164,9 @@
     private static final int CMD_REEVALUATE = BASE + 6;
 
     /**
-     * Inform NetworkMonitor that the network has disconnected.
+     * ConnectivityService has sent a notification to indicate that network has disconnected.
      */
-    public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
+    private static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
 
     /**
      * Force evaluation even if it has succeeded in the past.
@@ -199,21 +184,13 @@
     private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
 
     /**
-     * Request ConnectivityService display provisioning notification.
-     * arg1    = Whether to make the notification visible.
-     * arg2    = NetID.
-     * obj     = Intent to be launched when notification selected by user, null if !arg1.
-     */
-    public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10;
-
-    /**
      * Message indicating sign-in app should be launched.
      * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
      * user touches the sign in notification, or sent by
      * ConnectivityService when the user touches the "sign into
      * network" button in the wifi access point detail page.
      */
-    public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
+    private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
 
     /**
      * Retest network to see if captive portal is still in place.
@@ -234,7 +211,6 @@
      * validation phase is completed.
      */
     private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13;
-    public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = BASE + 14;
     private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15;
 
     /**
@@ -263,23 +239,16 @@
     // Delay between reevaluations once a captive portal has been found.
     private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
 
-    private static final int NUM_VALIDATION_LOG_LINES = 20;
-
     private String mPrivateDnsProviderHostname = "";
 
-    public static boolean isValidationRequired(
-            NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
-        // TODO: Consider requiring validation for DUN networks.
-        return dfltNetCap.satisfiedByNetworkCapabilities(nc);
-    }
-
     private final Context mContext;
-    private final Handler mConnectivityServiceHandler;
-    private final NetworkAgentInfo mNetworkAgentInfo;
+    private final INetworkMonitorCallbacks mCallback;
     private final Network mNetwork;
+    private final Network mNonPrivateDnsBypassNetwork;
     private final int mNetId;
     private final TelephonyManager mTelephonyManager;
     private final WifiManager mWifiManager;
+    private final ConnectivityManager mCm;
     private final NetworkRequest mDefaultRequest;
     private final IpConnectivityLog mMetricsLog;
     private final Dependencies mDependencies;
@@ -292,6 +261,9 @@
     @Nullable
     private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
 
+    private NetworkCapabilities mNetworkCapabilities;
+    private LinkProperties mLinkProperties;
+
     @VisibleForTesting
     protected boolean mIsCaptivePortalCheckEnabled;
 
@@ -304,7 +276,7 @@
     // Avoids surfacing "Sign in to network" notification.
     private boolean mDontDisplaySigninNotification = false;
 
-    public boolean systemReady = false;
+    private volatile boolean mSystemReady = false;
 
     private final State mDefaultState = new DefaultState();
     private final State mValidatedState = new ValidatedState();
@@ -317,7 +289,7 @@
 
     private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
 
-    private final LocalLog validationLogs = new LocalLog(NUM_VALIDATION_LOG_LINES);
+    private final SharedLog mValidationLogs;
 
     private final Stopwatch mEvaluationTimer = new Stopwatch();
 
@@ -328,6 +300,7 @@
     private final Random mRandom;
     private int mNextFallbackUrlIndex = 0;
 
+
     private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
     private int mEvaluateAttempts = 0;
     private volatile int mProbeToken = 0;
@@ -338,17 +311,18 @@
     private final DnsStallDetector mDnsStallDetector;
     private long mLastProbeTime;
 
-    public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
-            NetworkRequest defaultRequest) {
-        this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog(),
+    public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
+            NetworkRequest defaultRequest, SharedLog validationLog) {
+        this(context, cb, network, defaultRequest, new IpConnectivityLog(), validationLog,
                 Dependencies.DEFAULT);
     }
 
     @VisibleForTesting
-    protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
-            NetworkRequest defaultRequest, IpConnectivityLog logger, Dependencies deps) {
+    protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
+            NetworkRequest defaultRequest, IpConnectivityLog logger, SharedLog validationLogs,
+            Dependencies deps) {
         // Add suffix indicating which NetworkMonitor we're talking about.
-        super(TAG + networkAgentInfo.name());
+        super(TAG + "/" + network.netId);
 
         // Logs with a tag of the form given just above, e.g.
         //     <timestamp>   862  2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
@@ -356,15 +330,18 @@
 
         mContext = context;
         mMetricsLog = logger;
-        mConnectivityServiceHandler = handler;
+        mValidationLogs = validationLogs;
+        mCallback = cb;
         mDependencies = deps;
-        mNetworkAgentInfo = networkAgentInfo;
-        mNetwork = deps.getNetwork(networkAgentInfo).getPrivateDnsBypassingCopy();
+        mNonPrivateDnsBypassNetwork = network;
+        mNetwork = deps.getPrivateDnsBypassNetwork(network);
         mNetId = mNetwork.netId;
         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);
         addState(mMaybeNotifyState, mDefaultState);
             addState(mEvaluatingState, mMaybeNotifyState);
@@ -374,12 +351,13 @@
         addState(mEvaluatingPrivateDnsState, mDefaultState);
         addState(mValidatedState, mDefaultState);
         setInitialState(mDefaultState);
+        // CHECKSTYLE:ON IndentationCheck
 
         mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
         mUseHttps = getUseHttpsValidation();
         mCaptivePortalUserAgent = getCaptivePortalUserAgent();
         mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
-        mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(deps, context));
+        mCaptivePortalHttpUrl = makeURL(deps.getCaptivePortalServerHttpUrl(context));
         mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
         mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
         mRandom = deps.getRandom();
@@ -390,7 +368,13 @@
         mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
         mDataStallEvaluationType = getDataStallEvalutionType();
 
-        start();
+        // mLinkProperties and mNetworkCapbilities must never be null or we will NPE.
+        // Provide empty objects in case we are started and the network disconnects before
+        // we can ever fetch them.
+        // TODO: Delete ASAP.
+        mLinkProperties = new LinkProperties();
+        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.clearAll();
     }
 
     /**
@@ -401,6 +385,14 @@
     }
 
     /**
+     * Send a notification to NetworkMonitor indicating that there was a DNS query response event.
+     * @param returnCode the DNS return code of the response.
+     */
+    public void notifyDnsResponse(int returnCode) {
+        sendMessage(EVENT_DNS_NOTIFICATION, returnCode);
+    }
+
+    /**
      * Send a notification to NetworkMonitor indicating that private DNS settings have changed.
      * @param newCfg The new private DNS configuration.
      */
@@ -411,9 +403,75 @@
         sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
     }
 
+    /**
+     * Send a notification to NetworkMonitor indicating that the system is ready.
+     */
+    public void notifySystemReady() {
+        // No need to run on the handler thread: mSystemReady is volatile and read only once on the
+        // isCaptivePortal() thread.
+        mSystemReady = true;
+    }
+
+    /**
+     * Send a notification to NetworkMonitor indicating that the network is now connected.
+     */
+    public void notifyNetworkConnected() {
+        sendMessage(CMD_NETWORK_CONNECTED);
+    }
+
+    /**
+     * Send a notification to NetworkMonitor indicating that the network is now disconnected.
+     */
+    public void notifyNetworkDisconnected() {
+        sendMessage(CMD_NETWORK_DISCONNECTED);
+    }
+
+    /**
+     * Send a notification to NetworkMonitor indicating that link properties have changed.
+     */
+    public void notifyLinkPropertiesChanged() {
+        getHandler().post(() -> {
+            updateLinkProperties();
+        });
+    }
+
+    private void updateLinkProperties() {
+        final LinkProperties lp = mCm.getLinkProperties(mNetwork);
+        // If null, we should soon get a message that the network was disconnected, and will stop.
+        if (lp != null) {
+            // TODO: send LinkProperties parceled in notifyLinkPropertiesChanged() and start().
+            mLinkProperties = lp;
+        }
+    }
+
+    /**
+     * Send a notification to NetworkMonitor indicating that network capabilities have changed.
+     */
+    public void notifyNetworkCapabilitiesChanged() {
+        getHandler().post(() -> {
+            updateNetworkCapabilities();
+        });
+    }
+
+    private void updateNetworkCapabilities() {
+        final NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork);
+        // If null, we should soon get a message that the network was disconnected, and will stop.
+        if (nc != null) {
+            // TODO: send NetworkCapabilities parceled in notifyNetworkCapsChanged() and start().
+            mNetworkCapabilities = nc;
+        }
+    }
+
+    /**
+     * Request the captive portal application to be launched.
+     */
+    public void launchCaptivePortalApp() {
+        sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+    }
+
     @Override
     protected void log(String s) {
-        if (DBG) Log.d(TAG + "/" + mNetworkAgentInfo.name(), s);
+        if (DBG) Log.d(TAG + "/" + mNetwork.netId, s);
     }
 
     private void validationLog(int probeType, Object url, String msg) {
@@ -423,11 +481,7 @@
 
     private void validationLog(String s) {
         if (DBG) log(s);
-        validationLogs.log(s);
-    }
-
-    public ReadOnlyLocalLog getValidationLogs() {
-        return validationLogs.readOnlyLocalLog();
+        mValidationLogs.log(s);
     }
 
     private ValidationStage validationStage() {
@@ -435,20 +489,46 @@
     }
 
     private boolean isValidationRequired() {
-        return isValidationRequired(
-                mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities);
+        return NetworkMonitorUtils.isValidationRequired(
+                mDefaultRequest.networkCapabilities, mNetworkCapabilities);
     }
 
 
-    private void notifyNetworkTestResultInvalid(Object obj) {
-        mConnectivityServiceHandler.sendMessage(obtainMessage(
-                EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj));
+    private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
+        try {
+            mCallback.notifyNetworkTested(result, redirectUrl);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error sending network test result", e);
+        }
+    }
+
+    private void showProvisioningNotification(String action) {
+        try {
+            mCallback.showProvisioningNotification(action);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error showing provisioning notification", e);
+        }
+    }
+
+    private void hideProvisioningNotification() {
+        try {
+            mCallback.hideProvisioningNotification();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error hiding provisioning notification", e);
+        }
     }
 
     // DefaultState is the parent of all States.  It exists only to handle CMD_* messages but
     // does not entail any real state (hence no enter() or exit() routines).
     private class DefaultState extends State {
         @Override
+        public void enter() {
+            // TODO: have those passed parceled in start() and remove this
+            updateLinkProperties();
+            updateNetworkCapabilities();
+        }
+
+        @Override
         public boolean processMessage(Message message) {
             switch (message.what) {
                 case CMD_NETWORK_CONNECTED:
@@ -499,7 +579,7 @@
                         case APP_RETURN_UNWANTED:
                             mDontDisplaySigninNotification = true;
                             mUserDoesNotWant = true;
-                            notifyNetworkTestResultInvalid(null);
+                            notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
                             // TODO: Should teardown network.
                             mUidResponsibleForReeval = 0;
                             transitionTo(mEvaluatingState);
@@ -563,8 +643,7 @@
         public void enter() {
             maybeLogEvaluationResult(
                     networkEventType(validationStage(), EvaluationResult.VALIDATED));
-            mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
-                    NETWORK_TEST_RESULT_VALID, mNetId, null));
+            notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null);
             mValidations++;
         }
 
@@ -633,8 +712,7 @@
 
         @Override
         public void exit() {
-            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetId, null);
-            mConnectivityServiceHandler.sendMessage(message);
+            hideProvisioningNotification();
         }
     }
 
@@ -751,9 +829,7 @@
                         CMD_LAUNCH_CAPTIVE_PORTAL_APP);
             }
             // Display the sign in notification.
-            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, mNetId,
-                    mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent());
-            mConnectivityServiceHandler.sendMessage(message);
+            showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
             // Retest for captive portal occasionally.
             sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
                     CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
@@ -839,12 +915,15 @@
         }
 
         private void notifyPrivateDnsConfigResolved() {
-            mConnectivityServiceHandler.sendMessage(obtainMessage(
-                    EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0, mNetId, mPrivateDnsConfig));
+            try {
+                mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error sending private DNS config resolved notification", e);
+            }
         }
 
         private void handlePrivateDnsEvaluationFailure() {
-            notifyNetworkTestResultInvalid(null);
+            notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
 
             // Queue up a re-evaluation with backoff.
             //
@@ -865,7 +944,7 @@
                     + oneTimeHostnameSuffix;
             final Stopwatch watch = new Stopwatch().start();
             try {
-                final InetAddress[] ips = mNetworkAgentInfo.network().getAllByName(host);
+                final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host);
                 final long time = watch.stop();
                 final String strIps = Arrays.toString(ips);
                 final boolean success = (ips != null && ips.length > 0);
@@ -915,12 +994,12 @@
                         // state (even if no Private DNS validation required).
                         transitionTo(mEvaluatingPrivateDnsState);
                     } else if (probeResult.isPortal()) {
-                        notifyNetworkTestResultInvalid(probeResult.redirectUrl);
+                        notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
                         mLastPortalProbeResult = probeResult;
                         transitionTo(mCaptivePortalState);
                     } else {
                         logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
-                        notifyNetworkTestResultInvalid(probeResult.redirectUrl);
+                        notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
                         transitionTo(mWaitingForNextProbeState);
                     }
                     return HANDLED;
@@ -996,18 +1075,18 @@
         }
     }
 
-    public boolean getIsCaptivePortalCheckEnabled() {
+    private boolean getIsCaptivePortalCheckEnabled() {
         String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
         int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
         int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
         return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
     }
 
-    public boolean getUseHttpsValidation() {
+    private boolean getUseHttpsValidation() {
         return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
     }
 
-    public boolean getWifiScansAlwaysAvailableDisabled() {
+    private boolean getWifiScansAlwaysAvailableDisabled() {
         return mDependencies.getSetting(
                 mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
     }
@@ -1040,15 +1119,6 @@
                 DEFAULT_DATA_STALL_EVALUATION_TYPES);
     }
 
-    // Static for direct access by ConnectivityService
-    public static String getCaptivePortalServerHttpUrl(Context context) {
-        return getCaptivePortalServerHttpUrl(Dependencies.DEFAULT, context);
-    }
-
-    public static String getCaptivePortalServerHttpUrl(Dependencies deps, Context context) {
-        return deps.getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
-    }
-
     private URL[] makeCaptivePortalFallbackUrls() {
         try {
             String separator = ",";
@@ -1144,7 +1214,7 @@
         // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
         //    fact block fetching of the generate_204 URL which would lead to false negative
         //    results for network validation.
-        final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy();
+        final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy();
         if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
             pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
             if (pacUrl == null) {
@@ -1416,89 +1486,86 @@
             return;
         }
 
-        if (!systemReady) {
+        if (!mSystemReady) {
             return;
         }
 
         Intent latencyBroadcast =
-                new Intent(ConnectivityConstants.ACTION_NETWORK_CONDITIONS_MEASURED);
-        switch (mNetworkAgentInfo.networkInfo.getType()) {
-            case ConnectivityManager.TYPE_WIFI:
-                WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
-                if (currentWifiInfo != null) {
-                    // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
-                    // surrounded by double quotation marks (thus violating the Javadoc), but this
-                    // was changed to match the Javadoc in API 17. Since clients may have started
-                    // sanitizing the output of this method since API 17 was released, we should
-                    // not change it here as it would become impossible to tell whether the SSID is
-                    // simply being surrounded by quotes due to the API, or whether those quotes
-                    // are actually part of the SSID.
-                    latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_SSID,
-                            currentWifiInfo.getSSID());
-                    latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_BSSID,
-                            currentWifiInfo.getBSSID());
-                } else {
-                    if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
-                    return;
-                }
-                break;
-            case ConnectivityManager.TYPE_MOBILE:
-                latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_NETWORK_TYPE,
-                        mTelephonyManager.getNetworkType());
-                List<CellInfo> info = mTelephonyManager.getAllCellInfo();
-                if (info == null) return;
-                int numRegisteredCellInfo = 0;
-                for (CellInfo cellInfo : info) {
-                    if (cellInfo.isRegistered()) {
-                        numRegisteredCellInfo++;
-                        if (numRegisteredCellInfo > 1) {
-                            if (VDBG) {
-                                logw("more than one registered CellInfo."
-                                        + " Can't tell which is active.  Bailing.");
-                            }
-                            return;
+                new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
+        if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
+            WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
+            if (currentWifiInfo != null) {
+                // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
+                // surrounded by double quotation marks (thus violating the Javadoc), but this
+                // was changed to match the Javadoc in API 17. Since clients may have started
+                // sanitizing the output of this method since API 17 was released, we should
+                // not change it here as it would become impossible to tell whether the SSID is
+                // simply being surrounded by quotes due to the API, or whether those quotes
+                // are actually part of the SSID.
+                latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
+                        currentWifiInfo.getSSID());
+                latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
+                        currentWifiInfo.getBSSID());
+            } else {
+                if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
+                return;
+            }
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
+        } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
+                    mTelephonyManager.getNetworkType());
+            List<CellInfo> info = mTelephonyManager.getAllCellInfo();
+            if (info == null) return;
+            int numRegisteredCellInfo = 0;
+            for (CellInfo cellInfo : info) {
+                if (cellInfo.isRegistered()) {
+                    numRegisteredCellInfo++;
+                    if (numRegisteredCellInfo > 1) {
+                        if (VDBG) {
+                            logw("more than one registered CellInfo."
+                                    + " Can't tell which is active.  Bailing.");
                         }
-                        if (cellInfo instanceof CellInfoCdma) {
-                            CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
-                            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
-                        } else if (cellInfo instanceof CellInfoGsm) {
-                            CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
-                            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
-                        } else if (cellInfo instanceof CellInfoLte) {
-                            CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
-                            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
-                        } else if (cellInfo instanceof CellInfoWcdma) {
-                            CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
-                            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
-                        } else {
-                            if (VDBG) logw("Registered cellinfo is unrecognized");
-                            return;
-                        }
+                        return;
+                    }
+                    if (cellInfo instanceof CellInfoCdma) {
+                        CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
+                        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+                    } else if (cellInfo instanceof CellInfoGsm) {
+                        CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
+                        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+                    } else if (cellInfo instanceof CellInfoLte) {
+                        CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
+                        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+                    } else if (cellInfo instanceof CellInfoWcdma) {
+                        CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
+                        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+                    } else {
+                        if (VDBG) logw("Registered cellinfo is unrecognized");
+                        return;
                     }
                 }
-                break;
-            default:
-                return;
+            }
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
+        } else {
+            return;
         }
-        latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CONNECTIVITY_TYPE,
-                mNetworkAgentInfo.networkInfo.getType());
-        latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_RECEIVED,
+        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
                 responseReceived);
-        latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_REQUEST_TIMESTAMP_MS,
+        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
                 requestTimestampMs);
 
         if (responseReceived) {
-            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_IS_CAPTIVE_PORTAL,
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
                     isCaptivePortal);
-            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_TIMESTAMP_MS,
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
                     responseTimestampMs);
         }
         mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
-                ConnectivityConstants.PERMISSION_ACCESS_NETWORK_CONDITIONS);
+                NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
     }
 
     private void logNetworkEvent(int evtype) {
-        int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+        int[] transports = mNetworkCapabilities.getTransportTypes();
         mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype));
     }
 
@@ -1520,14 +1587,14 @@
 
     private void maybeLogEvaluationResult(int evtype) {
         if (mEvaluationTimer.isRunning()) {
-            int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+            int[] transports = mNetworkCapabilities.getTransportTypes();
             mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop()));
             mEvaluationTimer.reset();
         }
     }
 
     private void logValidationProbe(long durationMs, int probeType, int probeResult) {
-        int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+        int[] transports = mNetworkCapabilities.getTransportTypes();
         boolean isFirstValidation = validationStage().mIsFirstValidation;
         ValidationProbeEvent ev = new ValidationProbeEvent();
         ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
@@ -1537,9 +1604,9 @@
     }
 
     @VisibleForTesting
-    public static class Dependencies {
-        public Network getNetwork(NetworkAgentInfo networkAgentInfo) {
-            return new OneAddressPerFamilyNetwork(networkAgentInfo.network());
+    static class Dependencies {
+        public Network getPrivateDnsBypassNetwork(Network network) {
+            return new OneAddressPerFamilyNetwork(network);
         }
 
         public Random getRandom() {
@@ -1547,6 +1614,13 @@
         }
 
         /**
+         * Get the captive portal server HTTP URL that is configured on the device.
+         */
+        public String getCaptivePortalServerHttpUrl(Context context) {
+            return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(context);
+        }
+
+        /**
          * Get the value of a global integer setting.
          * @param symbol Name of the setting
          * @param defaultValue Value to return if the setting is not defined.
@@ -1666,7 +1740,7 @@
         boolean result = false;
         // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
         // possible traffic cost in metered network.
-        if (mNetworkAgentInfo.networkCapabilities.isMetered()
+        if (mNetworkCapabilities.isMetered()
                 && (SystemClock.elapsedRealtime() - getLastProbeTime()
                 < mDataStallMinEvaluateTime)) {
             return false;
diff --git a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
similarity index 68%
rename from tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
rename to packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
index 6e07b26..d31fa77 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -16,6 +16,14 @@
 
 package com.android.server.connectivity;
 
+import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
+import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 
 import static org.junit.Assert.assertTrue;
@@ -24,13 +32,21 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.Intent;
+import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
+import android.net.INetworkMonitorCallbacks;
+import android.net.InetAddresses;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -38,9 +54,12 @@
 import android.net.NetworkRequest;
 import android.net.captiveportal.CaptivePortalProbeResult;
 import android.net.metrics.IpConnectivityLog;
+import android.net.util.SharedLog;
 import android.net.wifi.WifiManager;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -50,8 +69,10 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 import java.io.IOException;
 import java.net.HttpURLConnection;
@@ -68,21 +89,23 @@
     private static final String LOCATION_HEADER = "location";
 
     private @Mock Context mContext;
-    private @Mock Handler mHandler;
     private @Mock IpConnectivityLog mLogger;
-    private @Mock NetworkAgentInfo mAgent;
-    private @Mock NetworkAgentInfo mNotMeteredAgent;
+    private @Mock SharedLog mValidationLogger;
     private @Mock NetworkInfo mNetworkInfo;
-    private @Mock NetworkRequest mRequest;
+    private @Mock ConnectivityManager mCm;
     private @Mock TelephonyManager mTelephony;
     private @Mock WifiManager mWifi;
-    private @Mock Network mNetwork;
     private @Mock HttpURLConnection mHttpConnection;
     private @Mock HttpURLConnection mHttpsConnection;
     private @Mock HttpURLConnection mFallbackConnection;
     private @Mock HttpURLConnection mOtherFallbackConnection;
     private @Mock Random mRandom;
     private @Mock NetworkMonitor.Dependencies mDependencies;
+    private @Mock INetworkMonitorCallbacks mCallbacks;
+    private @Spy Network mNetwork = new Network(TEST_NETID);
+    private NetworkRequest mRequest;
+
+    private static final int TEST_NETID = 4242;
 
     private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
     private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
@@ -93,33 +116,37 @@
     private static final int RETURN_CODE_DNS_SUCCESS = 0;
     private static final int RETURN_CODE_DNS_TIMEOUT = 255;
 
+    private static final int HANDLER_TIMEOUT_MS = 1000;
+
+    private static final LinkProperties TEST_LINKPROPERTIES = new LinkProperties();
+
+    private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .addCapability(NET_CAPABILITY_INTERNET);
+
+    private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .addCapability(NET_CAPABILITY_INTERNET)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+
+    private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+
     @Before
     public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
-        mAgent.linkProperties = new LinkProperties();
-        mAgent.networkCapabilities = new NetworkCapabilities()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        mAgent.networkInfo = mNetworkInfo;
-
-        mNotMeteredAgent.linkProperties = new LinkProperties();
-        mNotMeteredAgent.networkCapabilities = new NetworkCapabilities()
-            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-        mNotMeteredAgent.networkInfo = mNetworkInfo;
-
-        when(mAgent.network()).thenReturn(mNetwork);
-        when(mDependencies.getNetwork(any())).thenReturn(mNetwork);
+        when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork);
         when(mDependencies.getRandom()).thenReturn(mRandom);
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
                 .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_USE_HTTPS),
                 anyInt())).thenReturn(1);
-        when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL),
-                anyString())).thenReturn(TEST_HTTP_URL);
+        when(mDependencies.getCaptivePortalServerHttpUrl(any())).thenReturn(TEST_HTTP_URL);
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL),
                 anyString())).thenReturn(TEST_HTTPS_URL);
-        when(mNetwork.getPrivateDnsBypassingCopy()).thenReturn(mNetwork);
+        doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
 
+        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
         when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
         when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
 
@@ -129,7 +156,7 @@
         setFallbackSpecs(null); // Test with no fallback spec by default
         when(mRandom.nextInt()).thenReturn(0);
 
-        when(mNetwork.openConnection(any())).then((invocation) -> {
+        doAnswer((invocation) -> {
             URL url = invocation.getArgument(0);
             switch(url.toString()) {
                 case TEST_HTTP_URL:
@@ -144,12 +171,20 @@
                     fail("URL not mocked: " + url.toString());
                     return null;
             }
-        });
+        }).when(mNetwork).openConnection(any());
         when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
         when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
-        when(mNetwork.getAllByName(any())).thenReturn(new InetAddress[] {
-            InetAddress.parseNumericAddress("192.168.0.0")
-        });
+        doReturn(new InetAddress[] {
+                InetAddresses.parseNumericAddress("192.168.0.0")
+        }).when(mNetwork).getAllByName(any());
+
+        mRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+        // Default values. Individual tests can override these.
+        when(mCm.getLinkProperties(any())).thenReturn(TEST_LINKPROPERTIES);
+        when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
 
         setMinDataStallEvaluateInterval(500);
         setDataStallEvaluationType(1 << DATA_STALL_EVALUATION_TYPE_DNS);
@@ -160,10 +195,10 @@
     private class WrappedNetworkMonitor extends NetworkMonitor {
         private long mProbeTime = 0;
 
-        WrappedNetworkMonitor(Context context, Handler handler,
-                NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
+        WrappedNetworkMonitor(Context context, Network network, NetworkRequest defaultRequest,
                 IpConnectivityLog logger, Dependencies deps) {
-                super(context, handler, networkAgentInfo, defaultRequest, logger, deps);
+                super(context, mCallbacks, network, defaultRequest, logger,
+                        new SharedLog("test_nm"), deps);
         }
 
         @Override
@@ -176,19 +211,39 @@
         }
     }
 
-    WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
-        return new WrappedNetworkMonitor(
-                mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
+    private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
+        final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
+                mContext, mNetwork, mRequest, mLogger, mDependencies);
+        when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
+        nm.start();
+        waitForIdle(nm.getHandler());
+        return nm;
     }
 
-    WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
-        return new WrappedNetworkMonitor(
-                mContext, mHandler, mNotMeteredAgent, mRequest, mLogger, mDependencies);
+    private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
+        final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
+                mContext, mNetwork, mRequest, mLogger, mDependencies);
+        when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES);
+        nm.start();
+        waitForIdle(nm.getHandler());
+        return nm;
     }
 
-    NetworkMonitor makeMonitor() {
-        return new NetworkMonitor(
-                mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
+    private NetworkMonitor makeMonitor() {
+        final NetworkMonitor nm = new NetworkMonitor(
+                mContext, mCallbacks, mNetwork, mRequest, mLogger, mValidationLogger,
+                mDependencies);
+        nm.start();
+        waitForIdle(nm.getHandler());
+        return nm;
+    }
+
+    private void waitForIdle(Handler handler) {
+        final ConditionVariable cv = new ConditionVariable(false);
+        handler.post(cv::open);
+        if (!cv.block(HANDLER_TIMEOUT_MS)) {
+            fail("Timed out waiting for handler");
+        }
     }
 
     @Test
@@ -319,6 +374,15 @@
     }
 
     @Test
+    public void testIsCaptivePortal_IgnorePortals() throws IOException {
+        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
+        setSslException(mHttpsConnection);
+        setPortal302(mHttpConnection);
+
+        assertNotPortal(makeMonitor().isCaptivePortal());
+    }
+
+    @Test
     public void testIsDataStall_EvaluationDisabled() {
         setDataStallEvaluationType(0);
         WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
@@ -390,6 +454,63 @@
         assertFalse(wrappedMonitor.isDataStall());
     }
 
+    @Test
+    public void testBrokenNetworkNotValidated() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 500);
+        setStatus(mFallbackConnection, 404);
+        when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
+
+        final NetworkMonitor nm = makeMonitor();
+        nm.notifyNetworkConnected();
+
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
+    }
+
+    @Test
+    public void testNoInternetCapabilityValidated() throws Exception {
+        when(mCm.getNetworkCapabilities(any())).thenReturn(NO_INTERNET_CAPABILITIES);
+
+        final NetworkMonitor nm = makeMonitor();
+        nm.notifyNetworkConnected();
+
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
+        verify(mNetwork, never()).openConnection(any());
+    }
+
+    @Test
+    public void testLaunchCaptivePortalApp() throws Exception {
+        setSslException(mHttpsConnection);
+        setPortal302(mHttpConnection);
+
+        final NetworkMonitor nm = makeMonitor();
+        nm.notifyNetworkConnected();
+
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .showProvisioningNotification(any());
+
+        // Check that startCaptivePortalApp sends the expected intent.
+        nm.launchCaptivePortalApp();
+
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .startActivityAsUser(intentCaptor.capture(), eq(UserHandle.CURRENT));
+        final Intent intent = intentCaptor.getValue();
+        assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
+        final Network network = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
+        assertEquals(TEST_NETID, network.netId);
+
+        // Have the app report that the captive portal is dismissed, and check that we revalidate.
+        setStatus(mHttpsConnection, 204);
+        setStatus(mHttpConnection, 204);
+        final CaptivePortal captivePortal = intent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL);
+        captivePortal.reportCaptivePortalDismissed();
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
+    }
+
     private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
         for (int i = 0; i < count; i++) {
             wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
@@ -440,6 +561,11 @@
                 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
     }
 
+    private void setCaptivePortalMode(int mode) {
+        when(mDependencies.getSetting(any(),
+                eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
+    }
+
     private void assertPortal(CaptivePortalProbeResult result) {
         assertTrue(result.isPortal());
         assertFalse(result.isFailed());
@@ -459,12 +585,12 @@
     }
 
     private void setSslException(HttpURLConnection connection) throws IOException {
-        when(connection.getResponseCode()).thenThrow(new SSLHandshakeException("Invalid cert"));
+        doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
     }
 
     private void set302(HttpURLConnection connection, String location) throws IOException {
         setStatus(connection, 302);
-        when(connection.getHeaderField(LOCATION_HEADER)).thenReturn(location);
+        doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
     }
 
     private void setPortal302(HttpURLConnection connection) throws IOException {
@@ -472,7 +598,7 @@
     }
 
     private void setStatus(HttpURLConnection connection, int status) throws IOException {
-        when(connection.getResponseCode()).thenReturn(status);
+        doReturn(status).when(connection).getResponseCode();
     }
 }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 419273e..bcf37ff 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1557,6 +1557,10 @@
                 Settings.Global.ZRAM_ENABLED,
                 GlobalSettingsProto.ZRAM_ENABLED);
 
+        dumpSetting(s, p,
+                Global.APP_OPS_CONSTANTS,
+                GlobalSettingsProto.APP_OPS_CONSTANTS);
+
         p.end(token);
         // Please insert new settings using the same order as in GlobalSettingsProto.
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bff2c84..fa95bf2 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -134,6 +134,8 @@
     <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <uses-permission android:name="android.permission.MANAGE_AUTO_FILL" />
     <uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" />
+    <uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" />
+    <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
     <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.SET_TIME" />
@@ -162,6 +164,8 @@
     <uses-permission android:name="android.permission.SUSPEND_APPS" />
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
 
+    <uses-permission android:name="android.permission.MANAGE_APPOPS" />
+
     <application android:label="@string/app_label"
                  android:defaultToDeviceProtectedStorage="true"
                  android:directBootAware="true">
diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
index d041556..0c6d57d 100644
--- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
+++ b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
@@ -18,7 +18,7 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="?android:attr/colorBackgroundFloating" />
-    <corners android:radius="1dp"
+    <corners
         android:topLeftRadius="@dimen/biometric_dialog_corner_size"
         android:topRightRadius="@dimen/biometric_dialog_corner_size"
         android:bottomLeftRadius="@dimen/biometric_dialog_corner_size"
diff --git a/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
new file mode 100644
index 0000000..26bf981
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="?android:attr/colorBackgroundFloating"/>
+            <corners
+                    android:topLeftRadius="@dimen/corner_size"
+                    android:topRightRadius="@dimen/corner_size"/>
+        </shape>
+    </item>
+    <item android:gravity="bottom">
+        <shape>
+            <size android:height="1dp"/>
+            <solid android:color="?android:attr/textColorSecondary" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index e52aa14..b9b1bb1 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -22,7 +22,7 @@
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:singleLine="true"
-        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
         android:textColor="?android:attr/textColorPrimary"
         android:gravity="center_vertical|start"
         android:paddingStart="@dimen/battery_level_padding_start"
diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml
index b2307e7..1aeb52c 100644
--- a/packages/SystemUI/res/layout/bubble_expanded_view.xml
+++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml
@@ -20,11 +20,23 @@
     android:layout_width="match_parent"
     android:id="@+id/bubble_expanded_view">
 
-    <!-- TODO: header -->
-
     <View
         android:id="@+id/pointer_view"
         android:layout_width="@dimen/bubble_pointer_width"
         android:layout_height="@dimen/bubble_pointer_height"
     />
+
+    <TextView
+        android:id="@+id/bubble_content_header"
+        android:background="@drawable/bubble_expanded_header_bg"
+        android:textAppearance="@*android:style/TextAppearance.Material.Title"
+        android:textSize="18sp"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/bubble_expanded_header_height"
+        android:gravity="start|center_vertical"
+        android:singleLine="true"
+        android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding"
+        android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding"
+    />
+
 </com.android.systemui.bubbles.BubbleExpandedViewContainer>
diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
index 307b538..5bcc1b3 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
@@ -39,7 +39,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/monitoring_title_device_owned"
-                style="@android:style/TextAppearance.Material.Title"
+                style="@style/TextAppearance.DeviceManagementDialog.Title"
                 android:textColor="?android:attr/textColorPrimary"
                 android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
             />
@@ -64,7 +64,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/monitoring_subtitle_ca_certificate"
-                style="@android:style/TextAppearance.Material.Title"
+                style="@style/TextAppearance.DeviceManagementDialog.Title"
                 android:textColor="?android:attr/textColorPrimary"
                 android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
             />
@@ -89,7 +89,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/monitoring_subtitle_network_logging"
-                style="@android:style/TextAppearance.Material.Title"
+                style="@style/TextAppearance.DeviceManagementDialog.Title"
                 android:textColor="?android:attr/textColorPrimary"
                 android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
             />
@@ -114,7 +114,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/monitoring_subtitle_vpn"
-                style="@android:style/TextAppearance.Material.Title"
+                style="@style/TextAppearance.DeviceManagementDialog.Title"
                 android:textColor="?android:attr/textColorPrimary"
                 android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
             />
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index 22b8d2f..4b65b6a 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -24,6 +24,7 @@
     android:clipToPadding="false"
     android:gravity="center"
     android:orientation="horizontal"
+    android:clickable="true"
     android:paddingStart="@dimen/status_bar_padding_start"
     android:paddingEnd="@dimen/status_bar_padding_end" >
 
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
deleted file mode 100644
index cfa372b..0000000
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ /dev/null
@@ -1,129 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2011, 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.
-*/
--->
-<!-- extends LinearLayout -->
-<com.android.systemui.statusbar.SignalClusterView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/signal_cluster"
-    android:layout_height="match_parent"
-    android:layout_width="wrap_content"
-    android:gravity="center_vertical"
-    android:orientation="horizontal"
-    android:paddingEnd="@dimen/signal_cluster_battery_padding"
-    >
-    <ImageView
-        android:id="@+id/vpn"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:paddingEnd="6dp"
-        android:src="@drawable/stat_sys_vpn_ic"
-        android:tint="@color/background_protect_secondary"
-        android:contentDescription="@string/accessibility_vpn_on"
-        />
-    <FrameLayout
-        android:id="@+id/ethernet_combo"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        >
-        <com.android.systemui.statusbar.AlphaOptimizedImageView
-            android:theme="?attr/lightIconTheme"
-            android:id="@+id/ethernet"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            />
-        <com.android.systemui.statusbar.AlphaOptimizedImageView
-            android:theme="?attr/darkIconTheme"
-            android:id="@+id/ethernet_dark"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:alpha="0.0"
-            />
-    </FrameLayout>
-    <FrameLayout
-        android:layout_height="17dp"
-        android:layout_width="wrap_content">
-        <ImageView
-            android:id="@+id/wifi_in"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:src="@drawable/ic_activity_down"
-            android:visibility="gone"
-            android:paddingEnd="2dp"
-            />
-        <ImageView
-            android:id="@+id/wifi_out"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:src="@drawable/ic_activity_up"
-            android:paddingEnd="2dp"
-            android:visibility="gone"
-            />
-    </FrameLayout>
-    <FrameLayout
-        android:id="@+id/wifi_combo"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        >
-        <com.android.systemui.statusbar.AlphaOptimizedImageView
-            android:theme="?attr/lightIconTheme"
-            android:id="@+id/wifi_signal"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            />
-        <com.android.systemui.statusbar.AlphaOptimizedImageView
-            android:theme="?attr/darkIconTheme"
-            android:id="@+id/wifi_signal_dark"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:alpha="0.0"
-            />
-        <ImageView
-            android:id="@+id/wifi_inout"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            />
-    </FrameLayout>
-    <View
-        android:id="@+id/wifi_signal_spacer"
-        android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
-        android:layout_height="4dp"
-        android:visibility="gone"
-        />
-    <ViewStub
-        android:id="@+id/connected_device_signals_stub"
-        android:layout="@layout/connected_device_signal"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
-    <LinearLayout
-        android:id="@+id/mobile_signal_group"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        >
-    </LinearLayout>
-    <View
-        android:id="@+id/wifi_airplane_spacer"
-        android:layout_width="@dimen/status_bar_airplane_spacer_width"
-        android:layout_height="4dp"
-        android:visibility="gone"
-        />
-    <ImageView
-        android:id="@+id/airplane"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        />
-</com.android.systemui.statusbar.SignalClusterView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 34c208a..02062bb 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -43,6 +43,14 @@
                    android:visibility="invisible" />
     </com.android.systemui.statusbar.BackDropView>
 
+    <com.android.systemui.wallpaper.AodMaskView
+        android:id="@+id/aod_mask"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        android:visibility="invisible"
+        sysui:ignoreRightInset="true" />
+
     <com.android.systemui.statusbar.ScrimView
         android:id="@+id/scrim_behind"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 889db66..7cc5524 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -449,6 +449,11 @@
          better (narrower) line-break for a double-line smart reply button. -->
     <integer name="config_smart_replies_in_notifications_max_squeeze_remeasure_attempts">3</integer>
 
+    <!-- Smart replies in notifications: Whether by default tapping on a choice should let the user
+         edit the input before it is sent to the app. Developers can override this via
+         RemoteInput.Builder.setEditChoicesBeforeSending. -->
+    <bool name="config_smart_replies_in_notifications_edit_choices_before_sending">false</bool>
+
     <!-- Screenshot editing default activity.  Must handle ACTION_EDIT image/png intents.
          Blank sends the user to the Chooser first.
          This name is in the ComponentName flattened format (package/class)  -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 06df0e7..10e5f74 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -997,4 +997,8 @@
     <dimen name="bubble_pointer_width">6dp</dimen>
     <!-- Extra padding around the dismiss target for bubbles -->
     <dimen name="bubble_dismiss_slop">16dp</dimen>
+    <!-- Height of the header within the expanded view. -->
+    <dimen name="bubble_expanded_header_height">48dp</dimen>
+    <!-- Left and right padding applied to the header. -->
+    <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index dac20b5..633f868 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -102,12 +102,17 @@
     <item type="id" name="action_snooze_assistant_suggestion_1"/>
     <item type="id" name="action_snooze"/>
 
-    <!-- For StatusBarIconContainer to tag its icon views -->
+    <!-- For StatusIconContainer to tag its icon views -->
     <item type="id" name="status_bar_view_state_tag" />
 
     <item type="id" name="display_cutout" />
 
     <!-- Optional cancel button on Keyguard -->
     <item type="id" name="cancel_button"/>
+
+    <!-- AodMaskView transition tag -->
+    <item type="id" name="aod_mask_transition_progress_tag" />
+    <item type="id" name="aod_mask_transition_progress_end_tag" />
+    <item type="id" name="aod_mask_transition_progress_start_tag" />
 </resources>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 8a5a69b..22a0b36 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -125,7 +125,7 @@
 
     <style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
         <item name="android:textSize">@dimen/status_bar_clock_size</item>
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
         <item name="android:textColor">@color/status_bar_clock_color</item>
     </style>
 
@@ -265,6 +265,10 @@
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
+    <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
+        <item name="android:gravity">center</item>
+    </style>
+
     <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index f3bdbae..078947c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -76,4 +76,10 @@
      */
     float getWindowCornerRadius() = 10;
 
+    /**
+     * If device supports live rounded corners on windows.
+     * This might be turned off for performance reasons
+     */
+    boolean supportsRoundedCornersOnWindows() = 11;
+
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index c0a1d89..e6acfbe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -16,14 +16,10 @@
 
 package com.android.systemui.shared.system;
 
+import android.graphics.HardwareRenderer;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.SyncRtSurfaceTransactionApplier;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
-import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.ViewRootImpl;
 
@@ -31,20 +27,21 @@
 
 /**
  * Helper class to apply surface transactions in sync with RenderThread.
+ *
+ * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't 
+ *       currently reference that class from the shared lib as it is hidden.
  */
 public class SyncRtSurfaceTransactionApplierCompat {
 
-    private final SyncRtSurfaceTransactionApplier mApplier;
+    private final Surface mTargetSurface;
+    private final ViewRootImpl mTargetViewRootImpl;
 
     /**
      * @param targetView The view in the surface that acts as synchronization anchor.
      */
     public SyncRtSurfaceTransactionApplierCompat(View targetView) {
-        mApplier = new SyncRtSurfaceTransactionApplier(targetView);
-    }
-
-    private SyncRtSurfaceTransactionApplierCompat(SyncRtSurfaceTransactionApplier applier) {
-        mApplier = applier;
+        mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
+        mTargetSurface = mTargetViewRootImpl != null ? mTargetViewRootImpl.mSurface : null;
     }
 
     /**
@@ -53,38 +50,74 @@
      * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into
      *               this method to avoid synchronization issues.
      */
-    public void scheduleApply(final SurfaceParams... params) {
-        mApplier.scheduleApply(convert(params));
-    }
-
-    private SyncRtSurfaceTransactionApplier.SurfaceParams[] convert(SurfaceParams[] params) {
-        SyncRtSurfaceTransactionApplier.SurfaceParams[] result =
-                new SyncRtSurfaceTransactionApplier.SurfaceParams[params.length];
-        for (int i = 0; i < params.length; i++) {
-            result[i] = params[i].mParams;
+    public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) {
+        if (mTargetViewRootImpl == null) {
+            return;
         }
-        return result;
+        mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
+            @Override
+            public void onFrameDraw(long frame) {
+                if (mTargetSurface == null || !mTargetSurface.isValid()) {
+                    return;
+                }
+                TransactionCompat t = new TransactionCompat();
+                for (int i = params.length - 1; i >= 0; i--) {
+                    SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams =
+                            params[i];
+                    SurfaceControlCompat surface = surfaceParams.surface;
+                    t.deferTransactionUntil(surface, mTargetSurface, frame);
+                    applyParams(t, surfaceParams);
+                }
+                t.setEarlyWakeup();
+                t.apply();
+            }
+        });
+
+        // Make sure a frame gets scheduled.
+        mTargetViewRootImpl.getView().invalidate();
     }
 
-    public static void applyParams(TransactionCompat t, SurfaceParams params) {
-        SyncRtSurfaceTransactionApplier.applyParams(t.mTransaction, params.mParams, t.mTmpValues);
+    public static void applyParams(TransactionCompat t,
+            SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
+        t.setMatrix(params.surface, params.matrix);
+        t.setWindowCrop(params.surface, params.windowCrop);
+        t.setAlpha(params.surface, params.alpha);
+        t.setLayer(params.surface, params.layer);
+        t.setCornerRadius(params.surface, params.cornerRadius);
+        t.show(params.surface);
     }
 
+    /**
+     * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
+     * attached if necessary.
+     */
     public static void create(final View targetView,
             final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) {
-        SyncRtSurfaceTransactionApplier.create(targetView,
-                new Consumer<SyncRtSurfaceTransactionApplier>() {
-                    @Override
-                    public void accept(SyncRtSurfaceTransactionApplier applier) {
-                        callback.accept(new SyncRtSurfaceTransactionApplierCompat(applier));
-                    }
-                });
+        if (targetView == null) {
+            // No target view, no applier
+            callback.accept(null);
+        } else if (targetView.getViewRootImpl() != null) {
+            // Already attached, we're good to go
+            callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
+        } else {
+            // Haven't been attached before we can get the view root
+            targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    targetView.removeOnAttachStateChangeListener(this);
+                    callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    // Do nothing
+                }
+            });
+        }
     }
 
     public static class SurfaceParams {
 
-        private final SyncRtSurfaceTransactionApplier.SurfaceParams mParams;
-
         /**
          * Constructs surface parameters to be applied when the current view state gets pushed to
          * RenderThread.
@@ -96,8 +129,19 @@
          */
         public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix,
                 Rect windowCrop, int layer, float cornerRadius) {
-            mParams = new SyncRtSurfaceTransactionApplier.SurfaceParams(surface.mSurfaceControl,
-                    alpha, matrix, windowCrop, layer, cornerRadius);
+            this.surface = surface;
+            this.alpha = alpha;
+            this.matrix = new Matrix(matrix);
+            this.windowCrop = new Rect(windowCrop);
+            this.layer = layer;
+            this.cornerRadius = cornerRadius;
         }
+
+        public final SurfaceControlCompat surface;
+        public final float alpha;
+        final float cornerRadius;
+        public final Matrix matrix;
+        public final Rect windowCrop;
+        public final int layer;
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
index 2aba3fa..af32f48 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
@@ -82,6 +82,11 @@
         return this;
     }
 
+    public TransactionCompat setCornerRadius(SurfaceControlCompat surfaceControl, float radius) {
+        mTransaction.setCornerRadius(surfaceControl.mSurfaceControl, radius);
+        return this;
+    }
+
     public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl,
             IBinder handle, long frameNumber) {
         mTransaction.deferTransactionUntil(surfaceControl.mSurfaceControl, handle, frameNumber);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 27d624a..17cc1d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -14,13 +14,13 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 
-import java.util.Objects;
 import java.util.TimeZone;
+import java.util.function.Consumer;
 
 /**
  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
@@ -47,43 +47,19 @@
      * or not to show it below the alternate clock.
      */
     private View mKeyguardStatusArea;
+    /**
+     * Used to select between plugin or default implementations of ClockPlugin interface.
+     */
+    private Extension<ClockPlugin> mClockExtension;
+    /**
+     * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
+     */
+    private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin);
+    /**
+     * Maintain state so that a newly connected plugin can be initialized.
+     */
+    private float mDarkAmount;
 
-    private final PluginListener<ClockPlugin> mClockPluginListener =
-            new PluginListener<ClockPlugin>() {
-                @Override
-                public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
-                    disconnectPlugin();
-                    View smallClockView = plugin.getView();
-                    if (smallClockView != null) {
-                        // For now, assume that the most recently connected plugin is the
-                        // selected clock face. In the future, the user should be able to
-                        // pick a clock face from the available plugins.
-                        mSmallClockFrame.addView(smallClockView, -1,
-                                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                                        ViewGroup.LayoutParams.WRAP_CONTENT));
-                        initPluginParams();
-                        mClockView.setVisibility(View.GONE);
-                    }
-                    View bigClockView = plugin.getBigClockView();
-                    if (bigClockView != null && mBigClockContainer != null) {
-                        mBigClockContainer.addView(bigClockView);
-                        mBigClockContainer.setVisibility(View.VISIBLE);
-                    }
-                    if (!plugin.shouldShowStatusArea()) {
-                        mKeyguardStatusArea.setVisibility(View.GONE);
-                    }
-                    mClockPlugin = plugin;
-                }
-
-                @Override
-                public void onPluginDisconnected(ClockPlugin plugin) {
-                    if (Objects.equals(plugin, mClockPlugin)) {
-                        disconnectPlugin();
-                        mClockView.setVisibility(View.VISIBLE);
-                        mKeyguardStatusArea.setVisibility(View.VISIBLE);
-                    }
-                }
-            };
     private final StatusBarStateController.StateListener mStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
@@ -122,18 +98,62 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        Dependency.get(PluginManager.class).addPluginListener(mClockPluginListener,
-                ClockPlugin.class);
+        mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
+                .withPlugin(ClockPlugin.class)
+                .withCallback(mClockPluginConsumer)
+                .build();
         Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        Dependency.get(PluginManager.class).removePluginListener(mClockPluginListener);
+        mClockExtension.destroy();
         Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
     }
 
+    private void setClockPlugin(ClockPlugin plugin) {
+        // Disconnect from existing plugin.
+        if (mClockPlugin != null) {
+            View smallClockView = mClockPlugin.getView();
+            if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) {
+                mSmallClockFrame.removeView(smallClockView);
+            }
+            if (mBigClockContainer != null) {
+                mBigClockContainer.removeAllViews();
+                mBigClockContainer.setVisibility(View.GONE);
+            }
+            mClockPlugin = null;
+        }
+        if (plugin == null) {
+            mClockView.setVisibility(View.VISIBLE);
+            mKeyguardStatusArea.setVisibility(View.VISIBLE);
+            return;
+        }
+        // Attach small and big clock views to hierarchy.
+        View smallClockView = plugin.getView();
+        if (smallClockView != null) {
+            mSmallClockFrame.addView(smallClockView, -1,
+                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.WRAP_CONTENT));
+            mClockView.setVisibility(View.GONE);
+        }
+        View bigClockView = plugin.getBigClockView();
+        if (bigClockView != null && mBigClockContainer != null) {
+            mBigClockContainer.addView(bigClockView);
+            mBigClockContainer.setVisibility(View.VISIBLE);
+        }
+        // Hide default clock.
+        if (!plugin.shouldShowStatusArea()) {
+            mKeyguardStatusArea.setVisibility(View.GONE);
+        }
+        // Initialize plugin parameters.
+        mClockPlugin = plugin;
+        mClockPlugin.setStyle(getPaint().getStyle());
+        mClockPlugin.setTextColor(getCurrentTextColor());
+        mClockPlugin.setDarkAmount(mDarkAmount);
+    }
+
     /**
      * Set container for big clock face appearing behind NSSL and KeyguardStatusView.
      */
@@ -193,6 +213,7 @@
      * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
      */
     public void setDarkAmount(float darkAmount) {
+        mDarkAmount = darkAmount;
         if (mClockPlugin != null) {
             mClockPlugin.setDarkAmount(darkAmount);
         }
@@ -232,33 +253,9 @@
         }
     }
 
-    /**
-     * When plugin changes, set all kept parameters into newer plugin.
-     */
-    private void initPluginParams() {
-        if (mClockPlugin != null) {
-            mClockPlugin.setStyle(getPaint().getStyle());
-            mClockPlugin.setTextColor(getCurrentTextColor());
-        }
-    }
-
-    private void disconnectPlugin() {
-        if (mClockPlugin != null) {
-            View smallClockView = mClockPlugin.getView();
-            if (smallClockView != null) {
-                mSmallClockFrame.removeView(smallClockView);
-            }
-            if (mBigClockContainer != null) {
-                mBigClockContainer.removeAllViews();
-                mBigClockContainer.setVisibility(View.GONE);
-            }
-            mClockPlugin = null;
-        }
-    }
-
     @VisibleForTesting (otherwise = VisibleForTesting.NONE)
-    PluginListener getClockPluginListener() {
-        return mClockPluginListener;
+    Consumer<ClockPlugin> getClockPluginConsumer() {
+        return mClockPluginConsumer;
     }
 
     @VisibleForTesting (otherwise = VisibleForTesting.NONE)
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 6864ea1..8e273ef 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -40,7 +40,6 @@
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.LayoutInflater;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -56,7 +55,6 @@
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.IconLogger;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.Utils.DisableStateTracker;
@@ -276,9 +274,6 @@
     public void onTuningChanged(String key, String newValue) {
         if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
             ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue);
-            boolean hidden = icons.contains(mSlotBattery);
-            Dependency.get(IconLogger.class).onIconVisibility(mSlotBattery, !hidden);
-            setVisibility(hidden ? View.GONE : View.VISIBLE);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 9d81064..ec6ecc6 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -86,7 +86,6 @@
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.IconLogger;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.NetworkController;
@@ -217,7 +216,6 @@
     @Inject Lazy<LeakDetector> mLeakDetector;
     @Inject Lazy<LeakReporter> mLeakReporter;
     @Inject Lazy<GarbageMonitor> mGarbageMonitor;
-    @Inject Lazy<IconLogger> mIconLogger;
     @Inject Lazy<TunerService> mTunerService;
     @Inject Lazy<StatusBarWindowController> mStatusBarWindowController;
     @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher;
@@ -392,8 +390,6 @@
 
         mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
 
-        mProviders.put(IconLogger.class, mIconLogger::get);
-
         mProviders.put(LightBarController.class, mLightBarController::get);
 
         mProviders.put(IWindowManager.class, mIWindowManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
index f324a05b..ce9c637 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
@@ -46,8 +46,6 @@
 import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
 import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.IconLogger;
-import com.android.systemui.statusbar.policy.IconLoggerImpl;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -133,11 +131,6 @@
     /**
      */
     @Binds
-    public abstract IconLogger provideIconLogger(IconLoggerImpl loggerImpl);
-
-    /**
-     */
-    @Binds
     public abstract CastController provideCastController(CastControllerImpl controllerImpl);
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 9f363f6..7e5b426 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -25,12 +25,20 @@
     private int mUid;
     private String mPackageName;
     private long mTimeStarted;
+    private String mState;
 
     public AppOpItem(int code, int uid, String packageName, long timeStarted) {
         this.mCode = code;
         this.mUid = uid;
         this.mPackageName = packageName;
         this.mTimeStarted = timeStarted;
+        mState = new StringBuilder()
+                .append("AppOpItem(")
+                .append("Op code=").append(code).append(", ")
+                .append("UID=").append(uid).append(", ")
+                .append("Package name=").append(packageName)
+                .append(")")
+                .toString();
     }
 
     public int getCode() {
@@ -48,4 +56,9 @@
     public long getTimeStarted() {
         return mTimeStarted;
     }
+
+    @Override
+    public String toString() {
+        return mState;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index e3bcb37..c013df3 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -29,7 +29,10 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -47,7 +50,7 @@
 @Singleton
 public class AppOpsControllerImpl implements AppOpsController,
         AppOpsManager.OnOpActiveChangedListener,
-        AppOpsManager.OnOpNotedListener {
+        AppOpsManager.OnOpNotedListener, Dumpable {
 
     private static final long NOTED_OP_TIME_DELAY_MS = 5000;
     private static final String TAG = "AppOpsControllerImpl";
@@ -271,6 +274,22 @@
         }
     }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("AppOpsController state:");
+        pw.println("  Active Items:");
+        for (int i = 0; i < mActiveItems.size(); i++) {
+            final AppOpItem item = mActiveItems.get(i);
+            pw.print("    "); pw.println(item.toString());
+        }
+        pw.println("  Noted Items:");
+        for (int i = 0; i < mNotedItems.size(); i++) {
+            final AppOpItem item = mNotedItems.get(i);
+            pw.print("    "); pw.println(item.toString());
+        }
+
+    }
+
     protected final class H extends Handler {
         H(Looper looper) {
             super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index 3167b9e..016b8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -33,9 +33,6 @@
 import com.android.systemui.SystemUI;
 import com.android.systemui.statusbar.CommandQueue;
 
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g.
  * BiometricDialogView).
@@ -52,10 +49,8 @@
     private static final int MSG_BUTTON_NEGATIVE = 6;
     private static final int MSG_USER_CANCELED = 7;
     private static final int MSG_BUTTON_POSITIVE = 8;
-    private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9;
-    private static final int MSG_TRY_AGAIN_PRESSED = 10;
+    private static final int MSG_TRY_AGAIN_PRESSED = 9;
 
-    private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view
     private SomeArgs mCurrentDialogArgs;
     private BiometricDialogView mCurrentDialog;
     private WindowManager mWindowManager;
@@ -63,21 +58,22 @@
     private boolean mDialogShowing;
     private Callback mCallback = new Callback();
 
-    private boolean mTryAgainShowing; // No good place to save state before config change :/
-    private boolean mConfirmShowing; // No good place to save state before config change :/
-
     private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             switch(msg.what) {
                 case MSG_SHOW_DIALOG:
-                    handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */);
+                    handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */,
+                            null /* savedState */);
                     break;
                 case MSG_BIOMETRIC_AUTHENTICATED:
-                    handleBiometricAuthenticated();
+                    handleBiometricAuthenticated((boolean) msg.obj);
                     break;
                 case MSG_BIOMETRIC_HELP:
-                    handleBiometricHelp((String) msg.obj);
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleBiometricHelp((String) args.arg1 /* message */,
+                            (boolean) args.arg2 /* requireTryAgain */);
+                    args.recycle();
                     break;
                 case MSG_BIOMETRIC_ERROR:
                     handleBiometricError((String) msg.obj);
@@ -94,9 +90,6 @@
                 case MSG_BUTTON_POSITIVE:
                     handleButtonPositive();
                     break;
-                case MSG_BIOMETRIC_SHOW_TRY_AGAIN:
-                    handleShowTryAgain();
-                    break;
                 case MSG_TRY_AGAIN_PRESSED:
                     handleTryAgainPressed();
                     break;
@@ -137,30 +130,22 @@
 
     @Override
     public void start() {
-        createDialogs();
-
-        if (!mDialogs.isEmpty()) {
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
+                || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
+                || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
             getComponent(CommandQueue.class).addCallback(this);
             mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
         }
     }
 
-    private void createDialogs() {
-        final PackageManager pm = mContext.getPackageManager();
-        mDialogs = new HashMap<>();
-        if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
-            mDialogs.put(BiometricAuthenticator.TYPE_FACE, new FaceDialogView(mContext, mCallback));
-        }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
-            mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT,
-                    new FingerprintDialogView(mContext, mCallback));
-        }
-    }
-
     @Override
     public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
             int type, boolean requireConfirmation, int userId) {
-        if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type);
+        if (DEBUG) {
+            Log.d(TAG, "showBiometricDialog, type: " + type
+                    + ", requireConfirmation: " + requireConfirmation);
+        }
         // Remove these messages as they are part of the previous client
         mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
         mHandler.removeMessages(MSG_BIOMETRIC_HELP);
@@ -176,15 +161,18 @@
     }
 
     @Override
-    public void onBiometricAuthenticated() {
-        if (DEBUG) Log.d(TAG, "onBiometricAuthenticated");
-        mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+    public void onBiometricAuthenticated(boolean authenticated) {
+        if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated);
+        mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
     }
 
     @Override
     public void onBiometricHelp(String message) {
         if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
-        mHandler.obtainMessage(MSG_BIOMETRIC_HELP, message).sendToTarget();
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = message;
+        args.arg2 = false; // requireTryAgain
+        mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget();
     }
 
     @Override
@@ -199,16 +187,21 @@
         mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
     }
 
-    @Override
-    public void showBiometricTryAgain() {
-        if (DEBUG) Log.d(TAG, "showBiometricTryAgain");
-        mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget();
-    }
-
-    private void handleShowDialog(SomeArgs args, boolean skipAnimation) {
+    private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
         mCurrentDialogArgs = args;
         final int type = args.argi1;
-        mCurrentDialog = mDialogs.get(type);
+
+        if (type == BiometricAuthenticator.TYPE_FINGERPRINT) {
+            mCurrentDialog = new FingerprintDialogView(mContext, mCallback);
+        } else if (type == BiometricAuthenticator.TYPE_FACE) {
+            mCurrentDialog = new FaceDialogView(mContext, mCallback);
+        } else {
+            Log.e(TAG, "Unsupported type: " + type);
+        }
+
+        if (savedState != null) {
+            mCurrentDialog.restoreState(savedState);
+        }
 
         if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: "
                 + mCurrentDialog.isAnimatingAway() + " type: " + type);
@@ -224,32 +217,36 @@
         mCurrentDialog.setRequireConfirmation((boolean) args.arg3);
         mCurrentDialog.setUserId(args.argi2);
         mCurrentDialog.setSkipIntro(skipAnimation);
-        mCurrentDialog.setPendingTryAgain(mTryAgainShowing);
-        mCurrentDialog.setPendingConfirm(mConfirmShowing);
         mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
         mDialogShowing = true;
     }
 
-    private void handleBiometricAuthenticated() {
-        if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated");
+    private void handleBiometricAuthenticated(boolean authenticated) {
+        if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated);
 
-        mCurrentDialog.announceForAccessibility(
-                mContext.getResources()
-                        .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
-        if (mCurrentDialog.requiresConfirmation()) {
-            mConfirmShowing = true;
-            mCurrentDialog.showConfirmationButton(true /* show */);
+        if (authenticated) {
+            mCurrentDialog.announceForAccessibility(
+                    mContext.getResources()
+                            .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
+            if (mCurrentDialog.requiresConfirmation()) {
+                mCurrentDialog.showConfirmationButton(true /* show */);
+            } else {
+                mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
+                mHandler.postDelayed(() -> {
+                    handleHideDialog(false /* userCanceled */);
+                }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
+            }
         } else {
-            mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
-            mHandler.postDelayed(() -> {
-                handleHideDialog(false /* userCanceled */);
-            }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
+            handleBiometricHelp(mContext.getResources()
+                    .getString(com.android.internal.R.string.biometric_not_recognized),
+                    true /* requireTryAgain */);
+            mCurrentDialog.showTryAgainButton(true /* show */);
         }
     }
 
-    private void handleBiometricHelp(String message) {
+    private void handleBiometricHelp(String message, boolean requireTryAgain) {
         if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message);
-        mCurrentDialog.showHelpMessage(message);
+        mCurrentDialog.showHelpMessage(message, requireTryAgain);
     }
 
     private void handleBiometricError(String error) {
@@ -258,7 +255,6 @@
             if (DEBUG) Log.d(TAG, "Dialog already dismissed");
             return;
         }
-        mTryAgainShowing = false;
         mCurrentDialog.showErrorMessage(error);
     }
 
@@ -279,8 +275,6 @@
         }
         mReceiver = null;
         mDialogShowing = false;
-        mConfirmShowing = false;
-        mTryAgainShowing = false;
         mCurrentDialog.startDismiss();
     }
 
@@ -294,7 +288,6 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Remote exception when handling negative button", e);
         }
-        mTryAgainShowing = false;
         handleHideDialog(false /* userCanceled */);
     }
 
@@ -308,25 +301,16 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Remote exception when handling positive button", e);
         }
-        mConfirmShowing = false;
         handleHideDialog(false /* userCanceled */);
     }
 
     private void handleUserCanceled() {
-        mTryAgainShowing = false;
-        mConfirmShowing = false;
         handleHideDialog(true /* userCanceled */);
     }
 
-    private void handleShowTryAgain() {
-        mCurrentDialog.showTryAgainButton(true /* show */);
-        mTryAgainShowing = true;
-    }
-
     private void handleTryAgainPressed() {
         try {
             mCurrentDialog.clearTemporaryMessage();
-            mTryAgainShowing = false;
             mReceiver.onTryAgainPressed();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when handling try again", e);
@@ -337,13 +321,20 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         final boolean wasShowing = mDialogShowing;
+
+        // Save the state of the current dialog (buttons showing, etc)
+        final Bundle savedState = new Bundle();
+        if (mCurrentDialog != null) {
+            mCurrentDialog.onSaveState(savedState);
+        }
+
         if (mDialogShowing) {
             mCurrentDialog.forceRemove();
             mDialogShowing = false;
         }
-        createDialogs();
+
         if (wasShowing) {
-            handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */);
+            handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index 9934bfd..b8c69c80 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -56,12 +56,15 @@
 
     private static final String TAG = "BiometricDialogView";
 
+    private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
+    private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
+
     private static final int ANIMATION_DURATION_SHOW = 250; // ms
     private static final int ANIMATION_DURATION_AWAY = 350; // ms
 
     private static final int MSG_CLEAR_MESSAGE = 1;
 
-    protected static final int STATE_NONE = 0;
+    protected static final int STATE_IDLE = 0;
     protected static final int STATE_AUTHENTICATING = 1;
     protected static final int STATE_ERROR = 2;
     protected static final int STATE_PENDING_CONFIRMATION = 3;
@@ -78,12 +81,19 @@
     private final float mDialogWidth;
     private final DialogViewCallback mCallback;
 
-    private ViewGroup mLayout;
-    private final Button mPositiveButton;
-    private final Button mNegativeButton;
-    private final TextView mErrorText;
+    protected final ViewGroup mLayout;
+    protected final LinearLayout mDialog;
+    protected final TextView mTitleText;
+    protected final TextView mSubtitleText;
+    protected final TextView mDescriptionText;
+    protected final ImageView mBiometricIcon;
+    protected final TextView mErrorText;
+    protected final Button mPositiveButton;
+    protected final Button mNegativeButton;
+    protected final Button mTryAgainButton;
+
     private Bundle mBundle;
-    private final LinearLayout mDialog;
+
     private int mLastState;
     private boolean mAnimatingAway;
     private boolean mWasForceRemoved;
@@ -91,15 +101,13 @@
     protected boolean mRequireConfirmation;
     private int mUserId; // used to determine if we should show work background
 
-    private boolean mPendingShowTryAgain;
-    private boolean mPendingShowConfirm;
-
     protected abstract int getHintStringResourceId();
     protected abstract int getAuthenticatedAccessibilityResourceId();
     protected abstract int getIconDescriptionResourceId();
     protected abstract Drawable getAnimationForTransition(int oldState, int newState);
     protected abstract boolean shouldAnimateForTransition(int oldState, int newState);
     protected abstract int getDelayAfterAuthenticatedDurationMs();
+    protected abstract boolean shouldGrayAreaDismissDialog();
 
     private final Runnable mShowAnimationRunnable = new Runnable() {
         @Override
@@ -124,7 +132,7 @@
         public void handleMessage(Message msg) {
             switch(msg.what) {
                 case MSG_CLEAR_MESSAGE:
-                    handleClearMessage();
+                    handleClearMessage((boolean) msg.obj /* requireTryAgain */);
                     break;
                 default:
                     Log.e(TAG, "Unhandled message: " + msg.what);
@@ -158,10 +166,6 @@
         mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false);
         addView(mLayout);
 
-        mDialog = mLayout.findViewById(R.id.dialog);
-
-        mErrorText = mLayout.findViewById(R.id.error);
-
         mLayout.setOnKeyListener(new View.OnKeyListener() {
             boolean downPressed = false;
             @Override
@@ -184,12 +188,19 @@
         final View space = mLayout.findViewById(R.id.space);
         final View leftSpace = mLayout.findViewById(R.id.left_space);
         final View rightSpace = mLayout.findViewById(R.id.right_space);
-        final ImageView icon = mLayout.findViewById(R.id.biometric_icon);
-        final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
+
+        mDialog = mLayout.findViewById(R.id.dialog);
+        mTitleText = mLayout.findViewById(R.id.title);
+        mSubtitleText = mLayout.findViewById(R.id.subtitle);
+        mDescriptionText = mLayout.findViewById(R.id.description);
+        mBiometricIcon = mLayout.findViewById(R.id.biometric_icon);
+        mErrorText = mLayout.findViewById(R.id.error);
         mNegativeButton = mLayout.findViewById(R.id.button2);
         mPositiveButton = mLayout.findViewById(R.id.button1);
+        mTryAgainButton = mLayout.findViewById(R.id.button_try_again);
 
-        icon.setContentDescription(getResources().getString(getIconDescriptionResourceId()));
+        mBiometricIcon.setContentDescription(
+                getResources().getString(getIconDescriptionResourceId()));
 
         setDismissesDialog(space);
         setDismissesDialog(leftSpace);
@@ -206,8 +217,9 @@
             }, getDelayAfterAuthenticatedDurationMs());
         });
 
-        tryAgain.setOnClickListener((View v) -> {
+        mTryAgainButton.setOnClickListener((View v) -> {
             showTryAgainButton(false /* show */);
+            handleClearMessage(false /* requireTryAgain */);
             mCallback.onTryAgainPressed();
         });
 
@@ -215,15 +227,17 @@
         mLayout.requestFocus();
     }
 
+    public void onSaveState(Bundle bundle) {
+        bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility());
+        bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility());
+    }
+
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
 
         mErrorText.setText(getHintStringResourceId());
 
-        final TextView title = mLayout.findViewById(R.id.title);
-        final TextView subtitle = mLayout.findViewById(R.id.subtitle);
-        final TextView description = mLayout.findViewById(R.id.description);
         final ImageView backgroundView = mLayout.findViewById(R.id.background);
 
         if (mUserManager.isManagedProfile(mUserId)) {
@@ -244,36 +258,34 @@
             mDialog.getLayoutParams().width = (int) mDialogWidth;
         }
 
-        mLastState = STATE_NONE;
+        mLastState = STATE_IDLE;
         updateState(STATE_AUTHENTICATING);
 
         CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE);
 
-        title.setText(titleText);
-        title.setSelected(true);
+        mTitleText.setVisibility(View.VISIBLE);
+        mTitleText.setText(titleText);
+        mTitleText.setSelected(true);
 
         final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
         if (TextUtils.isEmpty(subtitleText)) {
-            subtitle.setVisibility(View.GONE);
+            mSubtitleText.setVisibility(View.GONE);
         } else {
-            subtitle.setVisibility(View.VISIBLE);
-            subtitle.setText(subtitleText);
+            mSubtitleText.setVisibility(View.VISIBLE);
+            mSubtitleText.setText(subtitleText);
         }
 
         final CharSequence descriptionText =
                 mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
         if (TextUtils.isEmpty(descriptionText)) {
-            description.setVisibility(View.GONE);
+            mDescriptionText.setVisibility(View.GONE);
         } else {
-            description.setVisibility(View.VISIBLE);
-            description.setText(descriptionText);
+            mDescriptionText.setVisibility(View.VISIBLE);
+            mDescriptionText.setText(descriptionText);
         }
 
         mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
 
-        showTryAgainButton(mPendingShowTryAgain);
-        showConfirmationButton(mPendingShowConfirm);
-
         if (mWasForceRemoved || mSkipIntro) {
             // Show the dialog immediately
             mLayout.animate().cancel();
@@ -302,8 +314,7 @@
                 ? (AnimatedVectorDrawable) icon
                 : null;
 
-        final ImageView imageView = getLayout().findViewById(R.id.biometric_icon);
-        imageView.setImageDrawable(icon);
+        mBiometricIcon.setImageDrawable(icon);
 
         if (animation != null && shouldAnimateForTransition(lastState, newState)) {
             animation.forceAnimationOnUI();
@@ -314,7 +325,7 @@
     private void setDismissesDialog(View v) {
         v.setClickable(true);
         v.setOnTouchListener((View view, MotionEvent event) -> {
-            if (mLastState != STATE_AUTHENTICATED) {
+            if (mLastState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
                 mCallback.onUserCanceled();
             }
             return true;
@@ -331,11 +342,9 @@
                 mWindowManager.removeView(BiometricDialogView.this);
                 mAnimatingAway = false;
                 // Set the icons / text back to normal state
-                handleClearMessage();
+                handleClearMessage(false /* requireTryAgain */);
                 showTryAgainButton(false /* show */);
-                mPendingShowTryAgain = false;
-                mPendingShowConfirm = false;
-                updateState(STATE_NONE);
+                updateState(STATE_IDLE);
             }
         };
 
@@ -412,35 +421,42 @@
         return mLayout;
     }
 
-    // Clears the temporary message and shows the help message.
-    private void handleClearMessage() {
-        updateState(STATE_AUTHENTICATING);
-        mErrorText.setText(getHintStringResourceId());
-        mErrorText.setTextColor(mTextColor);
+    // Clears the temporary message and shows the help message. If requireTryAgain is true,
+    // we will start the authenticating state again.
+    private void handleClearMessage(boolean requireTryAgain) {
+        if (!requireTryAgain) {
+            updateState(STATE_AUTHENTICATING);
+            mErrorText.setText(getHintStringResourceId());
+            mErrorText.setTextColor(mTextColor);
+            mErrorText.setVisibility(View.VISIBLE);
+        } else {
+            updateState(STATE_IDLE);
+            mErrorText.setVisibility(View.INVISIBLE);
+        }
     }
 
     // Shows an error/help message
-    private void showTemporaryMessage(String message) {
+    private void showTemporaryMessage(String message, boolean requireTryAgain) {
         mHandler.removeMessages(MSG_CLEAR_MESSAGE);
         updateState(STATE_ERROR);
         mErrorText.setText(message);
         mErrorText.setTextColor(mErrorColor);
         mErrorText.setContentDescription(message);
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE),
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE, requireTryAgain),
                 BiometricPrompt.HIDE_DIALOG_DELAY);
     }
 
     public void clearTemporaryMessage() {
         mHandler.removeMessages(MSG_CLEAR_MESSAGE);
-        mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget();
+        mHandler.obtainMessage(MSG_CLEAR_MESSAGE, false /* requireTryAgain */).sendToTarget();
     }
 
-    public void showHelpMessage(String message) {
-        showTemporaryMessage(message);
+    public void showHelpMessage(String message, boolean requireTryAgain) {
+        showTemporaryMessage(message, requireTryAgain);
     }
 
     public void showErrorMessage(String error) {
-        showTemporaryMessage(error);
+        showTemporaryMessage(error, false /* requireTryAgain */);
         showTryAgainButton(false /* show */);
         mCallback.onErrorShown();
     }
@@ -459,22 +475,16 @@
     }
 
     public void showTryAgainButton(boolean show) {
-        final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
         if (show) {
-            tryAgain.setVisibility(View.VISIBLE);
+            mTryAgainButton.setVisibility(View.VISIBLE);
         } else {
-            tryAgain.setVisibility(View.GONE);
+            mTryAgainButton.setVisibility(View.GONE);
         }
     }
 
-    // Set the state before the window is attached, so we know if the dialog should be started
-    // with or without the button. This is because there's no good onPause signal
-    public void setPendingTryAgain(boolean show) {
-        mPendingShowTryAgain = show;
-    }
-
-    public void setPendingConfirm(boolean show) {
-        mPendingShowConfirm = show;
+    public void restoreState(Bundle bundle) {
+        mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY));
+        mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY));
     }
 
     public WindowManager.LayoutParams getLayoutParams() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
index de3f947..359cb04 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
@@ -16,8 +16,18 @@
 
 package com.android.systemui.biometrics;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.graphics.Outline;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewOutlineProvider;
 
 import com.android.systemui.R;
 
@@ -28,13 +38,243 @@
  */
 public class FaceDialogView extends BiometricDialogView {
 
+    private static final String TAG = "FaceDialogView";
+    private static final String KEY_DIALOG_SIZE = "key_dialog_size";
+
     private static final int HIDE_DIALOG_DELAY = 500; // ms
+    private static final int IMPLICIT_Y_PADDING = 16; // dp
+    private static final int GROW_DURATION = 150; // ms
+    private static final int TEXT_ANIMATE_DISTANCE = 32; // dp
+
+    private static final int SIZE_UNKNOWN = 0;
+    private static final int SIZE_SMALL = 1;
+    private static final int SIZE_GROWING = 2;
+    private static final int SIZE_BIG = 3;
+
+    private int mSize;
+    private float mIconOriginalY;
+    private DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider();
+
+    private final class DialogOutlineProvider extends ViewOutlineProvider {
+
+        float mY;
+
+        @Override
+        public void getOutline(View view, Outline outline) {
+            outline.setRoundRect(
+                    0 /* left */,
+                    (int) mY, /* top */
+                    mDialog.getWidth() /* right */,
+                    mDialog.getBottom(), /* bottom */
+                    getResources().getDimension(R.dimen.biometric_dialog_corner_size));
+        }
+
+        int calculateSmall() {
+            final float padding = dpToPixels(IMPLICIT_Y_PADDING);
+            return mDialog.getHeight() - mBiometricIcon.getHeight() - 2 * (int) padding;
+        }
+
+        void setOutlineY(float y) {
+            mY = y;
+        }
+    }
 
     public FaceDialogView(Context context,
             DialogViewCallback callback) {
         super(context, callback);
     }
 
+    private void updateSize(int newSize) {
+        final float padding = dpToPixels(IMPLICIT_Y_PADDING);
+        final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding;
+
+        if (newSize == SIZE_SMALL) {
+            // These fields are required and/or always hold a spot on the UI, so should be set to
+            // INVISIBLE so they keep their position
+            mTitleText.setVisibility(View.INVISIBLE);
+            mErrorText.setVisibility(View.INVISIBLE);
+            mNegativeButton.setVisibility(View.INVISIBLE);
+
+            // These fields are optional, so set them to gone or invisible depending on their
+            // usage. If they're empty, they're already set to GONE in BiometricDialogView.
+            if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+                mSubtitleText.setVisibility(View.INVISIBLE);
+            }
+            if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+                mDescriptionText.setVisibility(View.INVISIBLE);
+            }
+
+            // Move the biometric icon to the small spot
+            mBiometricIcon.setY(iconSmallPositionY);
+
+            // Clip the dialog to the small size
+            mDialog.setOutlineProvider(mOutlineProvider);
+            mOutlineProvider.setOutlineY(mOutlineProvider.calculateSmall());
+
+            mDialog.setClipToOutline(true);
+            mDialog.invalidateOutline();
+
+            mSize = newSize;
+        } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) {
+            mSize = SIZE_GROWING;
+
+            // Animate the outline
+            final ValueAnimator outlineAnimator =
+                    ValueAnimator.ofFloat(mOutlineProvider.calculateSmall(), 0);
+            outlineAnimator.addUpdateListener((animation) -> {
+                final float y = (float) animation.getAnimatedValue();
+                mOutlineProvider.setOutlineY(y);
+                mDialog.invalidateOutline();
+            });
+
+            // Animate the icon back to original big position
+            final ValueAnimator iconAnimator =
+                    ValueAnimator.ofFloat(iconSmallPositionY, mIconOriginalY);
+            iconAnimator.addUpdateListener((animation) -> {
+                final float y = (float) animation.getAnimatedValue();
+                mBiometricIcon.setY(y);
+            });
+
+            // Animate the error text so it slides up with the icon
+            final ValueAnimator textSlideAnimator =
+                    ValueAnimator.ofFloat(dpToPixels(TEXT_ANIMATE_DISTANCE), 0);
+            textSlideAnimator.addUpdateListener((animation) -> {
+                final float y = (float) animation.getAnimatedValue();
+                mErrorText.setTranslationY(y);
+            });
+
+            // Opacity animator for things that should fade in (title, subtitle, details, negative
+            // button)
+            final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
+            opacityAnimator.addUpdateListener((animation) -> {
+                final float opacity = (float) animation.getAnimatedValue();
+
+                // These fields are required and/or always hold a spot on the UI
+                mTitleText.setAlpha(opacity);
+                mErrorText.setAlpha(opacity);
+                mNegativeButton.setAlpha(opacity);
+                mTryAgainButton.setAlpha(opacity);
+
+                // These fields are optional, so only animate them if they're supposed to be showing
+                if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+                    mSubtitleText.setAlpha(opacity);
+                }
+                if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+                    mDescriptionText.setAlpha(opacity);
+                }
+            });
+
+            // Choreograph together
+            final AnimatorSet as = new AnimatorSet();
+            as.setDuration(GROW_DURATION);
+            as.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    super.onAnimationStart(animation);
+                    // Set the visibility of opacity-animating views back to VISIBLE
+                    mTitleText.setVisibility(View.VISIBLE);
+                    mErrorText.setVisibility(View.VISIBLE);
+                    mNegativeButton.setVisibility(View.VISIBLE);
+                    mTryAgainButton.setVisibility(View.VISIBLE);
+
+                    if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+                        mSubtitleText.setVisibility(View.VISIBLE);
+                    }
+                    if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+                        mDescriptionText.setVisibility(View.VISIBLE);
+                    }
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    super.onAnimationEnd(animation);
+                    mSize = SIZE_BIG;
+                }
+            });
+            as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator)
+                    .with(textSlideAnimator);
+            as.start();
+        } else if (mSize == SIZE_BIG) {
+            mDialog.setClipToOutline(false);
+            mDialog.invalidateOutline();
+
+            mBiometricIcon.setY(mIconOriginalY);
+
+            mSize = newSize;
+        }
+    }
+
+    @Override
+    public void onSaveState(Bundle bundle) {
+        super.onSaveState(bundle);
+        bundle.putInt(KEY_DIALOG_SIZE, mSize);
+    }
+
+    @Override
+    public void restoreState(Bundle bundle) {
+        super.restoreState(bundle);
+        // Keep in mind that this happens before onAttachedToWindow()
+        mSize = bundle.getInt(KEY_DIALOG_SIZE);
+    }
+
+    /**
+     * Do small/big layout here instead of onAttachedToWindow, since:
+     * 1) We need the big layout to be measured, etc for small -> big animation
+     * 2) We need the dialog measurements to know where to move the biometric icon to
+     *
+     * BiometricDialogView already sets the views to their default big state, so here we only
+     * need to hide the ones that are unnecessary.
+     */
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        if (mIconOriginalY == 0) {
+            mIconOriginalY = mBiometricIcon.getY();
+        }
+
+        // UNKNOWN means size hasn't been set yet. First time we create the dialog.
+        // onLayout can happen when visibility of views change (during animation, etc).
+        if (mSize != SIZE_UNKNOWN) {
+            // Probably not the cleanest way to do this, but since dialog is big by default,
+            // and small dialogs can persist across orientation changes, we need to set it to
+            // small size here again.
+            if (mSize == SIZE_SMALL) {
+                updateSize(SIZE_SMALL);
+            }
+            return;
+        }
+
+        // If we don't require confirmation, show the small dialog first (until errors occur).
+        if (!requiresConfirmation()) {
+            updateSize(SIZE_SMALL);
+        } else {
+            updateSize(SIZE_BIG);
+        }
+    }
+
+    @Override
+    public void showErrorMessage(String error) {
+        super.showErrorMessage(error);
+
+        // All error messages will cause the dialog to go from small -> big. Error messages
+        // are messages such as lockout, auth failed, etc.
+        if (mSize == SIZE_SMALL) {
+            updateSize(SIZE_BIG);
+        }
+    }
+
+    @Override
+    public void showTryAgainButton(boolean show) {
+        if (show && mSize == SIZE_SMALL) {
+            // Do not call super, we will nicely animate the alpha together with the rest
+            // of the elements in here.
+            updateSize(SIZE_BIG);
+        } else {
+            super.showTryAgainButton(show);
+        }
+    }
+
     @Override
     protected int getHintStringResourceId() {
         return R.string.face_dialog_looking_for_face;
@@ -56,7 +296,9 @@
 
     @Override
     protected boolean shouldAnimateForTransition(int oldState, int newState) {
-        if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+        if (oldState == STATE_ERROR && newState == STATE_IDLE) {
+            return true;
+        } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
             return false;
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
             return true;
@@ -78,9 +320,19 @@
     }
 
     @Override
+    protected boolean shouldGrayAreaDismissDialog() {
+        if (mSize == SIZE_SMALL) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
     protected Drawable getAnimationForTransition(int oldState, int newState) {
         int iconRes;
-        if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+        if (oldState == STATE_ERROR && newState == STATE_IDLE) {
+            iconRes = R.drawable.face_dialog_error_to_face;
+        } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
             iconRes = R.drawable.face_dialog_face_to_error;
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
             iconRes = R.drawable.face_dialog_face_to_error;
@@ -97,4 +349,14 @@
         }
         return mContext.getDrawable(iconRes);
     }
+
+    private float dpToPixels(float dp) {
+        return dp * ((float) mContext.getResources().getDisplayMetrics().densityDpi
+                / DisplayMetrics.DENSITY_DEFAULT);
+    }
+
+    private float pixelsToDp(float pixels) {
+        return pixels / ((float) mContext.getResources().getDisplayMetrics().densityDpi
+                / DisplayMetrics.DENSITY_DEFAULT);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
index 1a6cee2..d63836b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
@@ -49,7 +49,7 @@
 
     @Override
     protected boolean shouldAnimateForTransition(int oldState, int newState) {
-        if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+        if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
             return false;
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
             return true;
@@ -68,9 +68,15 @@
     }
 
     @Override
+    protected boolean shouldGrayAreaDismissDialog() {
+        // Fingerprint dialog always dismisses when region outside the dialog is tapped
+        return true;
+    }
+
+    @Override
     protected Drawable getAnimationForTransition(int oldState, int newState) {
         int iconRes;
-        if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+        if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
             iconRes = R.drawable.fingerprint_dialog_fp_to_error;
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
             iconRes = R.drawable.fingerprint_dialog_fp_to_error;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7577609..9f3ff78 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -23,15 +23,20 @@
 import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
 
 import android.app.Notification;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
+import android.util.Log;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -68,6 +73,8 @@
     private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
     private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
     private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
+    private static final String ENABLE_BUBBLE_ACTIVITY_VIEW = "experiment_bubble_activity_view";
+    private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
 
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
@@ -189,6 +196,9 @@
             // It's new
             BubbleView bubble = new BubbleView(mContext);
             bubble.setNotif(notif);
+            if (shouldUseActivityView(mContext)) {
+                bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+            }
             mBubbles.put(bubble.getKey(), bubble);
 
             boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
@@ -216,6 +226,21 @@
         }
     }
 
+    @Nullable
+    private PendingIntent getAppOverlayIntent(NotificationEntry notif) {
+        Notification notification = notif.notification.getNotification();
+        if (canLaunchInActivityView(notification.getAppOverlayIntent())) {
+            return notification.getAppOverlayIntent();
+        } else if (shouldUseContentIntent(mContext)
+                && canLaunchInActivityView(notification.contentIntent)) {
+            Log.d(TAG, "[addBubble " + notif.key
+                    + "]: No appOverlayIntent, using contentIntent.");
+            return notification.contentIntent;
+        }
+        Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView.");
+        return null;
+    }
+
     /**
      * Removes the bubble associated with the {@param uri}.
      */
@@ -223,6 +248,7 @@
         BubbleView bv = mBubbles.get(key);
         if (mStackView != null && bv != null) {
             mStackView.removeBubble(bv);
+            bv.destroyActivityView(mStackView);
             bv.getEntry().setBubbleDismissed(true);
         }
 
@@ -282,9 +308,10 @@
                 }
             }
         }
-        for (BubbleView view : viewsToRemove) {
-            mBubbles.remove(view.getKey());
-            mStackView.removeBubble(view);
+        for (BubbleView bubbleView : viewsToRemove) {
+            mBubbles.remove(bubbleView.getKey());
+            mStackView.removeBubble(bubbleView);
+            bubbleView.destroyActivityView(mStackView);
         }
         if (mStackView != null) {
             mStackView.setVisibility(visible ? VISIBLE : INVISIBLE);
@@ -306,6 +333,17 @@
         return mTempRect;
     }
 
+    private boolean canLaunchInActivityView(PendingIntent intent) {
+        if (intent == null) {
+            return false;
+        }
+        ActivityInfo info =
+                intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
+        return info != null
+                && ActivityInfo.isResizeableMode(info.resizeMode)
+                && (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
+    }
+
     @VisibleForTesting
     BubbleStackView getStackView() {
         return mStackView;
@@ -378,4 +416,14 @@
         return Settings.Secure.getInt(context.getContentResolver(),
                 ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
     }
+
+    private static boolean shouldUseActivityView(Context context) {
+        return Settings.Secure.getInt(context.getContentResolver(),
+                ENABLE_BUBBLE_ACTIVITY_VIEW, 0) != 0;
+    }
+
+    private static boolean shouldUseContentIntent(Context context) {
+        return Settings.Secure.getInt(context.getContentResolver(),
+                ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
index e28d96b..badefe1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
@@ -24,6 +24,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
@@ -35,6 +36,8 @@
 
     // The triangle pointing to the expanded view
     private View mPointerView;
+    // The view displayed between the pointer and the expanded view
+    private TextView mHeaderView;
     // The view that is being displayed for the expanded state
     private View mExpandedView;
 
@@ -68,6 +71,7 @@
                 TriangleShape.create(width, height, true /* pointUp */));
         triangleDrawable.setTint(Color.WHITE); // TODO: dark mode
         mPointerView.setBackground(triangleDrawable);
+        mHeaderView = findViewById(R.id.bubble_content_header);
     }
 
     /**
@@ -80,9 +84,19 @@
     }
 
     /**
+     * Set the text displayed within the header.
+     */
+    public void setHeaderText(CharSequence text) {
+        mHeaderView.setText(text);
+    }
+
+    /**
      * Set the view to display for the expanded state. Passing null will clear the view.
      */
     public void setExpandedView(View view) {
+        if (mExpandedView == view) {
+            return;
+        }
         if (mExpandedView != null) {
             removeView(mExpandedView);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index d69e7b3..3280a33 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -21,10 +21,13 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.app.ActivityView;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.RectF;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -50,6 +53,7 @@
  */
 public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView {
 
+    private static final String TAG = "BubbleStackView";
     private Point mDisplaySize;
 
     private FrameLayout mBubbleContainer;
@@ -59,6 +63,7 @@
     private int mBubblePadding;
 
     private boolean mIsExpanded;
+    private int mExpandedBubbleHeight;
     private BubbleView mExpandedBubble;
     private Point mCollapsedPosition;
     private BubbleTouchHandler mTouchHandler;
@@ -106,6 +111,7 @@
         mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
         mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
 
+        mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
         mDisplaySize = new Point();
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         wm.getDefaultDisplay().getSize(mDisplaySize);
@@ -389,27 +395,63 @@
     }
 
     private void updateExpandedBubble() {
-        if (mExpandedBubble != null) {
+        if (mExpandedBubble == null) {
+            return;
+        }
+
+        if (mExpandedBubble.hasAppOverlayIntent()) {
+            ActivityView expandedView = mExpandedBubble.getActivityView();
+            expandedView.setLayoutParams(new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
+
+            final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
+            mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString());
+            mExpandedViewContainer.setExpandedView(expandedView);
+            expandedView.setCallback(new ActivityView.StateCallback() {
+                @Override
+                public void onActivityViewReady(ActivityView view) {
+                    Log.d(TAG, "onActivityViewReady("
+                            + mExpandedBubble.getEntry().key + "): " + view);
+                    view.startActivity(intent);
+                }
+
+                @Override
+                public void onActivityViewDestroyed(ActivityView view) {
+                    NotificationEntry entry = mExpandedBubble.getEntry();
+                    Log.d(TAG, "onActivityViewDestroyed(key="
+                            + ((entry != null) ? entry.key : "(none)") + "): " + view);
+                }
+            });
+        } else {
             ExpandableNotificationRow row = mExpandedBubble.getRowView();
-            if (!row.equals(mExpandedViewContainer.getChildAt(0))) {
+            if (!row.equals(mExpandedViewContainer.getExpandedView())) {
                 // Different expanded view than what we have
                 mExpandedViewContainer.setExpandedView(null);
             }
-            int pointerPosition = mExpandedBubble.getPosition().x
-                    + (mExpandedBubble.getWidth() / 2);
-            mExpandedViewContainer.setPointerPosition(pointerPosition);
             mExpandedViewContainer.setExpandedView(row);
+            mExpandedViewContainer.setHeaderText(null);
         }
+        int pointerPosition = mExpandedBubble.getPosition().x
+                + (mExpandedBubble.getWidth() / 2);
+        mExpandedViewContainer.setPointerPosition(pointerPosition);
     }
 
     private void applyCurrentState() {
+        Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+
         mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
         if (!mIsExpanded) {
             mExpandedViewContainer.setExpandedView(null);
         } else {
             mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
-            ExpandableNotificationRow row = mExpandedBubble.getRowView();
-            applyRowState(row);
+            View expandedView = mExpandedViewContainer.getExpandedView();
+            if (expandedView instanceof ActivityView) {
+                if (expandedView.isAttachedToWindow()) {
+                    ((ActivityView) expandedView).onLocationChanged();
+                }
+            } else {
+                applyRowState(mExpandedBubble.getRowView());
+            }
         }
         int bubbsCount = mBubbleContainer.getChildCount();
         for (int i = 0; i < bubbsCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 88030ee..96b2dba 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -33,7 +33,7 @@
  * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
  * dismissing, and flings.
  */
-public class BubbleTouchHandler implements View.OnTouchListener {
+class BubbleTouchHandler implements View.OnTouchListener {
 
     private BubbleController mController = Dependency.get(BubbleController.class);
     private PipDismissViewController mDismissViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 3307992..c1bbb93 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.bubbles;
 
+import android.app.ActivityView;
 import android.app.Notification;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Point;
@@ -25,7 +27,9 @@
 import android.graphics.drawable.ShapeDrawable;
 import android.graphics.drawable.shapes.OvalShape;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
@@ -37,7 +41,7 @@
 /**
  * A floating object on the screen that has a collapsed and expanded state.
  */
-public class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
+class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
     private static final String TAG = "BubbleView";
 
     private Context mContext;
@@ -46,6 +50,8 @@
     private NotificationEntry mEntry;
     private int mBubbleSize;
     private int mIconSize;
+    private PendingIntent mAppOverlayIntent;
+    private ActivityView mActivityView;
 
     public BubbleView(Context context) {
         this(context, null);
@@ -117,12 +123,51 @@
     }
 
     /**
-     * @return the view to display when the bubble is expanded.
+     * @return the view to display notification content when the bubble is expanded.
      */
     public ExpandableNotificationRow getRowView() {
         return mEntry.getRow();
     }
 
+    /**
+     * @return a view used to display app overlay content when expanded.
+     */
+    public ActivityView getActivityView() {
+        if (mActivityView == null) {
+            mActivityView = new ActivityView(mContext);
+            Log.d(TAG, "[getActivityView] created: " + mActivityView);
+        }
+        return mActivityView;
+    }
+
+    /**
+     * Removes and releases an ActivityView if one was previously created for this bubble.
+     */
+    public void destroyActivityView(ViewGroup tmpParent) {
+        if (mActivityView == null) {
+            return;
+        }
+        // HACK: Only release if initialized. There's no way to know if the ActivityView has
+        // been initialized. Calling release() if it hasn't been initialized will crash.
+
+        if (!mActivityView.isAttachedToWindow()) {
+            // HACK: release() will crash if the view is not attached.
+
+            mActivityView.setVisibility(View.GONE);
+            tmpParent.addView(mActivityView, new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT));
+        }
+        try {
+            mActivityView.release();
+        } catch (IllegalStateException ex) {
+            Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex);
+        }
+
+        ((ViewGroup) mActivityView.getParent()).removeView(mActivityView);
+        mActivityView = null;
+    }
+
     @Override
     public void setPosition(int x, int y) {
         setTranslationX(x);
@@ -162,4 +207,20 @@
         lp.height = mBubbleSize;
         v.setLayoutParams(lp);
     }
+
+    /**
+     * @return whether an ActivityView should be used to display the content of this Bubble
+     */
+    public boolean hasAppOverlayIntent() {
+        return mAppOverlayIntent != null;
+    }
+
+    public PendingIntent getAppOverlayIntent() {
+        return mAppOverlayIntent;
+
+    }
+
+    public void setAppOverlayIntent(PendingIntent intent) {
+        mAppOverlayIntent = intent;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 323cf1f..f14495b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1843,6 +1843,13 @@
      */
     private void handleHide() {
         Trace.beginSection("KeyguardViewMediator#handleHide");
+
+        // It's possible that the device was unlocked in a dream state. It's time to wake up.
+        if (mAodShowing) {
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:BOUNCER_DOZING");
+        }
+
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleHide");
 
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 01ee5ca..4388200 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -52,7 +52,8 @@
 
                         @Suppress("DEPRECATION")
                         override fun onClick(dialog: DialogInterface?, which: Int) {
-                            Dependency.get(ActivityStarter::class.java).startActivity(intent, false)
+                            Dependency.get(ActivityStarter::class.java)
+                                    .postStartActivityDismissingKeyguard(intent, 0)
                         }
                     })
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index e030e40..e63f88a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -284,10 +284,17 @@
     public void updateEverything() {
         post(() -> {
             updateVisibilities();
+            updateClickabilities();
             setClickable(false);
         });
     }
 
+    private void updateClickabilities() {
+        mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
+        mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
+        mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
+    }
+
     private void updateVisibilities() {
         mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 40c6039..28285e14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -699,7 +699,7 @@
     }
 
     public void updateEverything() {
-        post(() -> setClickable(false));
+        post(() -> setClickable(!mExpanded));
     }
 
     public void setQSPanel(final QSPanel qsPanel) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 81757d0..c474faf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -101,6 +101,7 @@
     private float mBackButtonAlpha;
     private MotionEvent mStatusBarGestureDownEvent;
     private float mWindowCornerRadius;
+    private boolean mSupportsRoundedCornersOnWindows;
 
     private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
 
@@ -244,6 +245,18 @@
             }
         }
 
+        public boolean supportsRoundedCornersOnWindows() {
+            if (!verifyCaller("supportsRoundedCornersOnWindows")) {
+                return false;
+            }
+            long token = Binder.clearCallingIdentity();
+            try {
+                return mSupportsRoundedCornersOnWindows;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -353,6 +366,8 @@
         mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS,
                 getDefaultInteractionFlags());
         mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources());
+        mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
+                .supportsRoundedCornersOnWindows(mContext.getResources());
 
         // Listen for the package update changes.
         if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6a01563..904478e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -111,7 +111,6 @@
     private static final int MSG_SHOW_CHARGING_ANIMATION       = 44 << MSG_SHIFT;
     private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
     private static final int MSG_SHOW_PINNING_TOAST_ESCAPE     = 46 << MSG_SHIFT;
-    private static final int MSG_BIOMETRIC_TRY_AGAIN           = 47 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -271,11 +270,10 @@
 
         default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
                 int type, boolean requireConfirmation, int userId) { }
-        default void onBiometricAuthenticated() { }
+        default void onBiometricAuthenticated(boolean authenticated) { }
         default void onBiometricHelp(String message) { }
         default void onBiometricError(String error) { }
         default void hideBiometricDialog() { }
-        default void showBiometricTryAgain() { }
     }
 
     @VisibleForTesting
@@ -736,9 +734,9 @@
     }
 
     @Override
-    public void onBiometricAuthenticated() {
+    public void onBiometricAuthenticated(boolean authenticated) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+            mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
         }
     }
 
@@ -763,13 +761,6 @@
         }
     }
 
-    @Override
-    public void showBiometricTryAgain() {
-        synchronized (mLock) {
-            mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget();
-        }
-    }
-
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -991,7 +982,7 @@
                     break;
                 case MSG_BIOMETRIC_AUTHENTICATED:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).onBiometricAuthenticated();
+                        mCallbacks.get(i).onBiometricAuthenticated((boolean) msg.obj);
                     }
                     break;
                 case MSG_BIOMETRIC_HELP:
@@ -1024,11 +1015,6 @@
                         mCallbacks.get(i).showPinningEscapeToast();
                     }
                     break;
-                case MSG_BIOMETRIC_TRY_AGAIN:
-                    for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).showBiometricTryAgain();
-                    }
-                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
deleted file mode 100644
index 6d2c001..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * Copyright (C) 2011 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.systemui.statusbar;
-
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_NONE;
-
-import android.annotation.DrawableRes;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.telephony.SubscriptionInfo;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.policy.IconLogger;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.Utils.DisableStateTracker;
-
-import java.util.ArrayList;
-import java.util.List;
-
-// Intimately tied to the design of res/layout/signal_cluster_view.xml
-public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
-        SecurityController.SecurityControllerCallback, Tunable, DarkReceiver {
-
-    static final String TAG = "SignalClusterView";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final String SLOT_AIRPLANE = "airplane";
-    private static final String SLOT_MOBILE = "mobile";
-    private static final String SLOT_WIFI = "wifi";
-    private static final String SLOT_ETHERNET = "ethernet";
-    private static final String SLOT_VPN = "vpn";
-
-    private final NetworkController mNetworkController;
-    private final SecurityController mSecurityController;
-
-    private boolean mVpnVisible = false;
-    private int mVpnIconId = 0;
-    private int mLastVpnIconId = -1;
-    private boolean mEthernetVisible = false;
-    private int mEthernetIconId = 0;
-    private int mLastEthernetIconId = -1;
-    private boolean mWifiVisible = false;
-    private int mWifiStrengthId = 0;
-    private int mLastWifiStrengthId = -1;
-    private boolean mWifiIn;
-    private boolean mWifiOut;
-    private boolean mIsAirplaneMode = false;
-    private int mAirplaneIconId = 0;
-    private int mLastAirplaneIconId = -1;
-    private String mAirplaneContentDescription;
-    private String mWifiDescription;
-    private String mEthernetDescription;
-    private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
-    private int mIconTint = Color.WHITE;
-    private float mDarkIntensity;
-    private final Rect mTintArea = new Rect();
-
-    ViewGroup mEthernetGroup, mWifiGroup;
-    ImageView mVpn, mEthernet, mWifi, mAirplane, mEthernetDark, mWifiDark;
-    ImageView mWifiActivityIn;
-    ImageView mWifiActivityOut;
-    View mWifiAirplaneSpacer;
-    View mWifiSignalSpacer;
-    LinearLayout mMobileSignalGroup;
-
-    private final int mMobileSignalGroupEndPadding;
-    private final int mMobileDataIconStartPadding;
-    private final int mSecondaryTelephonyPadding;
-    private final int mEndPadding;
-    private final int mEndPaddingNothingVisible;
-    private final float mIconScaleFactor;
-
-    private boolean mBlockAirplane;
-    private boolean mBlockMobile;
-    private boolean mBlockWifi;
-    private boolean mBlockEthernet;
-    private boolean mActivityEnabled;
-    private boolean mForceBlockWifi;
-
-    private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
-
-    public SignalClusterView(Context context) {
-        this(context, null);
-    }
-
-    public SignalClusterView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public SignalClusterView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        Resources res = getResources();
-        mMobileSignalGroupEndPadding =
-                res.getDimensionPixelSize(R.dimen.mobile_signal_group_end_padding);
-        mMobileDataIconStartPadding =
-                res.getDimensionPixelSize(R.dimen.mobile_data_icon_start_padding);
-        mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding);
-        mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding);
-        mEndPaddingNothingVisible = res.getDimensionPixelSize(
-                R.dimen.no_signal_cluster_battery_padding);
-
-        TypedValue typedValue = new TypedValue();
-        res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
-        mIconScaleFactor = typedValue.getFloat();
-        mNetworkController = Dependency.get(NetworkController.class);
-        mSecurityController = Dependency.get(SecurityController.class);
-        addOnAttachStateChangeListener(
-                new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
-        updateActivityEnabled();
-    }
-
-    public void setForceBlockWifi() {
-        mForceBlockWifi = true;
-        mBlockWifi = true;
-        if (isAttachedToWindow()) {
-            // Re-register to get new callbacks.
-            mNetworkController.removeCallback(this);
-            mNetworkController.addCallback(this);
-        }
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
-            return;
-        }
-        ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue);
-        boolean blockAirplane = blockList.contains(SLOT_AIRPLANE);
-        boolean blockMobile = blockList.contains(SLOT_MOBILE);
-        boolean blockWifi = blockList.contains(SLOT_WIFI);
-        boolean blockEthernet = blockList.contains(SLOT_ETHERNET);
-
-        if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile
-                || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) {
-            mBlockAirplane = blockAirplane;
-            mBlockMobile = blockMobile;
-            mBlockEthernet = blockEthernet;
-            mBlockWifi = blockWifi || mForceBlockWifi;
-            // Re-register to get new callbacks.
-            mNetworkController.removeCallback(this);
-            mNetworkController.addCallback(this);
-        }
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mVpn            = findViewById(R.id.vpn);
-        mEthernetGroup  = findViewById(R.id.ethernet_combo);
-        mEthernet       = findViewById(R.id.ethernet);
-        mEthernetDark   = findViewById(R.id.ethernet_dark);
-        mWifiGroup      = findViewById(R.id.wifi_combo);
-        mWifi           = findViewById(R.id.wifi_signal);
-        mWifiDark       = findViewById(R.id.wifi_signal_dark);
-        mWifiActivityIn = findViewById(R.id.wifi_in);
-        mWifiActivityOut= findViewById(R.id.wifi_out);
-        mAirplane       = findViewById(R.id.airplane);
-        mWifiAirplaneSpacer =         findViewById(R.id.wifi_airplane_spacer);
-        mWifiSignalSpacer =           findViewById(R.id.wifi_signal_spacer);
-        mMobileSignalGroup =          findViewById(R.id.mobile_signal_group);
-
-        maybeScaleVpnAndNoSimsIcons();
-    }
-
-    /**
-     * Extracts the icon off of the VPN and no sims views and maybe scale them by
-     * {@link #mIconScaleFactor}. Note that the other icons are not scaled here because they are
-     * dynamic. As such, they need to be scaled each time the icon changes in {@link #apply()}.
-     */
-    private void maybeScaleVpnAndNoSimsIcons() {
-        if (mIconScaleFactor == 1.f) {
-            return;
-        }
-
-        mVpn.setImageDrawable(new ScalingDrawableWrapper(mVpn.getDrawable(), mIconScaleFactor));
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mVpnVisible = mSecurityController.isVpnEnabled();
-        mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
-
-        for (PhoneState state : mPhoneStates) {
-            if (state.mMobileGroup.getParent() == null) {
-                mMobileSignalGroup.addView(state.mMobileGroup);
-            }
-        }
-
-        int endPadding = mMobileSignalGroup.getChildCount() > 0 ? mMobileSignalGroupEndPadding : 0;
-        mMobileSignalGroup.setPaddingRelative(0, 0, endPadding, 0);
-
-        Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
-
-        apply();
-        applyIconTint();
-        mNetworkController.addCallback(this);
-        mSecurityController.addCallback(this);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        mMobileSignalGroup.removeAllViews();
-        Dependency.get(TunerService.class).removeTunable(this);
-        mSecurityController.removeCallback(this);
-        mNetworkController.removeCallback(this);
-
-        super.onDetachedFromWindow();
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-
-        // Re-run all checks against the tint area for all icons
-        applyIconTint();
-    }
-
-    // From SecurityController.
-    @Override
-    public void onStateChanged() {
-        post(new Runnable() {
-            @Override
-            public void run() {
-                mVpnVisible = mSecurityController.isVpnEnabled();
-                mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
-                apply();
-            }
-        });
-    }
-
-    private void updateActivityEnabled() {
-        mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
-    }
-
-    @Override
-    public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
-            boolean activityIn, boolean activityOut, String description, boolean isTransient,
-            String secondaryLabel) {
-        mWifiVisible = statusIcon.visible && !mBlockWifi;
-        mWifiStrengthId = statusIcon.icon;
-        mWifiDescription = statusIcon.contentDescription;
-        mWifiIn = activityIn && mActivityEnabled && mWifiVisible;
-        mWifiOut = activityOut && mActivityEnabled && mWifiVisible;
-
-        apply();
-    }
-
-    @Override
-    public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
-            int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
-            String description, boolean isWide, int subId, boolean roaming) {
-        PhoneState state = getState(subId);
-        if (state == null) {
-            return;
-        }
-        state.mMobileVisible = statusIcon.visible && !mBlockMobile;
-        state.mMobileStrengthId = statusIcon.icon;
-        state.mMobileTypeId = statusType;
-        state.mMobileDescription = statusIcon.contentDescription;
-        state.mMobileTypeDescription = typeContentDescription;
-        state.mRoaming = roaming;
-        state.mActivityIn = activityIn && mActivityEnabled;
-        state.mActivityOut = activityOut && mActivityEnabled;
-
-        apply();
-    }
-
-    @Override
-    public void setEthernetIndicators(IconState state) {
-        mEthernetVisible = state.visible && !mBlockEthernet;
-        mEthernetIconId = state.icon;
-        mEthernetDescription = state.contentDescription;
-
-        apply();
-    }
-
-    @Override
-    public void setNoSims(boolean show, boolean simDetected) {
-        // Noop. Status bar no longer shows no sim icon.
-    }
-
-    @Override
-    public void setSubs(List<SubscriptionInfo> subs) {
-        if (hasCorrectSubs(subs)) {
-            return;
-        }
-        mPhoneStates.clear();
-        if (mMobileSignalGroup != null) {
-            mMobileSignalGroup.removeAllViews();
-        }
-        final int n = subs.size();
-        for (int i = 0; i < n; i++) {
-            inflatePhoneState(subs.get(i).getSubscriptionId());
-        }
-        if (isAttachedToWindow()) {
-            applyIconTint();
-        }
-    }
-
-    private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
-        final int N = subs.size();
-        if (N != mPhoneStates.size()) {
-            return false;
-        }
-        for (int i = 0; i < N; i++) {
-            if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private PhoneState getState(int subId) {
-        for (PhoneState state : mPhoneStates) {
-            if (state.mSubId == subId) {
-                return state;
-            }
-        }
-        Log.e(TAG, "Unexpected subscription " + subId);
-        return null;
-    }
-
-    private PhoneState inflatePhoneState(int subId) {
-        PhoneState state = new PhoneState(subId, mContext);
-        if (mMobileSignalGroup != null) {
-            mMobileSignalGroup.addView(state.mMobileGroup);
-        }
-        mPhoneStates.add(state);
-        return state;
-    }
-
-    @Override
-    public void setIsAirplaneMode(IconState icon) {
-        mIsAirplaneMode = icon.visible && !mBlockAirplane;
-        mAirplaneIconId = icon.icon;
-        mAirplaneContentDescription = icon.contentDescription;
-
-        apply();
-    }
-
-    @Override
-    public void setMobileDataEnabled(boolean enabled) {
-        // Don't care.
-    }
-
-    @Override
-    public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
-        // Standard group layout onPopulateAccessibilityEvent() implementations
-        // ignore content description, so populate manually
-        if (mEthernetVisible && mEthernetGroup != null &&
-                mEthernetGroup.getContentDescription() != null)
-            event.getText().add(mEthernetGroup.getContentDescription());
-        if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
-            event.getText().add(mWifiGroup.getContentDescription());
-        for (PhoneState state : mPhoneStates) {
-            state.populateAccessibilityEvent(event);
-        }
-        return super.dispatchPopulateAccessibilityEventInternal(event);
-    }
-
-    @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        super.onRtlPropertiesChanged(layoutDirection);
-
-        if (mEthernet != null) {
-            mEthernet.setImageDrawable(null);
-            mEthernetDark.setImageDrawable(null);
-            mLastEthernetIconId = -1;
-        }
-
-        if (mWifi != null) {
-            mWifi.setImageDrawable(null);
-            mWifiDark.setImageDrawable(null);
-            mLastWifiStrengthId = -1;
-        }
-
-        for (PhoneState state : mPhoneStates) {
-            if (state.mMobileType != null) {
-                state.mMobileType.setImageDrawable(null);
-                state.mLastMobileTypeId = -1;
-            }
-        }
-
-        if (mAirplane != null) {
-            mAirplane.setImageDrawable(null);
-            mLastAirplaneIconId = -1;
-        }
-
-        apply();
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    // Run after each indicator change.
-    private void apply() {
-        if (mWifiGroup == null) return;
-
-        if (mVpnVisible) {
-            if (mLastVpnIconId != mVpnIconId) {
-                setIconForView(mVpn, mVpnIconId);
-                mLastVpnIconId = mVpnIconId;
-            }
-            mIconLogger.onIconShown(SLOT_VPN);
-            mVpn.setVisibility(View.VISIBLE);
-        } else {
-            mIconLogger.onIconHidden(SLOT_VPN);
-            mVpn.setVisibility(View.GONE);
-        }
-        if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
-
-        if (mEthernetVisible) {
-            if (mLastEthernetIconId != mEthernetIconId) {
-                setIconForView(mEthernet, mEthernetIconId);
-                setIconForView(mEthernetDark, mEthernetIconId);
-                mLastEthernetIconId = mEthernetIconId;
-            }
-            mEthernetGroup.setContentDescription(mEthernetDescription);
-            mIconLogger.onIconShown(SLOT_ETHERNET);
-            mEthernetGroup.setVisibility(View.VISIBLE);
-        } else {
-            mIconLogger.onIconHidden(SLOT_ETHERNET);
-            mEthernetGroup.setVisibility(View.GONE);
-        }
-
-        if (DEBUG) Log.d(TAG,
-                String.format("ethernet: %s",
-                    (mEthernetVisible ? "VISIBLE" : "GONE")));
-
-        if (mWifiVisible) {
-            if (mWifiStrengthId != mLastWifiStrengthId) {
-                setIconForView(mWifi, mWifiStrengthId);
-                setIconForView(mWifiDark, mWifiStrengthId);
-                mLastWifiStrengthId = mWifiStrengthId;
-            }
-            mIconLogger.onIconShown(SLOT_WIFI);
-            mWifiGroup.setContentDescription(mWifiDescription);
-            mWifiGroup.setVisibility(View.VISIBLE);
-        } else {
-            mIconLogger.onIconHidden(SLOT_WIFI);
-            mWifiGroup.setVisibility(View.GONE);
-        }
-
-        if (DEBUG) Log.d(TAG,
-                String.format("wifi: %s sig=%d",
-                    (mWifiVisible ? "VISIBLE" : "GONE"),
-                    mWifiStrengthId));
-
-        mWifiActivityIn.setVisibility(mWifiIn ? View.VISIBLE : View.GONE);
-        mWifiActivityOut.setVisibility(mWifiOut ? View.VISIBLE : View.GONE);
-
-        boolean anyMobileVisible = false;
-        int firstMobileTypeId = 0;
-        for (PhoneState state : mPhoneStates) {
-            if (state.apply(anyMobileVisible)) {
-                if (!anyMobileVisible) {
-                    firstMobileTypeId = state.mMobileTypeId;
-                    anyMobileVisible = true;
-                }
-            }
-        }
-        if (anyMobileVisible) {
-            mIconLogger.onIconShown(SLOT_MOBILE);
-        } else {
-            mIconLogger.onIconHidden(SLOT_MOBILE);
-        }
-
-        if (mIsAirplaneMode) {
-            if (mLastAirplaneIconId != mAirplaneIconId) {
-                setIconForView(mAirplane, mAirplaneIconId);
-                mLastAirplaneIconId = mAirplaneIconId;
-            }
-            mAirplane.setContentDescription(mAirplaneContentDescription);
-            mIconLogger.onIconShown(SLOT_AIRPLANE);
-            mAirplane.setVisibility(View.VISIBLE);
-        } else {
-            mIconLogger.onIconHidden(SLOT_AIRPLANE);
-            mAirplane.setVisibility(View.GONE);
-        }
-
-        if (mIsAirplaneMode && mWifiVisible) {
-            mWifiAirplaneSpacer.setVisibility(View.VISIBLE);
-        } else {
-            mWifiAirplaneSpacer.setVisibility(View.GONE);
-        }
-
-        if ((anyMobileVisible && firstMobileTypeId != 0) && mWifiVisible) {
-            mWifiSignalSpacer.setVisibility(View.VISIBLE);
-        } else {
-            mWifiSignalSpacer.setVisibility(View.GONE);
-        }
-
-        boolean anythingVisible = mWifiVisible || mIsAirplaneMode
-                || anyMobileVisible || mVpnVisible || mEthernetVisible;
-        setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
-    }
-
-    /**
-     * Sets the given drawable id on the view. This method will also scale the icon by
-     * {@link #mIconScaleFactor} if appropriate.
-     */
-    private void setIconForView(ImageView imageView, @DrawableRes int iconId) {
-        // Using the imageView's context to retrieve the Drawable so that theme is preserved.
-        Drawable icon = imageView.getContext().getDrawable(iconId);
-
-        if (mIconScaleFactor == 1.f) {
-            imageView.setImageDrawable(icon);
-        } else {
-            imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor));
-        }
-    }
-
-
-    @Override
-    public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) {
-        boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity
-                || !mTintArea.equals(tintArea);
-        mIconTint = tint;
-        mDarkIntensity = darkIntensity;
-        mTintArea.set(tintArea);
-        if (changed && isAttachedToWindow()) {
-            applyIconTint();
-        }
-    }
-
-    private void applyIconTint() {
-        setTint(mVpn, DarkIconDispatcher.getTint(mTintArea, mVpn, mIconTint));
-        setTint(mAirplane, DarkIconDispatcher.getTint(mTintArea, mAirplane, mIconTint));
-        applyDarkIntensity(
-                DarkIconDispatcher.getDarkIntensity(mTintArea, mWifi, mDarkIntensity),
-                mWifi, mWifiDark);
-        setTint(mWifiActivityIn,
-                DarkIconDispatcher.getTint(mTintArea, mWifiActivityIn, mIconTint));
-        setTint(mWifiActivityOut,
-                DarkIconDispatcher.getTint(mTintArea, mWifiActivityOut, mIconTint));
-        applyDarkIntensity(
-                DarkIconDispatcher.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity),
-                mEthernet, mEthernetDark);
-        for (int i = 0; i < mPhoneStates.size(); i++) {
-            mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity, mTintArea);
-        }
-    }
-
-    private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) {
-        lightIcon.setAlpha(1 - darkIntensity);
-        darkIcon.setAlpha(darkIntensity);
-    }
-
-    private void setTint(ImageView v, int tint) {
-        v.setImageTintList(ColorStateList.valueOf(tint));
-    }
-
-    private int currentVpnIconId(boolean isBranded) {
-        return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
-    }
-
-    private class PhoneState {
-        private final int mSubId;
-        private boolean mMobileVisible = false;
-        private int mMobileStrengthId = 0, mMobileTypeId = 0;
-        private int mLastMobileStrengthId = -1;
-        private int mLastMobileTypeId = -1;
-        private String mMobileDescription, mMobileTypeDescription;
-
-        private ViewGroup mMobileGroup;
-        private ImageView mMobile, mMobileType, mMobileRoaming;
-        private View mMobileRoamingSpace;
-        public boolean mRoaming;
-        private ImageView mMobileActivityIn;
-        private ImageView mMobileActivityOut;
-        public boolean mActivityIn;
-        public boolean mActivityOut;
-        private SignalDrawable mMobileSignalDrawable;
-
-        public PhoneState(int subId, Context context) {
-            ViewGroup root = (ViewGroup) LayoutInflater.from(context)
-                    .inflate(R.layout.mobile_signal_group, null);
-            setViews(root);
-            mSubId = subId;
-        }
-
-        public void setViews(ViewGroup root) {
-            mMobileGroup    = root;
-            mMobile         = root.findViewById(R.id.mobile_signal);
-            mMobileType     = root.findViewById(R.id.mobile_type);
-            mMobileRoaming  = root.findViewById(R.id.mobile_roaming);
-            mMobileRoamingSpace  = root.findViewById(R.id.mobile_roaming_space);
-            mMobileActivityIn = root.findViewById(R.id.mobile_in);
-            mMobileActivityOut = root.findViewById(R.id.mobile_out);
-            mMobileSignalDrawable = new SignalDrawable(mMobile.getContext());
-            mMobile.setImageDrawable(mMobileSignalDrawable);
-        }
-
-        public boolean apply(boolean isSecondaryIcon) {
-            if (mMobileVisible && !mIsAirplaneMode) {
-                if (mLastMobileStrengthId != mMobileStrengthId) {
-                    mMobile.getDrawable().setLevel(mMobileStrengthId);
-                    mLastMobileStrengthId = mMobileStrengthId;
-                }
-
-                if (mLastMobileTypeId != mMobileTypeId) {
-                    mMobileType.setImageResource(mMobileTypeId);
-                    mLastMobileTypeId = mMobileTypeId;
-                }
-
-                mMobileGroup.setContentDescription(mMobileTypeDescription
-                        + " " + mMobileDescription);
-                mMobileGroup.setVisibility(View.VISIBLE);
-            } else {
-                mMobileGroup.setVisibility(View.GONE);
-            }
-
-            // When this isn't next to wifi, give it some extra padding between the signals.
-            mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,
-                    0, 0, 0);
-            mMobile.setPaddingRelative(mMobileDataIconStartPadding, 0, 0, 0);
-
-            if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
-                        (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
-
-            mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
-            mMobileRoaming.setVisibility(mRoaming ? View.VISIBLE : View.GONE);
-            mMobileRoamingSpace.setVisibility(mRoaming ? View.VISIBLE : View.GONE);
-            mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE);
-            mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE);
-
-            return mMobileVisible;
-        }
-
-        public void populateAccessibilityEvent(AccessibilityEvent event) {
-            if (mMobileVisible && mMobileGroup != null
-                    && mMobileGroup.getContentDescription() != null) {
-                event.getText().add(mMobileGroup.getContentDescription());
-            }
-        }
-
-        public void setIconTint(int tint, float darkIntensity, Rect tintArea) {
-            mMobileSignalDrawable.setDarkIntensity(darkIntensity);
-            setTint(mMobileType, DarkIconDispatcher.getTint(tintArea, mMobileType, tint));
-            setTint(mMobileRoaming, DarkIconDispatcher.getTint(tintArea, mMobileRoaming,
-                    tint));
-            setTint(mMobileActivityIn,
-                    DarkIconDispatcher.getTint(tintArea, mMobileActivityIn, tint));
-            setTint(mMobileActivityOut,
-                    DarkIconDispatcher.getTint(tintArea, mMobileActivityOut, tint));
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
deleted file mode 100644
index 0652227..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar;
-
-import com.android.internal.statusbar.StatusBarIcon;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Holds an array of {@link com.android.internal.statusbar.StatusBarIcon}s and draws them
- * in a linear layout
- */
-public class StatusBarIconContainer {
-    private final List<StatusBarIcon> mIcons = new ArrayList<>();
-
-    public StatusBarIconContainer(List<StatusBarIcon> icons) {
-        mIcons.addAll(icons);
-    }
-}
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 e698e64..45db002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -18,7 +18,6 @@
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
@@ -51,7 +50,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 
 /**
  * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
@@ -64,11 +62,10 @@
         NotificationUpdateHandler,
         VisualStabilityManager.Callback {
     private static final String TAG = "NotificationEntryMgr";
-    protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
-
-    protected final Context mContext;
+    private final Context mContext;
+    @VisibleForTesting
     protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>();
 
     private final DeviceProvisionedController mDeviceProvisionedController =
@@ -80,13 +77,11 @@
     private NotificationRemoteInputManager mRemoteInputManager;
     private NotificationRowBinder mNotificationRowBinder;
 
-    private final Handler mDeferredNotificationViewUpdateHandler;
-    private Runnable mUpdateNotificationViewsCallback;
-
     private NotificationPresenter mPresenter;
     private NotificationListenerService.RankingMap mLatestRankingMap;
+    @VisibleForTesting
     protected NotificationData mNotificationData;
-    protected NotificationListContainer mListContainer;
+    private NotificationListContainer mListContainer;
     @VisibleForTesting
     final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
             = new ArrayList<>();
@@ -121,7 +116,6 @@
     public NotificationEntryManager(Context context) {
         mContext = context;
         mNotificationData = new NotificationData();
-        mDeferredNotificationViewUpdateHandler = new Handler();
     }
 
     /** Adds a {@link NotificationEntryListener}. */
@@ -156,7 +150,6 @@
             NotificationListContainer listContainer,
             HeadsUpManager headsUpManager) {
         mPresenter = presenter;
-        mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
         mNotificationData.setHeadsUpManager(headsUpManager);
         mListContainer = listContainer;
 
@@ -180,14 +173,6 @@
         return mNotificationData;
     }
 
-    protected Context getContext() {
-        return mContext;
-    }
-
-    protected NotificationPresenter getPresenter() {
-        return mPresenter;
-    }
-
     @Override
     public void onReorderingAllowed() {
         updateNotifications();
@@ -229,15 +214,6 @@
         }
     }
 
-    private void maybeScheduleUpdateNotificationViews(NotificationEntry entry) {
-        long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS
-                - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs);
-        if (audibleAlertTimeout > 0) {
-            mDeferredNotificationViewUpdateHandler.postDelayed(
-                    mUpdateNotificationViewsCallback, audibleAlertTimeout);
-        }
-    }
-
     @Override
     public void onAsyncInflationFinished(NotificationEntry entry,
             @InflationFlag int inflatedFlags) {
@@ -256,7 +232,6 @@
                 for (NotificationEntryListener listener : mNotificationEntryListeners) {
                     listener.onNotificationAdded(entry);
                 }
-                maybeScheduleUpdateNotificationViews(entry);
             } else {
                 for (NotificationEntryListener listener : mNotificationEntryListeners) {
                     listener.onEntryReinflated(entry);
@@ -463,8 +438,6 @@
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onPostEntryUpdated(entry);
         }
-
-        maybeScheduleUpdateNotificationViews(entry);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9e0dd84..95bd1ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -86,7 +86,6 @@
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -106,6 +105,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
 
@@ -122,6 +122,7 @@
     private static final int MENU_VIEW_INDEX = 0;
     private static final String TAG = "ExpandableNotifRow";
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
+    private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
     private boolean mUpdateBackgroundOnUpdate;
 
     /**
@@ -1693,17 +1694,31 @@
     /** Sets the last time the notification being displayed audibly alerted the user. */
     public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
         if (NotificationUtils.useNewInterruptionModel(mContext)) {
-            boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs
-                    < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS;
-            if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
-                mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(
-                        recentlyAudiblyAlerted);
+            long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
+            boolean alertedRecently =
+                    timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
+
+            applyAudiblyAlertedRecently(alertedRecently);
+
+            removeCallbacks(mExpireRecentlyAlertedFlag);
+            if (alertedRecently) {
+                long timeUntilNoLongerRecent =
+                        RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly;
+                postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent);
             }
-            mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
-            mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
         }
     }
 
+    private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
+
+    private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
+        if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
+            mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+        }
+        mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+        mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+    }
+
     public View.OnClickListener getAppOpsOnClickListener() {
         return mOnAppOpsClickListener;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 280dda0..577e8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -84,6 +84,14 @@
         public void onCancelled() {
             pulseFinished();
         }
+
+        /**
+         * Whether to fade out wallpaper.
+         */
+        @Override
+        public  boolean isFadeOutWallpaper() {
+            return mPulseReason == DozeLog.PULSE_REASON_DOCKING;
+        }
     };
 
     public DozeScrimController(DozeParameters dozeParameters) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index d873b0c..75adf50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -956,7 +956,7 @@
             handled = true;
         }
         handled |= super.onTouchEvent(event);
-        return mDozing ? handled : true;
+        return !mDozing || mPulsing || handled;
     }
 
     private boolean handleQsTouch(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e25c829..853d7ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -275,9 +275,12 @@
             holdWakeLock();
         }
 
-        // AOD wallpapers should fade away after a while
-        if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
-                && mState == ScrimState.AOD) {
+        // AOD wallpapers should fade away after a while.
+        // Docking pulses may take a long time, wallpapers should also fade away after a while.
+        if (mWallpaperSupportsAmbientMode && (
+                mDozeParameters.getAlwaysOn() && mState == ScrimState.AOD
+                        || mState == ScrimState.PULSING && mCallback != null
+                        && mCallback.isFadeOutWallpaper())) {
             if (!mWallpaperVisibilityTimedOut) {
                 mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
                         AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
@@ -329,7 +332,7 @@
 
     @VisibleForTesting
     protected void onHideWallpaperTimeout() {
-        if (mState != ScrimState.AOD) {
+        if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
             return;
         }
 
@@ -364,7 +367,7 @@
             mExpansionFraction = fraction;
 
             final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
-                    || mState == ScrimState.KEYGUARD;
+                    || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
             if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
                 return;
             }
@@ -409,7 +412,7 @@
             behindFraction = (float) Math.pow(behindFraction, 0.8f);
             mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
             mCurrentInFrontAlpha = 0;
-        } else if (mState == ScrimState.KEYGUARD) {
+        } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
             // Either darken of make the scrim transparent when you
             // pull down the shade
             float interpolatedFract = getInterpolatedFraction();
@@ -504,7 +507,8 @@
 
         // We want to override the back scrim opacity for the AOD state
         // when it's time to fade the wallpaper away.
-        boolean aodWallpaperTimeout = mState == ScrimState.AOD && mWallpaperVisibilityTimedOut;
+        boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING)
+                && mWallpaperVisibilityTimedOut;
         // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
         boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
                 && mKeyguardOccluded;
@@ -562,8 +566,8 @@
         if (alpha == 0f) {
             scrim.setClickable(false);
         } else {
-            // Eat touch events (unless dozing or pulsing).
-            scrim.setClickable(mState != ScrimState.AOD && mState != ScrimState.PULSING);
+            // Eat touch events (unless dozing).
+            scrim.setClickable(mState != ScrimState.AOD);
         }
         updateScrim(scrim, alpha);
     }
@@ -917,6 +921,9 @@
         }
         default void onCancelled() {
         }
+        default boolean isFadeOutWallpaper() {
+            return false;
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index fb3c4aa..72519ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -219,6 +219,14 @@
     public void prepare(ScrimState previousState) {
     }
 
+    /**
+     * Check if lockscreen wallpaper or music album art exists.
+     * @return true if lockscreen wallpaper or music album art exists.
+     */
+    public boolean hasBackdrop() {
+        return mHasBackdrop;
+    }
+
     public int getIndex() {
         return mIndex;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6d78f72..9abd86d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -70,6 +70,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -98,6 +99,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.view.Display;
@@ -478,8 +480,13 @@
             WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
             final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
+            final boolean aodImageWallpaperEnabled = FeatureFlagUtils.isEnabled(mContext,
+                    FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+            updateAodMaskVisibility(deviceSupportsAodWallpaper && aodImageWallpaperEnabled);
+            // If WallpaperInfo is null, it must be ImageWallpaper.
             final boolean supportsAmbientMode = deviceSupportsAodWallpaper
-                    && info != null && info.supportsAmbientMode();
+                    && (info == null && aodImageWallpaperEnabled
+                        || info != null && info.supportsAmbientMode());
 
             mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
             mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
@@ -581,6 +588,7 @@
     protected NotificationPresenter mPresenter;
     private NotificationActivityStarter mNotificationActivityStarter;
     private boolean mPulsing;
+    private ContentObserver mFeatureFlagObserver;
 
     @Override
     public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
@@ -697,6 +705,9 @@
         mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL,
                 wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
         mWallpaperChangedReceiver.onReceive(mContext, null);
+        mFeatureFlagObserver = new FeatureFlagObserver(
+                FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED /* feature */,
+                () -> mWallpaperChangedReceiver.onReceive(mContext, null) /* callback */);
 
         // Set up the initial notification state. This needs to happen before CommandQueue.disable()
         setUpPresenter();
@@ -3573,10 +3584,7 @@
             mVisualStabilityManager.setScreenOn(false);
             updateVisibleToUser();
 
-            // We need to disable touch events because these might
-            // collapse the panel after we expanded it, and thus we would end up with a blank
-            // Keyguard.
-            mNotificationPanel.setTouchAndAnimationDisabled(true);
+            updateNotificationPanelTouchState();
             mStatusBarWindow.cancelCurrentTouch();
             if (mLaunchCameraOnFinishedGoingToSleep) {
                 mLaunchCameraOnFinishedGoingToSleep = false;
@@ -3599,13 +3607,22 @@
             mDeviceInteractive = true;
             mAmbientPulseManager.releaseAllImmediately();
             mVisualStabilityManager.setScreenOn(true);
-            mNotificationPanel.setTouchAndAnimationDisabled(false);
+            updateNotificationPanelTouchState();
             updateVisibleToUser();
             updateIsKeyguard();
             mDozeServiceHost.stopDozing();
         }
     };
 
+    /**
+     * We need to disable touch events because these might
+     * collapse the panel after we expanded it, and thus we would end up with a blank
+     * Keyguard.
+     */
+    private void updateNotificationPanelTouchState() {
+        mNotificationPanel.setTouchAndAnimationDisabled(!mDeviceInteractive && !mPulsing);
+    }
+
     final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurningOn() {
@@ -3871,17 +3888,15 @@
                 @Override
                 public void onPulseStarted() {
                     callback.onPulseStarted();
-                    if (mAmbientPulseManager.hasNotifications()) {
-                        // Only pulse the stack scroller if there's actually something to show.
-                        // Otherwise just show the always-on screen.
-                        setPulsing(true);
-                    }
+                    updateNotificationPanelTouchState();
+                    setPulsing(true);
                 }
 
                 @Override
                 public void onPulseFinished() {
                     mPulsing = false;
                     callback.onPulseFinished();
+                    updateNotificationPanelTouchState();
                     setPulsing(false);
                 }
 
@@ -4411,4 +4426,33 @@
     public @TransitionMode int getStatusBarMode() {
         return mStatusBarMode;
     }
+
+    private void updateAodMaskVisibility(boolean supportsAodWallpaper) {
+        View mask = mStatusBarWindow.findViewById(R.id.aod_mask);
+        if (mask != null) {
+            mask.setVisibility(supportsAodWallpaper ? View.VISIBLE : View.INVISIBLE);
+        }
+    }
+
+    private final class FeatureFlagObserver extends ContentObserver {
+        private final Runnable mCallback;
+
+        FeatureFlagObserver(String feature, Runnable callback) {
+            this(null, feature, callback);
+        }
+
+        private FeatureFlagObserver(Handler handler, String feature, Runnable callback) {
+            super(handler);
+            mCallback = callback;
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(feature), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            if (mCallback != null) {
+                mStatusBarWindow.post(mCallback);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index db7589d..f846036 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -36,7 +36,6 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.IconLogger;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -61,7 +60,6 @@
 
     private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
     private final ArraySet<String> mIconBlacklist = new ArraySet<>();
-    private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
 
     // Points to light or dark context depending on the... context?
     private Context mContext;
@@ -147,7 +145,6 @@
         int viewIndex = getViewIndex(index, holder.getTag());
         boolean blocked = mIconBlacklist.contains(slot);
 
-        mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
         mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder));
     }
 
@@ -281,8 +278,6 @@
             return;
         }
 
-        mIconLogger.onIconHidden(slotName);
-
         int slotIndex = getSlotIndex(slotName);
         List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder();
         for (StatusBarIconHolder holder : iconsToRemove) {
@@ -297,7 +292,6 @@
         if (getIcon(index, tag) == null) {
             return;
         }
-        mIconLogger.onIconHidden(getSlotName(index));
         super.removeIcon(index, tag);
         int viewIndex = getViewIndex(index, 0);
         mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
@@ -305,7 +299,6 @@
 
     private void handleSet(int index, StatusBarIconHolder holder) {
         int viewIndex = getViewIndex(index, holder.getTag());
-        mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
         mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0f8970f..bb23608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -187,7 +187,7 @@
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
         } else if (bouncerNeedsScrimming()) {
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
-        } else if (mShowing && !mDozing) {
+        } else if (mShowing) {
             if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) {
                 mBouncer.setExpansion(expansion);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 53e461d..8b25c34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -339,7 +339,7 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout();
-        if (mService.isDozing() && !stackScrollLayout.hasPulsingNotifications()) {
+        if (mService.isDozing() && !mService.isPulsing()) {
             // Capture all touch events in always-on.
             return true;
         }
@@ -347,8 +347,7 @@
         if (mNotificationPanel.isFullyExpanded()
                 && stackScrollLayout.getVisibility() == View.VISIBLE
                 && mStatusBarStateController.getState() == StatusBarState.KEYGUARD
-                && !mService.isBouncerShowing()
-                && !mService.isDozing()) {
+                && !mService.isBouncerShowing()) {
             intercept = mDragDownHelper.onInterceptTouchEvent(ev);
         }
         if (!intercept) {
@@ -369,7 +368,7 @@
         boolean handled = false;
         if (mService.isDozing()) {
             mDoubleTapHelper.onTouchEvent(ev);
-            handled = true;
+            handled = !mService.isPulsing();
         }
         if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled)
                 || mDragDownHelper.isDraggingDown()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index de7ef3b..4299af7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -274,7 +274,6 @@
 
     private void updateClockVisibility() {
         boolean visible = shouldBeVisible();
-        Dependency.get(IconLogger.class).onIconVisibility("clock", visible);
         int visibility = visible ? View.VISIBLE : View.GONE;
         super.setVisibility(visibility);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java
deleted file mode 100644
index 710e1df..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.policy;
-
-public interface IconLogger {
-
-    void onIconShown(String tag);
-    void onIconHidden(String tag);
-
-    default void onIconVisibility(String tag, boolean visible) {
-        if (visible) {
-            onIconShown(tag);
-        } else {
-            onIconHidden(tag);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
deleted file mode 100644
index ba6369e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.policy;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.STATUS_BAR_ICONS_CHANGED;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-
-import android.content.Context;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.ArraySet;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.logging.MetricsLogger;
-
-import java.util.Arrays;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-public class IconLoggerImpl implements IconLogger {
-
-    // Minimum ms between log statements.
-    // NonFinalForTesting
-    @VisibleForTesting
-    protected static long MIN_LOG_INTERVAL = 1000;
-
-    private final Context mContext;
-    private final Handler mHandler;
-    private final MetricsLogger mLogger;
-    private final ArraySet<String> mIcons = new ArraySet<>();
-    private final List<String> mIconIndex;
-    private long mLastLog = System.currentTimeMillis();
-
-    @Inject
-    public IconLoggerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
-            MetricsLogger logger) {
-        mContext = context;
-        mHandler = new Handler(bgLooper);
-        mLogger = logger;
-        String[] icons = mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_statusBarIcons);
-        mIconIndex = Arrays.asList(icons);
-        doLog();
-    }
-
-    @Override
-    public void onIconShown(String tag) {
-        synchronized (mIcons) {
-            if (mIcons.contains(tag)) return;
-            mIcons.add(tag);
-        }
-        if (!mHandler.hasCallbacks(mLog)) {
-            mHandler.postDelayed(mLog, MIN_LOG_INTERVAL);
-        }
-    }
-
-    @Override
-    public void onIconHidden(String tag) {
-        synchronized (mIcons) {
-            if (!mIcons.contains(tag)) return;
-            mIcons.remove(tag);
-        }
-        if (!mHandler.hasCallbacks(mLog)) {
-            mHandler.postDelayed(mLog, MIN_LOG_INTERVAL);
-        }
-    }
-
-    private void doLog() {
-        long time = System.currentTimeMillis();
-        long timeSinceLastLog = time - mLastLog;
-        mLastLog = time;
-
-        ArraySet<String> icons;
-        synchronized (mIcons) {
-            icons = new ArraySet<>(mIcons);
-        }
-        mLogger.write(new LogMaker(STATUS_BAR_ICONS_CHANGED)
-                .setType(TYPE_ACTION)
-                .setLatency(timeSinceLastLog)
-                .addTaggedData(FIELD_NUM_STATUS_ICONS, icons.size())
-                .addTaggedData(FIELD_STATUS_ICONS, getBitField(icons)));
-    }
-
-    private int getBitField(ArraySet<String> icons) {
-        int iconsVisible = 0;
-        for (String icon : icons) {
-            int index = mIconIndex.indexOf(icon);
-            if (index >= 0) {
-                iconsVisible |= (1 << index);
-            }
-        }
-        return iconsVisible;
-    }
-
-    private final Runnable mLog = this::doLog;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 6193159..0c63e29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
 
+import android.app.RemoteInput;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -42,14 +43,18 @@
     private static final String KEY_REQUIRES_TARGETING_P = "requires_targeting_p";
     private static final String KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS =
             "max_squeeze_remeasure_attempts";
+    private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
+            "edit_choices_before_sending";
 
     private final boolean mDefaultEnabled;
     private final boolean mDefaultRequiresP;
     private final int mDefaultMaxSqueezeRemeasureAttempts;
+    private final boolean mDefaultEditChoicesBeforeSending;
 
     private boolean mEnabled;
     private boolean mRequiresTargetingP;
     private int mMaxSqueezeRemeasureAttempts;
+    private boolean mEditChoicesBeforeSending;
 
     private final Context mContext;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -66,6 +71,8 @@
                 R.bool.config_smart_replies_in_notifications_requires_targeting_p);
         mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger(
                 R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts);
+        mDefaultEditChoicesBeforeSending = resources.getBoolean(
+                R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
 
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -90,6 +97,8 @@
             mRequiresTargetingP = mParser.getBoolean(KEY_REQUIRES_TARGETING_P, mDefaultRequiresP);
             mMaxSqueezeRemeasureAttempts = mParser.getInt(
                     KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts);
+            mEditChoicesBeforeSending = mParser.getBoolean(
+                    KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
         }
     }
 
@@ -113,4 +122,24 @@
     public int getMaxSqueezeRemeasureAttempts() {
         return mMaxSqueezeRemeasureAttempts;
     }
+
+    /**
+     * Returns whether by tapping on a choice should let the user edit the input before it
+     * is sent to the app.
+     *
+     * @param remoteInputEditChoicesBeforeSending The value from
+     *         {@link RemoteInput#getEditChoicesBeforeSending()}
+     */
+    public boolean getEffectiveEditChoicesBeforeSending(
+            @RemoteInput.EditChoicesBeforeSending int remoteInputEditChoicesBeforeSending) {
+        switch (remoteInputEditChoicesBeforeSending) {
+            case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED:
+                return false;
+            case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED:
+                return true;
+            case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO:
+            default:
+                return mEditChoicesBeforeSending;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 68b172d..a76cf16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -242,9 +242,8 @@
         b.setText(choice);
 
         OnDismissAction action = () -> {
-            // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags.
-            if (smartReplies.remoteInput.getEditChoicesBeforeSending()
-                    == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) {
+            if (mConstants.getEffectiveEditChoicesBeforeSending(
+                    smartReplies.remoteInput.getEditChoicesBeforeSending())) {
                 entry.remoteInputText = choice;
                 mRemoteInputManager.activateRemoteInput(b,
                         new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java
new file mode 100644
index 0000000..52cabe2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java
@@ -0,0 +1,222 @@
+/*
+ * 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.systemui.wallpaper;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.util.AttributeSet;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.widget.ImageView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.phone.ScrimState;
+
+/**
+ * A view that draws mask upon either image wallpaper or music album art in AOD.
+ */
+public class AodMaskView extends ImageView implements StatusBarStateController.StateListener,
+        ImageWallpaperTransformer.TransformationListener {
+    private static final String TAG = AodMaskView.class.getSimpleName();
+    private static final int TRANSITION_DURATION = 1000;
+
+    private static final AnimatableProperty TRANSITION_PROGRESS = AnimatableProperty.from(
+            "transition_progress",
+            AodMaskView::setTransitionAmount,
+            AodMaskView::getTransitionAmount,
+            R.id.aod_mask_transition_progress_tag,
+            R.id.aod_mask_transition_progress_start_tag,
+            R.id.aod_mask_transition_progress_end_tag
+    );
+
+    private final AnimationProperties mTransitionProperties = new AnimationProperties();
+    private final ImageWallpaperTransformer mTransformer;
+    private final RectF mBounds = new RectF();
+    private boolean mChangingStates;
+    private boolean mNeedMask;
+    private float mTransitionAmount;
+    private final WallpaperManager mWallpaperManager;
+    private final DisplayManager mDisplayManager;
+    private DisplayListener mDisplayListener = new DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            // We just support DEFAULT_DISPLAY currently.
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                mTransformer.updateDisplayInfo(getDisplayInfo(displayId));
+            }
+        }
+    };
+
+    public AodMaskView(Context context) {
+        this(context, null);
+    }
+
+    public AodMaskView(Context context, AttributeSet attrs) {
+        this(context, attrs, null);
+    }
+
+    @VisibleForTesting
+    public AodMaskView(Context context, AttributeSet attrs, ImageWallpaperTransformer transformer) {
+        super(context, attrs);
+        setClickable(false);
+
+        StatusBarStateController controller = Dependency.get(StatusBarStateController.class);
+        if (controller != null) {
+            controller.addCallback(this);
+        } else {
+            Log.d(TAG, "Can not get StatusBarStateController!");
+        }
+
+        mDisplayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
+        mDisplayManager.registerDisplayListener(mDisplayListener, null);
+        mWallpaperManager =
+                (WallpaperManager) getContext().getSystemService(Context.WALLPAPER_SERVICE);
+
+        if (transformer == null) {
+            mTransformer = new ImageWallpaperTransformer(this);
+            mTransformer.addFilter(new ScrimFilter());
+            mTransformer.addFilter(new VignetteFilter());
+            mTransformer.updateOffsets();
+            mTransformer.updateDisplayInfo(getDisplayInfo(Display.DEFAULT_DISPLAY));
+
+            mTransitionProperties.setDuration(TRANSITION_DURATION);
+            mTransitionProperties.setAnimationFinishListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mTransformer.setIsTransiting(false);
+                }
+
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mTransformer.setIsTransiting(true);
+                }
+            });
+        } else {
+            // This part should only be hit by test cases.
+            mTransformer = transformer;
+        }
+    }
+
+    private DisplayInfo getDisplayInfo(int displayId) {
+        DisplayInfo displayInfo = new DisplayInfo();
+        mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
+        return displayInfo;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        mBounds.set(0, 0, w, h);
+        mTransformer.updateOffsets();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mNeedMask) {
+            mTransformer.drawTransformedImage(canvas, null /* target */, null /* src */, mBounds);
+        }
+    }
+
+    private boolean checkIfNeedMask() {
+        // We need mask for ImageWallpaper / LockScreen Wallpaper (Music album art).
+        return mWallpaperManager.getWallpaperInfo() == null || ScrimState.AOD.hasBackdrop();
+    }
+
+    @Override
+    public void onStatePreChange(int oldState, int newState) {
+        mChangingStates = oldState != newState;
+        mNeedMask = checkIfNeedMask();
+    }
+
+    @Override
+    public void onStatePostChange() {
+        mChangingStates = false;
+    }
+
+    @Override
+    public void onStateChanged(int newState) {
+    }
+
+    @Override
+    public void onDozingChanged(boolean isDozing) {
+        if (!mNeedMask) {
+            return;
+        }
+
+        boolean enabled = checkFeatureIsEnabled();
+        mTransformer.updateAmbientModeState(enabled && isDozing);
+
+        if (enabled && !mChangingStates) {
+            setAnimatorProperty(isDozing);
+        } else {
+            invalidate();
+        }
+    }
+
+    private boolean checkFeatureIsEnabled() {
+        return FeatureFlagUtils.isEnabled(
+                getContext(), FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+    }
+
+    @VisibleForTesting
+    void setAnimatorProperty(boolean isDozing) {
+        PropertyAnimator.setProperty(
+                this,
+                TRANSITION_PROGRESS,
+                isDozing ? 1f : 0f /* newEndValue */,
+                mTransitionProperties,
+                true /* animated */);
+    }
+
+    @Override
+    public void onTransformationUpdated() {
+        invalidate();
+    }
+
+    private void setTransitionAmount(float amount) {
+        mTransitionAmount = amount;
+        mTransformer.updateTransitionAmount(amount);
+    }
+
+    private float getTransitionAmount() {
+        return mTransitionAmount;
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java
new file mode 100644
index 0000000..d457dac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java
@@ -0,0 +1,81 @@
+/*
+ * 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.systemui.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * Abstract filter used by static image wallpaper.
+ */
+abstract class ImageWallpaperFilter {
+    protected static final boolean DEBUG = false;
+
+    private ImageWallpaperTransformer mTransformer;
+
+    /**
+     * Apply this filter to the bitmap before drawing on canvas.
+     * @param c      The canvas that will draw to.
+     * @param bitmap The bitmap to apply this filter.
+     * @param src    The subset of the bitmap to be drawn.
+     * @param dest   The rectangle that the bitmap will be scaled/translated to fit into.
+     */
+    public abstract void apply(@NonNull Canvas c, @Nullable Bitmap bitmap,
+            @Nullable Rect src, @NonNull RectF dest);
+
+    /**
+     * Notifies the occurrence of built-in transition of the animation.
+     * @param animator The animator which was animated.
+     */
+    public abstract void onAnimatorUpdate(ValueAnimator animator);
+
+    /**
+     * Notifies the occurrence of another transition of the animation.
+     * @param amount The transition amount.
+     */
+    public abstract void onTransitionAmountUpdate(float amount);
+
+    /**
+     * To set the associated transformer.
+     * @param transformer The transformer that is associated with this filter.
+     */
+    public void setTransformer(ImageWallpaperTransformer transformer) {
+        if (transformer != null) {
+            mTransformer = transformer;
+        }
+    }
+
+    protected ImageWallpaperTransformer getTransformer() {
+        return mTransformer;
+    }
+
+    /**
+     * Notifies the changing of the offset value of the ImageWallpaper.
+     * @param force True to force re-evaluate offsets.
+     * @param xOffset X offset of the ImageWallpaper in percentage.
+     * @param yOffset Y offset of the ImageWallpaper in percentage.
+     */
+    public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
+        // No-op
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java
new file mode 100644
index 0000000..25b0b0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java
@@ -0,0 +1,173 @@
+/*
+ * 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.systemui.wallpaper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.DisplayInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to manage the filters that will be applied.
+ */
+public class ImageWallpaperTransformer {
+    private static final String TAG = ImageWallpaperTransformer.class.getSimpleName();
+
+    private DisplayInfo mDisplayInfo;
+    private final List<ImageWallpaperFilter> mFilters;
+    private final TransformationListener mListener;
+    private boolean mIsInAmbientMode;
+    private boolean mIsTransiting;
+
+    /**
+     * Constructor.
+     * @param listener A listener to inform you the transformation has updated.
+     */
+    public ImageWallpaperTransformer(TransformationListener listener) {
+        mFilters = new ArrayList<>();
+        mListener = listener;
+    }
+
+    /**
+     * Claim that we want to use the specified filter.
+     * @param filter The filter will be used.
+     */
+    public void addFilter(ImageWallpaperFilter filter) {
+        if (filter != null) {
+            filter.setTransformer(this);
+            mFilters.add(filter);
+        }
+    }
+
+    /**
+     * Check if any transition is running.
+     * @return True if the transition is running, false otherwise.
+     */
+    boolean isTransiting() {
+        return mIsTransiting;
+    }
+
+    /**
+     * Indicate if any transition is running. <br/>
+     * @param isTransiting True if the transition is running.
+     */
+    void setIsTransiting(boolean isTransiting) {
+        mIsTransiting = isTransiting;
+    }
+
+    /**
+     * Check if the device is in ambient mode.
+     * @return True if the device is in ambient mode, false otherwise.
+     */
+    public boolean isInAmbientMode() {
+        return mIsInAmbientMode;
+    }
+
+    /**
+     * Update current state of ambient mode.
+     * @param isInAmbientMode Current ambient mode state.
+     */
+    public void updateAmbientModeState(boolean isInAmbientMode) {
+        mIsInAmbientMode = isInAmbientMode;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        int idx = 0;
+        for (ImageWallpaperFilter filter : mFilters) {
+            sb.append(idx++).append(": ").append(filter.getClass().getSimpleName()).append("\n");
+        }
+        if (sb.length() == 0) {
+            sb.append("No filters applied");
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Set a new display info.
+     * @param displayInfo New display info.
+     */
+    public void updateDisplayInfo(DisplayInfo displayInfo) {
+        mDisplayInfo = displayInfo;
+    }
+
+    /**
+     * To get current display info.
+     * @return Current display info.
+     */
+    public DisplayInfo getDisplayInfo() {
+        return mDisplayInfo;
+    }
+
+    /**
+     * Update the offsets with default value.
+     */
+    public void updateOffsets() {
+        this.updateOffsets(true, 0f, .5f);
+    }
+
+    /**
+     * To notify the filters that the offset of the ImageWallpaper changes.
+     * @param force True to force re-evaluate offsets.
+     * @param offsetX X offset of the ImageWallpaper in percentage.
+     * @param offsetY Y offset of the ImageWallpaper in percentage.
+     */
+    public void updateOffsets(boolean force, float offsetX, float offsetY) {
+        mFilters.forEach(filter -> filter.onOffsetsUpdate(force, offsetX, offsetY));
+    }
+
+    /**
+     * Apply all specified filters to the bitmap then draw to the canvas.
+     * @param c      The canvas that will draw to.
+     * @param target The bitmap to apply filters.
+     * @param src    The subset of the bitmap to be drawn
+     * @param dest   The rectangle that the bitmap will be scaled/translated to fit into.
+     */
+    void drawTransformedImage(@NonNull Canvas c, @Nullable Bitmap target,
+            @Nullable Rect src, @NonNull RectF dest) {
+        mFilters.forEach(filter -> filter.apply(c, target, src, dest));
+    }
+
+    /**
+     * Update the transition amount. <br/>
+     * Must invoke this to update transition amount if not running built-in transition.
+     * @param amount The transition amount.
+     */
+    void updateTransitionAmount(float amount) {
+        mFilters.forEach(filter -> filter.onTransitionAmountUpdate(amount));
+        if (mListener != null) {
+            mListener.onTransformationUpdated();
+        }
+    }
+
+    /**
+     * An interface that informs the transformation status.
+     */
+    public interface TransformationListener {
+        /**
+         * Notifies the update of the transformation.
+         */
+        void onTransformationUpdated();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java
new file mode 100644
index 0000000..637e48e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.systemui.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * A filter that implements 70% black scrim effect.
+ */
+public class ScrimFilter extends ImageWallpaperFilter {
+    private static final int MAX_ALPHA = (int) (255 * .7f);
+    private static final int MIN_ALPHA = 0;
+
+    private final Paint mPaint;
+
+    public ScrimFilter() {
+        mPaint = new Paint();
+        mPaint.setColor(Color.BLACK);
+        mPaint.setAlpha(MAX_ALPHA);
+    }
+
+    @Override
+    public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
+        ImageWallpaperTransformer transformer = getTransformer();
+
+        // If it is not in the transition, we need to set the property according to aod state.
+        if (!transformer.isTransiting()) {
+            mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
+        }
+
+        c.drawRect(dest, mPaint);
+    }
+
+    @Override
+    public void onAnimatorUpdate(ValueAnimator animator) {
+        ImageWallpaperTransformer transformer = getTransformer();
+        float fraction = animator.getAnimatedFraction();
+        float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
+        mPaint.setAlpha((int) (factor * MAX_ALPHA));
+    }
+
+    @Override
+    public void onTransitionAmountUpdate(float amount) {
+        mPaint.setAlpha((int) (amount * MAX_ALPHA));
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java
new file mode 100644
index 0000000..ad0b98b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java
@@ -0,0 +1,124 @@
+/*
+ * 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.systemui.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.util.Log;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A filter that implements vignette effect.
+ */
+public class VignetteFilter extends ImageWallpaperFilter {
+    private static final String TAG = VignetteFilter.class.getSimpleName();
+    private static final int MAX_ALPHA = 255;
+    private static final int MIN_ALPHA = 0;
+
+    private final Paint mPaint;
+    private final Matrix mMatrix;
+    private final Shader mShader;
+
+    private float mXOffset;
+    private float mYOffset;
+    private float mCenterX;
+    private float mCenterY;
+    private float mStretchX;
+    private float mStretchY;
+    private boolean mCalculateOffsetNeeded;
+
+    public VignetteFilter() {
+        mPaint = new Paint();
+        mMatrix = new Matrix();
+        mShader = new RadialGradient(0, 0, 1,
+                Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
+    }
+
+    @Override
+    public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
+        DisplayInfo info = getTransformer().getDisplayInfo();
+
+        if (mCalculateOffsetNeeded) {
+            int lw = info.logicalWidth;
+            int lh = info.logicalHeight;
+            mCenterX = lw / 2 + (dest.width() - lw) * mXOffset;
+            mCenterY = lh / 2 + (dest.height() - lh) * mYOffset;
+            mStretchX = info.logicalWidth / 2;
+            mStretchY = info.logicalHeight / 2;
+            mCalculateOffsetNeeded = false;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "apply: lw=" + info.logicalWidth + ", lh=" + info.logicalHeight
+                    + ", center=(" + mCenterX + "," + mCenterY + ")"
+                    + ", stretch=(" + mStretchX + "," + mStretchY + ")");
+        }
+
+        mMatrix.reset();
+        mMatrix.postTranslate(mCenterX, mCenterY);
+        mMatrix.postScale(mStretchX, mStretchY, mCenterX, mCenterY);
+        mShader.setLocalMatrix(mMatrix);
+        mPaint.setShader(mShader);
+
+        ImageWallpaperTransformer transformer = getTransformer();
+
+        // If it is not in the transition, we need to set the property according to aod state.
+        if (!transformer.isTransiting()) {
+            mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
+        }
+
+        c.drawRect(dest, mPaint);
+    }
+
+    @Override
+    public void onAnimatorUpdate(ValueAnimator animator) {
+        ImageWallpaperTransformer transformer = getTransformer();
+        float fraction = animator.getAnimatedFraction();
+        float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
+        mPaint.setAlpha((int) (factor * MAX_ALPHA));
+    }
+
+    @Override
+    public void onTransitionAmountUpdate(float amount) {
+        mPaint.setAlpha((int) (amount * MAX_ALPHA));
+    }
+
+    @Override
+    public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
+        if (force || mXOffset != xOffset || mYOffset != yOffset) {
+            mXOffset = xOffset;
+            mYOffset = yOffset;
+            mCalculateOffsetNeeded = true;
+        }
+    }
+
+    @VisibleForTesting
+    public PointF getCenterPoint() {
+        return new PointF(mCenterX, mCenterY);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 1844df5..fbc1c20 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -21,7 +21,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -42,19 +41,18 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateController;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Consumer;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 // Need to run on the main thread because KeyguardSliceView$Row init checks for
@@ -62,7 +60,6 @@
 // the keyguard_clcok_switch layout is inflated.
 @RunWithLooper(setAsMainLooper = true)
 public class KeyguardClockSwitchTest extends SysuiTestCase {
-    private PluginManager mPluginManager;
     private FrameLayout mClockContainer;
     private StatusBarStateController.StateListener mStateListener;
 
@@ -73,7 +70,6 @@
 
     @Before
     public void setUp() {
-        mPluginManager = mDependency.injectMockDependency(PluginManager.class);
         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
         mKeyguardClockSwitch =
                 (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
@@ -84,29 +80,12 @@
     }
 
     @Test
-    public void onAttachToWindow_addPluginListener() {
-        mKeyguardClockSwitch.onAttachedToWindow();
-
-        ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
-        verify(mPluginManager).addPluginListener(listener.capture(), eq(ClockPlugin.class));
-    }
-
-    @Test
-    public void onDetachToWindow_removePluginListener() {
-        mKeyguardClockSwitch.onDetachedFromWindow();
-
-        ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
-        verify(mPluginManager).removePluginListener(listener.capture());
-    }
-
-    @Test
     public void onPluginConnected_showPluginClock() {
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
 
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
 
         verify(mClockView).setVisibility(GONE);
         assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer);
@@ -122,9 +101,8 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getBigClockView()).thenReturn(pluginView);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
         // WHEN the plugin is connected
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
         // THEN the big clock container is visible and it is the parent of the
         // big clock view.
         assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE);
@@ -134,8 +112,7 @@
     @Test
     public void onPluginConnected_nullView() {
         ClockPlugin plugin = mock(ClockPlugin.class);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
         verify(mClockView, never()).setVisibility(GONE);
     }
 
@@ -144,27 +121,36 @@
         // GIVEN a plugin has already connected
         ClockPlugin plugin1 = mock(ClockPlugin.class);
         when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin1, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1);
         // WHEN a second plugin is connected
         ClockPlugin plugin2 = mock(ClockPlugin.class);
         when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        listener.onPluginConnected(plugin2, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2);
         // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
         assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
         assertThat(plugin1.getView().getParent()).isNull();
     }
 
     @Test
+    public void onPluginConnected_darkAmountInitialized() {
+        // GIVEN that the dark amount has already been set
+        mKeyguardClockSwitch.setDarkAmount(0.5f);
+        // WHEN a plugin is connected
+        ClockPlugin plugin = mock(ClockPlugin.class);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        // THEN dark amount should be initalized on the plugin.
+        verify(plugin).setDarkAmount(0.5f);
+    }
+
+    @Test
     public void onPluginDisconnected_showDefaultClock() {
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
         mClockView.setVisibility(GONE);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
 
-        listener.onPluginConnected(plugin, null);
-        listener.onPluginDisconnected(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
 
         verify(mClockView).setVisibility(VISIBLE);
         assertThat(plugin.getView().getParent()).isNull();
@@ -180,10 +166,9 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getBigClockView()).thenReturn(pluginView);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
-        // WHEN the plugin is disconnected
-        listener.onPluginDisconnected(plugin);
+        // WHEN the plugin is connected and then disconnected
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
         // THEN the big lock container is GONE and the big clock view doesn't have
         // a parent.
         assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE);
@@ -193,41 +178,23 @@
     @Test
     public void onPluginDisconnected_nullView() {
         ClockPlugin plugin = mock(ClockPlugin.class);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
-        listener.onPluginDisconnected(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
         verify(mClockView, never()).setVisibility(GONE);
     }
 
     @Test
-    public void onPluginDisconnected_firstOfTwoDisconnected() {
-        // GIVEN two plugins are connected
-        ClockPlugin plugin1 = mock(ClockPlugin.class);
-        when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin1, null);
-        ClockPlugin plugin2 = mock(ClockPlugin.class);
-        when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        listener.onPluginConnected(plugin2, null);
-        // WHEN the first plugin is disconnected
-        listener.onPluginDisconnected(plugin1);
-        // THEN the view from the second plugin is still a child of KeyguardClockSwitch.
-        assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
-        assertThat(plugin1.getView().getParent()).isNull();
-    }
-
-    @Test
     public void onPluginDisconnected_secondOfTwoDisconnected() {
         // GIVEN two plugins are connected
         ClockPlugin plugin1 = mock(ClockPlugin.class);
         when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin1, null);
+        Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer();
+        consumer.accept(plugin1);
         ClockPlugin plugin2 = mock(ClockPlugin.class);
         when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        listener.onPluginConnected(plugin2, null);
+        consumer.accept(plugin2);
         // WHEN the second plugin is disconnected
-        listener.onPluginDisconnected(plugin2);
+        consumer.accept(null);
         // THEN the default clock should be shown.
         verify(mClockView).setVisibility(VISIBLE);
         assertThat(plugin1.getView().getParent()).isNull();
@@ -246,8 +213,7 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
 
         mKeyguardClockSwitch.setTextColor(Color.WHITE);
 
@@ -271,8 +237,7 @@
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
         Style style = mock(Style.class);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
 
         mKeyguardClockSwitch.setStyle(style);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 521d5d1..53ad0b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -16,17 +16,12 @@
 
 package com.android.systemui;
 
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.graphics.Bitmap;
@@ -58,13 +53,15 @@
     @Mock private SurfaceHolder mSurfaceHolder;
     @Mock private DisplayInfo mDisplayInfo;
 
-    CountDownLatch mEventCountdown;
+    private CountDownLatch mEventCountdown;
+    private CountDownLatch mAmbientEventCountdown;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         mEventCountdown = new CountDownLatch(1);
+        mAmbientEventCountdown = new CountDownLatch(2);
 
         mImageWallpaper = new ImageWallpaper() {
             @Override
@@ -86,6 +83,11 @@
                         assertTrue("mFixedSizeAllowed should be true", allowed);
                         mEventCountdown.countDown();
                     }
+
+                    @Override
+                    public void onAmbientModeChanged(boolean inAmbientMode, long duration) {
+                        mAmbientEventCountdown.countDown();
+                    }
                 };
             }
         };
@@ -132,4 +134,23 @@
         verify(mSurfaceHolder, times(1)).setFixedSize(ImageWallpaper.DrawableEngine.MIN_BACKGROUND_WIDTH, ImageWallpaper.DrawableEngine.MIN_BACKGROUND_HEIGHT);
     }
 
+    @Test
+    public void testDeliversAmbientModeChanged() {
+        ImageWallpaper.DrawableEngine wallpaperEngine =
+                (ImageWallpaper.DrawableEngine) mImageWallpaper.onCreateEngine();
+
+        assertEquals("setFixedSizeAllowed should have been called.",
+                0, mEventCountdown.getCount());
+
+        wallpaperEngine.setCreated(true);
+        wallpaperEngine.doAmbientModeChanged(false, 1000);
+        assertFalse("ambient mode should be false", wallpaperEngine.isInAmbientMode());
+        assertEquals("onAmbientModeChanged should have been called.",
+                1, mAmbientEventCountdown.getCount());
+
+        wallpaperEngine.doAmbientModeChanged(true, 1000);
+        assertTrue("ambient mode should be true", wallpaperEngine.isInAmbientMode());
+        assertEquals("onAmbientModeChanged should have been called.",
+                0, mAmbientEventCountdown.getCount());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 146c5d6..8eb42c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -54,7 +54,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.function.Consumer;
 
@@ -94,6 +94,7 @@
                 },
                 visible -> mScrimVisibility = visible, mDozeParamenters, mAlarmManager);
         mScrimController.setHasBackdrop(false);
+        mScrimController.setWallpaperSupportsAmbientMode(false);
     }
 
     @Test
@@ -474,6 +475,26 @@
     }
 
     @Test
+    public void testHoldsPulsingWallpaperAnimationLock() {
+        // Pre-conditions
+        mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {
+            @Override
+            public boolean isFadeOutWallpaper() {
+                return true;
+            }
+        });
+        mScrimController.finishAnimationsImmediately();
+        reset(mWakeLock);
+
+        mScrimController.onHideWallpaperTimeout();
+        verify(mWakeLock).acquire();
+        verify(mWakeLock, never()).release();
+        mScrimController.finishAnimationsImmediately();
+        verify(mWakeLock).release();
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+    }
+
+    @Test
     public void testWillHideAodWallpaper() {
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
@@ -483,6 +504,34 @@
     }
 
     @Test
+    public void testWillHidePulsingWallpaper_withRequestFadeOut() {
+        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {
+            @Override
+            public boolean isFadeOutWallpaper() {
+                return true;
+            }
+        });
+        verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
+    }
+
+    @Test
+    public void testDoesNotHidePulsingWallpaper_withoutRequestFadeOut() {
+        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {});
+        verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any());
+    }
+
+    @Test
+    public void testDoesNotHidePulsingWallpaper_withoutCallback() {
+        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mScrimController.transitionTo(ScrimState.PULSING);
+        verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any());
+    }
+
+    @Test
     public void testConservesExpansionOpacityAfterTransition() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.setPanelExpansion(0.5f);
@@ -578,7 +627,7 @@
     @Test
     public void testEatsTouchEvent() {
         HashSet<ScrimState> eatsTouches =
-                new HashSet<>(Arrays.asList(ScrimState.AOD, ScrimState.PULSING));
+                new HashSet<>(Collections.singletonList(ScrimState.AOD));
         for (ScrimState state : ScrimState.values()) {
             if (state == ScrimState.UNINITIALIZED) {
                 continue;
@@ -738,5 +787,4 @@
             callback.run();
         }
     }
-
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java
deleted file mode 100644
index 5c34730..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.policy;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
-        .RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS;
-
-import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static java.lang.Thread.sleep;
-
-import android.metrics.LogMaker;
-import android.support.test.filters.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.MessageHandler;
-import android.testing.TestableLooper.RunWithLooper;
-import android.util.Log;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class IconLoggerImplTest extends SysuiTestCase {
-
-    private MetricsLogger mMetricsLogger;
-    private IconLoggerImpl mIconLogger;
-    private TestableLooper mTestableLooper;
-    private MessageHandler mMessageHandler;
-
-    @Before
-    public void setup() {
-        IconLoggerImpl.MIN_LOG_INTERVAL = 5; // Low interval for testing
-        mMetricsLogger = mock(MetricsLogger.class);
-        mTestableLooper = TestableLooper.get(this);
-        mMessageHandler = mock(MessageHandler.class);
-        mTestableLooper.setMessageHandler(mMessageHandler);
-        String[] iconArray = new String[] {
-                "test_icon_1",
-                "test_icon_2",
-        };
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.array.config_statusBarIcons, iconArray);
-        mIconLogger = new IconLoggerImpl(mContext, mTestableLooper.getLooper(), mMetricsLogger);
-        when(mMessageHandler.onMessageHandled(any())).thenReturn(true);
-        clearInvocations(mMetricsLogger);
-    }
-
-    @Test
-    public void testIconShown() throws InterruptedException {
-        // Should only get one message, for the same icon shown twice.
-        mIconLogger.onIconShown("test_icon_2");
-        mIconLogger.onIconShown("test_icon_2");
-
-        // There should be some delay before execute.
-        mTestableLooper.processAllMessages();
-        verify(mMessageHandler, never()).onMessageHandled(any());
-
-        sleep(10);
-        mTestableLooper.processAllMessages();
-        verify(mMessageHandler, times(1)).onMessageHandled(any());
-    }
-
-    @Test
-    public void testIconHidden() throws InterruptedException {
-        // Add the icon so that it can be removed.
-        mIconLogger.onIconShown("test_icon_2");
-        sleep(10);
-        mTestableLooper.processAllMessages();
-        clearInvocations(mMessageHandler);
-
-        // Should only get one message, for the same icon shown twice.
-        mIconLogger.onIconHidden("test_icon_2");
-        mIconLogger.onIconHidden("test_icon_2");
-
-        // There should be some delay before execute.
-        mTestableLooper.processAllMessages();
-        verify(mMessageHandler, never()).onMessageHandled(any());
-
-        sleep(10);
-        mTestableLooper.processAllMessages();
-        verify(mMessageHandler, times(1)).onMessageHandled(any());
-    }
-
-    @Test
-    public void testLog() throws InterruptedException {
-        mIconLogger.onIconShown("test_icon_2");
-        sleep(10);
-        mTestableLooper.processAllMessages();
-
-        verify(mMetricsLogger).write(argThat(maker -> {
-            if (IconLoggerImpl.MIN_LOG_INTERVAL >
-                    (long) maker.getTaggedData(RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS)) {
-                Log.e("IconLoggerImplTest", "Invalid latency "
-                        + maker.getTaggedData(RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS));
-                return false;
-            }
-            if (1 != (int) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)) {
-                Log.e("IconLoggerImplTest", "Invalid icon count "
-                        + maker.getTaggedData(FIELD_NUM_STATUS_ICONS));
-                return false;
-            }
-            return true;
-        }));
-    }
-
-    @Test
-    public void testBitField() throws InterruptedException {
-        mIconLogger.onIconShown("test_icon_2");
-        sleep(10);
-        mTestableLooper.processAllMessages();
-
-        verify(mMetricsLogger).write(argThat(maker -> {
-            if ((1 << 1) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) {
-                Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString(
-                        (Integer) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)));
-                return false;
-            }
-            return true;
-        }));
-
-        mIconLogger.onIconShown("test_icon_1");
-        sleep(10);
-        mTestableLooper.processAllMessages();
-
-        verify(mMetricsLogger).write(argThat(maker -> {
-            if ((1 << 1 | 1 << 0) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) {
-                Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString(
-                        (Integer) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)));
-                return false;
-            }
-            return true;
-        }));
-
-        mIconLogger.onIconHidden("test_icon_2");
-        sleep(10);
-        mTestableLooper.processAllMessages();
-
-        verify(mMetricsLogger).write(argThat(maker -> {
-            if ((1 << 0) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) {
-                Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString(
-                        (Integer) maker.getTaggedData(FIELD_STATUS_ICONS)));
-                return false;
-            }
-            return true;
-        }));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 2266b47..37a56a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -20,6 +20,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import android.app.RemoteInput;
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
@@ -51,6 +52,8 @@
         resources.addOverride(R.bool.config_smart_replies_in_notifications_enabled, true);
         resources.addOverride(
                 R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts, 7);
+        resources.addOverride(
+                R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false);
         mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
     }
 
@@ -104,6 +107,51 @@
         assertEquals(5, mConstants.getMaxSqueezeRemeasureAttempts());
     }
 
+    @Test
+    public void testGetEffectiveEditChoicesBeforeSendingWithNoConfig() {
+        overrideSetting("enabled=true");
+        triggerConstantsOnChange();
+        assertFalse(
+                mConstants.getEffectiveEditChoicesBeforeSending(
+                        RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
+        assertTrue(
+                mConstants.getEffectiveEditChoicesBeforeSending(
+                        RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED));
+        assertFalse(
+                mConstants.getEffectiveEditChoicesBeforeSending(
+                        RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
+    }
+
+    @Test
+    public void testGetEffectiveEditChoicesBeforeSendingWithEnabledConfig() {
+        overrideSetting("enabled=true,edit_choices_before_sending=true");
+        triggerConstantsOnChange();
+        assertTrue(
+                mConstants.getEffectiveEditChoicesBeforeSending(
+                        RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
+        assertTrue(
+                mConstants.getEffectiveEditChoicesBeforeSending(
+                        RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED));
+        assertFalse(
+                mConstants.getEffectiveEditChoicesBeforeSending(
+                        RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
+    }
+
+    @Test
+    public void testGetEffectiveEditChoicesBeforeSendingWithDisabledConfig() {
+        overrideSetting("enabled=true,edit_choices_before_sending=false");
+        triggerConstantsOnChange();
+        assertFalse(
+                mConstants.getEffectiveEditChoicesBeforeSending(
+                        RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
+        assertTrue(
+                mConstants.getEffectiveEditChoicesBeforeSending(
+                        RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED));
+        assertFalse(
+                mConstants.getEffectiveEditChoicesBeforeSending(
+                        RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
+    }
+
     private void overrideSetting(String flags) {
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java
new file mode 100644
index 0000000..c44a366
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.systemui.wallpaper;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.FeatureFlagUtils;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AodMaskViewTest extends SysuiTestCase {
+    private AodMaskView mMaskView;
+    private DisplayInfo mDisplayInfo;
+    private ImageWallpaperTransformer mTransformer;
+
+    @Before
+    public void setUp() throws Exception {
+        DisplayManager displayManager =
+                spy((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE));
+        doNothing().when(displayManager).registerDisplayListener(any(), any());
+        mContext.addMockSystemService(DisplayManager.class, displayManager);
+
+        WallpaperManager wallpaperManager =
+                spy((WallpaperManager) mContext.getSystemService(Context.WALLPAPER_SERVICE));
+        doReturn(null).when(wallpaperManager).getWallpaperInfo();
+        mContext.addMockSystemService(WallpaperManager.class, wallpaperManager);
+
+        mTransformer = spy(new ImageWallpaperTransformer(null /* listener */));
+        mMaskView = spy(new AodMaskView(getContext(), null /* attrs */, mTransformer));
+        mDisplayInfo = new DisplayInfo();
+
+        ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
+                .getDefaultDisplay().getDisplayInfo(mDisplayInfo);
+
+        FeatureFlagUtils.setEnabled(
+                mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, true);
+    }
+
+    @After
+    public void tearDown() {
+        FeatureFlagUtils.setEnabled(
+                mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, false);
+    }
+
+    @Test
+    public void testCreateMaskView_TransformerIsNotNull() {
+        assertNotNull("mTransformer should not be null", mTransformer);
+    }
+
+    @Test
+    public void testAodMaskView_ShouldNotClickable() {
+        assertFalse("MaskView should not be clickable", mMaskView.isClickable());
+    }
+
+    @Test
+    public void testAodMaskView_OnSizeChange_ShouldUpdateTransformerOffsets() {
+        mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0);
+        verify(mTransformer, times(1)).updateOffsets();
+    }
+
+    @Test
+    public void testAodMaskView_OnDraw_ShouldDrawTransformedImage() {
+        Canvas c = new Canvas();
+        RectF bounds = new RectF(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0);
+        mMaskView.onStatePreChange(0, 1);
+        mMaskView.onDraw(c);
+        verify(mTransformer, times(1)).drawTransformedImage(c, null, null, bounds);
+    }
+
+    @Test
+    public void testAodMaskView_IsDozing_ShouldUpdateAmbientModeState() {
+        doNothing().when(mMaskView).setAnimatorProperty(anyBoolean());
+        mMaskView.onStatePreChange(0, 1);
+        mMaskView.onDozingChanged(true);
+        verify(mTransformer, times(1)).updateAmbientModeState(true);
+    }
+
+    @Test
+    public void testAodMaskView_IsDozing_ShouldDoTransitionOrDrawFinalFrame() {
+        doNothing().when(mMaskView).setAnimatorProperty(anyBoolean());
+        mMaskView.onStatePreChange(0, 1);
+        mMaskView.onDozingChanged(true);
+        mMaskView.onStatePostChange();
+        mMaskView.onDozingChanged(false);
+        verify(mMaskView, times(1)).invalidate();
+        verify(mMaskView, times(1)).setAnimatorProperty(false);
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java
new file mode 100644
index 0000000..55b0aae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2009 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.systemui.wallpaper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ImageWallpaperTransformerTest extends SysuiTestCase {
+    private DisplayInfo mDisplayInfo;
+    private Bitmap mBitmap;
+    private Canvas mCanvas;
+    private RectF mDestination;
+
+    @Before
+    public void setUp() throws Exception {
+        mDisplayInfo = new DisplayInfo();
+        ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
+                .getDefaultDisplay().getDisplayInfo(mDisplayInfo);
+        int dimension = Math.max(mDisplayInfo.logicalHeight, mDisplayInfo.logicalWidth);
+        mBitmap = Bitmap.createBitmap(dimension, dimension, Bitmap.Config.ARGB_8888);
+        mCanvas = new Canvas(mBitmap);
+        mCanvas.drawColor(Color.RED);
+        mDestination = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+    }
+
+    @Test
+    public void testVignetteFilter() {
+        VignetteFilter vignette = new VignetteFilter();
+
+        ImageWallpaperTransformer transformer = getTransformer(vignette);
+        transformer.drawTransformedImage(mCanvas, mBitmap, null, mDestination);
+
+        PointF center = vignette.getCenterPoint();
+        int p1 = mBitmap.getPixel((int) center.x, (int) center.y);
+        int p2 = mBitmap.getPixel(0, 0);
+        int p3 = mBitmap.getPixel(mBitmap.getWidth() - 1, mBitmap.getHeight() - 1);
+
+        assertThat(p1).isEqualTo(Color.RED);
+        assertThat(p2 | p3).isEqualTo(Color.BLACK);
+    }
+
+    @Test
+    public void testScrimFilter() {
+        getTransformer(new ScrimFilter())
+                .drawTransformedImage(mCanvas, mBitmap, null, mDestination);
+
+        int pixel = mBitmap.getPixel(0, 0);
+
+        // 0xff4d0000 is the result of 70% alpha pre-multiplied which is 0.7*(0,0,0)+0.3*(255,0,0).
+        assertThat(pixel).isEqualTo(0xff4d0000);
+    }
+
+    private ImageWallpaperTransformer getTransformer(ImageWallpaperFilter filter) {
+        ImageWallpaperTransformer transformer = new ImageWallpaperTransformer(null);
+        transformer.addFilter(filter);
+        transformer.updateDisplayInfo(mDisplayInfo);
+        transformer.updateOffsets();
+        transformer.updateAmbientModeState(true);
+        return transformer;
+    }
+}
diff --git a/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml b/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml
index 165aab2..a6aa64c 100644
--- a/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml
+++ b/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml
@@ -19,7 +19,7 @@
      <!-- Name of a font family to use for body text. -->
     <string name="config_bodyFontFamily" translatable="false">source-sans-pro</string>
     <!-- Name of a font family to use for medium body text. -->
-    <string name="config_bodyFontFamilyMedium" translatable="false">source-sans-pro-medium</string>
+    <string name="config_bodyFontFamilyMedium" translatable="false">source-sans-pro-semi-bold</string>
     <!-- Name of a font family to use for headlines. If empty, falls back to platform default -->
     <string name="config_headlineFontFamily" translatable="false">arbutus-slab</string>
     <!-- Name of the font family used for system surfaces where the font should use medium weight -->
diff --git a/packages/overlays/FontArvoLatoOverlay/res/values/config.xml b/packages/overlays/FontArvoLatoOverlay/res/values/config.xml
index 229c578..4e70d72 100644
--- a/packages/overlays/FontArvoLatoOverlay/res/values/config.xml
+++ b/packages/overlays/FontArvoLatoOverlay/res/values/config.xml
@@ -19,10 +19,10 @@
      <!-- Name of a font family to use for body text. -->
     <string name="config_bodyFontFamily" translatable="false">lato</string>
     <!-- Name of a font family to use for medium body text. -->
-    <string name="config_bodyFontFamilyMedium" translatable="false">lato-medium</string>
+    <string name="config_bodyFontFamilyMedium" translatable="false">lato-bold</string>
     <!-- Name of a font family to use for headlines. If empty, falls back to platform default -->
     <string name="config_headlineFontFamily" translatable="false">arvo</string>
     <!-- Name of the font family used for system surfaces where the font should use medium weight -->
-    <string name="config_headlineFontFamilyMedium" translatable="false">arvo-medium</string>
+    <string name="config_headlineFontFamilyMedium" translatable="false">arvo-bold</string>
 </resources>
 
diff --git a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
index a40cf1c..d969c69 100644
--- a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
+++ b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
@@ -16,6 +16,9 @@
 
 #define LOG_TAG "PacProcessor"
 
+#include <stdlib.h>
+#include <string>
+
 #include <utils/Log.h>
 #include <utils/Mutex.h>
 #include "android_runtime/AndroidRuntime.h"
@@ -23,24 +26,24 @@
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 
-#include "proxy_resolver_v8.h"
+#include "proxy_resolver_v8_wrapper.h"
 
 namespace android {
 
-net::ProxyResolverV8* proxyResolver = NULL;
+ProxyResolverV8Handle* proxyResolver = NULL;
 bool pacSet = false;
 
-String16 jstringToString16(JNIEnv* env, jstring jstr) {
+std::u16string jstringToString16(JNIEnv* env, jstring jstr) {
     const jchar* str = env->GetStringCritical(jstr, 0);
-    String16 str16(reinterpret_cast<const char16_t*>(str),
+    std::u16string str16(reinterpret_cast<const char16_t*>(str),
                    env->GetStringLength(jstr));
     env->ReleaseStringCritical(jstr, str);
     return str16;
 }
 
-jstring string16ToJstring(JNIEnv* env, String16 string) {
-    const char16_t* str = string.string();
-    size_t len = string.size();
+jstring string16ToJstring(JNIEnv* env, std::u16string string) {
+    const char16_t* str = string.data();
+    size_t len = string.length();
 
     return env->NewString(reinterpret_cast<const jchar*>(str), len);
 }
@@ -48,7 +51,7 @@
 static jboolean com_android_pacprocessor_PacNative_createV8ParserNativeLocked(JNIEnv* /* env */,
         jobject) {
     if (proxyResolver == NULL) {
-        proxyResolver = new net::ProxyResolverV8(net::ProxyResolverJSBindings::CreateDefault());
+        proxyResolver = ProxyResolverV8Handle_new();
         pacSet = false;
         return JNI_FALSE;
     }
@@ -58,7 +61,7 @@
 static jboolean com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked(JNIEnv* /* env */,
         jobject) {
     if (proxyResolver != NULL) {
-        delete proxyResolver;
+        ProxyResolverV8Handle_delete(proxyResolver);
         proxyResolver = NULL;
         return JNI_FALSE;
     }
@@ -67,14 +70,14 @@
 
 static jboolean com_android_pacprocessor_PacNative_setProxyScriptNativeLocked(JNIEnv* env, jobject,
         jstring script) {
-    String16 script16 = jstringToString16(env, script);
+    std::u16string script16 = jstringToString16(env, script);
 
     if (proxyResolver == NULL) {
         ALOGE("V8 Parser not started when setting PAC script");
         return JNI_TRUE;
     }
 
-    if (proxyResolver->SetPacScript(script16) != OK) {
+    if (ProxyResolverV8Handle_SetPacScript(proxyResolver, script16.data()) != OK) {
         ALOGE("Unable to set PAC script");
         return JNI_TRUE;
     }
@@ -85,9 +88,8 @@
 
 static jstring com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked(JNIEnv* env, jobject,
         jstring url, jstring host) {
-    String16 url16 = jstringToString16(env, url);
-    String16 host16 = jstringToString16(env, host);
-    String16 ret;
+    std::u16string url16 = jstringToString16(env, url);
+    std::u16string host16 = jstringToString16(env, host);
 
     if (proxyResolver == NULL) {
         ALOGE("V8 Parser not initialized when running PAC script");
@@ -99,12 +101,14 @@
         return NULL;
     }
 
-    if (proxyResolver->GetProxyForURL(url16, host16, &ret) != OK) {
-        String8 ret8(ret);
-        ALOGE("Error Running PAC: %s", ret8.string());
+    std::unique_ptr<char16_t, decltype(&free)> result = std::unique_ptr<char16_t, decltype(&free)>(
+        ProxyResolverV8Handle_GetProxyForURL(proxyResolver, url16.data(), host16.data()), &free);
+    if (result.get() == NULL) {
+        ALOGE("Error Running PAC");
         return NULL;
     }
 
+    std::u16string ret(result.get());
     jstring jret = string16ToJstring(env, ret);
 
     return jret;
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 665773c..aff5e13 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -5325,7 +5325,7 @@
     // OS: P
     ACCESSIBILITY_VIBRATION = 1292;
 
-    // OPEN: Settings > Accessibility > Vibration > Ring & notification vibration
+    // OPEN: Settings > Accessibility > Vibration > Notification vibration
     // CATEGORY: SETTINGS
     // OS: P
     ACCESSIBILITY_VIBRATION_NOTIFICATION = 1293;
@@ -6763,6 +6763,11 @@
     // OS: Q
     ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED = 1619;
 
+    // OPEN: Settings > Accessibility > Vibration > Ring vibration
+    // CATEGORY: SETTINGS
+    // OS: Q
+    ACCESSIBILITY_VIBRATION_RING = 1620;
+
     // ---- End Q Constants, all Q constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/Android.bp b/services/Android.bp
index 01734f4..31385ed 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -17,13 +17,16 @@
     static_libs: [
         "services.core",
         "services.accessibility",
+        "services.appprediction",
         "services.appwidget",
         "services.autofill",
         "services.backup",
         "services.companion",
         "services.contentcapture",
+        "services.contentsuggestions",
         "services.coverage",
         "services.devicepolicy",
+        "services.ipmemorystore",
         "services.midi",
         "services.net",
         "services.print",
diff --git a/services/appprediction/Android.bp b/services/appprediction/Android.bp
new file mode 100644
index 0000000..a7be587
--- /dev/null
+++ b/services/appprediction/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+    name: "services.appprediction",
+    srcs: ["java/**/*.java"],
+    libs: ["services.core"],
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
new file mode 100644
index 0000000..833eaa0
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -0,0 +1,156 @@
+/*
+ * 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.appprediction;
+
+import static android.Manifest.permission.MANAGE_APP_PREDICTIONS;
+import static android.content.Context.APP_PREDICTION_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.app.prediction.IPredictionManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to predict app and shortcut usage.
+ *
+ * <p>The data collected by this service can be analyzed and combined with other sources to provide
+ * predictions in different areas of the system such as Launcher and Share sheet.
+ */
+public class AppPredictionManagerService extends
+        AbstractMasterSystemService<AppPredictionManagerService, AppPredictionPerUserService> {
+
+    private static final String TAG = AppPredictionManagerService.class.getSimpleName();
+
+    private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+    public AppPredictionManagerService(Context context) {
+        super(context, new FrameworkResourcesServiceNameResolver(context,
+                com.android.internal.R.string.config_defaultAppPredictionService), null);
+    }
+
+    @Override
+    protected AppPredictionPerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+        return new AppPredictionPerUserService(this, mLock, resolvedUserId);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(APP_PREDICTION_SERVICE, new PredictionManagerServiceStub());
+    }
+
+    @Override
+    protected void enforceCallingPermissionForManagement() {
+        getContext().enforceCallingPermission(MANAGE_APP_PREDICTIONS, TAG);
+    }
+
+    @Override
+    protected int getMaximumTemporaryServiceDurationMs() {
+        return MAX_TEMP_SERVICE_DURATION_MS;
+    }
+
+    private class PredictionManagerServiceStub extends IPredictionManager.Stub {
+
+        @Override
+        public void createPredictionSession(@NonNull AppPredictionContext context,
+                @NonNull AppPredictionSessionId sessionId) {
+            runForUserLocked((service) ->
+                    service.onCreatePredictionSessionLocked(context, sessionId));
+        }
+
+        @Override
+        public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
+                @NonNull AppTargetEvent event) {
+            runForUserLocked((service) -> service.notifyAppTargetEventLocked(sessionId, event));
+        }
+
+        @Override
+        public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId,
+                @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
+            runForUserLocked((service) ->
+                    service.notifyLocationShownLocked(sessionId, launchLocation, targetIds));
+        }
+
+        @Override
+        public void sortAppTargets(@NonNull AppPredictionSessionId sessionId,
+                @NonNull ParceledListSlice targets,
+                IPredictionCallback callback) {
+            runForUserLocked((service) ->
+                    service.sortAppTargetsLocked(sessionId, targets, callback));
+        }
+
+        @Override
+        public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+                @NonNull IPredictionCallback callback) {
+            runForUserLocked((service) ->
+                    service.registerPredictionUpdatesLocked(sessionId, callback));
+        }
+
+        public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+                @NonNull IPredictionCallback callback) {
+            runForUserLocked((service) ->
+                    service.unregisterPredictionUpdatesLocked(sessionId, callback));
+        }
+
+        @Override
+        public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+            runForUserLocked((service) -> service.requestPredictionUpdateLocked(sessionId));
+        }
+
+        @Override
+        public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
+            runForUserLocked((service) -> service.onDestroyPredictionSessionLocked(sessionId));
+        }
+
+        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err,
+                @NonNull String[] args, @Nullable ShellCallback callback,
+                @NonNull ResultReceiver resultReceiver) throws RemoteException {
+            new AppPredictionManagerServiceShellCommand(AppPredictionManagerService.this)
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+
+        private void runForUserLocked(@NonNull Consumer<AppPredictionPerUserService> c) {
+            final int userId = UserHandle.getCallingUserId();
+            // TODO(b/111701043): Determine what permission model we want for this
+            long origId = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    final AppPredictionPerUserService service = getServiceForUserLocked(userId);
+                    c.accept(service);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java
new file mode 100644
index 0000000..ed7cc67
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java
@@ -0,0 +1,83 @@
+/*
+ * 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.appprediction;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the AppPredictionManagerService.
+ */
+public class AppPredictionManagerServiceShellCommand extends ShellCommand {
+
+    private static final String TAG =
+            AppPredictionManagerServiceShellCommand.class.getSimpleName();
+
+    private final AppPredictionManagerService mService;
+
+    public AppPredictionManagerServiceShellCommand(@NonNull AppPredictionManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch (cmd) {
+            case "set": {
+                final String what = getNextArgRequired();
+                switch (what) {
+                    case "temporary-service": {
+                        final int userId = Integer.parseInt(getNextArgRequired());
+                        String serviceName = getNextArg();
+                        if (serviceName == null) {
+                            mService.resetTemporaryService(userId);
+                            return 0;
+                        }
+                        final int duration = Integer.parseInt(getNextArgRequired());
+                        mService.setTemporaryService(userId, serviceName, duration);
+                        pw.println("AppPredictionService temporarily set to " + serviceName
+                                + " for " + duration + "ms");
+                        break;
+                    }
+                }
+            }
+            break;
+            default:
+                return handleDefaultCommands(cmd);
+        }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        try (PrintWriter pw = getOutPrintWriter()) {
+            pw.println("AppPredictionManagerService commands:");
+            pw.println("  help");
+            pw.println("    Prints this help text.");
+            pw.println("");
+            pw.println("  set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+            pw.println("    Temporarily (for DURATION ms) changes the service implemtation.");
+            pw.println("    To reset, call with just the USER_ID argument.");
+            pw.println("");
+        }
+    }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
new file mode 100644
index 0000000..24d592c
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -0,0 +1,218 @@
+/*
+ * 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.appprediction;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.service.appprediction.AppPredictionService;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link AppPredictionManagerService}.
+ */
+public class AppPredictionPerUserService extends
+        AbstractPerUserSystemService<AppPredictionPerUserService, AppPredictionManagerService>
+             implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks {
+
+    private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
+
+    @Nullable
+    @GuardedBy("mLock")
+    private RemoteAppPredictionService mRemoteService;
+
+    protected AppPredictionPerUserService(AppPredictionManagerService master,
+            Object lock, int userId) {
+        super(master, lock, userId);
+    }
+
+    @Override // from PerUserSystemService
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws NameNotFoundException {
+
+        ServiceInfo si;
+        try {
+            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException e) {
+            throw new NameNotFoundException("Could not get service for " + serviceComponent);
+        }
+        // TODO(b/111701043): must check that either the service is from a system component,
+        // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+        // OEMs are implementing the real service and also verify the proper permissions
+        return si;
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected boolean updateLocked(boolean disabled) {
+        final boolean enabledChanged = super.updateLocked(disabled);
+        if (enabledChanged) {
+            if (!isEnabledLocked()) {
+                // Clear the remote service for the next call
+                mRemoteService = null;
+            }
+        }
+        return enabledChanged;
+    }
+
+    /**
+     * Notifies the service of a new prediction session.
+     */
+    @GuardedBy("mLock")
+    public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
+            @NonNull AppPredictionSessionId sessionId) {
+        final RemoteAppPredictionService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.onCreatePredictionSession(context, sessionId);
+        }
+    }
+
+    /**
+     * Records an app target event to the service.
+     */
+    @GuardedBy("mLock")
+    public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId,
+            @NonNull AppTargetEvent event) {
+        final RemoteAppPredictionService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.notifyAppTargetEvent(sessionId, event);
+        }
+    }
+
+    /**
+     * Records when a launch location is shown.
+     */
+    @GuardedBy("mLock")
+    public void notifyLocationShownLocked(@NonNull AppPredictionSessionId sessionId,
+            @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
+        final RemoteAppPredictionService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.notifyLocationShown(sessionId, launchLocation, targetIds);
+        }
+    }
+
+    /**
+     * Requests the service to sort a list of apps or shortcuts.
+     */
+    @GuardedBy("mLock")
+    public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId,
+            @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
+        final RemoteAppPredictionService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.sortAppTargets(sessionId, targets, callback);
+        }
+    }
+
+    /**
+     * Registers a callback for continuous updates of predicted apps or shortcuts.
+     */
+    @GuardedBy("mLock")
+    public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
+            @NonNull IPredictionCallback callback) {
+        final RemoteAppPredictionService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.registerPredictionUpdates(sessionId, callback);
+        }
+    }
+
+    /**
+     * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+     */
+    @GuardedBy("mLock")
+    public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
+            @NonNull IPredictionCallback callback) {
+        final RemoteAppPredictionService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.unregisterPredictionUpdates(sessionId, callback);
+        }
+    }
+
+    /**
+     * Requests a new set of predicted apps or shortcuts.
+     */
+    @GuardedBy("mLock")
+    public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) {
+        final RemoteAppPredictionService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.requestPredictionUpdate(sessionId);
+        }
+    }
+
+    /**
+     * Notifies the service of the end of an existing prediction session.
+     */
+    @GuardedBy("mLock")
+    public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) {
+        final RemoteAppPredictionService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.onDestroyPredictionSession(sessionId);
+        }
+    }
+
+    @Override
+    public void onFailureOrTimeout(boolean timedOut) {
+        if (isDebug()) {
+            Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
+        }
+
+        // Do nothing, we are just proxying to the prediction service
+    }
+
+    @Override
+    public void onServiceDied(RemoteAppPredictionService service) {
+        if (isDebug()) {
+            Slog.d(TAG, "onServiceDied():");
+        }
+
+        // Do nothing, we are just proxying to the prediction service
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteAppPredictionService getRemoteServiceLocked() {
+        if (mRemoteService == null) {
+            final String serviceName = getComponentNameLocked();
+            if (serviceName == null) {
+                if (mMaster.verbose) {
+                    Slog.v(TAG, "getRemoteServiceLocked(): not set");
+                }
+                return null;
+            }
+            ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+            mRemoteService = new RemoteAppPredictionService(getContext(),
+                    AppPredictionService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+                    mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+        }
+
+        return mRemoteService;
+    }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
new file mode 100644
index 0000000..45ea86f
--- /dev/null
+++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.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.appprediction;
+
+import android.annotation.NonNull;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.IBinder;
+import android.service.appprediction.IPredictionService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.appprediction.AppPredictionService} implemention in another
+ * process.
+ */
+public class RemoteAppPredictionService extends
+        AbstractMultiplePendingRequestsRemoteService<RemoteAppPredictionService,
+                IPredictionService> {
+
+    private static final String TAG = "RemoteAppPredictionService";
+
+    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+    public RemoteAppPredictionService(Context context, String serviceInterface,
+            ComponentName componentName, int userId,
+            RemoteAppPredictionServiceCallbacks callback, boolean bindInstantServiceAllowed,
+            boolean verbose) {
+        super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed,
+                verbose, /* initialCapacity= */ 1);
+    }
+
+    @Override
+    protected IPredictionService getServiceInterface(IBinder service) {
+        return IPredictionService.Stub.asInterface(service);
+    }
+
+    @Override
+    protected long getTimeoutIdleBindMillis() {
+        return PERMANENT_BOUND_TIMEOUT_MS;
+    }
+
+    @Override
+    protected long getRemoteRequestMillis() {
+        return TIMEOUT_REMOTE_REQUEST_MILLIS;
+    }
+
+    /**
+     * Notifies the service of a new prediction session.
+     */
+    public void onCreatePredictionSession(@NonNull AppPredictionContext context,
+            @NonNull AppPredictionSessionId sessionId) {
+        scheduleAsyncRequest((s) -> s.onCreatePredictionSession(context, sessionId));
+    }
+
+    /**
+     * Records an app target event to the service.
+     */
+    public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
+            @NonNull AppTargetEvent event) {
+        scheduleAsyncRequest((s) -> s.notifyAppTargetEvent(sessionId, event));
+    }
+
+    /**
+     * Records when a launch location is shown.
+     */
+    public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId,
+            @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
+        scheduleAsyncRequest((s) -> s.notifyLocationShown(sessionId, launchLocation, targetIds));
+    }
+
+    /**
+     * Requests the service to sort a list of apps or shortcuts.
+     */
+    public void sortAppTargets(@NonNull AppPredictionSessionId sessionId,
+            @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
+        scheduleAsyncRequest((s) -> s.sortAppTargets(sessionId, targets, callback));
+    }
+
+
+    /**
+     * Registers a callback for continuous updates of predicted apps or shortcuts.
+     */
+    public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+            @NonNull IPredictionCallback callback) {
+        scheduleAsyncRequest((s) -> s.registerPredictionUpdates(sessionId, callback));
+    }
+
+    /**
+     * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+     */
+    public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
+            @NonNull IPredictionCallback callback) {
+        scheduleAsyncRequest((s) -> s.unregisterPredictionUpdates(sessionId, callback));
+    }
+
+    /**
+     * Requests a new set of predicted apps or shortcuts.
+     */
+    public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
+        scheduleAsyncRequest((s) -> s.requestPredictionUpdate(sessionId));
+    }
+
+    /**
+     * Notifies the service of the end of an existing prediction session.
+     */
+    public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
+        scheduleAsyncRequest((s) -> s.onDestroyPredictionSession(sessionId));
+    }
+
+    /**
+     * Failure callback
+     */
+    public interface RemoteAppPredictionServiceCallbacks
+            extends VultureCallback<RemoteAppPredictionService> {
+
+        /**
+         * Notifies a the failure or timeout of a remote call.
+         */
+        void onFailureOrTimeout(boolean timedOut);
+    }
+}
diff --git a/services/art-profile b/services/art-profile
index af9d7a9..7e4884b 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -6970,6 +6970,7 @@
 PLcom/android/server/am/ActivityManagerService$LocalService;->getMemoryStateForProcesses()Ljava/util/List;
 PLcom/android/server/am/ActivityManagerService$LocalService;->getTopVisibleActivities()Ljava/util/List;
 PLcom/android/server/am/ActivityManagerService$LocalService;->getUidProcessState(I)I
+PLcom/android/server/am/ActivityManagerService$LocalService;->isAppForeground(I)Z
 PLcom/android/server/am/ActivityManagerService$LocalService;->isCallerRecents(I)Z
 PLcom/android/server/am/ActivityManagerService$LocalService;->isRuntimeRestarted()Z
 PLcom/android/server/am/ActivityManagerService$LocalService;->isSystemReady()Z
@@ -7125,7 +7126,6 @@
 PLcom/android/server/am/ActivityManagerService;->installEncryptionUnawareProviders(I)V
 PLcom/android/server/am/ActivityManagerService;->installSystemProviders()V
 PLcom/android/server/am/ActivityManagerService;->isAllowedWhileBooting(Landroid/content/pm/ApplicationInfo;)Z
-PLcom/android/server/am/ActivityManagerService;->isAppForeground(I)Z
 PLcom/android/server/am/ActivityManagerService;->isAssistDataAllowedOnCurrentActivity()Z
 PLcom/android/server/am/ActivityManagerService;->isGetTasksAllowed(Ljava/lang/String;II)Z
 PLcom/android/server/am/ActivityManagerService;->isInLockTaskMode()Z
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index d7a2365..6b0adfb 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -850,19 +850,29 @@
             mFullBackupQueue = readFullBackupSchedule();
         }
 
-        // Register for broadcasts about package install, etc., so we can
-        // update the provider list.
+        // Register for broadcasts about package changes.
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         filter.addDataScheme("package");
-        mContext.registerReceiver(mBroadcastReceiver, filter);
+        mContext.registerReceiverAsUser(
+                mBroadcastReceiver,
+                UserHandle.of(mUserId),
+                filter,
+                /* broadcastPermission */ null,
+                /* scheduler */ null);
+
         // Register for events related to sdcard installation.
         IntentFilter sdFilter = new IntentFilter();
         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
-        mContext.registerReceiver(mBroadcastReceiver, sdFilter);
+        mContext.registerReceiverAsUser(
+                mBroadcastReceiver,
+                UserHandle.of(mUserId),
+                sdFilter,
+                /* broadcastPermission */ null,
+                /* scheduler */ null);
     }
 
     private ArrayList<FullBackupEntry> readFullBackupSchedule() {
@@ -1127,17 +1137,23 @@
         }
     }
 
-    // ----- Track installation/removal of packages -----
+    /**
+     * A {@link BroadcastReceiver} tracking changes to packages and sd cards in order to update our
+     * internal bookkeeping.
+     */
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         public void onReceive(Context context, Intent intent) {
-            if (MORE_DEBUG) Slog.d(TAG, "Received broadcast " + intent);
+            if (MORE_DEBUG) {
+                Slog.d(TAG, "Received broadcast " + intent);
+            }
 
             String action = intent.getAction();
             boolean replacing = false;
             boolean added = false;
             boolean changed = false;
             Bundle extras = intent.getExtras();
-            String[] pkgList = null;
+            String[] packageList = null;
+
             if (Intent.ACTION_PACKAGE_ADDED.equals(action)
                     || Intent.ACTION_PACKAGE_REMOVED.equals(action)
                     || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
@@ -1145,69 +1161,70 @@
                 if (uri == null) {
                     return;
                 }
-                final String pkgName = uri.getSchemeSpecificPart();
-                if (pkgName != null) {
-                    pkgList = new String[]{pkgName};
-                }
-                changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
 
-                // At package-changed we only care about looking at new transport states
+                String packageName = uri.getSchemeSpecificPart();
+                if (packageName != null) {
+                    packageList = new String[]{packageName};
+                }
+
+                changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
                 if (changed) {
-                    final String[] components =
+                    // Look at new transport states for package changed events.
+                    String[] components =
                             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
                     if (MORE_DEBUG) {
-                        Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
+                        Slog.i(TAG, "Package " + packageName + " changed");
                         for (int i = 0; i < components.length; i++) {
                             Slog.i(TAG, "   * " + components[i]);
                         }
                     }
 
                     mBackupHandler.post(
-                            () -> mTransportManager.onPackageChanged(pkgName, components));
-                    return; // nothing more to do in the PACKAGE_CHANGED case
+                            () -> mTransportManager.onPackageChanged(packageName, components));
+                    return;
                 }
 
                 added = Intent.ACTION_PACKAGE_ADDED.equals(action);
                 replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
             } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
                 added = true;
-                pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
             } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
                 added = false;
-                pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
             }
 
-            if (pkgList == null || pkgList.length == 0) {
+            if (packageList == null || packageList.length == 0) {
                 return;
             }
 
-            final int uid = extras.getInt(Intent.EXTRA_UID);
+            int uid = extras.getInt(Intent.EXTRA_UID);
             if (added) {
                 synchronized (mBackupParticipants) {
                     if (replacing) {
-                        // This is the package-replaced case; we just remove the entry
-                        // under the old uid and fall through to re-add.  If an app
-                        // just added key/value backup participation, this picks it up
-                        // as a known participant.
-                        removePackageParticipantsLocked(pkgList, uid);
+                        // Remove the entry under the old uid and fall through to re-add. If an app
+                        // just opted into key/value backup, add it as a known participant.
+                        removePackageParticipantsLocked(packageList, uid);
                     }
-                    addPackageParticipantsLocked(pkgList);
+                    addPackageParticipantsLocked(packageList);
                 }
-                // If they're full-backup candidates, add them there instead
-                final long now = System.currentTimeMillis();
-                for (final String packageName : pkgList) {
+
+                long now = System.currentTimeMillis();
+                for (String packageName : packageList) {
                     try {
-                        PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
+                        PackageInfo app =
+                                mPackageManager.getPackageInfoAsUser(
+                                        packageName, /* flags */ 0, mUserId);
                         if (AppBackupUtils.appGetsFullBackup(app)
                                 && AppBackupUtils.appIsEligibleForBackup(
                                 app.applicationInfo, mPackageManager)) {
                             enqueueFullBackup(packageName, now);
                             scheduleNextFullBackupJob(0);
                         } else {
-                            // The app might have just transitioned out of full-data into
-                            // doing key/value backups, or might have just disabled backups
-                            // entirely.  Make sure it is no longer in the full-data queue.
+                            // The app might have just transitioned out of full-data into doing
+                            // key/value backups, or might have just disabled backups entirely. Make
+                            // sure it is no longer in the full-data queue.
                             synchronized (mQueueLock) {
                                 dequeueFullBackupLocked(packageName);
                             }
@@ -1216,32 +1233,28 @@
 
                         mBackupHandler.post(
                                 () -> mTransportManager.onPackageAdded(packageName));
-
                     } catch (NameNotFoundException e) {
-                        // doesn't really exist; ignore it
                         if (DEBUG) {
                             Slog.w(TAG, "Can't resolve new app " + packageName);
                         }
                     }
                 }
 
-                // Whenever a package is added or updated we need to update
-                // the package metadata bookkeeping.
+                // Whenever a package is added or updated we need to update the package metadata
+                // bookkeeping.
                 dataChangedImpl(PACKAGE_MANAGER_SENTINEL);
             } else {
-                if (replacing) {
-                    // The package is being updated.  We'll receive a PACKAGE_ADDED shortly.
-                } else {
-                    // Outright removal.  In the full-data case, the app will be dropped
-                    // from the queue when its (now obsolete) name comes up again for
-                    // backup.
+                if (!replacing) {
+                    // Outright removal. In the full-data case, the app will be dropped from the
+                    // queue when its (now obsolete) name comes up again for backup.
                     synchronized (mBackupParticipants) {
-                        removePackageParticipantsLocked(pkgList, uid);
+                        removePackageParticipantsLocked(packageList, uid);
                     }
                 }
-                for (final String pkgName : pkgList) {
+
+                for (String packageName : packageList) {
                     mBackupHandler.post(
-                            () -> mTransportManager.onPackageRemoved(pkgName));
+                            () -> mTransportManager.onPackageRemoved(packageName));
                 }
             }
         }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 4b24ef9..dc0f602 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -39,6 +39,7 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.SyncResultReceiver;
 import com.android.server.LocalServices;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -50,8 +51,9 @@
 /**
  * A service used to observe the contents of the screen.
  *
- * <p>The data collected by this service can be analyzed and combined with other sources to provide
- * contextual data in other areas of the system such as Autofill.
+ * <p>The data collected by this service can be analyzed on-device and combined
+ * with other sources to provide contextual data in other areas of the system
+ * such as Autofill.
  */
 public final class ContentCaptureManagerService extends
         AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
@@ -196,6 +198,22 @@
         }
 
         @Override
+        public void getReceiverServiceComponentName(@UserIdInt int userId,
+                IResultReceiver receiver) {
+            ComponentName connectedServiceComponentName;
+            synchronized (mLock) {
+                final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+                connectedServiceComponentName = service.getServiceComponentName();
+            }
+            try {
+                receiver.send(0, SyncResultReceiver.bundleFor(connectedServiceComponentName));
+            } catch (RemoteException e) {
+                // Ignore exception as we need to be resilient against app behavior.
+                Slog.w(TAG, "Unable to send service component name: " + e);
+            }
+        }
+
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
 
diff --git a/services/contentsuggestions/Android.bp b/services/contentsuggestions/Android.bp
new file mode 100644
index 0000000..fc09d2e
--- /dev/null
+++ b/services/contentsuggestions/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+    name: "services.contentsuggestions",
+    srcs: ["java/**/*.java"],
+    libs: ["services.core"],
+}
\ No newline at end of file
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
new file mode 100644
index 0000000..58dbea4
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
@@ -0,0 +1,205 @@
+/*
+ * 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.contentsuggestions;
+
+import static android.Manifest.permission.MANAGE_CONTENT_SUGGESTIONS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.IContentSuggestionsManager;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+
+/**
+ * The system service for providing recents / overview with content suggestion selections and
+ * classifications.
+ *
+ * <p>Calls are received here from
+ * {@link android.app.contentsuggestions.ContentSuggestionsManager} then delegated to
+ * a per user version of the service. From there they are routed to the remote actual implementation
+ * that provides the suggestion selections and classifications.
+ */
+public class ContentSuggestionsManagerService extends
+        AbstractMasterSystemService<
+                        ContentSuggestionsManagerService, ContentSuggestionsPerUserService> {
+
+    private static final String TAG = ContentSuggestionsManagerService.class.getSimpleName();
+    private static final boolean VERBOSE = false; // TODO: make dynamic
+
+    private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+    private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+    public ContentSuggestionsManagerService(Context context) {
+        super(context, new FrameworkResourcesServiceNameResolver(context,
+                com.android.internal.R.string.config_defaultContentSuggestionsService), null);
+        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+    }
+
+    @Override
+    protected ContentSuggestionsPerUserService newServiceLocked(int resolvedUserId,
+            boolean disabled) {
+        return new ContentSuggestionsPerUserService(this, mLock, resolvedUserId);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(
+                Context.CONTENT_SUGGESTIONS_SERVICE, new ContentSuggestionsManagerStub());
+    }
+
+    @Override
+    protected void enforceCallingPermissionForManagement() {
+        getContext().enforceCallingPermission(MANAGE_CONTENT_SUGGESTIONS, TAG);
+    }
+
+    @Override
+    protected int getMaximumTemporaryServiceDurationMs() {
+        return MAX_TEMP_SERVICE_DURATION_MS;
+    }
+
+    private boolean isCallerRecents(int userId) {
+        if (mServiceNameResolver.isTemporary(userId)) {
+            // If a temporary service is set then skip the recents check
+            return true;
+        }
+        return mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid());
+    }
+
+    private void enforceCallerIsRecents(int userId, String func) {
+        if (isCallerRecents(userId)) {
+            return;
+        }
+
+        String msg = "Permission Denial: " + func + " from pid="
+                + Binder.getCallingPid()
+                + ", uid=" + Binder.getCallingUid()
+                + " expected caller is recents";
+        Slog.w(TAG, msg);
+        throw new SecurityException(msg);
+    }
+
+    private class ContentSuggestionsManagerStub extends IContentSuggestionsManager.Stub {
+        @Override
+        public void provideContextImage(int taskId, @NonNull Bundle imageContextRequestExtras) {
+            if (imageContextRequestExtras == null) {
+                throw new IllegalArgumentException("Expected non-null imageContextRequestExtras");
+            }
+
+            final int userId = UserHandle.getCallingUserId();
+            enforceCallerIsRecents(userId, "provideContextImage");
+
+            synchronized (mLock) {
+                final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    service.provideContextImageLocked(taskId, imageContextRequestExtras);
+                } else {
+                    if (VERBOSE) {
+                        Slog.v(TAG, "provideContextImageLocked: no service for " + userId);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void suggestContentSelections(
+                @NonNull SelectionsRequest selectionsRequest,
+                @NonNull ISelectionsCallback selectionsCallback) {
+            final int userId = UserHandle.getCallingUserId();
+            enforceCallerIsRecents(userId, "suggestContentSelections");
+
+            synchronized (mLock) {
+                final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    service.suggestContentSelectionsLocked(selectionsRequest, selectionsCallback);
+                } else {
+                    if (VERBOSE) {
+                        Slog.v(TAG, "suggestContentSelectionsLocked: no service for " + userId);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void classifyContentSelections(
+                @NonNull ClassificationsRequest classificationsRequest,
+                @NonNull IClassificationsCallback callback) {
+            final int userId = UserHandle.getCallingUserId();
+            enforceCallerIsRecents(userId, "classifyContentSelections");
+
+            synchronized (mLock) {
+                final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    service.classifyContentSelectionsLocked(classificationsRequest, callback);
+                } else {
+                    if (VERBOSE) {
+                        Slog.v(TAG, "classifyContentSelectionsLocked: no service for " + userId);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void notifyInteraction(@NonNull String requestId, @NonNull Bundle bundle) {
+            final int userId = UserHandle.getCallingUserId();
+            enforceCallerIsRecents(userId, "notifyInteraction");
+
+            synchronized (mLock) {
+                final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    service.notifyInteractionLocked(requestId, bundle);
+                } else {
+                    if (VERBOSE) {
+                        Slog.v(TAG, "reportInteractionLocked: no service for " + userId);
+                    }
+                }
+            }
+        }
+
+        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err,
+                @NonNull String[] args, @Nullable ShellCallback callback,
+                @NonNull ResultReceiver resultReceiver) throws RemoteException {
+            // Ensure that the caller is the shell process
+            final int callingUid = Binder.getCallingUid();
+            if (callingUid != android.os.Process.SHELL_UID
+                    && callingUid != android.os.Process.ROOT_UID) {
+                Slog.e(TAG, "Expected shell caller");
+                return;
+            }
+            new ContentSuggestionsManagerServiceShellCommand(ContentSuggestionsManagerService.this)
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+    }
+}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java
new file mode 100644
index 0000000..e34f1ea
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * 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.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the ContentSuggestionsManagerService.
+ */
+public class ContentSuggestionsManagerServiceShellCommand extends ShellCommand {
+
+    private static final String TAG =
+            ContentSuggestionsManagerServiceShellCommand.class.getSimpleName();
+
+    private final ContentSuggestionsManagerService mService;
+
+    public ContentSuggestionsManagerServiceShellCommand(
+            @NonNull ContentSuggestionsManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch (cmd) {
+            case "set": {
+                final String what = getNextArgRequired();
+                switch (what) {
+                    case "temporary-service": {
+                        final int userId = Integer.parseInt(getNextArgRequired());
+                        String serviceName = getNextArg();
+                        if (serviceName == null) {
+                            mService.resetTemporaryService(userId);
+                            return 0;
+                        }
+                        final int duration = Integer.parseInt(getNextArgRequired());
+                        mService.setTemporaryService(userId, serviceName, duration);
+                        pw.println("ContentSuggestionsService temporarily set to " + serviceName
+                                + " for " + duration + "ms");
+                        break;
+                    }
+                }
+            }
+            break;
+            default:
+                return handleDefaultCommands(cmd);
+        }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        try (PrintWriter pw = getOutPrintWriter()) {
+            pw.println("ContentSuggestionsManagerService commands:");
+            pw.println("  help");
+            pw.println("    Prints this help text.");
+            pw.println("");
+            pw.println("  set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+            pw.println("    Temporarily (for DURATION ms) changes the service implemtation.");
+            pw.println("    To reset, call with just the USER_ID argument.");
+            pw.println("");
+        }
+    }
+}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
new file mode 100644
index 0000000..385bc6c
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
@@ -0,0 +1,162 @@
+/*
+ * 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.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+/**
+ * Per user delegate of {@link ContentSuggestionsManagerService}.
+ *
+ * <p>Main job is to forward calls to the remote implementation that can provide suggestion
+ * selections and classifications.
+ */
+public final class ContentSuggestionsPerUserService extends
+        AbstractPerUserSystemService<
+                        ContentSuggestionsPerUserService, ContentSuggestionsManagerService> {
+    private static final String TAG = ContentSuggestionsPerUserService.class.getSimpleName();
+
+    @Nullable
+    @GuardedBy("mLock")
+    private RemoteContentSuggestionsService mRemoteService;
+
+    @NonNull
+    private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+    ContentSuggestionsPerUserService(
+            ContentSuggestionsManagerService master, Object lock, int userId) {
+        super(master, lock, userId);
+        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        ServiceInfo si;
+        try {
+            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException e) {
+            throw new PackageManager.NameNotFoundException(
+                    "Could not get service for " + serviceComponent);
+        }
+        return si;
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected boolean updateLocked(boolean disabled) {
+        final boolean enabledChanged = super.updateLocked(disabled);
+        if (enabledChanged) {
+            if (!isEnabledLocked()) {
+                // Clear the remote service for the next call
+                mRemoteService = null;
+            }
+        }
+        return enabledChanged;
+    }
+
+    @GuardedBy("mLock")
+    void provideContextImageLocked(int taskId, @NonNull Bundle imageContextRequestExtras) {
+        RemoteContentSuggestionsService service = getRemoteServiceLocked();
+        if (service != null) {
+            ActivityManager.TaskSnapshot snapshot =
+                    mActivityTaskManagerInternal.getTaskSnapshot(taskId, false);
+            GraphicBuffer snapshotBuffer = null;
+            if (snapshot != null) {
+                snapshotBuffer = snapshot.getSnapshot();
+            }
+
+            service.provideContextImage(taskId, snapshotBuffer, imageContextRequestExtras);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void suggestContentSelectionsLocked(
+            @NonNull SelectionsRequest selectionsRequest,
+            @NonNull ISelectionsCallback selectionsCallback) {
+        RemoteContentSuggestionsService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.suggestContentSelections(selectionsRequest, selectionsCallback);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void classifyContentSelectionsLocked(
+            @NonNull ClassificationsRequest classificationsRequest,
+            @NonNull IClassificationsCallback callback) {
+        RemoteContentSuggestionsService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.classifyContentSelections(classificationsRequest, callback);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void notifyInteractionLocked(@NonNull String requestId, @NonNull Bundle bundle) {
+        RemoteContentSuggestionsService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.notifyInteraction(requestId, bundle);
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteContentSuggestionsService getRemoteServiceLocked() {
+        if (mRemoteService == null) {
+            final String serviceName = getComponentNameLocked();
+            if (serviceName == null) {
+                if (mMaster.verbose) {
+                    Slog.v(TAG, "getRemoteServiceLocked(): not set");
+                }
+                return null;
+            }
+            ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+            mRemoteService = new RemoteContentSuggestionsService(getContext(),
+                    serviceComponent, mUserId,
+                    new RemoteContentSuggestionsService.Callbacks() {
+                        @Override
+                        public void onServiceDied(
+                                @NonNull RemoteContentSuggestionsService service) {
+                            // TODO(b/120865921): properly implement
+                            Slog.w(TAG, "remote content suggestions service died");
+                        }
+                    }, mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+        }
+
+        return mRemoteService;
+    }
+}
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java
new file mode 100644
index 0000000..bf48d76
--- /dev/null
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java
@@ -0,0 +1,97 @@
+/*
+ * 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.contentsuggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.IClassificationsCallback;
+import android.app.contentsuggestions.ISelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.service.contentsuggestions.ContentSuggestionsService;
+import android.service.contentsuggestions.IContentSuggestionsService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+/**
+ * Delegates calls from {@link ContentSuggestionsPerUserService} to the remote actual implementation
+ * of the suggestion selection and classification service.
+ */
+public class RemoteContentSuggestionsService extends
+        AbstractMultiplePendingRequestsRemoteService<RemoteContentSuggestionsService,
+                                IContentSuggestionsService> {
+
+    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+    RemoteContentSuggestionsService(Context context, ComponentName serviceName,
+            int userId, Callbacks callbacks,
+            boolean bindInstantServiceAllowed, boolean verbose) {
+        super(context, ContentSuggestionsService.SERVICE_INTERFACE, serviceName, userId, callbacks,
+                bindInstantServiceAllowed, verbose, /* initialCapacity= */ 1);
+    }
+
+    @Override
+    protected IContentSuggestionsService getServiceInterface(IBinder service) {
+        return IContentSuggestionsService.Stub.asInterface(service);
+    }
+
+    @Override
+    protected long getTimeoutIdleBindMillis() {
+        return PERMANENT_BOUND_TIMEOUT_MS;
+    }
+
+    @Override
+    protected long getRemoteRequestMillis() {
+        return TIMEOUT_REMOTE_REQUEST_MILLIS;
+    }
+
+    void provideContextImage(int taskId, @Nullable GraphicBuffer contextImage,
+            @NonNull Bundle imageContextRequestExtras) {
+        scheduleAsyncRequest((s) -> s.provideContextImage(taskId, contextImage,
+                imageContextRequestExtras));
+    }
+
+    void suggestContentSelections(
+            @NonNull SelectionsRequest selectionsRequest,
+            @NonNull ISelectionsCallback selectionsCallback) {
+        scheduleAsyncRequest(
+                (s) -> s.suggestContentSelections(selectionsRequest, selectionsCallback));
+    }
+
+    void classifyContentSelections(
+            @NonNull ClassificationsRequest classificationsRequest,
+            @NonNull IClassificationsCallback callback) {
+        scheduleAsyncRequest((s) -> s.classifyContentSelections(classificationsRequest, callback));
+    }
+
+    void notifyInteraction(@NonNull String requestId, @NonNull Bundle bundle) {
+        scheduleAsyncRequest((s) -> s.notifyInteraction(requestId, bundle));
+    }
+
+    interface Callbacks
+            extends VultureCallback<RemoteContentSuggestionsService> {
+        // NOTE: so far we don't need to notify the callback implementation
+        // (ContentSuggestionsManager) of the request results (success, timeouts, etc..), so this
+        // callback interface is empty.
+    }
+}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 7bbc543..a333381 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -279,7 +279,7 @@
 
                 Slog.d(TAG,
                         "Airplane Mode change - current state:  " + BluetoothAdapter.nameForState(
-                                st));
+                                st) + ", isAirplaneModeOn()=" + isAirplaneModeOn());
 
                 if (isAirplaneModeOn()) {
                     // Clear registered LE apps to force shut-off
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 66ceae4..d0666b9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -25,6 +25,7 @@
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -37,6 +38,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkPolicyManager.RULE_NONE;
 import static android.net.NetworkPolicyManager.uidRulesToString;
+import static android.net.NetworkStack.NETWORKSTACK_PACKAGE_NAME;
+import static android.net.shared.NetworkMonitorUtils.isValidationRequired;
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
@@ -62,6 +65,8 @@
 import android.net.INetd;
 import android.net.INetdEventCallback;
 import android.net.INetworkManagementEventObserver;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
@@ -79,9 +84,11 @@
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.NetworkWatchlistManager;
+import android.net.PrivateDnsConfigParcel;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
 import android.net.UidRange;
@@ -90,12 +97,13 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.FileUtils;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -123,8 +131,8 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -149,7 +157,6 @@
 import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.DataConnectionStats;
 import com.android.server.connectivity.DnsManager;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.KeepaliveTracker;
@@ -158,7 +165,6 @@
 import com.android.server.connectivity.MultipathPolicyTracker;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkDiagnostics;
-import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.NetworkNotificationManager;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.PermissionMonitor;
@@ -186,7 +192,6 @@
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -442,6 +447,43 @@
      */
     private static final int EVENT_DATA_SAVER_CHANGED = 40;
 
+     /**
+      * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
+      * been tested.
+      * obj = String representing URL that Internet probe was redirect to, if it was redirected.
+      * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
+      * arg2 = NetID.
+      */
+    public static final int EVENT_NETWORK_TESTED = 41;
+
+    /**
+     * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the private DNS
+     * config was resolved.
+     * obj = PrivateDnsConfig
+     * arg2 = netid
+     */
+    public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = 42;
+
+    /**
+     * Request ConnectivityService display provisioning notification.
+     * arg1    = Whether to make the notification visible.
+     * arg2    = NetID.
+     * obj     = Intent to be launched when notification selected by user, null if !arg1.
+     */
+    public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
+
+    /**
+     * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+     * should be shown.
+     */
+    public static final int PROVISIONING_NOTIFICATION_SHOW = 1;
+
+    /**
+     * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+     * should be hidden.
+     */
+    public static final int PROVISIONING_NOTIFICATION_HIDE = 0;
+
     private static String eventName(int what) {
         return sMagicDecoderRing.get(what, Integer.toString(what));
     }
@@ -506,30 +548,6 @@
     private long mMaxWakelockDurationMs = 0;
     private long mLastWakeLockAcquireTimestamp = 0;
 
-    // Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
-    private static final int MAX_VALIDATION_LOGS = 10;
-    private static class ValidationLog {
-        final Network mNetwork;
-        final String mName;
-        final ReadOnlyLocalLog mLog;
-
-        ValidationLog(Network network, String name, ReadOnlyLocalLog log) {
-            mNetwork = network;
-            mName = name;
-            mLog = log;
-        }
-    }
-    private final ArrayDeque<ValidationLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
-
-    private void addValidationLogs(ReadOnlyLocalLog log, Network network, String name) {
-        synchronized (mValidationLogs) {
-            while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
-                mValidationLogs.removeLast();
-            }
-            mValidationLogs.addFirst(new ValidationLog(network, name, log));
-        }
-    }
-
     private final IpConnectivityLog mMetricsLog;
 
     @GuardedBy("mBandwidthRequests")
@@ -1684,7 +1702,11 @@
             // caller type. Need to re-factor NetdEventListenerService to allow multiple
             // NetworkMonitor registrants.
             if (nai != null && nai.satisfies(mDefaultRequest)) {
-                nai.networkMonitor.sendMessage(NetworkMonitor.EVENT_DNS_NOTIFICATION, returnCode);
+                try {
+                    nai.networkMonitor().notifyDnsResponse(returnCode);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
             }
         }
     };
@@ -2266,17 +2288,6 @@
 
         if (ArrayUtils.contains(args, SHORT_ARG) == false) {
             pw.println();
-            synchronized (mValidationLogs) {
-                pw.println("mValidationLogs (most recent first):");
-                for (ValidationLog p : mValidationLogs) {
-                    pw.println(p.mNetwork + " - " + p.mName);
-                    pw.increaseIndent();
-                    p.mLog.dump(fd, pw, args);
-                    pw.decreaseIndent();
-                }
-            }
-
-            pw.println();
             pw.println("mNetworkRequestInfoLogs (most recent first):");
             pw.increaseIndent();
             mNetworkRequestInfoLogs.reverseDump(fd, pw, args);
@@ -2455,11 +2466,11 @@
             switch (msg.what) {
                 default:
                     return false;
-                case NetworkMonitor.EVENT_NETWORK_TESTED: {
+                case EVENT_NETWORK_TESTED: {
                     final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
                     if (nai == null) break;
 
-                    final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+                    final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
                     final boolean wasValidated = nai.lastValidated;
                     final boolean wasDefault = isDefaultNetwork(nai);
 
@@ -2497,7 +2508,7 @@
                     }
                     break;
                 }
-                case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
+                case EVENT_PROVISIONING_NOTIFICATION: {
                     final int netId = msg.arg2;
                     final boolean visible = toBool(msg.arg1);
                     final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
@@ -2530,7 +2541,7 @@
                     }
                     break;
                 }
-                case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
+                case EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
                     final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
                     if (nai == null) break;
 
@@ -2572,8 +2583,61 @@
         }
     }
 
+    private class NetworkMonitorCallbacks extends INetworkMonitorCallbacks.Stub {
+        private final NetworkAgentInfo mNai;
+
+        private NetworkMonitorCallbacks(NetworkAgentInfo nai) {
+            mNai = nai;
+        }
+
+        @Override
+        public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+            mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT,
+                    new Pair<>(mNai, networkMonitor)));
+        }
+
+        @Override
+        public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
+            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED,
+                    testResult, mNai.network.netId, redirectUrl));
+        }
+
+        @Override
+        public void notifyPrivateDnsConfigResolved(PrivateDnsConfigParcel config) {
+            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+                    EVENT_PRIVATE_DNS_CONFIG_RESOLVED,
+                    0, mNai.network.netId, PrivateDnsConfig.fromParcel(config)));
+        }
+
+        @Override
+        public void showProvisioningNotification(String action) {
+            final Intent intent = new Intent(action);
+            intent.setPackage(NETWORKSTACK_PACKAGE_NAME);
+
+            final PendingIntent pendingIntent;
+            // Only the system server can register notifications with package "android"
+            final long token = Binder.clearCallingIdentity();
+            try {
+                pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+                    EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_SHOW,
+                    mNai.network.netId,
+                    pendingIntent));
+        }
+
+        @Override
+        public void hideProvisioningNotification() {
+            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+                    EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE,
+                    mNai.network.netId));
+        }
+    }
+
     private boolean networkRequiresValidation(NetworkAgentInfo nai) {
-        return NetworkMonitor.isValidationRequired(
+        return isValidationRequired(
                 mDefaultRequest.networkCapabilities, nai.networkCapabilities);
     }
 
@@ -2603,10 +2667,14 @@
         // Internet access and therefore also require validation.
         if (!networkRequiresValidation(nai)) return;
 
-        // Notify the NetworkMonitor thread in case it needs to cancel or
+        // Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
         // schedule DNS resolutions. If a DNS resolution is required the
         // result will be sent back to us.
-        nai.networkMonitor.notifyPrivateDnsSettingsChanged(cfg);
+        try {
+            nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
 
         // With Private DNS bypass support, we can proceed to update the
         // Private DNS config immediately, even if we're in strict mode
@@ -2736,7 +2804,11 @@
             // Disable wakeup packet monitoring for each interface.
             wakeupModifyInterface(iface, nai.networkCapabilities, false);
         }
-        nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
+        try {
+            nai.networkMonitor().notifyNetworkDisconnected();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
         mNetworkAgentInfos.remove(nai.messenger);
         nai.maybeStopClat();
         synchronized (mNetworkForNetId) {
@@ -3096,7 +3168,11 @@
             NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
             if (nai == null) return;
             if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return;
-            nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+            try {
+                nai.networkMonitor().launchCaptivePortalApp();
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
         });
     }
 
@@ -3217,6 +3293,11 @@
         return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
     }
 
+    @Override
+    public NetworkRequest getDefaultRequest() {
+        return mDefaultRequest;
+    }
+
     private class InternalHandler extends Handler {
         public InternalHandler(Looper looper) {
             super(looper);
@@ -3247,7 +3328,9 @@
                     break;
                 }
                 case EVENT_REGISTER_NETWORK_AGENT: {
-                    handleRegisterNetworkAgent((NetworkAgentInfo)msg.obj);
+                    final Pair<NetworkAgentInfo, INetworkMonitor> arg =
+                            (Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj;
+                    handleRegisterNetworkAgent(arg.first, arg.second);
                     break;
                 }
                 case EVENT_REGISTER_NETWORK_REQUEST:
@@ -3305,7 +3388,14 @@
                 }
                 case EVENT_SYSTEM_READY: {
                     for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-                        nai.networkMonitor.systemReady = true;
+                        // Might have been called already in handleRegisterNetworkAgent since
+                        // mSystemReady is set before sending EVENT_SYSTEM_READY, but calling
+                        // this several times is fine.
+                        try {
+                            nai.networkMonitor().notifySystemReady();
+                        } catch (RemoteException e) {
+                            e.rethrowFromSystemServer();
+                        }
                     }
                     mMultipathPolicyTracker.start();
                     break;
@@ -3577,7 +3667,11 @@
         if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
             return;
         }
-        nai.networkMonitor.forceReevaluation(uid);
+        try {
+            nai.networkMonitor().forceReevaluation(uid);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     @Override
@@ -4785,27 +4879,49 @@
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
         final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
                 new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore,
-                mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
+                mContext, mTrackerHandler, new NetworkMisc(networkMisc), this);
         // Make sure the network capabilities reflect what the agent info says.
         nai.networkCapabilities = mixInCapabilities(nai, nc);
-        synchronized (this) {
-            nai.networkMonitor.systemReady = mSystemReady;
-        }
         final String extraInfo = networkInfo.getExtraInfo();
         final String name = TextUtils.isEmpty(extraInfo)
                 ? nai.networkCapabilities.getSSID() : extraInfo;
-        addValidationLogs(nai.networkMonitor.getValidationLogs(), nai.network, name);
         if (DBG) log("registerNetworkAgent " + nai);
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mContext.getSystemService(NetworkStack.class)
+                    .makeNetworkMonitor(nai.network, name, new NetworkMonitorCallbacks(nai));
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        // NetworkAgentInfo registration will finish when the NetworkMonitor is created.
+        // If the network disconnects or sends any other event before that, messages are deferred by
+        // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the
+        // registration.
         return nai.network.netId;
     }
 
-    private void handleRegisterNetworkAgent(NetworkAgentInfo nai) {
+    private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+        nai.onNetworkMonitorCreated(networkMonitor);
         if (VDBG) log("Got NetworkAgent Messenger");
         mNetworkAgentInfos.put(nai.messenger, nai);
         synchronized (mNetworkForNetId) {
             mNetworkForNetId.put(nai.network.netId, nai);
         }
+        synchronized (this) {
+            if (mSystemReady) {
+                try {
+                    networkMonitor.notifySystemReady();
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        try {
+            networkMonitor.start();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
         nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
         NetworkInfo networkInfo = nai.networkInfo;
         nai.networkInfo = null;
@@ -4855,6 +4971,11 @@
             networkAgent.updateClat(mNMS);
             notifyIfacesChangedForNetworkStats();
             if (networkAgent.everConnected) {
+                try {
+                    networkAgent.networkMonitor().notifyLinkPropertiesChanged();
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
                 notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
             }
         }
@@ -5092,6 +5213,11 @@
             // If the requestable capabilities have changed or the score changed, we can't have been
             // called by rematchNetworkAndRequests, so it's safe to start a rematch.
             rematchAllNetworksAndRequests(nai, oldScore);
+            try {
+                nai.networkMonitor().notifyNetworkCapabilitiesChanged();
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
 
@@ -5339,6 +5465,11 @@
         }
 
         if (capabilitiesChanged) {
+            try {
+                nai.networkMonitor().notifyNetworkCapabilitiesChanged();
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
 
@@ -5739,7 +5870,15 @@
             updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
                     null);
 
-            networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+            // Until parceled LinkProperties are sent directly to NetworkMonitor, the connect
+            // command must be sent after updating LinkProperties to maximize chances of
+            // NetworkMonitor seeing the correct LinkProperties when starting.
+            // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
+            try {
+                networkAgent.networkMonitor().notifyNetworkConnected();
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
             scheduleUnvalidatedPrompt(networkAgent);
 
             if (networkAgent.isVPN()) {
@@ -6020,7 +6159,7 @@
     @Override
     public String getCaptivePortalServerUrl() {
         enforceConnectivityInternalPermission();
-        return NetworkMonitor.getCaptivePortalServerHttpUrl(mContext);
+        return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(mContext);
     }
 
     @Override
@@ -6113,12 +6252,6 @@
     }
 
     @VisibleForTesting
-    public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
-            NetworkAgentInfo nai, NetworkRequest defaultRequest) {
-        return new NetworkMonitor(context, handler, nai, defaultRequest);
-    }
-
-    @VisibleForTesting
     MultinetworkPolicyTracker createMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
         return new MultinetworkPolicyTracker(c, h, r);
     }
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 2346cfc..869d564 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -17,9 +17,14 @@
 package com.android.server;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.location.LocationProvider.AVAILABLE;
 import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.internal.util.Preconditions.checkState;
 
 import android.Manifest;
@@ -29,7 +34,6 @@
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -64,7 +68,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Message;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -81,12 +84,14 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
 import com.android.server.location.AbstractLocationProvider;
 import com.android.server.location.ActivityRecognitionProxy;
 import com.android.server.location.GeocoderProxy;
@@ -116,7 +121,6 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.NoSuchElementException;
-import java.util.Set;
 
 /**
  * The service class that manages LocationProviders and issues location
@@ -137,16 +141,12 @@
 
     private static final String ACCESS_LOCATION_EXTRA_COMMANDS =
             android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS;
-    private static final String INSTALL_LOCATION_PROVIDER =
-            android.Manifest.permission.INSTALL_LOCATION_PROVIDER;
 
     private static final String NETWORK_LOCATION_SERVICE_ACTION =
             "com.android.location.service.v3.NetworkLocationProvider";
     private static final String FUSED_LOCATION_SERVICE_ACTION =
             "com.android.location.service.FusedLocationProvider";
 
-    private static final int MSG_LOCATION_CHANGED = 1;
-
     private static final long NANOS_PER_MILLI = 1000000L;
 
     // The maximum interval a location request can have and still be considered "high power".
@@ -170,75 +170,62 @@
 
     private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest();
 
-    private final Context mContext;
-    private final AppOpsManager mAppOps;
-
-    // used internally for synchronization
     private final Object mLock = new Object();
+    private final Context mContext;
+    private final Handler mHandler;
 
-    // --- fields below are final after systemRunning() ---
-    private LocationFudger mLocationFudger;
-    private GeofenceManager mGeofenceManager;
+    private AppOpsManager mAppOps;
     private PackageManager mPackageManager;
     private PowerManager mPowerManager;
     private ActivityManager mActivityManager;
     private UserManager mUserManager;
+
+    private GeofenceManager mGeofenceManager;
+    private LocationFudger mLocationFudger;
     private GeocoderProxy mGeocodeProvider;
     private GnssStatusListenerHelper mGnssStatusProvider;
     private INetInitiatedListener mNetInitiatedListener;
-    private LocationWorkerHandler mLocationHandler;
     private PassiveProvider mPassiveProvider;  // track passive provider for special cases
     private LocationBlacklist mBlacklist;
     private GnssMeasurementsProvider mGnssMeasurementsProvider;
     private GnssNavigationMessageProvider mGnssNavigationMessageProvider;
+    @GuardedBy("mLock")
     private String mLocationControllerExtraPackage;
     private boolean mLocationControllerExtraPackageEnabled;
     private IGpsGeofenceHardware mGpsGeofenceProxy;
 
-    // --- fields below are protected by mLock ---
+    // list of currently active providers
+    @GuardedBy("mLock")
+    private final ArrayList<LocationProvider> mProviders = new ArrayList<>();
 
-    // Mock (test) providers
-    private final HashMap<String, MockProvider> mMockProviders =
-            new HashMap<>();
+    // list of non-mock providers, so that when mock providers replace real providers, they can be
+    // later re-replaced
+    @GuardedBy("mLock")
+    private final ArrayList<LocationProvider> mRealProviders = new ArrayList<>();
 
-    // all receivers
+    @GuardedBy("mLock")
     private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
-
-    // currently installed providers (with mocks replacing real providers)
-    private final ArrayList<LocationProvider> mProviders =
-            new ArrayList<>();
-
-    // real providers, saved here when mocked out
-    private final HashMap<String, LocationProvider> mRealProviders =
-            new HashMap<>();
-
-    // mapping from provider name to provider
-    private final HashMap<String, LocationProvider> mProvidersByName =
-            new HashMap<>();
-
-    // mapping from provider name to all its UpdateRecords
     private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
             new HashMap<>();
 
     private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
 
     // mapping from provider name to last known location
+    @GuardedBy("mLock")
     private final HashMap<String, Location> mLastLocation = new HashMap<>();
 
     // same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
     // locations stored here are not fudged for coarse permissions.
+    @GuardedBy("mLock")
     private final HashMap<String, Location> mLastLocationCoarseInterval =
             new HashMap<>();
 
-    // all providers that operate over proxy, for authorizing incoming location and whitelisting
-    // throttling
-    private final ArrayList<LocationProviderProxy> mProxyProviders =
-            new ArrayList<>();
-
     private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
 
+    @GuardedBy("mLock")
     private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>();
 
+    @GuardedBy("mLock")
     private final ArrayMap<IBinder, Identity>
             mGnssNavigationMessageListeners = new ArrayMap<>();
 
@@ -246,22 +233,22 @@
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
     private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
 
-    // Maximum age of last location returned to clients with foreground-only location permissions.
-    private long mLastLocationMaxAgeMs;
-
     private GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider;
 
     private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider;
 
     private GnssBatchingProvider mGnssBatchingProvider;
+    @GuardedBy("mLock")
     private IBatchedLocationCallback mGnssBatchingCallback;
+    @GuardedBy("mLock")
     private LinkedCallback mGnssBatchingDeathCallback;
+    @GuardedBy("mLock")
     private boolean mGnssBatchingInProgress = false;
 
     public LocationManagerService(Context context) {
         super();
         mContext = context;
-        mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        mHandler = BackgroundThread.getHandler();
 
         // Let the package manager query which are the default location
         // providers as they get certain permissions granted by default.
@@ -271,134 +258,110 @@
                 userId -> mContext.getResources().getStringArray(
                         com.android.internal.R.array.config_locationProviderPackageNames));
 
-        if (D) Log.d(TAG, "Constructed");
-
         // most startup is deferred until systemRunning()
     }
 
     public void systemRunning() {
         synchronized (mLock) {
-            if (D) Log.d(TAG, "systemRunning()");
-
-            // fetch package manager
-            mPackageManager = mContext.getPackageManager();
-
-            // fetch power manager
-            mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-
-            // fetch activity manager
-            mActivityManager
-                    = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-
-            // prepare worker thread
-            mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper());
-
-            // prepare mLocationHandler's dependents
-            mLocationFudger = new LocationFudger(mContext, mLocationHandler);
-            mBlacklist = new LocationBlacklist(mContext, mLocationHandler);
-            mBlacklist.init();
-            mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
-
-            // Monitor for app ops mode changes.
-            AppOpsManager.OnOpChangedListener callback
-                    = new AppOpsManager.OnOpChangedInternalListener() {
-                public void onOpChanged(int op, String packageName) {
-                            mLocationHandler.post(() -> {
-                                synchronized (mLock) {
-                                    for (Receiver receiver : mReceivers.values()) {
-                                        receiver.updateMonitoring(true);
-                                    }
-                                    applyAllProviderRequirementsLocked();
-                                }
-                            });
-                }
-            };
-            mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null,
-                    AppOpsManager.WATCH_FOREGROUND_CHANGES, callback);
-
-            PackageManager.OnPermissionsChangedListener permissionListener = uid -> {
-                synchronized (mLock) {
-                    applyAllProviderRequirementsLocked();
-                }
-            };
-            mPackageManager.addOnPermissionsChangeListener(permissionListener);
-
-            // listen for background/foreground changes
-            ActivityManager.OnUidImportanceListener uidImportanceListener =
-                    (uid, importance) -> mLocationHandler.post(
-                            () -> onUidImportanceChanged(uid, importance));
-            mActivityManager.addOnUidImportanceListener(uidImportanceListener,
-                    FOREGROUND_IMPORTANCE_CUTOFF);
-
-            mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            updateUserProfiles(mCurrentUserId);
-
-            updateBackgroundThrottlingWhitelistLocked();
-            updateLastLocationMaxAgeLocked();
-
-            // prepare providers
-            loadProvidersLocked();
-            updateProvidersSettingsLocked();
-            for (LocationProvider provider : mProviders) {
-                applyRequirementsLocked(provider.getName());
-            }
+            initializeLocked();
         }
+    }
 
-        // listen for settings changes
+    @GuardedBy("mLock")
+    private void initializeLocked() {
+        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mPackageManager = mContext.getPackageManager();
+        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+        mLocationFudger = new LocationFudger(mContext, mHandler);
+        mBlacklist = new LocationBlacklist(mContext, mHandler);
+        mBlacklist.init();
+        mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
+
+        // prepare providers
+        initializeProvidersLocked();
+
+        // add listeners
+        mAppOps.startWatchingMode(
+                AppOpsManager.OP_COARSE_LOCATION,
+                null,
+                AppOpsManager.WATCH_FOREGROUND_CHANGES,
+                new AppOpsManager.OnOpChangedInternalListener() {
+                    public void onOpChanged(int op, String packageName) {
+                        synchronized (mLock) {
+                            onAppOpChangedLocked();
+                        }
+                    }
+                });
+        mPackageManager.addOnPermissionsChangeListener(
+                uid -> {
+                    synchronized (mLock) {
+                        onPermissionsChangedLocked();
+                    }
+                });
+
+        mActivityManager.addOnUidImportanceListener(
+                (uid, importance) -> {
+                    synchronized (mLock) {
+                        onUidImportanceChangedLocked(uid, importance);
+                    }
+                },
+                FOREGROUND_IMPORTANCE_CUTOFF);
         mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
-                new ContentObserver(mLocationHandler) {
+                Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE), true,
+                new ContentObserver(mHandler) {
                     @Override
                     public void onChange(boolean selfChange) {
                         synchronized (mLock) {
-                            updateProvidersSettingsLocked();
+                            onLocationModeChangedLocked(true);
+                        }
+                    }
+                }, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
+                new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        synchronized (mLock) {
+                            onProviderAllowedChangedLocked(true);
                         }
                     }
                 }, UserHandle.USER_ALL);
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS),
                 true,
-                new ContentObserver(mLocationHandler) {
+                new ContentObserver(mHandler) {
                     @Override
                     public void onChange(boolean selfChange) {
                         synchronized (mLock) {
-                            for (LocationProvider provider : mProviders) {
-                                applyRequirementsLocked(provider.getName());
-                            }
+                            onBackgroundThrottleIntervalChangedLocked();
                         }
                     }
                 }, UserHandle.USER_ALL);
         mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS),
-                true,
-                new ContentObserver(mLocationHandler) {
-                    @Override
-                    public void onChange(boolean selfChange) {
-                        synchronized (mLock) {
-                            updateLastLocationMaxAgeLocked();
-                        }
-                    }
-                }
-        );
-        mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(
                         Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
                 true,
-                new ContentObserver(mLocationHandler) {
+                new ContentObserver(mHandler) {
                     @Override
                     public void onChange(boolean selfChange) {
                         synchronized (mLock) {
-                            updateBackgroundThrottlingWhitelistLocked();
-                            for (LocationProvider provider : mProviders) {
-                                applyRequirementsLocked(provider.getName());
-                            }
+                            onBackgroundThrottleWhitelistChangedLocked();
                         }
                     }
                 }, UserHandle.USER_ALL);
 
-        mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
+        new PackageMonitor() {
+            @Override
+            public void onPackageDisappeared(String packageName, int reason) {
+                synchronized (mLock) {
+                    LocationManagerService.this.onPackageDisappearedLocked(packageName);
+                }
+            }
+        }.register(mContext, mHandler.getLooper(), true);
 
-        // listen for user change
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
         intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
@@ -407,81 +370,152 @@
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-                } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
-                        || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
-                    updateUserProfiles(mCurrentUserId);
+                synchronized (mLock) {
+                    String action = intent.getAction();
+                    if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                        onUserChangedLocked(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+                    } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
+                            || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+                        onUserProfilesChangedLocked();
+                    }
                 }
             }
-        }, UserHandle.ALL, intentFilter, null, mLocationHandler);
+        }, UserHandle.ALL, intentFilter, null, mHandler);
+
+        // switching the user from null to system here performs the bulk of the initialization work.
+        // the user being changed will cause a reload of all user specific settings, which causes
+        // provider initialization, and propagates changes until a steady state is reached
+        mCurrentUserId = UserHandle.USER_NULL;
+        onUserChangedLocked(UserHandle.USER_SYSTEM);
+
+        // initialize in-memory settings values
+        onBackgroundThrottleWhitelistChangedLocked();
     }
 
-    private void onUidImportanceChanged(int uid, int importance) {
+    @GuardedBy("mLock")
+    private void onAppOpChangedLocked() {
+        for (Receiver receiver : mReceivers.values()) {
+            receiver.updateMonitoring(true);
+        }
+        for (LocationProvider p : mProviders) {
+            applyRequirementsLocked(p);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onPermissionsChangedLocked() {
+        for (LocationProvider p : mProviders) {
+            applyRequirementsLocked(p);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onLocationModeChangedLocked(boolean broadcast) {
+        for (LocationProvider p : mProviders) {
+            p.onLocationModeChangedLocked();
+        }
+
+        if (broadcast) {
+            mContext.sendBroadcastAsUser(
+                    new Intent(LocationManager.MODE_CHANGED_ACTION),
+                    UserHandle.ALL);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onProviderAllowedChangedLocked(boolean broadcast) {
+        for (LocationProvider p : mProviders) {
+            p.onAllowedChangedLocked();
+        }
+
+        if (broadcast) {
+            mContext.sendBroadcastAsUser(
+                    new Intent(LocationManager.PROVIDERS_CHANGED_ACTION),
+                    UserHandle.ALL);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onPackageDisappearedLocked(String packageName) {
+        ArrayList<Receiver> deadReceivers = null;
+
+        for (Receiver receiver : mReceivers.values()) {
+            if (receiver.mIdentity.mPackageName.equals(packageName)) {
+                if (deadReceivers == null) {
+                    deadReceivers = new ArrayList<>();
+                }
+                deadReceivers.add(receiver);
+            }
+        }
+
+        // perform removal outside of mReceivers loop
+        if (deadReceivers != null) {
+            for (Receiver receiver : deadReceivers) {
+                removeUpdatesLocked(receiver);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onUidImportanceChangedLocked(int uid, int importance) {
         boolean foreground = isImportanceForeground(importance);
         HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
-        synchronized (mLock) {
-            for (Entry<String, ArrayList<UpdateRecord>> entry
-                    : mRecordsByProvider.entrySet()) {
-                String provider = entry.getKey();
-                for (UpdateRecord record : entry.getValue()) {
-                    if (record.mReceiver.mIdentity.mUid == uid
-                            && record.mIsForegroundUid != foreground) {
-                        if (D) {
-                            Log.d(TAG, "request from uid " + uid + " is now "
-                                    + (foreground ? "foreground" : "background)"));
-                        }
-                        record.updateForeground(foreground);
-
-                        if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
-                            affectedProviders.add(provider);
-                        }
-                    }
-                }
-            }
-            for (String provider : affectedProviders) {
-                applyRequirementsLocked(provider);
-            }
-
-            for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) {
-                Identity callerIdentity = entry.getValue();
-                if (callerIdentity.mUid == uid) {
+        for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
+            String provider = entry.getKey();
+            for (UpdateRecord record : entry.getValue()) {
+                if (record.mReceiver.mIdentity.mUid == uid
+                        && record.mIsForegroundUid != foreground) {
                     if (D) {
-                        Log.d(TAG, "gnss measurements listener from uid " + uid
-                                + " is now " + (foreground ? "foreground" : "background)"));
-                    }
-                    if (foreground || isThrottlingExemptLocked(entry.getValue())) {
-                        mGnssMeasurementsProvider.addListener(
-                                IGnssMeasurementsListener.Stub.asInterface(entry.getKey()),
-                                callerIdentity.mUid, callerIdentity.mPackageName);
-                    } else {
-                        mGnssMeasurementsProvider.removeListener(
-                                IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
-                    }
-                }
-            }
-
-            for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) {
-                Identity callerIdentity = entry.getValue();
-                if (callerIdentity.mUid == uid) {
-                    if (D) {
-                        Log.d(TAG, "gnss navigation message listener from uid "
-                                + uid + " is now "
+                        Log.d(TAG, "request from uid " + uid + " is now "
                                 + (foreground ? "foreground" : "background)"));
                     }
-                    if (foreground || isThrottlingExemptLocked(entry.getValue())) {
-                        mGnssNavigationMessageProvider.addListener(
-                                IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()),
-                                callerIdentity.mUid, callerIdentity.mPackageName);
-                    } else {
-                        mGnssNavigationMessageProvider.removeListener(
-                                IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
+                    record.updateForeground(foreground);
+
+                    if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
+                        affectedProviders.add(provider);
                     }
                 }
             }
+        }
+        for (String provider : affectedProviders) {
+            applyRequirementsLocked(provider);
+        }
 
-            // TODO(b/120449926): The GNSS status listeners should be handled similar to the above.
+        for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) {
+            Identity callerIdentity = entry.getValue();
+            if (callerIdentity.mUid == uid) {
+                if (D) {
+                    Log.d(TAG, "gnss measurements listener from uid " + uid
+                            + " is now " + (foreground ? "foreground" : "background)"));
+                }
+                if (foreground || isThrottlingExemptLocked(entry.getValue())) {
+                    mGnssMeasurementsProvider.addListener(
+                            IGnssMeasurementsListener.Stub.asInterface(entry.getKey()),
+                            callerIdentity.mUid, callerIdentity.mPackageName);
+                } else {
+                    mGnssMeasurementsProvider.removeListener(
+                            IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
+                }
+            }
+        }
+
+        for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) {
+            Identity callerIdentity = entry.getValue();
+            if (callerIdentity.mUid == uid) {
+                if (D) {
+                    Log.d(TAG, "gnss navigation message listener from uid "
+                            + uid + " is now "
+                            + (foreground ? "foreground" : "background)"));
+                }
+                if (foreground || isThrottlingExemptLocked(entry.getValue())) {
+                    mGnssNavigationMessageProvider.addListener(
+                            IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()),
+                            callerIdentity.mUid, callerIdentity.mPackageName);
+                } else {
+                    mGnssNavigationMessageProvider.removeListener(
+                            IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()));
+                }
+            }
         }
     }
 
@@ -489,30 +523,43 @@
         return importance <= FOREGROUND_IMPORTANCE_CUTOFF;
     }
 
-    /**
-     * Makes a list of userids that are related to the current user. This is
-     * relevant when using managed profiles. Otherwise the list only contains
-     * the current user.
-     *
-     * @param currentUserId the current user, who might have an alter-ego.
-     */
-    private void updateUserProfiles(int currentUserId) {
-        int[] profileIds = mUserManager.getProfileIdsWithDisabled(currentUserId);
-        synchronized (mLock) {
-            mCurrentUserProfiles = profileIds;
+    @GuardedBy("mLock")
+    private void onBackgroundThrottleIntervalChangedLocked() {
+        for (LocationProvider provider : mProviders) {
+            applyRequirementsLocked(provider);
         }
     }
 
-    /**
-     * Checks if the specified userId matches any of the current foreground
-     * users stored in mCurrentUserProfiles.
-     */
-    private boolean isCurrentProfile(int userId) {
-        synchronized (mLock) {
-            return ArrayUtils.contains(mCurrentUserProfiles, userId);
+    @GuardedBy("mLock")
+    private void onBackgroundThrottleWhitelistChangedLocked() {
+        String setting = Settings.Global.getString(
+                mContext.getContentResolver(),
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+        if (setting == null) {
+            setting = "";
+        }
+
+        mBackgroundThrottlePackageWhitelist.clear();
+        mBackgroundThrottlePackageWhitelist.addAll(
+                SystemConfig.getInstance().getAllowUnthrottledLocation());
+        mBackgroundThrottlePackageWhitelist.addAll(Arrays.asList(setting.split(",")));
+
+        for (LocationProvider p : mProviders) {
+            applyRequirementsLocked(p);
         }
     }
 
+    @GuardedBy("mLock")
+    private void onUserProfilesChangedLocked() {
+        mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
+    }
+
+    @GuardedBy("mLock")
+    private boolean isCurrentProfileLocked(int userId) {
+        return ArrayUtils.contains(mCurrentUserProfiles, userId);
+    }
+
+    @GuardedBy("mLock")
     private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
         PackageManager pm = mContext.getPackageManager();
         String systemPackageName = mContext.getPackageName();
@@ -583,30 +630,30 @@
                 + "partition. The fallback must also be marked coreApp=\"true\" in the manifest");
     }
 
-    private void loadProvidersLocked() {
+    @GuardedBy("mLock")
+    private void initializeProvidersLocked() {
         // create a passive location provider, which is always enabled
-        LocationProvider passiveProviderManager = new LocationProvider(
-                LocationManager.PASSIVE_PROVIDER);
-        PassiveProvider passiveProvider = new PassiveProvider(passiveProviderManager);
-
+        LocationProvider passiveProviderManager = new LocationProvider(PASSIVE_PROVIDER);
         addProviderLocked(passiveProviderManager);
-        mPassiveProvider = passiveProvider;
+        mPassiveProvider = new PassiveProvider(passiveProviderManager);
+        passiveProviderManager.attachLocked(mPassiveProvider);
 
         if (GnssLocationProvider.isSupported()) {
             // Create a gps location provider
-            LocationProvider gnssProviderManager = new LocationProvider(
-                    LocationManager.GPS_PROVIDER);
+            LocationProvider gnssProviderManager = new LocationProvider(GPS_PROVIDER, true);
+            mRealProviders.add(gnssProviderManager);
+            addProviderLocked(gnssProviderManager);
+
             GnssLocationProvider gnssProvider = new GnssLocationProvider(mContext,
                     gnssProviderManager,
-                    mLocationHandler.getLooper());
+                    mHandler.getLooper());
+            gnssProviderManager.attachLocked(gnssProvider);
 
             mGnssSystemInfoProvider = gnssProvider.getGnssSystemInfoProvider();
             mGnssBatchingProvider = gnssProvider.getGnssBatchingProvider();
             mGnssMetricsProvider = gnssProvider.getGnssMetricsProvider();
             mGnssStatusProvider = gnssProvider.getGnssStatusProvider();
             mNetInitiatedListener = gnssProvider.getNetInitiatedListener();
-            addProviderLocked(gnssProviderManager);
-            mRealProviders.put(LocationManager.GPS_PROVIDER, gnssProviderManager);
             mGnssMeasurementsProvider = gnssProvider.getGnssMeasurementsProvider();
             mGnssNavigationMessageProvider = gnssProvider.getGnssNavigationMessageProvider();
             mGpsGeofenceProxy = gnssProvider.getGpsGeofenceProxy();
@@ -634,9 +681,7 @@
         ensureFallbackFusedProviderPresentLocked(pkgs);
 
         // bind to network provider
-
-        LocationProvider networkProviderManager = new LocationProvider(
-                LocationManager.NETWORK_PROVIDER);
+        LocationProvider networkProviderManager = new LocationProvider(NETWORK_PROVIDER, true);
         LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
                 mContext,
                 networkProviderManager,
@@ -645,16 +690,15 @@
                 com.android.internal.R.string.config_networkLocationProviderPackageName,
                 com.android.internal.R.array.config_locationProviderPackageNames);
         if (networkProvider != null) {
-            mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProviderManager);
-            mProxyProviders.add(networkProvider);
+            mRealProviders.add(networkProviderManager);
             addProviderLocked(networkProviderManager);
+            networkProviderManager.attachLocked(networkProvider);
         } else {
             Slog.w(TAG, "no network location provider found");
         }
 
         // bind to fused provider
-        LocationProvider fusedProviderManager = new LocationProvider(
-                LocationManager.FUSED_PROVIDER);
+        LocationProvider fusedProviderManager = new LocationProvider(FUSED_PROVIDER);
         LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
                 mContext,
                 fusedProviderManager,
@@ -663,9 +707,9 @@
                 com.android.internal.R.string.config_fusedLocationProviderPackageName,
                 com.android.internal.R.array.config_locationProviderPackageNames);
         if (fusedProvider != null) {
+            mRealProviders.add(fusedProviderManager);
             addProviderLocked(fusedProviderManager);
-            mProxyProviders.add(fusedProvider);
-            mRealProviders.put(LocationManager.FUSED_PROVIDER, fusedProviderManager);
+            fusedProviderManager.attachLocked(fusedProvider);
         } else {
             Slog.e(TAG, "no fused location provider found",
                     new IllegalStateException("Location service needs a fused location provider"));
@@ -715,9 +759,6 @@
         for (String testProviderString : testProviderStrings) {
             String fragments[] = testProviderString.split(",");
             String name = fragments[0].trim();
-            if (mProvidersByName.get(name) != null) {
-                throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
-            }
             ProviderProperties properties = new ProviderProperties(
                     Boolean.parseBoolean(fragments[1]) /* requiresNetwork */,
                     Boolean.parseBoolean(fragments[2]) /* requiresSatellite */,
@@ -728,28 +769,37 @@
                     Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
                     Integer.parseInt(fragments[8]) /* powerRequirement */,
                     Integer.parseInt(fragments[9]) /* accuracy */);
-            addTestProviderLocked(name, properties);
+            LocationProvider testProviderManager = new LocationProvider(name);
+            addProviderLocked(testProviderManager);
+            new MockProvider(testProviderManager, properties);
         }
     }
 
-    /**
-     * Called when the device's active user changes.
-     *
-     * @param userId the new active user's UserId
-     */
-    private void switchUser(int userId) {
+    @GuardedBy("mLock")
+    private void onUserChangedLocked(int userId) {
         if (mCurrentUserId == userId) {
             return;
         }
-        mBlacklist.switchUser(userId);
-        mLocationHandler.removeMessages(MSG_LOCATION_CHANGED);
-        synchronized (mLock) {
-            mLastLocation.clear();
-            mLastLocationCoarseInterval.clear();
-            updateUserProfiles(userId);
-            updateProvidersSettingsLocked();
-            mCurrentUserId = userId;
+
+        // this call has the side effect of forcing a write to the LOCATION_MODE setting in an OS
+        // upgrade case, and ensures that if anyone checks the LOCATION_MODE setting directly, they
+        // will see it in an appropriate state (at least after that user becomes foreground for the
+        // first time...)
+        isLocationEnabledForUser(userId);
+
+        // let providers know the current user is on the way out before changing the user
+        for (LocationProvider p : mProviders) {
+            p.onUserChangingLocked();
         }
+
+        mCurrentUserId = userId;
+        onUserProfilesChangedLocked();
+
+        mBlacklist.switchUser(userId);
+
+        // if the user changes, per-user settings may also have changed
+        onLocationModeChangedLocked(false);
+        onProviderAllowedChangedLocked(false);
     }
 
     private static final class Identity {
@@ -767,158 +817,380 @@
     private class LocationProvider implements AbstractLocationProvider.LocationProviderManager {
 
         private final String mName;
-        private AbstractLocationProvider mProvider;
 
-        // whether the provider is enabled in location settings
-        private boolean mSettingsEnabled;
+        // whether this provider should respect LOCATION_PROVIDERS_ALLOWED (ie gps and network)
+        private final boolean mIsManagedBySettings;
 
-        // whether the provider considers itself enabled
-        private volatile boolean mEnabled;
+        // remember to clear binder identity before invoking any provider operation
+        @GuardedBy("mLock")
+        @Nullable protected AbstractLocationProvider mProvider;
 
-        @Nullable
-        private volatile ProviderProperties mProperties;
+        @GuardedBy("mLock")
+        private boolean mUseable;  // combined state
+        @GuardedBy("mLock")
+        private boolean mAllowed;  // state of LOCATION_PROVIDERS_ALLOWED
+        @GuardedBy("mLock")
+        private boolean mEnabled;  // state of provider
+
+        @GuardedBy("mLock")
+        @Nullable private ProviderProperties mProperties;
 
         private LocationProvider(String name) {
-            mName = name;
-            // TODO: initialize settings enabled?
+            this(name, false);
         }
 
-        @Override
-        public void onAttachProvider(AbstractLocationProvider provider, boolean initiallyEnabled) {
-            checkState(mProvider == null);
+        private LocationProvider(String name, boolean isManagedBySettings) {
+            mName = name;
+            mIsManagedBySettings = isManagedBySettings;
 
-            // the provider is not yet fully constructed at this point, so we may not do anything
-            // except save a reference for later use here. do not call any provider methods.
-            mProvider = provider;
-            mEnabled = initiallyEnabled;
+            mProvider = null;
+            mUseable = false;
+            mAllowed = !mIsManagedBySettings;
+            mEnabled = false;
             mProperties = null;
         }
 
+        @GuardedBy("mLock")
+        public void attachLocked(AbstractLocationProvider provider) {
+            checkNotNull(provider);
+            checkState(mProvider == null);
+            mProvider = provider;
+
+            onUseableChangedLocked();
+        }
+
         public String getName() {
             return mName;
         }
 
-        public boolean isEnabled() {
-            return mSettingsEnabled && mEnabled;
+        @GuardedBy("mLock")
+        @Nullable
+        public String getPackageLocked() {
+            if (mProvider == null) {
+                return null;
+            } else if (mProvider instanceof LocationProviderProxy) {
+                // safe to not clear binder context since this doesn't call into the actual provider
+                return ((LocationProviderProxy) mProvider).getConnectedPackageName();
+            } else {
+                return mContext.getPackageName();
+            }
         }
 
+        public boolean isMock() {
+            return false;
+        }
+
+        @GuardedBy("mLock")
+        public boolean isPassiveLocked() {
+            return mProvider == mPassiveProvider;
+        }
+
+        @GuardedBy("mLock")
         @Nullable
-        public ProviderProperties getProperties() {
+        public ProviderProperties getPropertiesLocked() {
             return mProperties;
         }
 
-        public void setRequest(ProviderRequest request, WorkSource workSource) {
-            mProvider.setRequest(request, workSource);
+        @GuardedBy("mLock")
+        public void setRequestLocked(ProviderRequest request, WorkSource workSource) {
+            if (mProvider != null) {
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    mProvider.setRequest(request, workSource);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
         }
 
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        @GuardedBy("mLock")
+        public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println(mName + " provider:");
-            pw.println(" setting=" + mSettingsEnabled);
+            if (isMock()) {
+                pw.println(" mock=true");
+            }
+            pw.println(" attached=" + (mProvider != null));
+            if (mIsManagedBySettings) {
+                pw.println(" allowed=" + mAllowed);
+            }
             pw.println(" enabled=" + mEnabled);
+            pw.println(" useable=" + mUseable);
             pw.println(" properties=" + mProperties);
-            mProvider.dump(fd, pw, args);
+
+            if (mProvider != null) {
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    mProvider.dump(fd, pw, args);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
         }
 
-        public long getStatusUpdateTime() {
-            return mProvider.getStatusUpdateTime();
+        @GuardedBy("mLock")
+        public long getStatusUpdateTimeLocked() {
+            if (mProvider != null) {
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    return mProvider.getStatusUpdateTime();
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            } else {
+                return 0;
+            }
         }
 
-        public int getStatus(Bundle extras) {
-            return mProvider.getStatus(extras);
+        @GuardedBy("mLock")
+        public int getStatusLocked(Bundle extras) {
+            if (mProvider != null) {
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    return mProvider.getStatus(extras);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            } else {
+                return AVAILABLE;
+            }
         }
 
-        public void sendExtraCommand(String command, Bundle extras) {
-            mProvider.sendExtraCommand(command, extras);
+        @GuardedBy("mLock")
+        public void sendExtraCommandLocked(String command, Bundle extras) {
+            if (mProvider != null) {
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    mProvider.sendExtraCommand(command, extras);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
         }
 
         // called from any thread
         @Override
         public void onReportLocation(Location location) {
-            runOnHandler(() -> LocationManagerService.this.reportLocation(location,
-                    mProvider == mPassiveProvider));
+            // no security check necessary because this is coming from an internal-only interface
+            // move calls coming from below LMS onto a different thread to avoid deadlock
+            runInternal(() -> {
+                synchronized (mLock) {
+                    handleLocationChangedLocked(location, this);
+                }
+            });
         }
 
         // called from any thread
         @Override
         public void onReportLocation(List<Location> locations) {
-            runOnHandler(() -> LocationManagerService.this.reportLocationBatch(locations));
+            // move calls coming from below LMS onto a different thread to avoid deadlock
+            runInternal(() -> {
+                synchronized (mLock) {
+                    LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
+                    if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
+                        Slog.w(TAG, "reportLocationBatch() called without user permission");
+                        return;
+                    }
+
+                    if (mGnssBatchingCallback == null) {
+                        Slog.e(TAG, "reportLocationBatch() called without active Callback");
+                        return;
+                    }
+
+                    try {
+                        mGnssBatchingCallback.onLocationBatch(locations);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e);
+                    }
+                }
+            });
         }
 
         // called from any thread
         @Override
         public void onSetEnabled(boolean enabled) {
-            runOnHandler(() -> {
-                if (enabled == mEnabled) {
-                    return;
-                }
-
-                mEnabled = enabled;
-
-                if (!mSettingsEnabled) {
-                    // this provider was disabled in settings anyways, so a change to it's own
-                    // enabled status won't have any affect.
-                    return;
-                }
-
-                // traditionally clients can listen for changes to the LOCATION_PROVIDERS_ALLOWED
-                // setting to detect when providers are enabled or disabled (even though they aren't
-                // supposed to). to continue to support this we must force a change to this setting.
-                // we use the fused provider because this is forced to be always enabled in settings
-                // anyways, and so won't have any visible effect beyond triggering content observers
-                Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        "+" + LocationManager.FUSED_PROVIDER, mCurrentUserId);
-                Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        "-" + LocationManager.FUSED_PROVIDER, mCurrentUserId);
-
+            // move calls coming from below LMS onto a different thread to avoid deadlock
+            runInternal(() -> {
                 synchronized (mLock) {
-                    if (!enabled) {
-                        // If any provider has been disabled, clear all last locations for all
-                        // providers. This is to be on the safe side in case a provider has location
-                        // derived from this disabled provider.
-                        mLastLocation.clear();
-                        mLastLocationCoarseInterval.clear();
+                    if (enabled == mEnabled) {
+                        return;
                     }
 
-                    updateProviderListenersLocked(mName);
-                }
+                    mEnabled = enabled;
 
-                mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION),
-                        UserHandle.ALL);
+                    // update provider allowed settings to reflect enabled status
+                    if (mIsManagedBySettings) {
+                        if (mEnabled && !mAllowed) {
+                            Settings.Secure.putStringForUser(
+                                    mContext.getContentResolver(),
+                                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                                    "+" + mName,
+                                    mCurrentUserId);
+                        } else if (!mEnabled && mAllowed) {
+                            Settings.Secure.putStringForUser(
+                                    mContext.getContentResolver(),
+                                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                                    "-" + mName,
+                                    mCurrentUserId);
+                        }
+                    }
+
+                    onUseableChangedLocked();
+                }
             });
         }
 
         @Override
         public void onSetProperties(ProviderProperties properties) {
-            runOnHandler(() -> mProperties = properties);
+            // move calls coming from below LMS onto a different thread to avoid deadlock
+            runInternal(() -> {
+                synchronized (mLock) {
+                    mProperties = properties;
+                }
+            });
         }
 
-        private void setSettingsEnabled(boolean enabled) {
-            synchronized (mLock) {
-                if (mSettingsEnabled == enabled) {
+        @GuardedBy("mLock")
+        public void onLocationModeChangedLocked() {
+            onUseableChangedLocked();
+        }
+
+        private boolean isAllowed() {
+            return isAllowedForUser(mCurrentUserId);
+        }
+
+        private boolean isAllowedForUser(int userId) {
+            String allowedProviders = Settings.Secure.getStringForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                    userId);
+            return TextUtils.delimitedStringContains(allowedProviders, ',', mName);
+        }
+
+        @GuardedBy("mLock")
+        public void onAllowedChangedLocked() {
+            if (mIsManagedBySettings) {
+                boolean allowed = isAllowed();
+                if (allowed == mAllowed) {
                     return;
                 }
+                mAllowed = allowed;
 
-                mSettingsEnabled = enabled;
-                if (!mSettingsEnabled) {
-                    // if any provider has been disabled, clear all last locations for all
-                    // providers. this is to be on the safe side in case a provider has location
-                    // derived from this disabled provider.
-                    mLastLocation.clear();
-                    mLastLocationCoarseInterval.clear();
-                    updateProviderListenersLocked(mName);
-                } else if (mEnabled) {
-                    updateProviderListenersLocked(mName);
+                // make a best effort to keep the setting matching the real enabled state of the
+                // provider so that legacy applications aren't broken.
+                if (mAllowed && !mEnabled) {
+                    Settings.Secure.putStringForUser(
+                            mContext.getContentResolver(),
+                            Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                            "-" + mName,
+                            mCurrentUserId);
+                }
+
+                onUseableChangedLocked();
+            }
+        }
+
+        @GuardedBy("mLock")
+        public boolean isUseableLocked() {
+            return isUseableForUserLocked(mCurrentUserId);
+        }
+
+        @GuardedBy("mLock")
+        public boolean isUseableForUserLocked(int userId) {
+            return userId == mCurrentUserId && mUseable;
+        }
+
+        @GuardedBy("mLock")
+        public void onUseableChangedLocked() {
+            // if any property that contributes to "useability" here changes state, it MUST result
+            // in a direct or indrect call to onUseableChangedLocked. this allows the provider to
+            // guarantee that it will always eventually reach the correct state.
+            boolean useable = mProvider != null
+                    && mProviders.contains(this) && isLocationEnabled() && mAllowed && mEnabled;
+            if (useable == mUseable) {
+                return;
+            }
+            mUseable = useable;
+
+            if (!mUseable) {
+                // If any provider has been disabled, clear all last locations for all
+                // providers. This is to be on the safe side in case a provider has location
+                // derived from this disabled provider.
+                mLastLocation.clear();
+                mLastLocationCoarseInterval.clear();
+            }
+
+            updateProviderUseableLocked(this);
+        }
+
+        @GuardedBy("mLock")
+        public void onUserChangingLocked() {
+            // when the user is about to change, we set this provider to un-useable, and notify all
+            // of the current user clients. when the user is finished changing, useability will be
+            // updated back via onLocationModeChanged() and onAllowedChanged().
+            mUseable = false;
+            updateProviderUseableLocked(this);
+        }
+
+        // binder transactions coming from below LMS (ie location providers) need to be moved onto
+        // a different thread to avoid potential deadlock as code reenters the location providers
+        private void runInternal(Runnable runnable) {
+            if (Looper.myLooper() == mHandler.getLooper()) {
+                runnable.run();
+            } else {
+                mHandler.post(runnable);
+            }
+        }
+    }
+
+    private class MockLocationProvider extends LocationProvider {
+
+        private MockLocationProvider(String name) {
+            super(name);
+        }
+
+        @Override
+        public void attachLocked(AbstractLocationProvider provider) {
+            checkState(provider instanceof MockProvider);
+            super.attachLocked(provider);
+        }
+
+        public boolean isMock() {
+            return true;
+        }
+
+        @GuardedBy("mLock")
+        public void setEnabledLocked(boolean enabled) {
+            if (mProvider != null) {
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    ((MockProvider) mProvider).setEnabled(enabled);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
                 }
             }
         }
 
-        private void runOnHandler(Runnable runnable) {
-            if (Looper.myLooper() == mLocationHandler.getLooper()) {
-                runnable.run();
-            } else {
-                mLocationHandler.post(runnable);
+        @GuardedBy("mLock")
+        public void setLocationLocked(Location location) {
+            if (mProvider != null) {
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    ((MockProvider) mProvider).setLocation(location);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        public void setStatusLocked(int status, Bundle extras, long updateTime) {
+            if (mProvider != null) {
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    ((MockProvider) mProvider).setStatus(status, extras, updateTime);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
         }
     }
@@ -1022,19 +1294,18 @@
                 // See if receiver has any enabled update records.  Also note if any update records
                 // are high power (has a high power provider with an interval under a threshold).
                 for (UpdateRecord updateRecord : mUpdateRecords.values()) {
-                    if (isAllowedByUserSettingsLockedForUser(updateRecord.mProvider,
-                            mCurrentUserId)) {
-                        requestingLocation = true;
-                        LocationManagerService.LocationProvider locationProvider
-                                = mProvidersByName.get(updateRecord.mProvider);
-                        ProviderProperties properties = locationProvider != null
-                                ? locationProvider.getProperties() : null;
-                        if (properties != null
-                                && properties.mPowerRequirement == Criteria.POWER_HIGH
-                                && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
-                            requestingHighPowerLocation = true;
-                            break;
-                        }
+                    LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
+                    if (provider == null || !provider.isUseableLocked()) {
+                        continue;
+                    }
+
+                    requestingLocation = true;
+                    ProviderProperties properties = provider.getPropertiesLocked();
+                    if (properties != null
+                            && properties.mPowerRequirement == Criteria.POWER_HIGH
+                            && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
+                        requestingHighPowerLocation = true;
+                        break;
                     }
                 }
             }
@@ -1122,7 +1393,7 @@
                     synchronized (this) {
                         // synchronize to ensure incrementPendingBroadcastsLocked()
                         // is called before decrementPendingBroadcasts()
-                        mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler,
+                        mPendingIntent.send(mContext, 0, statusChanged, this, mHandler,
                                 getResolutionPermission(mAllowedResolutionLevel),
                                 PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
                         // call this after broadcasting so we do not increment
@@ -1158,7 +1429,7 @@
                     synchronized (this) {
                         // synchronize to ensure incrementPendingBroadcastsLocked()
                         // is called before decrementPendingBroadcasts()
-                        mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler,
+                        mPendingIntent.send(mContext, 0, locationChanged, this, mHandler,
                                 getResolutionPermission(mAllowedResolutionLevel),
                                 PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
                         // call this after broadcasting so we do not increment
@@ -1201,7 +1472,7 @@
                     synchronized (this) {
                         // synchronize to ensure incrementPendingBroadcastsLocked()
                         // is called before decrementPendingBroadcasts()
-                        mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler,
+                        mPendingIntent.send(mContext, 0, providerIntent, this, mHandler,
                                 getResolutionPermission(mAllowedResolutionLevel),
                                 PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
                         // call this after broadcasting so we do not increment
@@ -1273,16 +1544,16 @@
                 synchronized (receiver) {
                     // so wakelock calls will succeed
                     long identity = Binder.clearCallingIdentity();
-                    receiver.decrementPendingBroadcastsLocked();
-                    Binder.restoreCallingIdentity(identity);
+                    try {
+                        receiver.decrementPendingBroadcastsLocked();
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
                 }
             }
         }
     }
 
-    /**
-     * Returns the year of the GNSS hardware.
-     */
     @Override
     public int getGnssYearOfHardware() {
         if (mGnssSystemInfoProvider != null) {
@@ -1292,10 +1563,6 @@
         }
     }
 
-
-    /**
-     * Returns the model name of the GNSS hardware.
-     */
     @Override
     @Nullable
     public String getGnssHardwareModelName() {
@@ -1306,32 +1573,24 @@
         }
     }
 
-    /**
-     * Runs some checks for GNSS (FINE) level permissions, used by several methods which directly
-     * (try to) access GNSS information at this layer.
-     */
     private boolean hasGnssPermissions(String packageName) {
-        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
-        checkResolutionLevelIsSufficientForProviderUse(
-                allowedResolutionLevel,
-                LocationManager.GPS_PROVIDER);
+        synchronized (mLock) {
+            int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+            checkResolutionLevelIsSufficientForProviderUseLocked(
+                    allowedResolutionLevel,
+                    GPS_PROVIDER);
 
-        int pid = Binder.getCallingPid();
-        int uid = Binder.getCallingUid();
-        long identity = Binder.clearCallingIdentity();
-        boolean hasLocationAccess;
-        try {
-            hasLocationAccess = checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+            int pid = Binder.getCallingPid();
+            int uid = Binder.getCallingUid();
+            long identity = Binder.clearCallingIdentity();
+            try {
+                return checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
-
-        return hasLocationAccess;
     }
 
-    /**
-     * Returns the GNSS batching size, if available.
-     */
     @Override
     public int getGnssBatchSize(String packageName) {
         mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1344,10 +1603,6 @@
         }
     }
 
-    /**
-     * Adds a callback for GNSS Batching events, if permissions allow, which are transported
-     * to potentially multiple listeners by the BatchedLocationCallbackTransport above this.
-     */
     @Override
     public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
         mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1357,18 +1612,20 @@
             return false;
         }
 
-        mGnssBatchingCallback = callback;
-        mGnssBatchingDeathCallback = new LinkedCallback(callback);
-        try {
-            callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */);
-        } catch (RemoteException e) {
-            // if the remote process registering the listener is already dead, just swallow the
-            // exception and return
-            Log.e(TAG, "Remote listener already died.", e);
-            return false;
-        }
+        synchronized (mLock) {
+            mGnssBatchingCallback = callback;
+            mGnssBatchingDeathCallback = new LinkedCallback(callback);
+            try {
+                callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */);
+            } catch (RemoteException e) {
+                // if the remote process registering the listener is already dead, just swallow the
+                // exception and return
+                Log.e(TAG, "Remote listener already died.", e);
+                return false;
+            }
 
-        return true;
+            return true;
+        }
     }
 
     private class LinkedCallback implements IBinder.DeathRecipient {
@@ -1391,27 +1648,22 @@
         }
     }
 
-    /**
-     * Removes callback for GNSS batching
-     */
     @Override
     public void removeGnssBatchingCallback() {
-        try {
-            mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback,
-                    0 /* flags */);
-        } catch (NoSuchElementException e) {
-            // if the death callback isn't connected (it should be...), log error, swallow the
-            // exception and return
-            Log.e(TAG, "Couldn't unlink death callback.", e);
+        synchronized (mLock) {
+            try {
+                mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback,
+                        0 /* flags */);
+            } catch (NoSuchElementException e) {
+                // if the death callback isn't connected (it should be...), log error, swallow the
+                // exception and return
+                Log.e(TAG, "Couldn't unlink death callback.", e);
+            }
+            mGnssBatchingCallback = null;
+            mGnssBatchingDeathCallback = null;
         }
-        mGnssBatchingCallback = null;
-        mGnssBatchingDeathCallback = null;
     }
 
-
-    /**
-     * Starts GNSS batching, if available.
-     */
     @Override
     public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) {
         mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1421,20 +1673,20 @@
             return false;
         }
 
-        if (mGnssBatchingInProgress) {
-            // Current design does not expect multiple starts to be called repeatedly
-            Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch");
-            // Try to clean up anyway, and continue
-            stopGnssBatch();
-        }
+        synchronized (mLock) {
+            if (mGnssBatchingInProgress) {
+                // Current design does not expect multiple starts to be called repeatedly
+                Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch");
+                // Try to clean up anyway, and continue
+                stopGnssBatch();
+            }
 
-        mGnssBatchingInProgress = true;
-        return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull);
+            mGnssBatchingInProgress = true;
+            return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull);
+        }
     }
 
-    /**
-     * Flushes a GNSS batch in progress
-     */
+
     @Override
     public void flushGnssBatch(String packageName) {
         mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -1445,117 +1697,66 @@
             return;
         }
 
-        if (!mGnssBatchingInProgress) {
-            Log.w(TAG, "flushGnssBatch called with no batch in progress");
-        }
+        synchronized (mLock) {
+            if (!mGnssBatchingInProgress) {
+                Log.w(TAG, "flushGnssBatch called with no batch in progress");
+            }
 
-        if (mGnssBatchingProvider != null) {
-            mGnssBatchingProvider.flush();
+            if (mGnssBatchingProvider != null) {
+                mGnssBatchingProvider.flush();
+            }
         }
     }
 
-    /**
-     * Stops GNSS batching
-     */
     @Override
     public boolean stopGnssBatch() {
         mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
                 "Location Hardware permission not granted to access hardware batching");
 
-        if (mGnssBatchingProvider != null) {
-            mGnssBatchingInProgress = false;
-            return mGnssBatchingProvider.stop();
-        } else {
-            return false;
-        }
-    }
-
-    @Override
-    public void reportLocationBatch(List<Location> locations) {
-        checkCallerIsProvider();
-
-        // Currently used only for GNSS locations - update permissions check if changed
-        if (isAllowedByUserSettingsLockedForUser(LocationManager.GPS_PROVIDER, mCurrentUserId)) {
-            if (mGnssBatchingCallback == null) {
-                Slog.e(TAG, "reportLocationBatch() called without active Callback");
-                return;
-            }
-            try {
-                mGnssBatchingCallback.onLocationBatch(locations);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e);
-            }
-        } else {
-            Slog.w(TAG, "reportLocationBatch() called without user permission, locations blocked");
-        }
-    }
-
-    private void addProviderLocked(LocationProvider provider) {
-        mProviders.add(provider);
-        mProvidersByName.put(provider.getName(), provider);
-    }
-
-    private void removeProviderLocked(LocationProvider provider) {
-        mProviders.remove(provider);
-        mProvidersByName.remove(provider.getName());
-    }
-
-    /**
-     * Returns "true" if access to the specified location provider is allowed by the specified
-     * user's settings. Access to all location providers is forbidden to non-location-provider
-     * processes belonging to background users.
-     *
-     * @param provider the name of the location provider
-     * @param userId   the user id to query
-     */
-    private boolean isAllowedByUserSettingsLockedForUser(String provider, int userId) {
-        if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
-            return isLocationEnabledForUser(userId);
-        }
-        if (LocationManager.FUSED_PROVIDER.equals(provider)) {
-            return isLocationEnabledForUser(userId);
-        }
         synchronized (mLock) {
-            if (mMockProviders.containsKey(provider)) {
-                return isLocationEnabledForUser(userId);
+            if (mGnssBatchingProvider != null) {
+                mGnssBatchingInProgress = false;
+                return mGnssBatchingProvider.stop();
+            } else {
+                return false;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void addProviderLocked(LocationProvider provider) {
+        Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
+
+        mProviders.add(provider);
+
+        provider.onAllowedChangedLocked();  // allowed state may change while provider was inactive
+        provider.onUseableChangedLocked();
+    }
+
+    @GuardedBy("mLock")
+    private void removeProviderLocked(LocationProvider provider) {
+        if (mProviders.remove(provider)) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                provider.onUseableChangedLocked();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private LocationProvider getLocationProviderLocked(String providerName) {
+        for (LocationProvider provider : mProviders) {
+            if (providerName.equals(provider.getName())) {
+                return provider;
             }
         }
 
-        long identity = Binder.clearCallingIdentity();
-        try {
-            // Use system settings
-            ContentResolver cr = mContext.getContentResolver();
-            String allowedProviders = Settings.Secure.getStringForUser(
-                    cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId);
-            return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        return null;
     }
 
-
-    /**
-     * Returns "true" if access to the specified location provider is allowed by the specified
-     * user's settings. Access to all location providers is forbidden to non-location-provider
-     * processes belonging to background users.
-     *
-     * @param provider the name of the location provider
-     * @param uid      the requestor's UID
-     * @param userId   the user id to query
-     */
-    private boolean isAllowedByUserSettingsLocked(String provider, int uid, int userId) {
-        if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) {
-            return false;
-        }
-        return isAllowedByUserSettingsLockedForUser(provider, userId);
-    }
-
-    /**
-     * Returns the permission string associated with the specified resolution level.
-     *
-     * @param resolutionLevel the resolution level
-     * @return the permission string
-     */
     private String getResolutionPermission(int resolutionLevel) {
         switch (resolutionLevel) {
             case RESOLUTION_LEVEL_FINE:
@@ -1567,13 +1768,6 @@
         }
     }
 
-    /**
-     * Returns the resolution level allowed to the given PID/UID pair.
-     *
-     * @param pid the PID
-     * @param uid the UID
-     * @return resolution level allowed to the pid/uid pair
-     */
     private int getAllowedResolutionLevel(int pid, int uid) {
         if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
                 pid, uid) == PERMISSION_GRANTED) {
@@ -1586,39 +1780,22 @@
         }
     }
 
-    /**
-     * Returns the resolution level allowed to the caller
-     *
-     * @return resolution level allowed to caller
-     */
     private int getCallerAllowedResolutionLevel() {
         return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
     }
 
-    /**
-     * Throw SecurityException if specified resolution level is insufficient to use geofences.
-     *
-     * @param allowedResolutionLevel resolution level allowed to caller
-     */
     private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) {
         if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
             throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission");
         }
     }
 
-    /**
-     * Return the minimum resolution level required to use the specified location provider.
-     *
-     * @param provider the name of the location provider
-     * @return minimum resolution level required for provider
-     */
-    private int getMinimumResolutionLevelForProviderUse(String provider) {
-        if (LocationManager.GPS_PROVIDER.equals(provider) ||
-                LocationManager.PASSIVE_PROVIDER.equals(provider)) {
+    @GuardedBy("mLock")
+    private int getMinimumResolutionLevelForProviderUseLocked(String provider) {
+        if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
             // gps and passive providers require FINE permission
             return RESOLUTION_LEVEL_FINE;
-        } else if (LocationManager.NETWORK_PROVIDER.equals(provider) ||
-                LocationManager.FUSED_PROVIDER.equals(provider)) {
+        } else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) {
             // network and fused providers are ok with COARSE or FINE
             return RESOLUTION_LEVEL_COARSE;
         } else {
@@ -1627,7 +1804,7 @@
                     continue;
                 }
 
-                ProviderProperties properties = lp.getProperties();
+                ProviderProperties properties = lp.getPropertiesLocked();
                 if (properties != null) {
                     if (properties.mRequiresSatellite) {
                         // provider requiring satellites require FINE permission
@@ -1643,16 +1820,10 @@
         return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE
     }
 
-    /**
-     * Throw SecurityException if specified resolution level is insufficient to use the named
-     * location provider.
-     *
-     * @param allowedResolutionLevel resolution level allowed to caller
-     * @param providerName           the name of the location provider
-     */
-    private void checkResolutionLevelIsSufficientForProviderUse(int allowedResolutionLevel,
+    @GuardedBy("mLock")
+    private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel,
             String providerName) {
-        int requiredResolutionLevel = getMinimumResolutionLevelForProviderUse(providerName);
+        int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName);
         if (allowedResolutionLevel < requiredResolutionLevel) {
             switch (requiredResolutionLevel) {
                 case RESOLUTION_LEVEL_FINE:
@@ -1668,20 +1839,6 @@
         }
     }
 
-    /**
-     * Throw SecurityException if WorkSource use is not allowed (i.e. can't blame other packages
-     * for battery).
-     */
-    private void checkDeviceStatsAllowed() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.UPDATE_DEVICE_STATS, null);
-    }
-
-    private void checkUpdateAppOpsAllowed() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.UPDATE_APP_OPS_STATS, null);
-    }
-
     public static int resolutionLevelToOp(int allowedResolutionLevel) {
         if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
             if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
@@ -1739,19 +1896,17 @@
      */
     @Override
     public List<String> getAllProviders() {
-        ArrayList<String> out;
         synchronized (mLock) {
-            out = new ArrayList<>(mProviders.size());
+            ArrayList<String> providers = new ArrayList<>(mProviders.size());
             for (LocationProvider provider : mProviders) {
                 String name = provider.getName();
-                if (LocationManager.FUSED_PROVIDER.equals(name)) {
+                if (FUSED_PROVIDER.equals(name)) {
                     continue;
                 }
-                out.add(name);
+                providers.add(name);
             }
+            return providers;
         }
-        if (D) Log.d(TAG, "getAllProviders()=" + out);
-        return out;
     }
 
     /**
@@ -1762,37 +1917,28 @@
     @Override
     public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
         int allowedResolutionLevel = getCallerAllowedResolutionLevel();
-        ArrayList<String> out;
-        int uid = Binder.getCallingUid();
-        long identity = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                out = new ArrayList<>(mProviders.size());
-                for (LocationProvider provider : mProviders) {
-                    String name = provider.getName();
-                    if (LocationManager.FUSED_PROVIDER.equals(name)) {
-                        continue;
-                    }
-                    if (allowedResolutionLevel >= getMinimumResolutionLevelForProviderUse(name)) {
-                        if (enabledOnly
-                                && !isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) {
-                            continue;
-                        }
-                        if (criteria != null
-                                && !android.location.LocationProvider.propertiesMeetCriteria(
-                                name, provider.getProperties(), criteria)) {
-                            continue;
-                        }
-                        out.add(name);
-                    }
+        synchronized (mLock) {
+            ArrayList<String> providers = new ArrayList<>(mProviders.size());
+            for (LocationProvider provider : mProviders) {
+                String name = provider.getName();
+                if (FUSED_PROVIDER.equals(name)) {
+                    continue;
                 }
+                if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
+                    continue;
+                }
+                if (enabledOnly && !provider.isUseableLocked()) {
+                    continue;
+                }
+                if (criteria != null
+                        && !android.location.LocationProvider.propertiesMeetCriteria(
+                        name, provider.getPropertiesLocked(), criteria)) {
+                    continue;
+                }
+                providers.add(name);
             }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+            return providers;
         }
-
-        if (D) Log.d(TAG, "getProviders()=" + out);
-        return out;
     }
 
     /**
@@ -1804,71 +1950,36 @@
      */
     @Override
     public String getBestProvider(Criteria criteria, boolean enabledOnly) {
-        String result;
-
         List<String> providers = getProviders(criteria, enabledOnly);
-        if (!providers.isEmpty()) {
-            result = pickBest(providers);
-            if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result);
-            return result;
-        }
-        providers = getProviders(null, enabledOnly);
-        if (!providers.isEmpty()) {
-            result = pickBest(providers);
-            if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result);
-            return result;
+        if (providers.isEmpty()) {
+            providers = getProviders(null, enabledOnly);
         }
 
-        if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + null);
+        if (!providers.isEmpty()) {
+            if (providers.contains(GPS_PROVIDER)) {
+                return GPS_PROVIDER;
+            } else if (providers.contains(NETWORK_PROVIDER)) {
+                return NETWORK_PROVIDER;
+            } else {
+                return providers.get(0);
+            }
+        }
+
         return null;
     }
 
-    private String pickBest(List<String> providers) {
-        if (providers.contains(LocationManager.GPS_PROVIDER)) {
-            return LocationManager.GPS_PROVIDER;
-        } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
-            return LocationManager.NETWORK_PROVIDER;
-        } else {
-            return providers.get(0);
-        }
-    }
-
-    @Override
-    public boolean providerMeetsCriteria(String provider, Criteria criteria) {
-        LocationProvider p = mProvidersByName.get(provider);
-        if (p == null) {
-            throw new IllegalArgumentException("provider=" + provider);
-        }
-
-        boolean result = android.location.LocationProvider.propertiesMeetCriteria(
-                p.getName(), p.getProperties(), criteria);
-        if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result);
-        return result;
-    }
-
-    private void updateProvidersSettingsLocked() {
-        for (LocationProvider p : mProviders) {
-            p.setSettingsEnabled(isAllowedByUserSettingsLockedForUser(p.getName(), mCurrentUserId));
-        }
-
-        mContext.sendBroadcastAsUser(new Intent(LocationManager.MODE_CHANGED_ACTION),
-                UserHandle.ALL);
-    }
-
-    private void updateProviderListenersLocked(String provider) {
-        LocationProvider p = mProvidersByName.get(provider);
-        if (p == null) return;
-
-        boolean enabled = p.isEnabled();
+    @GuardedBy("mLock")
+    private void updateProviderUseableLocked(LocationProvider provider) {
+        boolean useable = provider.isUseableLocked();
 
         ArrayList<Receiver> deadReceivers = null;
 
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
         if (records != null) {
             for (UpdateRecord record : records) {
-                if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+                if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
                     // Sends a notification message to the receiver
-                    if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
+                    if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
                         if (deadReceivers == null) {
                             deadReceivers = new ArrayList<>();
                         }
@@ -1887,25 +1998,37 @@
         applyRequirementsLocked(provider);
     }
 
-    private void applyRequirementsLocked(String provider) {
-        LocationProvider p = mProvidersByName.get(provider);
-        if (p == null) return;
+    @GuardedBy("mLock")
+    private void applyRequirementsLocked(String providerName) {
+        LocationProvider provider = getLocationProviderLocked(providerName);
+        if (provider != null) {
+            applyRequirementsLocked(provider);
+        }
+    }
 
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+    @GuardedBy("mLock")
+    private void applyRequirementsLocked(LocationProvider provider) {
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
         WorkSource worksource = new WorkSource();
         ProviderRequest providerRequest = new ProviderRequest();
 
-        ContentResolver resolver = mContext.getContentResolver();
-        long backgroundThrottleInterval = Settings.Global.getLong(
-                resolver,
-                Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
-                DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+        long backgroundThrottleInterval;
 
-        if (p.isEnabled() && records != null && !records.isEmpty()) {
+        long identity = Binder.clearCallingIdentity();
+        try {
+            backgroundThrottleInterval = Settings.Global.getLong(
+                    mContext.getContentResolver(),
+                    Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+                    DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        if (provider.isUseableLocked() && records != null && !records.isEmpty()) {
             // initialize the low power mode to true and set to false if any of the records requires
             providerRequest.lowPowerMode = true;
             for (UpdateRecord record : records) {
-                if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+                if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
                     if (checkLocationAccess(
                             record.mReceiver.mIdentity.mPid,
                             record.mReceiver.mIdentity.mUid,
@@ -1945,7 +2068,8 @@
                 // under that threshold.
                 long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
                 for (UpdateRecord record : records) {
-                    if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+                    if (isCurrentProfileLocked(
+                            UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
                         LocationRequest locationRequest = record.mRequest;
 
                         // Don't assign battery blame for update records whose
@@ -1972,7 +2096,7 @@
         }
 
         if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest);
-        p.setRequest(providerRequest, worksource);
+        provider.setRequestLocked(providerRequest, worksource);
     }
 
     /**
@@ -1995,34 +2119,11 @@
     @Override
     public String[] getBackgroundThrottlingWhitelist() {
         synchronized (mLock) {
-            return mBackgroundThrottlePackageWhitelist.toArray(
-                    new String[0]);
+            return mBackgroundThrottlePackageWhitelist.toArray(new String[0]);
         }
     }
 
-    private void updateBackgroundThrottlingWhitelistLocked() {
-        String setting = Settings.Global.getString(
-                mContext.getContentResolver(),
-                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
-        if (setting == null) {
-            setting = "";
-        }
-
-        mBackgroundThrottlePackageWhitelist.clear();
-        mBackgroundThrottlePackageWhitelist.addAll(
-                SystemConfig.getInstance().getAllowUnthrottledLocation());
-        mBackgroundThrottlePackageWhitelist.addAll(
-                Arrays.asList(setting.split(",")));
-    }
-
-    private void updateLastLocationMaxAgeLocked() {
-        mLastLocationMaxAgeMs =
-                Settings.Global.getLong(
-                        mContext.getContentResolver(),
-                        Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
-                        DEFAULT_LAST_LOCATION_MAX_AGE_MS);
-    }
-
+    @GuardedBy("mLock")
     private boolean isThrottlingExemptLocked(Identity identity) {
         if (identity.mUid == Process.SYSTEM_UID) {
             return true;
@@ -2032,8 +2133,8 @@
             return true;
         }
 
-        for (LocationProviderProxy provider : mProxyProviders) {
-            if (identity.mPackageName.equals(provider.getConnectedPackageName())) {
+        for (LocationProvider provider : mProviders) {
+            if (identity.mPackageName.equals(provider.getPackageLocked())) {
                 return true;
             }
         }
@@ -2119,6 +2220,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
             String packageName, WorkSource workSource, boolean hideFromAppOps) {
         IBinder binder = listener.asBinder();
@@ -2137,6 +2239,7 @@
         return receiver;
     }
 
+    @GuardedBy("mLock")
     private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
             WorkSource workSource, boolean hideFromAppOps) {
         Receiver receiver = mReceivers.get(intent);
@@ -2202,67 +2305,65 @@
         throw new SecurityException("invalid package name: " + packageName);
     }
 
-    private void checkPendingIntent(PendingIntent intent) {
-        if (intent == null) {
-            throw new IllegalArgumentException("invalid pending intent: " + null);
-        }
-    }
-
-    private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent,
-            int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) {
-        if (intent == null && listener == null) {
-            throw new IllegalArgumentException("need either listener or intent");
-        } else if (intent != null && listener != null) {
-            throw new IllegalArgumentException("cannot register both listener and intent");
-        } else if (intent != null) {
-            checkPendingIntent(intent);
-            return getReceiverLocked(intent, pid, uid, packageName, workSource, hideFromAppOps);
-        } else {
-            return getReceiverLocked(listener, pid, uid, packageName, workSource, hideFromAppOps);
-        }
-    }
-
     @Override
     public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
             PendingIntent intent, String packageName) {
-        if (request == null) request = DEFAULT_LOCATION_REQUEST;
-        checkPackageName(packageName);
-        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
-        checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
-                request.getProvider());
-        WorkSource workSource = request.getWorkSource();
-        if (workSource != null && !workSource.isEmpty()) {
-            checkDeviceStatsAllowed();
-        }
-        boolean hideFromAppOps = request.getHideFromAppOps();
-        if (hideFromAppOps) {
-            checkUpdateAppOpsAllowed();
-        }
-        boolean callerHasLocationHardwarePermission =
-                mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
-                        == PERMISSION_GRANTED;
-        LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
-                callerHasLocationHardwarePermission);
-
-        final int pid = Binder.getCallingPid();
-        final int uid = Binder.getCallingUid();
-        // providers may use public location API's, need to clear identity
-        long identity = Binder.clearCallingIdentity();
-        try {
-            // We don't check for MODE_IGNORED here; we will do that when we go to deliver
-            // a location.
-            checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
-
-            synchronized (mLock) {
-                Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid,
-                        packageName, workSource, hideFromAppOps);
-                requestLocationUpdatesLocked(sanitizedRequest, recevier, uid, packageName);
+        synchronized (mLock) {
+            if (request == null) request = DEFAULT_LOCATION_REQUEST;
+            checkPackageName(packageName);
+            int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+            checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
+                    request.getProvider());
+            WorkSource workSource = request.getWorkSource();
+            if (workSource != null && !workSource.isEmpty()) {
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.UPDATE_DEVICE_STATS, null);
             }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+            boolean hideFromAppOps = request.getHideFromAppOps();
+            if (hideFromAppOps) {
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.UPDATE_APP_OPS_STATS, null);
+            }
+            boolean callerHasLocationHardwarePermission =
+                    mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
+                            == PERMISSION_GRANTED;
+            LocationRequest sanitizedRequest = createSanitizedRequest(request,
+                    allowedResolutionLevel,
+                    callerHasLocationHardwarePermission);
+
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+
+            long identity = Binder.clearCallingIdentity();
+            try {
+
+                // We don't check for MODE_IGNORED here; we will do that when we go to deliver
+                // a location.
+                checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
+
+                if (intent == null && listener == null) {
+                    throw new IllegalArgumentException("need either listener or intent");
+                } else if (intent != null && listener != null) {
+                    throw new IllegalArgumentException(
+                            "cannot register both listener and intent");
+                }
+
+                Receiver receiver;
+                if (intent != null) {
+                    receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
+                            hideFromAppOps);
+                } else {
+                    receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
+                            hideFromAppOps);
+                }
+                requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
+    @GuardedBy("mLock")
     private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver,
             int uid, String packageName) {
         // Figure out the provider. Either its explicitly request (legacy use cases), or
@@ -2273,7 +2374,7 @@
             throw new IllegalArgumentException("provider name must not be null");
         }
 
-        LocationProvider provider = mProvidersByName.get(name);
+        LocationProvider provider = getLocationProviderLocked(name);
         if (provider == null) {
             throw new IllegalArgumentException("provider doesn't exist: " + name);
         }
@@ -2292,7 +2393,7 @@
             oldRecord.disposeLocked(false);
         }
 
-        if (provider.isEnabled()) {
+        if (provider.isUseableLocked()) {
             applyRequirementsLocked(name);
         } else {
             // Notify the listener that updates are currently disabled
@@ -2308,14 +2409,23 @@
             String packageName) {
         checkPackageName(packageName);
 
-        final int pid = Binder.getCallingPid();
-        final int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        int uid = Binder.getCallingUid();
+
+        if (intent == null && listener == null) {
+            throw new IllegalArgumentException("need either listener or intent");
+        } else if (intent != null && listener != null) {
+            throw new IllegalArgumentException("cannot register both listener and intent");
+        }
 
         synchronized (mLock) {
-            Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid,
-                    packageName, null, false);
+            Receiver receiver;
+            if (intent != null) {
+                receiver = getReceiverLocked(intent, pid, uid, packageName, null, false);
+            } else {
+                receiver = getReceiverLocked(listener, pid, uid, packageName, null, false);
+            }
 
-            // providers may use public location API's, need to clear identity
             long identity = Binder.clearCallingIdentity();
             try {
                 removeUpdatesLocked(receiver);
@@ -2325,6 +2435,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void removeUpdatesLocked(Receiver receiver) {
         if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
 
@@ -2356,51 +2467,53 @@
         }
     }
 
-    private void applyAllProviderRequirementsLocked() {
-        for (LocationProvider p : mProviders) {
-            applyRequirementsLocked(p.getName());
-        }
-    }
-
     @Override
-    public Location getLastLocation(LocationRequest request, String packageName) {
-        if (D) Log.d(TAG, "getLastLocation: " + request);
-        if (request == null) request = DEFAULT_LOCATION_REQUEST;
-        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
-        checkPackageName(packageName);
-        checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
-                request.getProvider());
-        // no need to sanitize this request, as only the provider name is used
+    public Location getLastLocation(LocationRequest r, String packageName) {
+        if (D) Log.d(TAG, "getLastLocation: " + r);
+        synchronized (mLock) {
+            LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
+            int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+            checkPackageName(packageName);
+            checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
+                    request.getProvider());
+            // no need to sanitize this request, as only the provider name is used
 
-        final int pid = Binder.getCallingPid();
-        final int uid = Binder.getCallingUid();
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            if (mBlacklist.isBlacklisted(packageName)) {
-                if (D) {
-                    Log.d(TAG, "not returning last loc for blacklisted app: " +
-                            packageName);
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (mBlacklist.isBlacklisted(packageName)) {
+                    if (D) {
+                        Log.d(TAG, "not returning last loc for blacklisted app: "
+                                + packageName);
+                    }
+                    return null;
                 }
-                return null;
-            }
 
-            if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) {
-                if (D) {
-                    Log.d(TAG, "not returning last loc for no op app: " +
-                            packageName);
+                if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) {
+                    if (D) {
+                        Log.d(TAG, "not returning last loc for no op app: "
+                                + packageName);
+                    }
+                    return null;
                 }
-                return null;
-            }
 
-            synchronized (mLock) {
                 // Figure out the provider. Either its explicitly request (deprecated API's),
                 // or use the fused provider
                 String name = request.getProvider();
                 if (name == null) name = LocationManager.FUSED_PROVIDER;
-                LocationProvider provider = mProvidersByName.get(name);
+                LocationProvider provider = getLocationProviderLocked(name);
                 if (provider == null) return null;
 
-                if (!isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) return null;
+                // only the current user or location providers may get location this way
+                if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isLocationProviderLocked(
+                        uid)) {
+                    return null;
+                }
+
+                if (!provider.isUseableLocked()) {
+                    return null;
+                }
 
                 Location location;
                 if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
@@ -2416,9 +2529,12 @@
 
                 // Don't return stale location to apps with foreground-only location permission.
                 String op = resolutionLevelToOpStr(allowedResolutionLevel);
-                long locationAgeMs = SystemClock.elapsedRealtime() -
-                        location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
-                if ((locationAgeMs > mLastLocationMaxAgeMs)
+                long locationAgeMs = SystemClock.elapsedRealtime()
+                        - location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
+                if ((locationAgeMs > Settings.Global.getLong(
+                        mContext.getContentResolver(),
+                        Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
+                        DEFAULT_LAST_LOCATION_MAX_AGE_MS))
                         && (mAppOps.unsafeCheckOp(op, uid, packageName)
                         == AppOpsManager.MODE_FOREGROUND)) {
                     return null;
@@ -2433,24 +2549,13 @@
                 } else {
                     return new Location(location);
                 }
+                return null;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
-            return null;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
         }
     }
 
-    /**
-     * Provides an interface to inject and set the last location if location is not available
-     * currently.
-     *
-     * This helps in cases where the product (Cars for example) has saved the last known location
-     * before powering off.  This interface lets the client inject the saved location while the GPS
-     * chipset is getting its first fix, there by improving user experience.
-     *
-     * @param location - Location object to inject
-     * @return true if update was successful, false if not
-     */
     @Override
     public boolean injectLocation(Location location) {
         mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
@@ -2464,38 +2569,23 @@
             }
             return false;
         }
-        LocationProvider p = null;
-        String provider = location.getProvider();
-        if (provider != null) {
-            p = mProvidersByName.get(provider);
-        }
-        if (p == null) {
-            if (D) {
-                Log.d(TAG, "injectLocation(): unknown provider");
-            }
-            return false;
-        }
+
         synchronized (mLock) {
-            if (!isAllowedByUserSettingsLockedForUser(provider, mCurrentUserId)) {
-                if (D) {
-                    Log.d(TAG, "Location disabled in Settings for current user:" + mCurrentUserId);
-                }
+            LocationProvider provider = getLocationProviderLocked(location.getProvider());
+            if (provider == null || !provider.isUseableLocked()) {
                 return false;
-            } else {
-                // NOTE: If last location is already available, location is not injected.  If
-                // provider's normal source (like a GPS chipset) have already provided an output,
-                // there is no need to inject this location.
-                if (mLastLocation.get(provider) == null) {
-                    updateLastLocationLocked(location, provider);
-                } else {
-                    if (D) {
-                        Log.d(TAG, "injectLocation(): Location exists. Not updating");
-                    }
-                    return false;
-                }
             }
+
+            // NOTE: If last location is already available, location is not injected.  If
+            // provider's normal source (like a GPS chipset) have already provided an output
+            // there is no need to inject this location.
+            if (mLastLocation.get(provider.getName()) != null) {
+                return false;
+            }
+
+            updateLastLocationLocked(location, provider.getName());
+            return true;
         }
-        return true;
     }
 
     @Override
@@ -2504,39 +2594,49 @@
         if (request == null) request = DEFAULT_LOCATION_REQUEST;
         int allowedResolutionLevel = getCallerAllowedResolutionLevel();
         checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
-        checkPendingIntent(intent);
-        checkPackageName(packageName);
-        checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
-                request.getProvider());
-        // Require that caller can manage given document
-        boolean callerHasLocationHardwarePermission =
-                mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
-                        == PERMISSION_GRANTED;
-        LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
-                callerHasLocationHardwarePermission);
-
-        if (D) Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
-
-        // geo-fence manager uses the public location API, need to clear identity
-        int uid = Binder.getCallingUid();
-        // TODO: http://b/23822629
-        if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
-            // temporary measure until geofences work for secondary users
-            Log.w(TAG, "proximity alerts are currently available only to the primary user");
-            return;
+        if (intent == null) {
+            throw new IllegalArgumentException("invalid pending intent: " + null);
         }
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
-                    uid, packageName);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        checkPackageName(packageName);
+        synchronized (mLock) {
+            checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
+                    request.getProvider());
+            // Require that caller can manage given document
+            boolean callerHasLocationHardwarePermission =
+                    mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
+                            == PERMISSION_GRANTED;
+            LocationRequest sanitizedRequest = createSanitizedRequest(request,
+                    allowedResolutionLevel,
+                    callerHasLocationHardwarePermission);
+
+            if (D) {
+                Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
+            }
+
+            // geo-fence manager uses the public location API, need to clear identity
+            int uid = Binder.getCallingUid();
+            // TODO: http://b/23822629
+            if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
+                // temporary measure until geofences work for secondary users
+                Log.w(TAG, "proximity alerts are currently available only to the primary user");
+                return;
+            }
+            long identity = Binder.clearCallingIdentity();
+            try {
+                mGeofenceManager.addFence(sanitizedRequest, geofence, intent,
+                        allowedResolutionLevel,
+                        uid, packageName);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     @Override
     public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) {
-        checkPendingIntent(intent);
+        if (intent == null) {
+            throw new IllegalArgumentException("invalid pending intent: " + null);
+        }
         checkPackageName(packageName);
 
         if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent);
@@ -2641,6 +2741,7 @@
         synchronized (mLock) {
             Identity callerIdentity
                     = new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+
             // TODO(b/120481270): Register for client death notification and update map.
             mGnssNavigationMessageListeners.put(listener.asBinder(), callerIdentity);
             long identity = Binder.clearCallingIdentity();
@@ -2670,25 +2771,26 @@
     }
 
     @Override
-    public boolean sendExtraCommand(String provider, String command, Bundle extras) {
-        if (provider == null) {
+    public boolean sendExtraCommand(String providerName, String command, Bundle extras) {
+        if (providerName == null) {
             // throw NullPointerException to remain compatible with previous implementation
             throw new NullPointerException();
         }
-        checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
-                provider);
-
-        // and check for ACCESS_LOCATION_EXTRA_COMMANDS
-        if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
-                != PERMISSION_GRANTED)) {
-            throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
-        }
-
         synchronized (mLock) {
-            LocationProvider p = mProvidersByName.get(provider);
-            if (p == null) return false;
+            checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
+                    providerName);
 
-            p.sendExtraCommand(command, extras);
+            // and check for ACCESS_LOCATION_EXTRA_COMMANDS
+            if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
+                    != PERMISSION_GRANTED)) {
+                throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
+            }
+
+            LocationProvider provider = getLocationProviderLocked(providerName);
+            if (provider != null) {
+                provider.sendExtraCommandLocked(command, extras);
+            }
+
             return true;
         }
     }
@@ -2707,44 +2809,29 @@
         }
     }
 
-    /**
-     * @return null if the provider does not exist
-     * @throws SecurityException if the provider is not allowed to be
-     *                           accessed by the caller
-     */
     @Override
-    public ProviderProperties getProviderProperties(String provider) {
-        checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
-                provider);
-
-        LocationProvider p;
+    public ProviderProperties getProviderProperties(String providerName) {
         synchronized (mLock) {
-            p = mProvidersByName.get(provider);
-        }
+            checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
+                    providerName);
 
-        if (p == null) return null;
-        return p.getProperties();
+            LocationProvider provider = getLocationProviderLocked(providerName);
+            if (provider == null) {
+                return null;
+            }
+            return provider.getPropertiesLocked();
+        }
     }
 
-    /**
-     * @return null if the provider does not exist
-     * @throws SecurityException if the provider is not allowed to be
-     *                           accessed by the caller
-     */
     @Override
     public String getNetworkProviderPackage() {
-        LocationProvider p;
         synchronized (mLock) {
-            p = mProvidersByName.get(LocationManager.NETWORK_PROVIDER);
+            LocationProvider provider = getLocationProviderLocked(NETWORK_PROVIDER);
+            if (provider == null) {
+                return null;
+            }
+            return provider.getPackageLocked();
         }
-
-        if (p == null) {
-            return null;
-        }
-        if (p.mProvider instanceof LocationProviderProxy) {
-            return ((LocationProviderProxy) p.mProvider).getConnectedPackageName();
-        }
-        return null;
     }
 
     @Override
@@ -2780,241 +2867,96 @@
         }
     }
 
-    /**
-     * Returns the current location enabled/disabled status for a user
-     *
-     * @param userId the id of the user
-     * @return true if location is enabled
-     */
+    private boolean isLocationEnabled() {
+        return isLocationEnabledForUser(mCurrentUserId);
+    }
+
     @Override
     public boolean isLocationEnabledForUser(int userId) {
         // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
-        checkInteractAcrossUsersPermission(userId);
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    "Requires INTERACT_ACROSS_USERS permission");
+        }
 
         long identity = Binder.clearCallingIdentity();
         try {
-            synchronized (mLock) {
-                final String allowedProviders = Settings.Secure.getStringForUser(
+            boolean enabled;
+            try {
+                enabled = Settings.Secure.getIntForUser(
+                        mContext.getContentResolver(),
+                        Settings.Secure.LOCATION_MODE,
+                        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);
-                if (allowedProviders == null) {
-                    return false;
+                        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);
                 }
-                final List<String> providerList = Arrays.asList(allowedProviders.split(","));
-                for (String provider : mRealProviders.keySet()) {
-                    if (provider.equals(LocationManager.PASSIVE_PROVIDER)
-                            || provider.equals(LocationManager.FUSED_PROVIDER)) {
-                        continue;
-                    }
-                    if (providerList.contains(provider)) {
-                        return true;
-                    }
-                }
-                return false;
             }
+            return enabled;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
     }
 
-    /**
-     * Enable or disable location for a user
-     *
-     * @param enabled true to enable location, false to disable location
-     * @param userId  the id of the user
-     */
-    @Override
-    public void setLocationEnabledForUser(boolean enabled, int userId) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.WRITE_SECURE_SETTINGS,
-                "Requires WRITE_SECURE_SETTINGS permission");
-
-        // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
-        checkInteractAcrossUsersPermission(userId);
-
-        long identity = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                final Set<String> allRealProviders = mRealProviders.keySet();
-                // Update all providers on device plus gps and network provider when disabling
-                // location
-                Set<String> allProvidersSet = new ArraySet<>(allRealProviders.size() + 2);
-                allProvidersSet.addAll(allRealProviders);
-                // When disabling location, disable gps and network provider that could have been
-                // enabled by location mode api.
-                if (!enabled) {
-                    allProvidersSet.add(LocationManager.GPS_PROVIDER);
-                    allProvidersSet.add(LocationManager.NETWORK_PROVIDER);
-                }
-                if (allProvidersSet.isEmpty()) {
-                    return;
-                }
-                // to ensure thread safety, we write the provider name with a '+' or '-'
-                // and let the SettingsProvider handle it rather than reading and modifying
-                // the list of enabled providers.
-                final String prefix = enabled ? "+" : "-";
-                StringBuilder locationProvidersAllowed = new StringBuilder();
-                for (String provider : allProvidersSet) {
-                    if (provider.equals(LocationManager.PASSIVE_PROVIDER)
-                            || provider.equals(LocationManager.FUSED_PROVIDER)) {
-                        continue;
-                    }
-                    locationProvidersAllowed.append(prefix);
-                    locationProvidersAllowed.append(provider);
-                    locationProvidersAllowed.append(",");
-                }
-                // Remove the trailing comma
-                locationProvidersAllowed.setLength(locationProvidersAllowed.length() - 1);
-                Settings.Secure.putStringForUser(
-                        mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        locationProvidersAllowed.toString(),
-                        userId);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Returns the current enabled/disabled status of a location provider and user
-     *
-     * @param providerName name of the provider
-     * @param userId       the id of the user
-     * @return true if the provider exists and is enabled
-     */
     @Override
     public boolean isProviderEnabledForUser(String providerName, int userId) {
         // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
-        checkInteractAcrossUsersPermission(userId);
-
-        if (!isLocationEnabledForUser(userId)) {
-            return false;
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    "Requires INTERACT_ACROSS_USERS permission");
         }
 
         // Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
         // so we discourage its use
-        if (LocationManager.FUSED_PROVIDER.equals(providerName)) return false;
+        if (FUSED_PROVIDER.equals(providerName)) return false;
 
-        long identity = Binder.clearCallingIdentity();
-        try {
-            LocationProvider provider;
-            synchronized (mLock) {
-                provider = mProvidersByName.get(providerName);
-            }
-            return provider != null && provider.isEnabled();
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        synchronized (mLock) {
+            LocationProvider provider = getLocationProviderLocked(providerName);
+            return provider != null && provider.isUseableForUserLocked(userId);
         }
     }
 
-    /**
-     * Enable or disable a single location provider.
-     *
-     * @param provider name of the provider
-     * @param enabled  true to enable the provider. False to disable the provider
-     * @param userId   the id of the user to set
-     * @return true if the value was set, false on errors
-     */
-    @Override
-    public boolean setProviderEnabledForUser(String provider, boolean enabled, int userId) {
-        return false;
-    }
-
-    /**
-     * Method for checking INTERACT_ACROSS_USERS permission if specified user id is not the same as
-     * current user id
-     *
-     * @param userId the user id to get or set value
-     */
-    private void checkInteractAcrossUsersPermission(int userId) {
-        int uid = Binder.getCallingUid();
-        if (UserHandle.getUserId(uid) != userId) {
-            if (ActivityManager.checkComponentPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS, uid, -1, true)
-                    != PERMISSION_GRANTED) {
-                throw new SecurityException("Requires INTERACT_ACROSS_USERS permission");
-            }
-        }
-    }
-
-    /**
-     * Returns "true" if the UID belongs to a bound location provider.
-     *
-     * @param uid the uid
-     * @return true if uid belongs to a bound location provider
-     */
-    private boolean isUidALocationProvider(int uid) {
+    @GuardedBy("mLock")
+    private boolean isLocationProviderLocked(int uid) {
         if (uid == Process.SYSTEM_UID) {
             return true;
         }
-        if (mGeocodeProvider != null) {
-            if (doesUidHavePackage(uid, mGeocodeProvider.getConnectedPackageName())) return true;
-        }
-        for (LocationProviderProxy proxy : mProxyProviders) {
-            if (doesUidHavePackage(uid, proxy.getConnectedPackageName())) return true;
-        }
-        return false;
-    }
 
-    private void checkCallerIsProvider() {
-        if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER)
-                == PERMISSION_GRANTED) {
-            return;
-        }
-
-        // Previously we only used the INSTALL_LOCATION_PROVIDER
-        // check. But that is system or signature
-        // protection level which is not flexible enough for
-        // providers installed oustide the system image. So
-        // also allow providers with a UID matching the
-        // currently bound package name
-
-        if (isUidALocationProvider(Binder.getCallingUid())) {
-            return;
-        }
-
-        throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " +
-                "or UID of a currently bound location provider");
-    }
-
-    /**
-     * Returns true if the given package belongs to the given uid.
-     */
-    private boolean doesUidHavePackage(int uid, String packageName) {
-        if (packageName == null) {
-            return false;
-        }
         String[] packageNames = mPackageManager.getPackagesForUid(uid);
         if (packageNames == null) {
             return false;
         }
-        for (String name : packageNames) {
-            if (packageName.equals(name)) {
+        for (LocationProvider provider : mProviders) {
+            String packageName = provider.getPackageLocked();
+            if (packageName == null) {
+                continue;
+            }
+            if (ArrayUtils.contains(packageNames, packageName)) {
                 return true;
             }
         }
         return false;
     }
 
-    @Override
-    public void reportLocation(Location location, boolean passive) {
-        checkCallerIsProvider();
-
-        if (!location.isComplete()) {
-            Log.w(TAG, "Dropping incomplete location: " + location);
-            return;
-        }
-
-        mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location);
-        Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location);
-        m.arg1 = (passive ? 1 : 0);
-        mLocationHandler.sendMessageAtFrontOfQueue(m);
-    }
-
-
-    private static boolean shouldBroadcastSafe(
+    @GuardedBy("mLock")
+    private static boolean shouldBroadcastSafeLocked(
             Location loc, Location lastLoc, UpdateRecord record, long now) {
         // Always broadcast the first update
         if (lastLoc == null) {
@@ -3046,26 +2988,36 @@
         return record.mRealRequest.getExpireAt() >= now;
     }
 
-    private void handleLocationChangedLocked(Location location, boolean passive) {
+    @GuardedBy("mLock")
+    private void handleLocationChangedLocked(Location location, LocationProvider provider) {
+        if (!mProviders.contains(provider)) {
+            return;
+        }
+        if (!location.isComplete()) {
+            Log.w(TAG, "Dropping incomplete location: " + location);
+            return;
+        }
+
+        if (!provider.isPassiveLocked()) {
+            // notify passive provider of the new location
+            mPassiveProvider.updateLocation(location);
+        }
+
         if (D) Log.d(TAG, "incoming location: " + location);
         long now = SystemClock.elapsedRealtime();
-        String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider());
-        // Skip if the provider is unknown.
-        LocationProvider p = mProvidersByName.get(provider);
-        if (p == null) return;
-        updateLastLocationLocked(location, provider);
+        updateLastLocationLocked(location, provider.getName());
         // mLastLocation should have been updated from the updateLastLocationLocked call above.
-        Location lastLocation = mLastLocation.get(provider);
+        Location lastLocation = mLastLocation.get(provider.getName());
         if (lastLocation == null) {
             Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed");
             return;
         }
 
         // Update last known coarse interval location if enough time has passed.
-        Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider);
+        Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName());
         if (lastLocationCoarseInterval == null) {
             lastLocationCoarseInterval = new Location(location);
-            mLastLocationCoarseInterval.put(provider, lastLocationCoarseInterval);
+            mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
         }
         long timeDiffNanos = location.getElapsedRealtimeNanos()
                 - lastLocationCoarseInterval.getElapsedRealtimeNanos();
@@ -3079,7 +3031,7 @@
                 lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
 
         // Skip if there are no UpdateRecords for this provider.
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
         if (records == null || records.size() == 0) return;
 
         // Fetch coarse location
@@ -3088,13 +3040,6 @@
             coarseLocation = mLocationFudger.getOrCreate(noGPSLocation);
         }
 
-        // Fetch latest status update time
-        long newStatusUpdateTime = p.getStatusUpdateTime();
-
-        // Get latest status
-        Bundle extras = new Bundle();
-        int status = p.getStatus(extras);
-
         ArrayList<Receiver> deadReceivers = null;
         ArrayList<UpdateRecord> deadUpdateRecords = null;
 
@@ -3104,8 +3049,8 @@
             boolean receiverDead = false;
 
             int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid);
-            if (!isCurrentProfile(receiverUserId)
-                    && !isUidALocationProvider(receiver.mIdentity.mUid)) {
+            if (!isCurrentProfileLocked(receiverUserId)
+                    && !isLocationProviderLocked(receiver.mIdentity.mUid)) {
                 if (D) {
                     Log.d(TAG, "skipping loc update for background user " + receiverUserId +
                             " (current user: " + mCurrentUserId + ", app: " +
@@ -3142,7 +3087,8 @@
             }
             if (notifyLocation != null) {
                 Location lastLoc = r.mLastFixBroadcast;
-                if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r, now)) {
+                if ((lastLoc == null)
+                        || shouldBroadcastSafeLocked(notifyLocation, lastLoc, r, now)) {
                     if (lastLoc == null) {
                         lastLoc = new Location(notifyLocation);
                         r.mLastFixBroadcast = lastLoc;
@@ -3150,7 +3096,8 @@
                         lastLoc.set(notifyLocation);
                     }
                     if (!receiver.callLocationChangedLocked(notifyLocation)) {
-                        Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
+                        Slog.w(TAG, "RemoteException calling onLocationChanged on "
+                                + receiver);
                         receiverDead = true;
                     }
                     r.mRealRequest.decrementNumUpdates();
@@ -3161,12 +3108,16 @@
             // guarded behind this setting now. should be removed completely post-Q
             if (Settings.Global.getInt(mContext.getContentResolver(),
                     LOCATION_DISABLE_STATUS_CALLBACKS, 1) == 0) {
+                long newStatusUpdateTime = provider.getStatusUpdateTimeLocked();
+                Bundle extras = new Bundle();
+                int status = provider.getStatusLocked(extras);
+
                 long prevStatusUpdateTime = r.mLastStatusBroadcast;
                 if ((newStatusUpdateTime > prevStatusUpdateTime)
                         && (prevStatusUpdateTime != 0 || status != AVAILABLE)) {
 
                     r.mLastStatusBroadcast = newStatusUpdateTime;
-                    if (!receiver.callStatusChangedLocked(provider, status, extras)) {
+                    if (!receiver.callStatusChangedLocked(provider.getName(), status, extras)) {
                         receiverDead = true;
                         Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
                     }
@@ -3205,12 +3156,7 @@
         }
     }
 
-    /**
-     * Updates last location with the given location
-     *
-     * @param location new location to update
-     * @param provider Location provider to update for
-     */
+    @GuardedBy("mLock")
     private void updateLastLocationLocked(Location location, String provider) {
         Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
         Location lastNoGPSLocation;
@@ -3229,75 +3175,6 @@
         lastLocation.set(location);
     }
 
-    private class LocationWorkerHandler extends Handler {
-        public LocationWorkerHandler(Looper looper) {
-            super(looper, null, true);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_LOCATION_CHANGED:
-                    handleLocationChanged((Location) msg.obj, msg.arg1 == 1);
-                    break;
-            }
-        }
-    }
-
-    private boolean isMockProvider(String provider) {
-        synchronized (mLock) {
-            return mMockProviders.containsKey(provider);
-        }
-    }
-
-    private void handleLocationChanged(Location location, boolean passive) {
-        // create a working copy of the incoming Location so that the service can modify it without
-        // disturbing the caller's copy
-        Location myLocation = new Location(location);
-        String provider = myLocation.getProvider();
-
-        // set "isFromMockProvider" bit if location came from a mock provider. we do not clear this
-        // bit if location did not come from a mock provider because passive/fused providers can
-        // forward locations from mock providers, and should not grant them legitimacy in doing so.
-        if (!myLocation.isFromMockProvider() && isMockProvider(provider)) {
-            myLocation.setIsFromMockProvider(true);
-        }
-
-        synchronized (mLock) {
-            if (!passive) {
-                // notify passive provider of the new location
-                mPassiveProvider.updateLocation(myLocation);
-            }
-            handleLocationChangedLocked(myLocation, passive);
-        }
-    }
-
-    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
-        @Override
-        public void onPackageDisappeared(String packageName, int reason) {
-            // remove all receivers associated with this package name
-            synchronized (mLock) {
-                ArrayList<Receiver> deadReceivers = null;
-
-                for (Receiver receiver : mReceivers.values()) {
-                    if (receiver.mIdentity.mPackageName.equals(packageName)) {
-                        if (deadReceivers == null) {
-                            deadReceivers = new ArrayList<>();
-                        }
-                        deadReceivers.add(receiver);
-                    }
-                }
-
-                // perform removal outside of mReceivers loop
-                if (deadReceivers != null) {
-                    for (Receiver receiver : deadReceivers) {
-                        removeUpdatesLocked(receiver);
-                    }
-                }
-            }
-        }
-    };
-
     // Geocoder
 
     @Override
@@ -3343,63 +3220,60 @@
             return;
         }
 
-        if (LocationManager.PASSIVE_PROVIDER.equals(name)) {
+        if (PASSIVE_PROVIDER.equals(name)) {
             throw new IllegalArgumentException("Cannot mock the passive location provider");
         }
 
-        long identity = Binder.clearCallingIdentity();
         synchronized (mLock) {
-            // remove the real provider if we are replacing GPS or network provider
-            if (LocationManager.GPS_PROVIDER.equals(name)
-                    || LocationManager.NETWORK_PROVIDER.equals(name)
-                    || LocationManager.FUSED_PROVIDER.equals(name)) {
-                LocationProvider p = mProvidersByName.get(name);
-                if (p != null) {
-                    removeProviderLocked(p);
+            long identity = Binder.clearCallingIdentity();
+            try {
+                LocationProvider oldProvider = getLocationProviderLocked(name);
+                if (oldProvider != null) {
+                    if (oldProvider.isMock()) {
+                        throw new IllegalArgumentException(
+                                "Provider \"" + name + "\" already exists");
+                    }
+
+                    removeProviderLocked(oldProvider);
                 }
+
+                MockLocationProvider mockProviderManager = new MockLocationProvider(name);
+                addProviderLocked(mockProviderManager);
+                mockProviderManager.attachLocked(new MockProvider(mockProviderManager, properties));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
-            addTestProviderLocked(name, properties);
         }
-        Binder.restoreCallingIdentity(identity);
-    }
-
-    private void addTestProviderLocked(String name, ProviderProperties properties) {
-        if (mProvidersByName.get(name) != null) {
-            throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
-        }
-
-        LocationProvider provider = new LocationProvider(name);
-        MockProvider mockProvider = new MockProvider(provider, properties);
-
-        addProviderLocked(provider);
-        mMockProviders.put(name, mockProvider);
-        mLastLocation.put(name, null);
-        mLastLocationCoarseInterval.put(name, null);
     }
 
     @Override
-    public void removeTestProvider(String provider, String opPackageName) {
+    public void removeTestProvider(String name, String opPackageName) {
         if (!canCallerAccessMockLocation(opPackageName)) {
             return;
         }
 
         synchronized (mLock) {
-            MockProvider mockProvider = mMockProviders.remove(provider);
-            if (mockProvider == null) {
-                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
-            }
-
             long identity = Binder.clearCallingIdentity();
             try {
-                removeProviderLocked(mProvidersByName.get(provider));
+                LocationProvider testProvider = getLocationProviderLocked(name);
+                if (testProvider == null || !testProvider.isMock()) {
+                    throw new IllegalArgumentException("Provider \"" + name + "\" unknown");
+                }
+
+                removeProviderLocked(testProvider);
 
                 // reinstate real provider if available
-                LocationProvider realProvider = mRealProviders.get(provider);
+                LocationProvider realProvider = null;
+                for (LocationProvider provider : mRealProviders) {
+                    if (name.equals(provider.getName())) {
+                        realProvider = provider;
+                        break;
+                    }
+                }
+
                 if (realProvider != null) {
                     addProviderLocked(realProvider);
                 }
-                mLastLocation.put(provider, null);
-                mLastLocationCoarseInterval.put(provider, null);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -3407,78 +3281,60 @@
     }
 
     @Override
-    public void setTestProviderLocation(String provider, Location loc, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
-            return;
-        }
-
-        synchronized (mLock) {
-            MockProvider mockProvider = mMockProviders.get(provider);
-            if (mockProvider == null) {
-                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
-            }
-
-            // Ensure that the location is marked as being mock. There's some logic to do this in
-            // handleLocationChanged(), but it fails if loc has the wrong provider (bug 33091107).
-            Location mock = new Location(loc);
-            mock.setIsFromMockProvider(true);
-
-            if (!TextUtils.isEmpty(loc.getProvider()) && !provider.equals(loc.getProvider())) {
-                // The location has an explicit provider that is different from the mock provider
-                // name. The caller may be trying to fool us via bug 33091107.
-                EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
-                        provider + "!=" + loc.getProvider());
-            }
-
-            // clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required
-            long identity = Binder.clearCallingIdentity();
-            try {
-                mockProvider.setLocation(mock);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    @Override
-    public void setTestProviderEnabled(String provider, boolean enabled, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
-            return;
-        }
-
-        synchronized (mLock) {
-            MockProvider mockProvider = mMockProviders.get(provider);
-            if (mockProvider == null) {
-                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
-            }
-            long identity = Binder.clearCallingIdentity();
-            try {
-                mockProvider.setEnabled(enabled);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    @Override
-    public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime,
+    public void setTestProviderLocation(String providerName, Location location,
             String opPackageName) {
         if (!canCallerAccessMockLocation(opPackageName)) {
             return;
         }
 
         synchronized (mLock) {
-            MockProvider mockProvider = mMockProviders.get(provider);
-            if (mockProvider == null) {
-                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+            LocationProvider testProvider = getLocationProviderLocked(providerName);
+            if (testProvider == null || !testProvider.isMock()) {
+                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
             }
-            mockProvider.setStatus(status, extras, updateTime);
+
+            String locationProvider = location.getProvider();
+            if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) {
+                // The location has an explicit provider that is different from the mock
+                // provider name. The caller may be trying to fool us via b/33091107.
+                EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+                        providerName + "!=" + location.getProvider());
+            }
+
+            ((MockLocationProvider) testProvider).setLocationLocked(location);
         }
     }
 
-    private void log(String log) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Slog.d(TAG, log);
+    @Override
+    public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) {
+        if (!canCallerAccessMockLocation(opPackageName)) {
+            return;
+        }
+
+        synchronized (mLock) {
+            LocationProvider testProvider = getLocationProviderLocked(providerName);
+            if (testProvider == null || !testProvider.isMock()) {
+                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
+            }
+
+            ((MockLocationProvider) testProvider).setEnabledLocked(enabled);
+        }
+    }
+
+    @Override
+    public void setTestProviderStatus(String providerName, int status, Bundle extras,
+            long updateTime, String opPackageName) {
+        if (!canCallerAccessMockLocation(opPackageName)) {
+            return;
+        }
+
+        synchronized (mLock) {
+            LocationProvider testProvider = getLocationProviderLocked(providerName);
+            if (testProvider == null || !testProvider.isMock()) {
+                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
+            }
+
+            ((MockLocationProvider) testProvider).setStatusLocked(status, extras, updateTime);
         }
     }
 
@@ -3494,6 +3350,7 @@
                 return;
             }
             pw.println("Current Location Manager state:");
+            pw.println("  Location Mode: " + isLocationEnabled());
             pw.println("  Location Listeners:");
             for (Receiver receiver : mReceivers.values()) {
                 pw.println("    " + receiver);
@@ -3548,12 +3405,6 @@
 
             pw.append("  ");
             mBlacklist.dump(pw);
-            if (mMockProviders.size() > 0) {
-                pw.println("  Mock Providers:");
-                for (Map.Entry<String, MockProvider> i : mMockProviders.entrySet()) {
-                    i.getValue().dump(fd, pw, args);
-                }
-            }
 
             if (mLocationControllerExtraPackage != null) {
                 pw.println(" Location controller extra package: " + mLocationControllerExtraPackage
@@ -3574,7 +3425,7 @@
                 return;
             }
             for (LocationProvider provider : mProviders) {
-                provider.dump(fd, pw, args);
+                provider.dumpLocked(fd, pw, args);
             }
             if (mGnssBatchingInProgress) {
                 pw.println("  GNSS batching in progress");
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index f27d373..8adc416 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Environment;
 import android.os.Handler;
@@ -29,6 +30,7 @@
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
@@ -46,8 +48,10 @@
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Monitors the health of packages on the system and notifies interested observers when packages
@@ -58,7 +62,7 @@
     // Duration to count package failures before it resets to 0
     private static final int TRIGGER_DURATION_MS = 60000;
     // Number of package failures within the duration above before we notify observers
-    private static final int TRIGGER_FAILURE_COUNT = 5;
+    static final int TRIGGER_FAILURE_COUNT = 5;
     private static final int DB_VERSION = 1;
     private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
     private static final String TAG_PACKAGE = "package";
@@ -75,20 +79,13 @@
     // Handler to run package cleanup runnables
     private final Handler mTimerHandler;
     private final Handler mIoHandler;
-    // Contains (observer-name -> external-observer-handle) that have been registered during the
-    // current boot.
-    // It is populated when observers call #registerHealthObserver and it does not survive reboots.
-    @GuardedBy("mLock")
-    final ArrayMap<String, PackageHealthObserver> mRegisteredObservers = new ArrayMap<>();
-    // Contains (observer-name -> internal-observer-handle) that have ever been registered from
+    // Contains (observer-name -> observer-handle) that have ever been registered from
     // previous boots. Observers with all packages expired are periodically pruned.
     // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
     @GuardedBy("mLock")
-    final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
+    private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
     // File containing the XML data of monitored packages /data/system/package-watchdog.xml
-    private final AtomicFile mPolicyFile =
-            new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
-                           "package-watchdog.xml"));
+    private final AtomicFile mPolicyFile;
     // Runnable to prune monitored packages that have expired
     private final Runnable mPackageCleanup;
     // Last SystemClock#uptimeMillis a package clean up was executed.
@@ -98,14 +95,32 @@
     // 0 if mPackageCleanup not running.
     private long mDurationAtLastReschedule;
 
+    // TODO(zezeozue): Remove redundant context param
     private PackageWatchdog(Context context) {
         mContext = context;
+        mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
+                        "package-watchdog.xml"));
         mTimerHandler = new Handler(Looper.myLooper());
         mIoHandler = BackgroundThread.getHandler();
         mPackageCleanup = this::rescheduleCleanup;
         loadFromFile();
     }
 
+    /**
+     * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers
+     * and creates package-watchdog.xml in an apps data directory.
+     */
+    @VisibleForTesting
+    PackageWatchdog(Context context, Looper looper) {
+        mContext = context;
+        mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
+        mTimerHandler = new Handler(looper);
+        mIoHandler = mTimerHandler;
+        mPackageCleanup = this::rescheduleCleanup;
+        loadFromFile();
+    }
+
+
     /** Creates or gets singleton instance of PackageWatchdog. */
     public static PackageWatchdog getInstance(Context context) {
         synchronized (PackageWatchdog.class) {
@@ -124,7 +139,10 @@
      */
     public void registerHealthObserver(PackageHealthObserver observer) {
         synchronized (mLock) {
-            mRegisteredObservers.put(observer.getName(), observer);
+            ObserverInternal internalObserver = mAllObservers.get(observer.getName());
+            if (internalObserver != null) {
+                internalObserver.mRegisteredObserver = observer;
+            }
             if (mDurationAtLastReschedule == 0) {
                 // Nothing running, schedule
                 rescheduleCleanup();
@@ -143,7 +161,7 @@
      * or {@code durationMs} is less than 1
      */
     public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
-            int durationMs) {
+            long durationMs) {
         if (packageNames.isEmpty() || durationMs < 1) {
             throw new IllegalArgumentException("Observation not started, no packages specified"
                     + "or invalid duration");
@@ -180,11 +198,32 @@
     public void unregisterHealthObserver(PackageHealthObserver observer) {
         synchronized (mLock) {
             mAllObservers.remove(observer.getName());
-            mRegisteredObservers.remove(observer.getName());
         }
         saveToFileAsync();
     }
 
+    /**
+     * Returns packages observed by {@code observer}
+     *
+     * @return an empty set if {@code observer} has some packages observerd from a previous boot
+     * but has not registered itself in the current boot to receive notifications. Returns null
+     * if there are no active packages monitored from any boot.
+     */
+    @Nullable
+    public Set<String> getPackages(PackageHealthObserver observer) {
+        synchronized (mLock) {
+            for (int i = 0; i < mAllObservers.size(); i++) {
+                if (observer.getName().equals(mAllObservers.keyAt(i))) {
+                    if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
+                        return mAllObservers.valueAt(i).mPackages.keySet();
+                    }
+                    return Collections.emptySet();
+                }
+            }
+        }
+        return null;
+    }
+
     // TODO(zezeozue:) Accept current versionCodes of failing packages?
     /**
      * Called when a process fails either due to a crash or ANR.
@@ -198,24 +237,23 @@
     public void onPackageFailure(String[] packages) {
         ArrayMap<String, List<PackageHealthObserver>> packagesToReport = new ArrayMap<>();
         synchronized (mLock) {
-            if (mRegisteredObservers.isEmpty()) {
+            if (mAllObservers.isEmpty()) {
                 return;
             }
 
             for (int pIndex = 0; pIndex < packages.length; pIndex++) {
+                // Observers interested in receiving packageName failures
+                List<PackageHealthObserver> observersToNotify = new ArrayList<>();
                 for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
-                    // Observers interested in receiving packageName failures
-                    List<PackageHealthObserver> observersToNotify = new ArrayList<>();
-                    PackageHealthObserver activeObserver =
-                            mRegisteredObservers.get(mAllObservers.valueAt(oIndex).mName);
-                    if (activeObserver != null) {
-                        observersToNotify.add(activeObserver);
+                    PackageHealthObserver registeredObserver =
+                            mAllObservers.valueAt(oIndex).mRegisteredObserver;
+                    if (registeredObserver != null) {
+                        observersToNotify.add(registeredObserver);
                     }
-
-                    // Save interested observers and notify them outside the lock
-                    if (!observersToNotify.isEmpty()) {
-                        packagesToReport.put(packages[pIndex], observersToNotify);
-                    }
+                }
+                // Save interested observers and notify them outside the lock
+                if (!observersToNotify.isEmpty()) {
+                    packagesToReport.put(packages[pIndex], observersToNotify);
                 }
             }
         }
@@ -223,8 +261,11 @@
         // Notify observers
         for (int pIndex = 0; pIndex < packagesToReport.size(); pIndex++) {
             List<PackageHealthObserver> observers = packagesToReport.valueAt(pIndex);
+            String packageName = packages[pIndex];
             for (int oIndex = 0; oIndex < observers.size(); oIndex++) {
-                if (observers.get(oIndex).onHealthCheckFailed(packages[pIndex])) {
+                PackageHealthObserver observer = observers.get(oIndex);
+                if (mAllObservers.get(observer.getName()).onPackageFailure(packageName)
+                        && observer.onHealthCheckFailed(packageName)) {
                     // Observer has handled, do not notify others
                     break;
                 }
@@ -275,10 +316,12 @@
             // O if mPackageCleanup not running
             long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
                     ? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
-            // O if mPackageCleanup not running
+            // Less than O if mPackageCleanup unexpectedly didn't run yet even though
+            // and we are past the last duration scheduled to run
             long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
-
-            if (mUptimeAtLastRescheduleMs == 0 || nextDurationToScheduleMs < remainingDurationMs) {
+            if (mUptimeAtLastRescheduleMs == 0
+                    || remainingDurationMs <= 0
+                    || nextDurationToScheduleMs < remainingDurationMs) {
                 // First schedule or an earlier reschedule
                 pruneObservers(elapsedDurationMs);
                 mTimerHandler.removeCallbacks(mPackageCleanup);
@@ -305,6 +348,7 @@
             }
         }
         Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
+
         return shortestDurationMs;
     }
 
@@ -409,6 +453,8 @@
     static class ObserverInternal {
         public final String mName;
         public final ArrayMap<String, MonitoredPackage> mPackages;
+        @Nullable
+        public PackageHealthObserver mRegisteredObserver;
 
         ObserverInternal(String name, List<MonitoredPackage> packages) {
             mName = name;
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 9f353a8..ea6d435 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -141,6 +141,7 @@
     private boolean mLowPowerMode;
     private int mHapticFeedbackIntensity;
     private int mNotificationIntensity;
+    private int mRingIntensity;
 
     native static boolean vibratorExists();
     native static void vibratorInit();
@@ -149,6 +150,8 @@
     native static boolean vibratorSupportsAmplitudeControl();
     native static void vibratorSetAmplitude(int amplitude);
     native static long vibratorPerformEffect(long effect, long strength);
+    static native boolean vibratorSupportsExternalControl();
+    static native void vibratorSetExternalControl(boolean enabled);
 
     private final IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
@@ -426,6 +429,10 @@
                     Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY),
                     true, mSettingObserver, UserHandle.USER_ALL);
 
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY),
+                    true, mSettingObserver, UserHandle.USER_ALL);
+
             mContext.registerReceiver(new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
@@ -532,12 +539,6 @@
                 return;
             }
             verifyIncomingUid(uid);
-            if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
-                    > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
-                        + uid + " is background");
-                return;
-            }
             if (!verifyVibrationEffect(effect)) {
                 return;
             }
@@ -575,6 +576,13 @@
                 }
 
                 Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg, reason);
+                if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+                        > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+                        && vib.isHapticFeedback()) {
+                    Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
+                            + uid + " is background");
+                    return;
+                }
                 linkVibration(vib);
                 long ident = Binder.clearCallingIdentity();
                 try {
@@ -745,7 +753,9 @@
     }
 
     private int getCurrentIntensityLocked(Vibration vib) {
-        if (vib.isNotification() || vib.isRingtone()){
+        if (vib.isRingtone()) {
+            return mRingIntensity;
+        } else if (vib.isNotification()) {
             return mNotificationIntensity;
         } else if (vib.isHapticFeedback()) {
             return mHapticFeedbackIntensity;
@@ -767,7 +777,9 @@
         }
 
         final int defaultIntensity;
-        if (vib.isNotification() || vib.isRingtone()) {
+        if (vib.isRingtone()) {
+            defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
+        } else if (vib.isNotification()) {
             defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
         } else if (vib.isHapticFeedback()) {
             defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
@@ -930,6 +942,9 @@
         mNotificationIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
                 Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
                 mVibrator.getDefaultNotificationVibrationIntensity(), UserHandle.USER_CURRENT);
+        mRingIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.RING_VIBRATION_INTENSITY,
+                mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
     }
 
     @Override
@@ -1278,6 +1293,7 @@
             pw.println("  mLowPowerMode=" + mLowPowerMode);
             pw.println("  mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
             pw.println("  mNotificationIntensity=" + mNotificationIntensity);
+            pw.println("  mRingIntensity=" + mRingIntensity);
             pw.println("");
             pw.println("  Previous vibrations:");
             for (VibrationInfo info : mPreviousVibrations) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ed39d83..8ca4193 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1654,6 +1654,10 @@
                 }
             }
 
+            if ((flags & Context.BIND_RESTRICT_ASSOCIATIONS) != 0) {
+                mAm.requireAllowedAssociationsLocked(s.appInfo.packageName);
+            }
+
             mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
                     callerApp.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode,
                     s.instanceName, s.processName);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4f21ee8..bb239ac 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -50,6 +50,7 @@
 import static android.os.IServiceManager.DUMP_FLAG_PROTO;
 import static android.os.Process.BLUETOOTH_UID;
 import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.NETWORK_STACK_UID;
 import static android.os.Process.NFC_UID;
 import static android.os.Process.PHONE_UID;
 import static android.os.Process.PROC_CHAR;
@@ -328,7 +329,7 @@
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.TriFunction;
 import com.android.server.AlarmManagerInternal;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
 import com.android.server.AttributeCache;
 import com.android.server.DeviceIdleController;
 import com.android.server.DisplayThread;
@@ -2430,6 +2431,20 @@
     }
 
     /**
+     * Ensures that the given package name has an explicit set of allowed associations.
+     * If it does not, give it an empty set.
+     */
+    void requireAllowedAssociationsLocked(String packageName) {
+        if (mAllowedAssociations == null) {
+            mAllowedAssociations = new ArrayMap<>(
+                    SystemConfig.getInstance().getAllowedAssociations());
+        }
+        if (mAllowedAssociations.get(packageName) == null) {
+            mAllowedAssociations.put(packageName, new ArraySet<>());
+        }
+    }
+
+    /**
      * Returns true if the package {@code pkg1} running under user handle {@code uid1} is
      * allowed association with the package {@code pkg2} running under user handle {@code uid2}.
      * <p> If either of the packages are running as  part of the core system, then the
@@ -2437,7 +2452,8 @@
      */
     boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) {
         if (mAllowedAssociations == null) {
-            mAllowedAssociations = SystemConfig.getInstance().getAllowedAssociations();
+            mAllowedAssociations = new ArrayMap<>(
+                    SystemConfig.getInstance().getAllowedAssociations());
         }
         // Interactions with the system uid are always allowed, since that is the core system
         // that everyone needs to be able to interact with. Also allow reflexive associations
@@ -14343,6 +14359,7 @@
             case BLUETOOTH_UID:
             case NFC_UID:
             case SE_UID:
+            case NETWORK_STACK_UID:
                 isCallerSystem = true;
                 break;
             default:
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
similarity index 94%
rename from services/core/java/com/android/server/AppOpsService.java
rename to services/core/java/com/android/server/appop/AppOpsService.java
index 3cdf09e..7ede6dc 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.appop;
 
 import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_NONE;
 import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
 import static android.app.AppOpsManager.UID_STATE_CACHED;
 import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
@@ -35,8 +36,7 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
-import android.app.AppOpsManager.HistoricalOpEntry;
-import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalOps;
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
 import android.content.BroadcastReceiver;
@@ -48,7 +48,6 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.media.AudioAttributes;
@@ -59,6 +58,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -95,6 +95,8 @@
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
 
+import com.android.server.LocalServices;
+import com.android.server.LockGuard;
 import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -213,6 +215,8 @@
     @VisibleForTesting
     final SparseArray<UidState> mUidStates = new SparseArray<>();
 
+    private final HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
     long mLastRealtime;
 
     /*
@@ -654,6 +658,7 @@
 
     public void systemReady() {
         mConstants.startMonitoring(mContext.getContentResolver());
+        mHistoricalRegistry.systemReady(mContext.getContentResolver());
 
         synchronized (this) {
             boolean changed = false;
@@ -1012,113 +1017,99 @@
     }
 
     @Override
-    public @Nullable ParceledListSlice getAllHistoricalPackagesOps(@Nullable String[] opNames,
-            long beginTimeMillis, long endTimeMillis) {
+    public void getHistoricalOps(int uid, @NonNull String packageName,
+            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+            @NonNull RemoteCallback callback) {
+        Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
+                "uid must be " + Process.INVALID_UID + " or non negative");
         Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis,
                 "beginTimeMillis must be non negative and lesser than endTimeMillis");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        checkValidOpsOrNull(opNames);
 
         mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "getAllHistoricalPackagesOps");
+                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
 
-        ArrayList<HistoricalPackageOps> historicalPackageOpsList = null;
-
-        final int uidStateCount = mUidStates.size();
-        for (int i = 0; i < uidStateCount; i++) {
-            final UidState uidState = mUidStates.valueAt(i);
-            if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
-                continue;
-            }
-            final ArrayMap<String, Ops> packages = uidState.pkgOps;
-            final int packageCount = packages.size();
-            for (int j = 0; j < packageCount; j++) {
-                final Ops pkgOps = packages.valueAt(j);
-                final AppOpsManager.HistoricalPackageOps historicalPackageOps =
-                        createHistoricalPackageOps(uidState.uid, pkgOps, opNames,
-                                beginTimeMillis, endTimeMillis);
-                if (historicalPackageOps != null) {
-                    if (historicalPackageOpsList == null) {
-                        historicalPackageOpsList = new ArrayList<>();
-                    }
-                    historicalPackageOpsList.add(historicalPackageOps);
-                }
-            }
+        if (mHistoricalRegistry.getMode() == AppOpsManager.HISTORICAL_MODE_DISABLED) {
+            // TODO (bug:122218838): Remove once the feature fully enabled.
+            getHistoricalPackagesOpsCompat(uid, packageName, opNames, beginTimeMillis,
+                    endTimeMillis, callback);
+        } else {
+            // Must not hold the appops lock
+            mHistoricalRegistry.getHistoricalOps(uid, packageName, opNames,
+                    beginTimeMillis, endTimeMillis, callback);
         }
-
-        if (historicalPackageOpsList == null) {
-            return null;
-        }
-
-        return new ParceledListSlice<>(historicalPackageOpsList);
     }
 
-    private static @Nullable HistoricalPackageOps createHistoricalPackageOps(int uid,
-            @Nullable Ops pkgOps, @Nullable String[] opNames, long beginTimeMillis,
-            long endTimeMillis) {
-        // TODO: Implement historical data collection
-        if (pkgOps == null) {
-            return null;
+    private void getHistoricalPackagesOpsCompat(int uid, @NonNull String packageName,
+            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+            @NonNull RemoteCallback callback) {
+        synchronized (AppOpsService.this) {
+            final HistoricalOps ops = new HistoricalOps(beginTimeMillis, endTimeMillis);
+            if (opNames == null) {
+                opNames = AppOpsManager.getOpStrs();
+            }
+            final int uidStateCount = mUidStates.size();
+            for (int uidIdx = 0; uidIdx < uidStateCount; uidIdx++) {
+                final UidState uidState = mUidStates.valueAt(uidIdx);
+                if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()
+                        || (uid != Process.INVALID_UID && uid != uidState.uid)) {
+                    continue;
+                }
+                final ArrayMap<String, Ops> packages = uidState.pkgOps;
+                final int packageCount = packages.size();
+                for (int pkgIdx = 0; pkgIdx < packageCount; pkgIdx++) {
+                    final Ops pkgOps = packages.valueAt(pkgIdx);
+                    if (packageName != null && !packageName.equals(pkgOps.packageName)) {
+                        continue;
+                    }
+                    final int opCount = opNames.length;
+                    for (int opIdx = 0; opIdx < opCount; opIdx++) {
+                        final String opName = opNames[opIdx];
+                        if (!ArrayUtils.contains(opNames, opName)) {
+                            continue;
+                        }
+                        final int opCode = AppOpsManager.strOpToOp(opName);
+                        final Op op = pkgOps.get(opCode);
+                        if (op == null) {
+                            continue;
+                        }
+                        final int stateCount = AppOpsManager._NUM_UID_STATE;
+                        for (int stateIdx = 0; stateIdx < stateCount; stateIdx++) {
+                            if (op.rejectTime[stateIdx] != 0) {
+                                ops.increaseRejectCount(opCode, uidState.uid,
+                                        pkgOps.packageName, stateIdx, 1);
+                            } else if (op.time[stateIdx] != 0) {
+                                ops.increaseAccessCount(opCode, uidState.uid,
+                                        pkgOps.packageName, stateIdx, 1);
+                            }
+                        }
+                    }
+                }
+            }
+            final Bundle payload = new Bundle();
+            payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, ops);
+            callback.sendResult(payload);
         }
-
-        final HistoricalPackageOps historicalPackageOps = new HistoricalPackageOps(uid,
-                pkgOps.packageName);
-
-        if (opNames == null) {
-            opNames = AppOpsManager.getOpStrs();
-        }
-        for (String opName : opNames) {
-            addHistoricOpEntry(AppOpsManager.strOpToOp(opName), pkgOps, historicalPackageOps);
-        }
-
-        return historicalPackageOps;
     }
 
     @Override
-    public @Nullable HistoricalPackageOps getHistoricalPackagesOps(int uid,
-            @NonNull String packageName, @Nullable String[] opNames,
-            long beginTimeMillis, long endTimeMillis) {
-        Preconditions.checkNotNull(packageName,
-                "packageName cannot be null");
+    public void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
+            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+            @NonNull RemoteCallback callback) {
+        Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
+                "uid must be " + Process.INVALID_UID + " or non negative");
         Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis,
                 "beginTimeMillis must be non negative and lesser than endTimeMillis");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        checkValidOpsOrNull(opNames);
 
         mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalPackagesOps");
+                Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
 
-        final String resolvedPackageName = resolvePackageName(uid, packageName);
-        if (resolvedPackageName == null) {
-            return null;
-        }
-
-        // TODO: Implement historical data collection
-        final Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false /* edit */,
-                false /* uidMismatchExpected */);
-        return createHistoricalPackageOps(uid, pkgOps, opNames, beginTimeMillis, endTimeMillis);
-    }
-
-    private static void addHistoricOpEntry(int opCode, @NonNull Ops ops,
-            @NonNull HistoricalPackageOps outHistoricalPackageOps) {
-        final Op op = ops.get(opCode);
-        if (op == null) {
-            return;
-        }
-
-        final HistoricalOpEntry historicalOpEntry = new HistoricalOpEntry(opCode);
-
-        // TODO: Keep per UID state duration
-        for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) {
-            final int acceptCount;
-            final int rejectCount;
-            if (op.rejectTime[uidState] == 0) {
-                acceptCount = 1;
-                rejectCount = 0;
-            } else {
-                acceptCount = 0;
-                rejectCount = 1;
-            }
-            historicalOpEntry.addEntry(uidState, acceptCount, rejectCount, 0);
-        }
-
-        outHistoricalPackageOps.addEntry(historicalOpEntry);
+        // Must not hold the appops lock
+        mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNames,
+                beginTimeMillis, endTimeMillis, callback);
     }
 
     @Override
@@ -1884,6 +1875,8 @@
                             + packageName);
                     op.rejectTime[uidState.state] = System.currentTimeMillis();
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
+                    mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+                            uidState.state);
                     return uidMode;
                 }
             } else {
@@ -1895,12 +1888,16 @@
                             + packageName);
                     op.rejectTime[uidState.state] = System.currentTimeMillis();
                     scheduleOpNotedIfNeededLocked(op.op, uid, packageName, mode);
+                    mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+                            uidState.state);
                     return mode;
                 }
             }
             if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
                     + " package " + packageName);
             op.time[uidState.state] = System.currentTimeMillis();
+            mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
+                    uidState.state);
             op.rejectTime[uidState.state] = 0;
             op.proxyUid = proxyUid;
             op.proxyPackageName = proxyPackageName;
@@ -2035,6 +2032,8 @@
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + resolvedPackageName);
                     op.rejectTime[uidState.state] = System.currentTimeMillis();
+                    mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+                            uidState.state);
                     return uidMode;
                 }
             } else {
@@ -2046,6 +2045,8 @@
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + resolvedPackageName);
                     op.rejectTime[uidState.state] = System.currentTimeMillis();
+                    mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName,
+                            uidState.state);
                     return mode;
                 }
             }
@@ -2054,6 +2055,8 @@
             if (op.startNesting == 0) {
                 op.startRealtime = SystemClock.elapsedRealtime();
                 op.time[uidState.state] = System.currentTimeMillis();
+                mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
+                        uidState.state);
                 op.rejectTime[uidState.state] = 0;
                 op.duration = -1;
                 scheduleOpActiveChangedIfNeededLocked(code, uid, packageName, true);
@@ -2216,6 +2219,8 @@
         if (op.startNesting <= 1 || finishNested) {
             if (op.startNesting == 1 || finishNested) {
                 op.duration = (int)(SystemClock.elapsedRealtime() - op.startRealtime);
+                mHistoricalRegistry.increaseOpAccessDuration(op.op, op.uid, op.packageName,
+                        op.uidState.state, op.duration);
                 op.time[op.uidState.state] = System.currentTimeMillis();
             } else {
                 Slog.w(TAG, "Finishing op nesting under-run: uid " + op.uid + " pkg "
@@ -2344,7 +2349,7 @@
                         ApplicationInfo appInfo = ActivityThread.getPackageManager()
                                 .getApplicationInfo(packageName,
                                         PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
                                         UserHandle.getUserId(uid));
                         if (appInfo != null) {
                             pkgUid = appInfo.uid;
@@ -3391,6 +3396,8 @@
         pw.println("    Limit output to data associated with the given app op mode.");
         pw.println("  --package [PACKAGE]");
         pw.println("    Limit output to data associated with the given package name.");
+        pw.println("  --watchers");
+        pw.println("    Only output the watcher sections.");
     }
 
     private void dumpTimesLocked(PrintWriter pw, String firstPrefix, String prefix, long[] times,
@@ -3425,10 +3432,11 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
-        int dumpOp = -1;
+        int dumpOp = OP_NONE;
         String dumpPackage = null;
-        int dumpUid = -1;
+        int dumpUid = Process.INVALID_UID;
         int dumpMode = -1;
+        boolean dumpWatchers = false;
 
         if (args != null) {
             for (int i=0; i<args.length; i++) {
@@ -3476,6 +3484,8 @@
                     if (dumpMode < 0) {
                         return;
                     }
+                } else if ("--watchers".equals(arg)) {
+                    dumpWatchers = true;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
                     pw.println("Unknown option: " + arg);
                     return;
@@ -3496,7 +3506,8 @@
             final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
             final Date date = new Date();
             boolean needSep = false;
-            if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null) {
+            if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null
+                    && !dumpWatchers) {
                 pw.println("  Profile owners:");
                 for (int poi = 0; poi < mProfileOwners.size(); poi++) {
                     pw.print("    User #");
@@ -3517,7 +3528,7 @@
                     ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i);
                     for (int j=0; j<callbacks.size(); j++) {
                         final ModeCallback cb = callbacks.valueAt(j);
-                        if (dumpPackage != null && cb.mWatchingUid >= 0
+                        if (dumpPackage != null
                                 && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
                             continue;
                         }
@@ -3561,7 +3572,7 @@
                 boolean printedHeader = false;
                 for (int i=0; i<mModeWatchers.size(); i++) {
                     final ModeCallback cb = mModeWatchers.valueAt(i);
-                    if (dumpPackage != null && cb.mWatchingUid >= 0
+                    if (dumpPackage != null
                             && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
                         continue;
                     }
@@ -3587,7 +3598,7 @@
                     if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
                         continue;
                     }
-                    if (dumpPackage != null && cb.mWatchingUid >= 0
+                    if (dumpPackage != null
                             && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
                         continue;
                     }
@@ -3627,7 +3638,7 @@
                     if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
                         continue;
                     }
-                    if (dumpPackage != null && cb.mWatchingUid >= 0
+                    if (dumpPackage != null
                             && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
                         continue;
                     }
@@ -3655,7 +3666,7 @@
                     pw.println(cb);
                 }
             }
-            if (mClients.size() > 0 && dumpMode < 0) {
+            if (mClients.size() > 0 && dumpMode < 0 && !dumpWatchers) {
                 needSep = true;
                 boolean printedHeader = false;
                 for (int i=0; i<mClients.size(); i++) {
@@ -3692,7 +3703,7 @@
                 }
             }
             if (mAudioRestrictions.size() > 0 && dumpOp < 0 && dumpPackage != null
-                    && dumpMode < 0) {
+                    && dumpMode < 0 && !dumpWatchers) {
                 boolean printedHeader = false;
                 for (int o=0; o<mAudioRestrictions.size(); o++) {
                     final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
@@ -3725,6 +3736,9 @@
                 final SparseIntArray opModes = uidState.opModes;
                 final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
 
+                if (dumpWatchers) {
+                    continue;
+                }
                 if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
                     boolean hasOp = dumpOp < 0 || (uidState.opModes != null
                             && uidState.opModes.indexOfKey(dumpOp) >= 0);
@@ -3739,8 +3753,8 @@
                     }
                     if (pkgOps != null) {
                         for (int pkgi = 0;
-                                (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
-                                pkgi++) {
+                                 (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+                                 pkgi++) {
                             Ops ops = pkgOps.valueAt(pkgi);
                             if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
                                 hasOp = true;
@@ -3880,18 +3894,34 @@
             for (int i = 0; i < userRestrictionCount; i++) {
                 IBinder token = mOpUserRestrictions.keyAt(i);
                 ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
-                pw.println("  User restrictions for token " + token + ":");
+                boolean printedTokenHeader = false;
+
+                if (dumpMode >= 0 || dumpWatchers) {
+                    continue;
+                }
 
                 final int restrictionCount = restrictionState.perUserRestrictions != null
                         ? restrictionState.perUserRestrictions.size() : 0;
-                if (restrictionCount > 0) {
-                    pw.println("      Restricted ops:");
+                if (restrictionCount > 0 && dumpPackage == null) {
+                    boolean printedOpsHeader = false;
                     for (int j = 0; j < restrictionCount; j++) {
                         int userId = restrictionState.perUserRestrictions.keyAt(j);
                         boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
                         if (restrictedOps == null) {
                             continue;
                         }
+                        if (dumpOp >= 0 && (dumpOp >= restrictedOps.length
+                                || !restrictedOps[dumpOp])) {
+                            continue;
+                        }
+                        if (!printedTokenHeader) {
+                            pw.println("  User restrictions for token " + token + ":");
+                            printedTokenHeader = true;
+                        }
+                        if (!printedOpsHeader) {
+                            pw.println("      Restricted ops:");
+                            printedOpsHeader = true;
+                        }
                         StringBuilder restrictedOpsValue = new StringBuilder();
                         restrictedOpsValue.append("[");
                         final int restrictedOpCount = restrictedOps.length;
@@ -3911,17 +3941,46 @@
 
                 final int excludedPackageCount = restrictionState.perUserExcludedPackages != null
                         ? restrictionState.perUserExcludedPackages.size() : 0;
-                if (excludedPackageCount > 0) {
-                    pw.println("      Excluded packages:");
+                if (excludedPackageCount > 0 && dumpOp < 0) {
+                    boolean printedPackagesHeader = false;
                     for (int j = 0; j < excludedPackageCount; j++) {
                         int userId = restrictionState.perUserExcludedPackages.keyAt(j);
                         String[] packageNames = restrictionState.perUserExcludedPackages.valueAt(j);
+                        if (packageNames == null) {
+                            continue;
+                        }
+                        boolean hasPackage;
+                        if (dumpPackage != null) {
+                            hasPackage = false;
+                            for (String pkg : packageNames) {
+                                if (dumpPackage.equals(pkg)) {
+                                    hasPackage = true;
+                                    break;
+                                }
+                            }
+                        } else {
+                            hasPackage = true;
+                        }
+                        if (!hasPackage) {
+                            continue;
+                        }
+                        if (!printedTokenHeader) {
+                            pw.println("  User restrictions for token " + token + ":");
+                            printedTokenHeader = true;
+                        }
+                        if (!printedPackagesHeader) {
+                            pw.println("      Excluded packages:");
+                            printedPackagesHeader = true;
+                        }
                         pw.print("        "); pw.print("user: "); pw.print(userId);
                                 pw.print(" packages: "); pw.println(Arrays.toString(packageNames));
                     }
                 }
             }
         }
+
+        // Must not hold the appops lock
+        mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpOp);
     }
 
     private static final class Restriction {
@@ -4042,6 +4101,47 @@
         return false;
     }
 
+    @Override
+    public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+            long baseSnapshotInterval, int compressionStep) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "setHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+    }
+
+    @Override
+    public void offsetHistory(long offsetMillis) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "offsetHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.offsetHistory(offsetMillis);
+    }
+
+    @Override
+    public void addHistoricalOps(HistoricalOps ops) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "addHistoricalOps");
+        // Must not hold the appops lock
+        mHistoricalRegistry.addHistoricalOps(ops);
+    }
+
+    @Override
+    public void resetHistoryParameters() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "resetHistoryParameters");
+        // Must not hold the appops lock
+        mHistoricalRegistry.resetHistoryParameters();
+    }
+
+    @Override
+    public void clearHistory() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+                "clearHistory");
+        // Must not hold the appops lock
+        mHistoricalRegistry.clearHistory();
+    }
+
     private void removeUidsForUserLocked(int userHandle) {
         for (int i = mUidStates.size() - 1; i >= 0; --i) {
             final int uid = mUidStates.keyAt(i);
@@ -4112,6 +4212,16 @@
         return packageNames;
     }
 
+    private static void checkValidOpsOrNull(String[] opNames) {
+        if (opNames != null) {
+            for (String opName : opNames) {
+                if (AppOpsManager.strOpToOp(opName) == AppOpsManager.OP_NONE) {
+                    throw new IllegalArgumentException("Unknown op: " + opName);
+                }
+            }
+        }
+    }
+
     private final class ClientRestrictionState implements DeathRecipient {
         private final IBinder token;
         SparseArray<boolean[]> perUserRestrictions;
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
new file mode 100644
index 0000000..8d7811f
--- /dev/null
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -0,0 +1,1495 @@
+/*
+ * 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalMode;
+import android.app.AppOpsManager.HistoricalOp;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.AppOpsManager.UidState;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.AtomicDirectory;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+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.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class managers historical app op state. This includes reading, persistence,
+ * accounting, querying.
+ * <p>
+ * The history is kept forever in multiple files. Each file time contains the
+ * relative offset from the current time which time is encoded in the file name.
+ * The files contain historical app op state snapshots which have times that
+ * are relative to the time of the container file.
+ *
+ * The data in the files are stored in a logarithmic fashion where where every
+ * subsequent file would contain data for ten times longer interval with ten
+ * times more time distance between snapshots. Hence, the more time passes
+ * the lesser the fidelity.
+ * <p>
+ * For example, the first file would contain data for 1 days with snapshots
+ * every 0.1 days, the next file would contain data for the period 1 to 10
+ * days with snapshots every 1 days, and so on.
+ * <p>
+ * THREADING AND LOCKING: Reported ops must be processed as quickly as possible.
+ * We keep ops pending to be persisted in memory and write to disk on a background
+ * thread. Hence, methods that report op changes are locking only the in memory
+ * state guarded by the mInMemoryLock which happens to be the app ops service lock
+ * avoiding a lock addition on the critical path. When a query comes we need to
+ * evaluate it based off both in memory and on disk state. This means they need to
+ * be frozen with respect to each other and not change from the querying caller's
+ * perspective. To achieve this we add a dedicated mOnDiskLock to guard the on
+ * disk state. To have fast critical path we need to limit the locking of the
+ * mInMemoryLock, thus for operations that touch in memory and on disk state one
+ * must grab first the mOnDiskLock and then the mInMemoryLock and limit the
+ * in memory lock to extraction of relevant data. Locking order is critical to
+ * avoid deadlocks. The convention is that xxxDLocked suffix means the method
+ * must be called with the mOnDiskLock lock, xxxMLocked suffix means the method
+ * must be called with the mInMemoryLock, xxxDMLocked suffix means the method
+ * must be called with the mOnDiskLock and mInMemoryLock locks acquired in that
+ * exact order.
+ */
+// TODO (bug:122218838): Make sure we handle start of epoch time
+// TODO (bug:122218838): Validate changed time is handled correctly
+final class HistoricalRegistry {
+    private static final boolean DEBUG = false;
+
+    private static final String LOG_TAG = HistoricalRegistry.class.getSimpleName();
+
+    private static final String PARAMETER_DELIMITER = ",";
+    private static final String PARAMETER_ASSIGNMENT = "=";
+
+    @GuardedBy("mLock")
+    private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
+
+    // Lock for read/write access to on disk state
+    private final Object mOnDiskLock = new Object();
+
+    //Lock for read/write access to in memory state
+    private final @NonNull Object mInMemoryLock;
+
+    private static final int MSG_WRITE_PENDING_HISTORY = 1;
+
+    // See mMode
+    private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_DISABLED;
+
+    // See mBaseSnapshotInterval
+    private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15);
+
+    // See mIntervalCompressionMultiplier
+    private static final long DEFAULT_COMPRESSION_STEP = 10;
+
+    /**
+     * Whether history is enabled.
+     */
+    @GuardedBy("mInMemoryLock")
+    private int mMode = AppOpsManager.HISTORICAL_MODE_DISABLED;
+
+    /**
+     * This granularity has been chosen to allow clean delineation for intervals
+     * humans understand, 15 min, 60, min, a day, a week, a month (30 days).
+     */
+    @GuardedBy("mInMemoryLock")
+    private long mBaseSnapshotInterval = DEFAULT_SNAPSHOT_INTERVAL_MILLIS;
+
+    /**
+     * The compression between steps. Each subsequent step is this much longer
+     * in terms of duration and each snapshot is this much more apart from the
+     * previous step.
+     */
+    @GuardedBy("mInMemoryLock")
+    private long mIntervalCompressionMultiplier = DEFAULT_COMPRESSION_STEP;
+
+    // The current ops to which to add statistics.
+    @GuardedBy("mInMemoryLock")
+    private @Nullable HistoricalOps mCurrentHistoricalOps;
+
+    // The time we should write the next snapshot.
+    @GuardedBy("mInMemoryLock")
+    private long mNextPersistDueTimeMillis;
+
+    // How much to offset the history on the next write.
+    @GuardedBy("mInMemoryLock")
+    private long mPendingHistoryOffsetMillis;
+
+    // Object managing persistence (read/write)
+    @GuardedBy("mOnDiskLock")
+    private Persistence mPersistence = new Persistence(mBaseSnapshotInterval,
+            mIntervalCompressionMultiplier);
+
+    HistoricalRegistry(@NonNull Object lock) {
+        mInMemoryLock = lock;
+        if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
+            synchronized (mInMemoryLock) {
+                // When starting always adjust history to now.
+                mPendingHistoryOffsetMillis = System.currentTimeMillis()
+                        - mPersistence.getLastPersistTimeMillisDLocked();
+            }
+        }
+    }
+
+    void systemReady(@NonNull ContentResolver resolver) {
+        updateParametersFromSetting(resolver);
+        final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS);
+        resolver.registerContentObserver(uri, false, new ContentObserver(
+                FgThread.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                updateParametersFromSetting(resolver);
+            }
+        });
+    }
+
+    private void updateParametersFromSetting(@NonNull ContentResolver resolver) {
+        final String setting = Settings.Global.getString(resolver,
+                Settings.Global.APPOP_HISTORY_PARAMETERS);
+        if (setting == null) {
+            return;
+        }
+        String modeValue = null;
+        String baseSnapshotIntervalValue = null;
+        String intervalMultiplierValue = null;
+        final String[] parameters = setting.split(PARAMETER_DELIMITER);
+        for (String parameter : parameters) {
+            final String[] parts = parameter.split(PARAMETER_ASSIGNMENT);
+            if (parts.length == 2) {
+                final String key = parts[0].trim();
+                switch (key) {
+                    case Settings.Global.APPOP_HISTORY_MODE: {
+                        modeValue = parts[1].trim();
+                    } break;
+                    case Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS: {
+                        baseSnapshotIntervalValue = parts[1].trim();
+                    } break;
+                    case Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER: {
+                        intervalMultiplierValue = parts[1].trim();
+                    } break;
+                    default: {
+                        Slog.w(LOG_TAG, "Unknown parameter: " + parameter);
+                    }
+                }
+            }
+        }
+        if (modeValue != null && baseSnapshotIntervalValue != null
+                && intervalMultiplierValue != null) {
+            try {
+                final int mode = AppOpsManager.parseHistoricalMode(modeValue);
+                final long baseSnapshotInterval = Long.parseLong(baseSnapshotIntervalValue);
+                final int intervalCompressionMultiplier = Integer.parseInt(intervalMultiplierValue);
+                setHistoryParameters(mode, baseSnapshotInterval,intervalCompressionMultiplier);
+                return;
+            } catch (NumberFormatException ignored) {}
+        }
+        Slog.w(LOG_TAG, "Bad value for" + Settings.Global.APPOP_HISTORY_PARAMETERS
+                + "=" + setting + " resetting!");
+    }
+
+    void dump(String prefix, PrintWriter pw,  int filterUid,
+            String filterPackage, int filterOp) {
+        synchronized (mOnDiskLock) {
+            synchronized (mInMemoryLock) {
+                pw.println();
+                pw.print(prefix);
+                pw.print("History:");
+
+                pw.print("  mode=");
+                pw.println(AppOpsManager.historicalModeToString(mMode));
+
+                final StringDumpVisitor visitor = new StringDumpVisitor(prefix + "  ",
+                        pw, filterUid, filterPackage, filterOp);
+                final long nowMillis = System.currentTimeMillis();
+
+                // Dump in memory state first
+                final HistoricalOps currentOps = getUpdatedPendingHistoricalOpsMLocked(
+                        nowMillis);
+                makeRelativeToEpochStart(currentOps, nowMillis);
+                currentOps.accept(visitor);
+
+                final List<HistoricalOps> ops = mPersistence.readHistoryDLocked();
+                if (ops != null) {
+                    // TODO (bug:122218838): Make sure this is properly dumped
+                    final long remainingToFillBatchMillis = mNextPersistDueTimeMillis
+                            - nowMillis - mBaseSnapshotInterval;
+                    final int opCount = ops.size();
+                    for (int i = 0; i < opCount; i++) {
+                        final HistoricalOps op = ops.get(i);
+                        op.offsetBeginAndEndTime(remainingToFillBatchMillis);
+                        makeRelativeToEpochStart(op, nowMillis);
+                        op.accept(visitor);
+                    }
+                } else {
+                    pw.println("  Empty");
+                }
+            }
+        }
+    }
+
+    @HistoricalMode int getMode() {
+        synchronized (mInMemoryLock) {
+            return mMode;
+        }
+    }
+
+    @Nullable void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
+            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+            @NonNull RemoteCallback callback) {
+        final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
+        mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
+                beginTimeMillis, endTimeMillis);
+        final Bundle payload = new Bundle();
+        payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
+        callback.sendResult(payload);
+    }
+
+    @Nullable void getHistoricalOps(int uid, @NonNull String packageName,
+            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+            @NonNull RemoteCallback callback) {
+        final long currentTimeMillis = System.currentTimeMillis();
+        if (endTimeMillis == Long.MAX_VALUE) {
+            endTimeMillis = currentTimeMillis;
+        }
+
+        // Argument times are based off epoch start while our internal store is
+        // based off now, so take this into account.
+        final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0);
+        final long inMemoryAdjEndTimeMillis = Math.max(currentTimeMillis - beginTimeMillis, 0);
+        final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis,
+                inMemoryAdjEndTimeMillis);
+
+        synchronized (mOnDiskLock) {
+            final List<HistoricalOps> pendingWrites;
+            final HistoricalOps currentOps;
+            synchronized (mInMemoryLock) {
+                currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
+                if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
+                        || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
+                    // Some of the current batch falls into the query, so extract that.
+                    final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
+                    currentOpsCopy.filter(uid, packageName, opNames, inMemoryAdjBeginTimeMillis,
+                            inMemoryAdjEndTimeMillis);
+                    result.merge(currentOpsCopy);
+                }
+                pendingWrites = new ArrayList<>(mPendingWrites);
+                mPendingWrites.clear();
+            }
+
+            // If the query was only for in-memory state - done.
+            if (inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis()) {
+                // If there is a write in flight we need to force it now
+                persistPendingHistory(pendingWrites);
+                // Collect persisted state.
+                final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
+                        - mNextPersistDueTimeMillis + mBaseSnapshotInterval;
+                final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
+                        - onDiskAndInMemoryOffsetMillis, 0);
+                final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
+                        - onDiskAndInMemoryOffsetMillis, 0);
+                mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
+                        onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis);
+            }
+
+            // Rebase the result time to be since epoch.
+            result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
+
+            // Send back the result.
+            final Bundle payload = new Bundle();
+            payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
+            callback.sendResult(payload);
+        }
+    }
+
+    void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
+            @UidState int uidState) {
+        synchronized (mInMemoryLock) {
+            if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
+                getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
+                        .increaseAccessCount(op, uid, packageName, uidState, 1);
+
+            }
+        }
+    }
+
+    void incrementOpRejected(int op, int uid, @NonNull String packageName,
+            @UidState int uidState) {
+        synchronized (mInMemoryLock) {
+            if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
+                getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
+                        .increaseRejectCount(op, uid, packageName, uidState, 1);
+            }
+        }
+    }
+
+    void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
+            @UidState int uidState, long increment) {
+        synchronized (mInMemoryLock) {
+            if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
+                getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
+                        .increaseAccessDuration(op, uid, packageName, uidState, increment);
+            }
+        }
+    }
+
+    void setHistoryParameters(@HistoricalMode int mode,
+            long baseSnapshotInterval, long intervalCompressionMultiplier) {
+        synchronized (mOnDiskLock) {
+            synchronized (mInMemoryLock) {
+                boolean resampleHistory = false;
+                Slog.i(LOG_TAG, "New history parameters: mode:"
+                        + AppOpsManager.historicalModeToString(mMode) + " baseSnapshotInterval:"
+                        + baseSnapshotInterval + " intervalCompressionMultiplier:"
+                        + intervalCompressionMultiplier);
+                if (mMode != mode) {
+                    mMode = mode;
+                    if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) {
+                        clearHistoryOnDiskLocked();
+                    }
+                }
+                if (mBaseSnapshotInterval != baseSnapshotInterval) {
+                    mBaseSnapshotInterval = baseSnapshotInterval;
+                    resampleHistory = true;
+                }
+                if (mIntervalCompressionMultiplier != intervalCompressionMultiplier) {
+                    mIntervalCompressionMultiplier = intervalCompressionMultiplier;
+                    resampleHistory = true;
+                }
+                if (resampleHistory) {
+                    resampleHistoryOnDiskInMemoryDMLocked(0);
+                }
+            }
+        }
+    }
+
+    void offsetHistory(long offsetMillis) {
+        synchronized (mOnDiskLock) {
+            synchronized (mInMemoryLock) {
+                final List<HistoricalOps> history = mPersistence.readHistoryDLocked();
+                clearHistory();
+                if (history != null) {
+                    final int historySize = history.size();
+                    for (int i = 0; i < historySize; i++) {
+                        final HistoricalOps ops = history.get(i);
+                        ops.offsetBeginAndEndTime(offsetMillis);
+                    }
+                    if (offsetMillis < 0) {
+                        pruneFutureOps(history);
+                    }
+                    mPersistence.persistHistoricalOpsDLocked(history);
+                }
+            }
+        }
+    }
+
+    void addHistoricalOps(HistoricalOps ops) {
+        final List<HistoricalOps> pendingWrites;
+        synchronized (mInMemoryLock) {
+            // The history files start from mBaseSnapshotInterval - take this into account.
+            ops.offsetBeginAndEndTime(mBaseSnapshotInterval);
+            mPendingWrites.offerFirst(ops);
+            pendingWrites = new ArrayList<>(mPendingWrites);
+            mPendingWrites.clear();
+        }
+        persistPendingHistory(pendingWrites);
+    }
+
+    private void resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis) {
+        mPersistence = new Persistence(mBaseSnapshotInterval, mIntervalCompressionMultiplier);
+        offsetHistory(offsetMillis);
+    }
+
+    void resetHistoryParameters() {
+        setHistoryParameters(DEFAULT_MODE, DEFAULT_SNAPSHOT_INTERVAL_MILLIS,
+                DEFAULT_COMPRESSION_STEP);
+    }
+
+    void clearHistory() {
+        synchronized (mOnDiskLock) {
+            clearHistoryOnDiskLocked();
+        }
+    }
+
+    private void clearHistoryOnDiskLocked() {
+        BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
+        synchronized (mInMemoryLock) {
+            mCurrentHistoricalOps = null;
+            mNextPersistDueTimeMillis = System.currentTimeMillis();
+            mPendingWrites.clear();
+        }
+        mPersistence.clearHistoryDLocked();
+    }
+
+    private @NonNull HistoricalOps getUpdatedPendingHistoricalOpsMLocked(long now) {
+        if (mCurrentHistoricalOps != null) {
+            final long remainingTimeMillis = mNextPersistDueTimeMillis - now;
+            if (remainingTimeMillis > mBaseSnapshotInterval) {
+                // If time went backwards we need to push history to the future with the
+                // overflow over our snapshot interval. If time went forward do nothing
+                // as we would naturally push history into the past on the next write.
+                mPendingHistoryOffsetMillis = remainingTimeMillis - mBaseSnapshotInterval;
+            }
+            final long elapsedTimeMillis = mBaseSnapshotInterval - remainingTimeMillis;
+            mCurrentHistoricalOps.setEndTime(elapsedTimeMillis);
+            if (remainingTimeMillis > 0) {
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Returning current in-memory state");
+                }
+                return mCurrentHistoricalOps;
+            }
+            if (mCurrentHistoricalOps.isEmpty()) {
+                mCurrentHistoricalOps.setBeginAndEndTime(0, 0);
+                mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
+                return mCurrentHistoricalOps;
+            }
+            // The current batch is full, so persist taking into account overdue persist time.
+            mCurrentHistoricalOps.offsetBeginAndEndTime(mBaseSnapshotInterval);
+            mCurrentHistoricalOps.setBeginTime(mCurrentHistoricalOps.getEndTimeMillis()
+                    - mBaseSnapshotInterval);
+            final long overdueTimeMillis = Math.abs(remainingTimeMillis);
+            mCurrentHistoricalOps.offsetBeginAndEndTime(overdueTimeMillis);
+            schedulePersistHistoricalOpsMLocked(mCurrentHistoricalOps);
+        }
+        // The current batch is in the future, i.e. not complete yet.
+        mCurrentHistoricalOps = new HistoricalOps(0, 0);
+        mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Returning new in-memory state");
+        }
+        return mCurrentHistoricalOps;
+    }
+
+    private void persistPendingHistory() {
+        final List<HistoricalOps> pendingWrites;
+        synchronized (mOnDiskLock) {
+            synchronized (mInMemoryLock) {
+                pendingWrites = new ArrayList<>(mPendingWrites);
+                mPendingWrites.clear();
+                if (mPendingHistoryOffsetMillis != 0) {
+                    resampleHistoryOnDiskInMemoryDMLocked(mPendingHistoryOffsetMillis);
+                    mPendingHistoryOffsetMillis = 0;
+                }
+            }
+            persistPendingHistory(pendingWrites);
+        }
+    }
+    private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
+        synchronized (mOnDiskLock) {
+            BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
+            if (pendingWrites.isEmpty()) {
+                return;
+            }
+            final int opCount = pendingWrites.size();
+            // Pending writes are offset relative to each other, so take this
+            // into account to persist everything in one shot - single write.
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOps current = pendingWrites.get(i);
+                if (i > 0) {
+                    final HistoricalOps previous = pendingWrites.get(i - 1);
+                    current.offsetBeginAndEndTime(previous.getBeginTimeMillis());
+                }
+            }
+            mPersistence.persistHistoricalOpsDLocked(pendingWrites);
+        }
+    }
+
+    private void schedulePersistHistoricalOpsMLocked(@NonNull HistoricalOps ops) {
+        final Message message = PooledLambda.obtainMessage(
+                HistoricalRegistry::persistPendingHistory, HistoricalRegistry.this);
+        message.what = MSG_WRITE_PENDING_HISTORY;
+        BackgroundThread.getHandler().sendMessage(message);
+        mPendingWrites.offerFirst(ops);
+    }
+
+    private static void makeRelativeToEpochStart(@NonNull HistoricalOps ops, long nowMillis) {
+        ops.setBeginAndEndTime(nowMillis - ops.getEndTimeMillis(),
+                nowMillis- ops.getBeginTimeMillis());
+    }
+
+    private void pruneFutureOps(@NonNull List<HistoricalOps> ops) {
+        final int opCount = ops.size();
+        for (int i = opCount - 1; i >= 0; i--) {
+            final HistoricalOps op = ops.get(i);
+            if (op.getEndTimeMillis() <= mBaseSnapshotInterval) {
+                ops.remove(i);
+            } else if (op.getBeginTimeMillis() < mBaseSnapshotInterval) {
+                final double filterScale = (double) (op.getEndTimeMillis() - mBaseSnapshotInterval)
+                        / (double) op.getDurationMillis();
+                Persistence.spliceFromBeginning(op, filterScale);
+            }
+        }
+    }
+
+    private static final class Persistence {
+        private static final boolean DEBUG = false;
+
+        private static final String LOG_TAG = Persistence.class.getSimpleName();
+
+        private static final String HISTORY_FILE_SUFFIX = ".xml";
+
+        private static final String TAG_HISTORY = "history";
+        private static final String TAG_OPS = "ops";
+        private static final String TAG_UID = "uid";
+        private static final String TAG_PACKAGE = "package";
+        private static final String TAG_OP = "op";
+        private static final String TAG_STATE = "state";
+
+        private static final String ATTR_VERSION = "version";
+        private static final String ATTR_NAME = "name";
+        private static final String ATTR_ACCESS_COUNT = "accessCount";
+        private static final String ATTR_REJECT_COUNT = "rejectCount";
+        private static final String ATTR_ACCESS_DURATION = "accessDuration";
+        private static final String ATTR_BEGIN_TIME = "beginTime";
+        private static final String ATTR_END_TIME = "endTime";
+        private static final String ATTR_OVERFLOW = "overflow";
+
+        private static final int CURRENT_VERSION = 1;
+
+        private final long mBaseSnapshotInterval;
+        private final long mIntervalCompressionMultiplier;
+
+        Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier) {
+            mBaseSnapshotInterval = baseSnapshotInterval;
+            mIntervalCompressionMultiplier = intervalCompressionMultiplier;
+        }
+
+        private final AtomicDirectory mHistoricalAppOpsDir = new AtomicDirectory(
+                new File(new File(Environment.getDataSystemDeDirectory(), "appops"), "history"));
+
+        private File generateFile(@NonNull File baseDir, int depth) {
+            final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth);
+            return new File(baseDir, Long.toString(globalBeginMillis) + HISTORY_FILE_SUFFIX);
+        }
+
+        void clearHistoryDLocked() {
+            mHistoricalAppOpsDir.delete();
+        }
+
+        void persistHistoricalOpsDLocked(@NonNull List<HistoricalOps> ops) {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Persisting ops:\n" + opsToDebugString(ops));
+                enforceOpsWellFormed(ops);
+            }
+            try {
+                final File newBaseDir = mHistoricalAppOpsDir.startWrite();
+                final File oldBaseDir = mHistoricalAppOpsDir.getBackupDirectory();
+                handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops, 0);
+                mHistoricalAppOpsDir.finishWrite();
+            } catch (Throwable t) {
+                Slog.wtf(LOG_TAG, "Failed to write historical app ops, restoring backup", t);
+                mHistoricalAppOpsDir.failWrite();
+            }
+        }
+
+        @Nullable List<HistoricalOps> readHistoryRawDLocked() {
+            return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/,
+                    null /*filterPackageName*/, null /*filterOpNames*/,
+                    0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/);
+        }
+
+        @Nullable List<HistoricalOps> readHistoryDLocked() {
+            final List<HistoricalOps> result = readHistoryRawDLocked();
+            // Take into account in memory state duration.
+            if (result != null) {
+                final int opCount = result.size();
+                for (int i = 0; i < opCount; i++) {
+                    result.get(i).offsetBeginAndEndTime(mBaseSnapshotInterval);
+                }
+            }
+            return result;
+        }
+
+        long getLastPersistTimeMillisDLocked() {
+            try {
+                final File baseDir = mHistoricalAppOpsDir.startRead();
+                final File file = generateFile(baseDir, 0);
+                if (file.exists()) {
+                    return file.lastModified();
+                }
+                mHistoricalAppOpsDir.finishRead();
+            } catch (IOException e) {
+                Slog.wtf("Error reading historical app ops. Deleting history.", e);
+                mHistoricalAppOpsDir.delete();
+            }
+            return 0;
+        }
+
+        private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps,
+                int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+                long filterBeingMillis, long filterEndMillis) {
+            final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid,
+                    filterPackageName, filterOpNames, filterBeingMillis, filterEndMillis);
+            if (readOps != null) {
+                final int readCount = readOps.size();
+                for (int i = 0; i < readCount; i++) {
+                    final HistoricalOps readOp = readOps.get(i);
+                    currentOps.merge(readOp);
+                }
+             }
+        }
+
+        private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(
+                int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+                long filterBeginTimeMillis, long filterEndTimeMillis) {
+            try {
+                final File baseDir = mHistoricalAppOpsDir.startRead();
+                final File[] files = baseDir.listFiles();
+                if (files == null) {
+                    return null;
+                }
+                final ArraySet<File> historyFiles = new ArraySet<>(files.length);
+                for (File file : files) {
+                    if (file.isFile() && file.getName().endsWith(HISTORY_FILE_SUFFIX)) {
+                        historyFiles.add(file);
+                    }
+                }
+                final long[] globalContentOffsetMillis = {0};
+                final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
+                        baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+                        filterEndTimeMillis, globalContentOffsetMillis, null /*outOps*/,
+                        0 /*depth*/, historyFiles);
+                mHistoricalAppOpsDir.finishRead();
+                return ops;
+            } catch (IOException | XmlPullParserException e) {
+                Slog.wtf("Error reading historical app ops. Deleting history.", e);
+                mHistoricalAppOpsDir.delete();
+            }
+            return null;
+        }
+
+        private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked(
+                @NonNull File baseDir, int filterUid, @NonNull String filterPackageName,
+                @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+                long filterEndTimeMillis, @NonNull long[] globalContentOffsetMillis,
+                @Nullable LinkedList<HistoricalOps> outOps, int depth,
+                @NonNull ArraySet<File> historyFiles)
+                throws IOException, XmlPullParserException {
+            final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+                    depth) * mBaseSnapshotInterval;
+            final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+                    depth + 1) * mBaseSnapshotInterval;
+
+            filterBeginTimeMillis = Math.max(filterBeginTimeMillis - previousIntervalEndMillis, 0);
+            filterEndTimeMillis = filterEndTimeMillis - previousIntervalEndMillis;
+
+            // Read historical data at this level
+            final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir,
+                    previousIntervalEndMillis, currentIntervalEndMillis, filterUid,
+                    filterPackageName, filterOpNames, filterBeginTimeMillis, filterEndTimeMillis,
+                    globalContentOffsetMillis, depth, historyFiles);
+
+            // Empty is a special signal to stop diving
+            if (readOps != null && readOps.isEmpty()) {
+                return outOps;
+            }
+
+            // Collect older historical data from subsequent levels
+            outOps = collectHistoricalOpsRecursiveDLocked(
+                    baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+                    filterEndTimeMillis, globalContentOffsetMillis, outOps, depth + 1,
+                    historyFiles);
+
+            // Make older historical data relative to the current historical level
+            if (outOps != null) {
+                final int opCount = outOps.size();
+                for (int i = 0; i < opCount; i++) {
+                    final HistoricalOps collectedOp = outOps.get(i);
+                    collectedOp.offsetBeginAndEndTime(currentIntervalEndMillis);
+                }
+            }
+
+            if (readOps != null) {
+                if (outOps == null) {
+                    outOps = new LinkedList<>();
+                }
+                // Add the read ops to output
+                final int opCount = readOps.size();
+                for (int i = opCount - 1; i >= 0; i--) {
+                    outOps.offerFirst(readOps.get(i));
+                }
+            }
+
+            return outOps;
+        }
+
+        private boolean createHardLinkToExistingFile(@NonNull File fromFile, @NonNull File toFile)
+                throws IOException {
+            if (!fromFile.exists()) {
+                return false;
+            }
+            Files.createLink(toFile.toPath(), fromFile.toPath());
+            return true;
+        }
+
+        private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir,
+                @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps, int depth)
+                throws IOException, XmlPullParserException {
+            final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+                    depth) * mBaseSnapshotInterval;
+            final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
+                    depth + 1) * mBaseSnapshotInterval;
+
+            if (passedOps == null || passedOps.isEmpty()) {
+                // If there is an old file we need to copy it over to the new state.
+                final File oldFile = generateFile(oldBaseDir, depth);
+                final File newFile = generateFile(newBaseDir, depth);
+                if (createHardLinkToExistingFile(oldFile, newFile)) {
+                    handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
+                            passedOps, depth + 1);
+                }
+                return;
+            }
+
+            if (DEBUG) {
+                enforceOpsWellFormed(passedOps);
+            }
+
+            // Normalize passed ops time to be based off this interval start
+            final int passedOpCount = passedOps.size();
+            for (int i = 0; i < passedOpCount; i++) {
+                final HistoricalOps passedOp = passedOps.get(i);
+                passedOp.offsetBeginAndEndTime(-previousIntervalEndMillis);
+            }
+
+            if (DEBUG) {
+                enforceOpsWellFormed(passedOps);
+            }
+
+            // Read persisted ops for this interval
+            final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir,
+                    previousIntervalEndMillis, currentIntervalEndMillis,
+                    Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/,
+                    null /*filterOpNames*/, Long.MIN_VALUE /*filterBeginTimeMillis*/,
+                    Long.MAX_VALUE /*filterEndTimeMillis*/, null, depth,
+                    null /*historyFiles*/);
+
+            if (DEBUG) {
+                enforceOpsWellFormed(existingOps);
+            }
+
+            // Offset existing ops to account for elapsed time
+            final int existingOpCount = existingOps.size();
+            if (existingOpCount > 0) {
+                // Compute elapsed time
+                final long elapsedTimeMillis = passedOps.get(passedOps.size() - 1)
+                        .getEndTimeMillis();
+                for (int i = 0; i < existingOpCount; i++) {
+                    final HistoricalOps existingOp = existingOps.get(i);
+                    existingOp.offsetBeginAndEndTime(elapsedTimeMillis);
+                }
+            }
+
+            if (DEBUG) {
+                enforceOpsWellFormed(existingOps);
+            }
+
+            final long slotDurationMillis = previousIntervalEndMillis;
+
+            // Consolidate passed ops at the current slot duration ensuring each snapshot is
+            // full. To achieve this we put all passed and existing ops in a list and will
+            // merge them to ensure each represents a snapshot at the current granularity.
+            final List<HistoricalOps> allOps = new LinkedList<>();
+            allOps.addAll(passedOps);
+            allOps.addAll(existingOps);
+
+            if (DEBUG) {
+                enforceOpsWellFormed(allOps);
+            }
+
+            // Compute ops to persist and overflow ops
+            List<HistoricalOps> persistedOps = null;
+            List<HistoricalOps> overflowedOps = null;
+
+            // We move a snapshot into the next level only if the start time is
+            // after the end of the current interval. This avoids rewriting all
+            // files to propagate the information added to the history on every
+            // iteration. Instead, we would rewrite the next level file only if
+            // an entire snapshot from the previous level is being propagated.
+            // The trade off is that we need to store how much the last snapshot
+            // of the current interval overflows past the interval end. We write
+            // the overflow data to avoid parsing all snapshots on query.
+            long intervalOverflowMillis = 0;
+            final int opCount = allOps.size();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOps op = allOps.get(i);
+                final HistoricalOps persistedOp;
+                final HistoricalOps overflowedOp;
+                if (op.getEndTimeMillis() <= currentIntervalEndMillis) {
+                    persistedOp = op;
+                    overflowedOp = null;
+                } else if (op.getBeginTimeMillis() < currentIntervalEndMillis) {
+                    persistedOp = op;
+                    intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
+                    if (intervalOverflowMillis > previousIntervalEndMillis) {
+                        final double splitScale = (double) intervalOverflowMillis
+                                / op.getDurationMillis();
+                        overflowedOp = spliceFromEnd(op, splitScale);
+                        intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
+                    } else {
+                        overflowedOp = null;
+                    }
+                } else {
+                    persistedOp = null;
+                    overflowedOp = op;
+                }
+                if (persistedOp != null) {
+                    if (persistedOps == null) {
+                        persistedOps = new ArrayList<>();
+                    }
+                    persistedOps.add(persistedOp);
+                }
+                if (overflowedOp != null) {
+                    if (overflowedOps == null) {
+                        overflowedOps = new ArrayList<>();
+                    }
+                    overflowedOps.add(overflowedOp);
+                }
+            }
+
+            if (DEBUG) {
+                enforceOpsWellFormed(persistedOps);
+                enforceOpsWellFormed(overflowedOps);
+            }
+
+            if (persistedOps != null) {
+                normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis);
+                final File newFile = generateFile(newBaseDir, depth);
+                writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile);
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Persisted at depth: " + depth
+                            + " ops:\n" + opsToDebugString(persistedOps));
+                    enforceOpsWellFormed(persistedOps);
+                }
+            }
+
+            handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
+                    overflowedOps, depth + 1);
+        }
+
+        private @NonNull List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
+                long intervalBeginMillis, long intervalEndMillis, int filterUid,
+                @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+                long filterBeginTimeMillis, long filterEndTimeMillis,
+                @Nullable long[] cumulativeOverflowMillis, int depth,
+                @NonNull ArraySet<File> historyFiles)
+                throws IOException, XmlPullParserException {
+            final File file = generateFile(baseDir, depth);
+            if (historyFiles != null) {
+                historyFiles.remove(file);
+            }
+            if (filterBeginTimeMillis >= filterEndTimeMillis
+                    || filterEndTimeMillis < intervalBeginMillis) {
+                // Don't go deeper
+                return Collections.emptyList();
+            }
+            if (filterBeginTimeMillis >= (intervalEndMillis
+                    + ((intervalEndMillis - intervalBeginMillis) / mIntervalCompressionMultiplier)
+                    + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0))
+                    || !file.exists()) {
+                if (historyFiles == null || historyFiles.isEmpty()) {
+                    // Don't go deeper
+                    return Collections.emptyList();
+                } else {
+                    // Keep diving
+                    return null;
+                }
+            }
+            return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterOpNames,
+                    filterBeginTimeMillis, filterEndTimeMillis, cumulativeOverflowMillis);
+        }
+
+        private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file,
+                int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+                long filterBeginTimeMillis, long filterEndTimeMillis,
+                @Nullable long[] cumulativeOverflowMillis)
+                throws IOException, XmlPullParserException {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Reading ops from:" + file);
+            }
+            List<HistoricalOps> allOps = null;
+            try (FileInputStream stream = new FileInputStream(file)) {
+                final XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(stream, StandardCharsets.UTF_8.name());
+                XmlUtils.beginDocument(parser, TAG_HISTORY);
+                final long overflowMillis = XmlUtils.readLongAttribute(parser, ATTR_OVERFLOW, 0);
+                final int depth = parser.getDepth();
+                while (XmlUtils.nextElementWithin(parser, depth)) {
+                    if (TAG_OPS.equals(parser.getName())) {
+                        final HistoricalOps ops = readeHistoricalOpsDLocked(parser,
+                                filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+                                filterEndTimeMillis, cumulativeOverflowMillis);
+                        if (ops == null) {
+                            continue;
+                        }
+                        if (ops.isEmpty()) {
+                            XmlUtils.skipCurrentTag(parser);
+                            continue;
+                        }
+                        if (allOps == null) {
+                            allOps = new ArrayList<>();
+                        }
+                        allOps.add(ops);
+                    }
+                }
+                if (cumulativeOverflowMillis != null) {
+                    cumulativeOverflowMillis[0] += overflowMillis;
+                }
+            } catch (FileNotFoundException e) {
+                Slog.i(LOG_TAG, "No history file: " + file.getName());
+                return Collections.emptyList();
+            }
+            if (DEBUG) {
+                if (allOps != null) {
+                    Slog.i(LOG_TAG, "Read from file: " + file + "ops:\n"
+                            + opsToDebugString(allOps));
+                    enforceOpsWellFormed(allOps);
+                }
+            }
+            return allOps;
+        }
+
+        private @Nullable HistoricalOps readeHistoricalOpsDLocked(
+                @NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName,
+                @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+                long filterEndTimeMillis, @Nullable long[] cumulativeOverflowMillis)
+                throws IOException, XmlPullParserException {
+            final long beginTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_BEGIN_TIME, 0)
+                    + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
+            final long endTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_END_TIME, 0)
+                    + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
+            // Keep reading as subsequent records may start matching
+            if (filterEndTimeMillis < beginTimeMillis) {
+                return null;
+            }
+            // Stop reading as subsequent records will not match
+            if (filterBeginTimeMillis > endTimeMillis) {
+                return new HistoricalOps(0, 0);
+            }
+            final long filteredBeginTimeMillis = Math.max(beginTimeMillis, filterBeginTimeMillis);
+            final long filteredEndTimeMillis = Math.min(endTimeMillis, filterEndTimeMillis);
+            final double filterScale = (double) (filteredEndTimeMillis - filteredBeginTimeMillis)
+                    / (double) (endTimeMillis - beginTimeMillis);
+            HistoricalOps ops = null;
+            final int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                if (TAG_UID.equals(parser.getName())) {
+                    final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser,
+                            filterUid, filterPackageName, filterOpNames, filterScale);
+                    if (ops == null) {
+                        ops = returnedOps;
+                    }
+                }
+            }
+            if (ops != null) {
+                ops.setBeginAndEndTime(filteredBeginTimeMillis, filteredEndTimeMillis);
+            }
+            return ops;
+        }
+
+        private @Nullable HistoricalOps readHistoricalUidOpsDLocked(
+                @Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid,
+                @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+                double filterScale) throws IOException, XmlPullParserException {
+            final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME);
+            if (filterUid != Process.INVALID_UID && filterUid != uid) {
+                XmlUtils.skipCurrentTag(parser);
+                return null;
+            }
+            final int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                if (TAG_PACKAGE.equals(parser.getName())) {
+                    final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops,
+                            uid, parser, filterPackageName, filterOpNames, filterScale);
+                    if (ops == null) {
+                        ops = returnedOps;
+                    }
+                }
+            }
+            return ops;
+        }
+
+        private @Nullable HistoricalOps readHistoricalPackageOpsDLocked(
+                @Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser,
+                @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+                double filterScale) throws IOException, XmlPullParserException {
+            final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME);
+            if (filterPackageName != null && !filterPackageName.equals(packageName)) {
+                XmlUtils.skipCurrentTag(parser);
+                return null;
+            }
+            final int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                if (TAG_OP.equals(parser.getName())) {
+                    final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid,
+                            packageName, parser, filterOpNames, filterScale);
+                    if (ops == null) {
+                        ops = returnedOps;
+                    }
+                }
+            }
+            return ops;
+        }
+
+        private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops,
+                int uid, String packageName, @NonNull XmlPullParser parser,
+                @Nullable String[] filterOpNames, double filterScale)
+                throws IOException, XmlPullParserException {
+            final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME);
+            if (filterOpNames != null && !ArrayUtils.contains(filterOpNames,
+                    AppOpsManager.opToName(op))) {
+                XmlUtils.skipCurrentTag(parser);
+                return null;
+            }
+            final int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                if (TAG_STATE.equals(parser.getName())) {
+                    final HistoricalOps returnedOps = readUidStateDLocked(ops, uid,
+                            packageName, op, parser, filterScale);
+                    if (ops == null) {
+                        ops = returnedOps;
+                    }
+                }
+            }
+            return ops;
+        }
+
+        private @Nullable HistoricalOps readUidStateDLocked(@Nullable HistoricalOps ops,
+                int uid, String packageName, int op, @NonNull XmlPullParser parser,
+                double filterScale) throws IOException {
+            final int uidState = XmlUtils.readIntAttribute(parser, ATTR_NAME);
+            long accessCount = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_COUNT, 0);
+            if (accessCount > 0) {
+                if (!Double.isNaN(filterScale)) {
+                    accessCount = (long) HistoricalOps.round(
+                            (double) accessCount * filterScale);
+                }
+                if (ops == null) {
+                    ops = new HistoricalOps(0, 0);
+                }
+                ops.increaseAccessCount(op, uid, packageName, uidState, accessCount);
+            }
+            long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0);
+            if (rejectCount > 0) {
+                if (!Double.isNaN(filterScale)) {
+                    rejectCount = (long) HistoricalOps.round(
+                            (double) rejectCount * filterScale);
+                }
+                if (ops == null) {
+                    ops = new HistoricalOps(0, 0);
+                }
+                ops.increaseRejectCount(op, uid, packageName, uidState, rejectCount);
+            }
+            long accessDuration =  XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0);
+            if (accessDuration > 0) {
+                if (!Double.isNaN(filterScale)) {
+                    accessDuration = (long) HistoricalOps.round(
+                            (double) accessDuration * filterScale);
+                }
+                if (ops == null) {
+                    ops = new HistoricalOps(0, 0);
+                }
+                ops.increaseAccessDuration(op, uid, packageName, uidState, accessDuration);
+            }
+            return ops;
+        }
+
+        private void writeHistoricalOpsDLocked(@Nullable List<HistoricalOps> allOps,
+                long intervalOverflowMillis, @NonNull File file) throws IOException {
+            final FileOutputStream output = mHistoricalAppOpsDir.openWrite(file);
+            try {
+                final XmlSerializer serializer = Xml.newSerializer();
+                serializer.setOutput(output, StandardCharsets.UTF_8.name());
+                serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+                        true);
+                serializer.startDocument(null, true);
+                serializer.startTag(null, TAG_HISTORY);
+                serializer.attribute(null, ATTR_VERSION, String.valueOf(CURRENT_VERSION));
+                if (intervalOverflowMillis != 0) {
+                    serializer.attribute(null, ATTR_OVERFLOW,
+                            Long.toString(intervalOverflowMillis));
+                }
+                if (allOps != null) {
+                    final int opsCount = allOps.size();
+                    for (int i = 0; i < opsCount; i++) {
+                        final HistoricalOps ops = allOps.get(i);
+                        writeHistoricalOpDLocked(ops, serializer);
+                    }
+                }
+                serializer.endTag(null, TAG_HISTORY);
+                serializer.endDocument();
+                mHistoricalAppOpsDir.closeWrite(output);
+            } catch (IOException e) {
+                mHistoricalAppOpsDir.failWrite(output);
+                throw e;
+            }
+        }
+
+        private void writeHistoricalOpDLocked(@NonNull HistoricalOps ops,
+                @NonNull XmlSerializer serializer) throws IOException {
+            serializer.startTag(null, TAG_OPS);
+            serializer.attribute(null, ATTR_BEGIN_TIME, Long.toString(ops.getBeginTimeMillis()));
+            serializer.attribute(null, ATTR_END_TIME, Long.toString(ops.getEndTimeMillis()));
+            final int uidCount = ops.getUidCount();
+            for (int i = 0; i < uidCount; i++) {
+                final HistoricalUidOps uidOp = ops.getUidOpsAt(i);
+                writeHistoricalUidOpsDLocked(uidOp, serializer);
+            }
+            serializer.endTag(null, TAG_OPS);
+        }
+
+        private void writeHistoricalUidOpsDLocked(@NonNull HistoricalUidOps uidOps,
+                @NonNull XmlSerializer serializer) throws IOException {
+            serializer.startTag(null, TAG_UID);
+            serializer.attribute(null, ATTR_NAME, Integer.toString(uidOps.getUid()));
+            final int packageCount = uidOps.getPackageCount();
+            for (int i = 0; i < packageCount; i++) {
+                final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(i);
+                writeHistoricalPackageOpsDLocked(packageOps, serializer);
+            }
+            serializer.endTag(null, TAG_UID);
+        }
+
+        private void writeHistoricalPackageOpsDLocked(@NonNull HistoricalPackageOps packageOps,
+                @NonNull XmlSerializer serializer) throws IOException {
+            serializer.startTag(null, TAG_PACKAGE);
+            serializer.attribute(null, ATTR_NAME, packageOps.getPackageName());
+            final int opCount = packageOps.getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOp op = packageOps.getOpAt(i);
+                writeHistoricalOpDLocked(op, serializer);
+            }
+            serializer.endTag(null, TAG_PACKAGE);
+        }
+
+        private void writeHistoricalOpDLocked(@NonNull HistoricalOp op,
+                @NonNull XmlSerializer serializer) throws IOException {
+            serializer.startTag(null, TAG_OP);
+            serializer.attribute(null, ATTR_NAME, Integer.toString(op.getOpCode()));
+            for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) {
+                writeUidStateOnLocked(op, uidState, serializer);
+            }
+            serializer.endTag(null, TAG_OP);
+        }
+
+        private void writeUidStateOnLocked(@NonNull HistoricalOp op, @UidState int uidState,
+                @NonNull XmlSerializer serializer) throws IOException {
+            final long accessCount = op.getAccessCount(uidState);
+            final long rejectCount = op.getRejectCount(uidState);
+            final long accessDuration = op.getAccessDuration(uidState);
+            if (accessCount == 0 && rejectCount == 0 && accessDuration == 0) {
+                return;
+            }
+            serializer.startTag(null, TAG_STATE);
+            serializer.attribute(null, ATTR_NAME, Integer.toString(uidState));
+            if (accessCount > 0) {
+                serializer.attribute(null, ATTR_ACCESS_COUNT, Long.toString(accessCount));
+            }
+            if (rejectCount > 0) {
+                serializer.attribute(null, ATTR_REJECT_COUNT, Long.toString(rejectCount));
+            }
+            if (accessDuration > 0) {
+                serializer.attribute(null, ATTR_ACCESS_DURATION, Long.toString(accessDuration));
+            }
+            serializer.endTag(null, TAG_STATE);
+        }
+
+        private static void enforceOpsWellFormed(@NonNull List<HistoricalOps> ops) {
+            if (ops == null) {
+                return;
+            }
+            HistoricalOps previous;
+            HistoricalOps current = null;
+            final int opsCount = ops.size();
+            for (int i = 0; i < opsCount; i++) {
+                previous = current;
+                current = ops.get(i);
+                if (current.isEmpty()) {
+                    throw new IllegalStateException("Empty ops:\n"
+                            + opsToDebugString(ops));
+                }
+                if (current.getEndTimeMillis() < current.getBeginTimeMillis()) {
+                    throw new IllegalStateException("Begin after end:\n"
+                            + opsToDebugString(ops));
+                }
+                if (previous != null) {
+                    if (previous.getEndTimeMillis() > current.getBeginTimeMillis()) {
+                        throw new IllegalStateException("Intersecting ops:\n"
+                                + opsToDebugString(ops));
+                    }
+                    if (previous.getBeginTimeMillis() > current.getBeginTimeMillis()) {
+                        throw new IllegalStateException("Non increasing ops:\n"
+                                + opsToDebugString(ops));
+                    }
+                }
+            }
+        }
+
+        private long computeGlobalIntervalBeginMillis(int depth) {
+            long beginTimeMillis = 0;
+            for (int i = 0; i < depth + 1; i++) {
+                beginTimeMillis += Math.pow(mIntervalCompressionMultiplier, i);
+            }
+            return beginTimeMillis * mBaseSnapshotInterval;
+        }
+
+        private static @NonNull HistoricalOps spliceFromEnd(@NonNull HistoricalOps ops,
+                double spliceRatio) {
+            if (DEBUG) {
+                Slog.w(LOG_TAG, "Splicing from end:" + ops + " ratio:" + spliceRatio);
+            }
+            final HistoricalOps splice = ops.spliceFromEnd(spliceRatio);
+            if (DEBUG) {
+                Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
+            }
+            return splice;
+        }
+
+
+        private static @NonNull HistoricalOps spliceFromBeginning(@NonNull HistoricalOps ops,
+                double spliceRatio) {
+            if (DEBUG) {
+                Slog.w(LOG_TAG, "Splicing from beginning:" + ops + " ratio:" + spliceRatio);
+            }
+            final HistoricalOps splice = ops.spliceFromBeginning(spliceRatio);
+            if (DEBUG) {
+                Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
+            }
+            return splice;
+        }
+
+        private static void normalizeSnapshotForSlotDuration(@NonNull List<HistoricalOps> ops,
+                long slotDurationMillis) {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Normalizing for slot duration: " + slotDurationMillis
+                        + " ops:\n" + opsToDebugString(ops));
+                enforceOpsWellFormed(ops);
+            }
+            long slotBeginTimeMillis;
+            final int opCount = ops.size();
+            for (int processedIdx = opCount - 1; processedIdx >= 0; processedIdx--) {
+                final HistoricalOps processedOp = ops.get(processedIdx);
+                slotBeginTimeMillis = Math.max(processedOp.getEndTimeMillis()
+                        - slotDurationMillis, 0);
+                for (int candidateIdx = processedIdx - 1; candidateIdx >= 0; candidateIdx--) {
+                    final HistoricalOps candidateOp = ops.get(candidateIdx);
+                    final long candidateSlotIntersectionMillis = candidateOp.getEndTimeMillis()
+                            - Math.min(slotBeginTimeMillis, processedOp.getBeginTimeMillis());
+                    if (candidateSlotIntersectionMillis <= 0) {
+                        break;
+                    }
+                    final float candidateSplitRatio = candidateSlotIntersectionMillis
+                            / (float) candidateOp.getDurationMillis();
+                    if (Float.compare(candidateSplitRatio, 1.0f) >= 0) {
+                        ops.remove(candidateIdx);
+                        processedIdx--;
+                        processedOp.merge(candidateOp);
+                    } else {
+                        final HistoricalOps endSplice = spliceFromEnd(candidateOp,
+                                candidateSplitRatio);
+                        if (endSplice != null) {
+                            processedOp.merge(endSplice);
+                        }
+                        if (candidateOp.isEmpty()) {
+                            ops.remove(candidateIdx);
+                            processedIdx--;
+                        }
+                    }
+                }
+            }
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Normalized for slot duration: " + slotDurationMillis
+                        + " ops:\n" + opsToDebugString(ops));
+                enforceOpsWellFormed(ops);
+            }
+        }
+
+        private static @NonNull String opsToDebugString(@NonNull List<HistoricalOps> ops) {
+            StringBuilder builder = new StringBuilder();
+            final int opCount = ops.size();
+            for (int i = 0; i < opCount; i++) {
+                builder.append("  ");
+                builder.append(ops.get(i));
+                if (i < opCount - 1) {
+                    builder.append('\n');
+                }
+            }
+            return builder.toString();
+        }
+    }
+
+    private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor {
+        private final long mNow = System.currentTimeMillis();
+
+        private final SimpleDateFormat mDateFormatter = new SimpleDateFormat(
+                "yyyy-MM-dd HH:mm:ss.SSS");
+        private final Date mDate = new Date();
+
+        private final @NonNull String mOpsPrefix;
+        private final @NonNull String mUidPrefix;
+        private final @NonNull String mPackagePrefix;
+        private final @NonNull String mEntryPrefix;
+        private final @NonNull String mUidStatePrefix;
+        private final @NonNull PrintWriter mWriter;
+        private final int mFilterUid;
+        private final String mFilterPackage;
+        private final int mFilterOp;
+
+        StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer,
+                int filterUid, String filterPackage, int filterOp) {
+            mOpsPrefix = prefix + "  ";
+            mUidPrefix = mOpsPrefix + "  ";
+            mPackagePrefix = mUidPrefix + "  ";
+            mEntryPrefix = mPackagePrefix + "  ";
+            mUidStatePrefix = mEntryPrefix + "  ";
+            mWriter = writer;
+            mFilterUid = filterUid;
+            mFilterPackage = filterPackage;
+            mFilterOp = filterOp;
+        }
+
+        @Override
+        public void visitHistoricalOps(HistoricalOps ops) {
+            mWriter.println();
+            mWriter.print(mOpsPrefix);
+            mWriter.println("snapshot:");
+            mWriter.print(mUidPrefix);
+            mWriter.print("begin = ");
+            mDate.setTime(ops.getBeginTimeMillis());
+            mWriter.print(mDateFormatter.format(mDate));
+            mWriter.print("  (");
+            TimeUtils.formatDuration(ops.getBeginTimeMillis() - mNow, mWriter);
+            mWriter.println(")");
+            mWriter.print(mUidPrefix);
+            mWriter.print("end = ");
+            mDate.setTime(ops.getEndTimeMillis());
+            mWriter.print(mDateFormatter.format(mDate));
+            mWriter.print("  (");
+            TimeUtils.formatDuration(ops.getEndTimeMillis() - mNow, mWriter);
+            mWriter.println(")");
+        }
+
+        @Override
+        public void visitHistoricalUidOps(HistoricalUidOps ops) {
+            if (mFilterUid != Process.INVALID_UID && mFilterUid != ops.getUid()) {
+                return;
+            }
+            mWriter.println();
+            mWriter.print(mUidPrefix);
+            mWriter.print("Uid ");
+            UserHandle.formatUid(mWriter, ops.getUid());
+            mWriter.println(":");
+        }
+
+        @Override
+        public void visitHistoricalPackageOps(HistoricalPackageOps ops) {
+            if (mFilterPackage != null && !mFilterPackage.equals(ops.getPackageName())) {
+                return;
+            }
+            mWriter.print(mPackagePrefix);
+            mWriter.print("Package ");
+            mWriter.print(ops.getPackageName());
+            mWriter.println(":");
+        }
+
+        @Override
+        public void visitHistoricalOp(HistoricalOp ops) {
+            if (mFilterOp != AppOpsManager.OP_NONE && mFilterOp != ops.getOpCode()) {
+                return;
+            }
+            mWriter.print(mEntryPrefix);
+            mWriter.print(AppOpsManager.opToName(ops.getOpCode()));
+            mWriter.println(":");
+            for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) {
+                boolean printedUidState = false;
+                final long accessCount = ops.getAccessCount(uidState);
+                if (accessCount > 0) {
+                    if (!printedUidState) {
+                        mWriter.print(mUidStatePrefix);
+                        mWriter.print(AppOpsManager.uidStateToString(uidState));
+                        mWriter.print("[");
+                        printedUidState = true;
+                    }
+                    mWriter.print("access=");
+                    mWriter.print(accessCount);
+                }
+                final long rejectCount = ops.getRejectCount(uidState);
+                if (rejectCount > 0) {
+                    if (!printedUidState) {
+                        mWriter.print(mUidStatePrefix);
+                        mWriter.print(AppOpsManager.uidStateToString(uidState));
+                        mWriter.print("[");
+                        printedUidState = true;
+                    } else {
+                        mWriter.print(",");
+                    }
+                    mWriter.print("reject=");
+                    mWriter.print(rejectCount);
+                }
+                final long accessDuration = ops.getAccessDuration(uidState);
+                if (accessDuration > 0) {
+                    if (!printedUidState) {
+                        mWriter.print(mUidStatePrefix);
+                        mWriter.print(AppOpsManager.uidStateToString(uidState));
+                        printedUidState = true;
+                    } else {
+                        mWriter.print(",");
+                    }
+                    mWriter.print("duration=");
+                    mWriter.print(accessDuration);
+                }
+                if (printedUidState) {
+                    mWriter.println("]");
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
new file mode 100644
index 0000000..4901d3a
--- /dev/null
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppOpsTestCases",
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 905f826..9d6628c 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -206,7 +206,7 @@
             switch (event) {
             case AudioManager.RECORD_CONFIG_EVENT_STOP:
                 // return failure if an unknown recording session stopped
-                configChanged = (mRecordConfigs.remove(new Integer(session)) != null);
+                configChanged = (mRecordConfigs.remove(new Integer(portId)) != null);
                 if (configChanged) {
                     sEventLogger.log(new RecordingEvent(event, uid, session, source, null));
                 }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 36ca4dc..fc1e326 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -132,10 +132,13 @@
                 Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED);
         private final Uri FACE_UNLOCK_APP_ENABLED =
                 Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED);
+        private final Uri FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
+                Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
 
         private final ContentResolver mContentResolver;
         private boolean mFaceEnabledOnKeyguard;
         private boolean mFaceEnabledForApps;
+        private boolean mFaceAlwaysRequireConfirmation;
 
         /**
          * Creates a content observer.
@@ -158,10 +161,15 @@
                     false /* notifyForDescendents */,
                     this /* observer */,
                     UserHandle.USER_CURRENT);
+            mContentResolver.registerContentObserver(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+                    false /* notifyForDescendents */,
+                    this /* observer */,
+                    UserHandle.USER_CURRENT);
 
             // Update the value immediately
             onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED);
             onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED);
+            onChange(true /* selfChange */, FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
         }
 
         @Override
@@ -185,6 +193,13 @@
                                 Settings.Secure.FACE_UNLOCK_APP_ENABLED,
                                 1 /* default */,
                                 UserHandle.USER_CURRENT) != 0;
+            } else if (FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION.equals(uri)) {
+                mFaceAlwaysRequireConfirmation =
+                        Settings.Secure.getIntForUser(
+                                mContentResolver,
+                                Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+                                0 /* default */,
+                                UserHandle.USER_CURRENT) != 0;
             }
         }
 
@@ -195,6 +210,10 @@
         boolean getFaceEnabledForApps() {
             return mFaceEnabledForApps;
         }
+
+        boolean getFaceAlwaysRequireConfirmation() {
+            return mFaceAlwaysRequireConfirmation;
+        }
     }
 
     private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
@@ -395,7 +414,7 @@
 
                     // Notify SysUI that the biometric has been authenticated. SysUI already knows
                     // the implicit/explicit state and will react accordingly.
-                    mStatusBarService.onBiometricAuthenticated();
+                    mStatusBarService.onBiometricAuthenticated(true);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception", e);
                 }
@@ -412,17 +431,20 @@
                         return;
                     }
 
-                    mStatusBarService.onBiometricHelp(getContext().getResources().getString(
-                            com.android.internal.R.string.biometric_not_recognized));
-                    if (requireConfirmation) {
+                    mStatusBarService.onBiometricAuthenticated(false);
+
+                    // TODO: This logic will need to be updated if BP is multi-modal
+                    if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
+                        // Pause authentication. onBiometricAuthenticated(false) causes the
+                        // dialog to show a "try again" button for passive modalities.
                         mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
-                        mStatusBarService.showBiometricTryAgain();
                         // Cancel authentication. Skip the token/package check since we are
                         // cancelling from system server. The interface is permission protected so
                         // this is fine.
                         cancelInternal(null /* token */, null /* package */,
                                 false /* fromClient */);
                     }
+
                     mCurrentAuthSession.mClientReceiver.onAuthenticationFailed();
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception", e);
@@ -579,8 +601,10 @@
             }
 
             if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) {
-                final boolean mContinuing = mCurrentAuthSession != null
-                        && mCurrentAuthSession.mState == STATE_AUTH_PAUSED;
+                final boolean continuing = mCurrentAuthSession != null &&
+                        (mCurrentAuthSession.mState == STATE_AUTH_PAUSED
+                                || mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED);
+
                 mCurrentAuthSession = mPendingAuthSession;
                 mPendingAuthSession = null;
 
@@ -602,7 +626,7 @@
                         modality |= pair.getKey();
                     }
 
-                    if (!mContinuing) {
+                    if (!continuing) {
                         mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
                                 mInternalReceiver, modality, requireConfirmation, userId);
                         mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
@@ -706,7 +730,8 @@
 
                 mCurrentModality = modality;
 
-                // Actually start authentication
+                // Start preparing for authentication. Authentication starts when
+                // all modalities requested have invoked onReadyForAuthentication.
                 authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
                         callingUid, callingPid, callingUserId, modality);
             });
@@ -725,6 +750,9 @@
                 IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
                 int callingUid, int callingPid, int callingUserId, int modality) {
             try {
+                boolean requireConfirmation = bundle.getBoolean(
+                        BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
+
                 // Generate random cookies to pass to the services that should prepare to start
                 // authenticating. Store the cookie here and wait for all services to "ack"
                 // with the cookie. Once all cookies are received, we can show the prompt
@@ -748,7 +776,10 @@
                     Slog.w(TAG, "Iris unsupported");
                 }
                 if ((modality & TYPE_FACE) != 0) {
-                    mFaceService.prepareForAuthentication(true /* requireConfirmation */,
+                    // Check if the user has forced confirmation to be required in Settings.
+                    requireConfirmation = requireConfirmation
+                            || mSettingObserver.getFaceAlwaysRequireConfirmation();
+                    mFaceService.prepareForAuthentication(requireConfirmation,
                             token, sessionId, userId, mInternalReceiver, opPackageName,
                             cookie, callingUid, callingPid, callingUserId);
                 }
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 72f73f6..f4d8d4b 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -156,7 +156,7 @@
                     mDaemonWrapper, mHalDeviceId, token,
                     new BiometricPromptServiceListenerImpl(wrapperReceiver),
                     mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie,
-                    true /* requireConfirmation */);
+                    requireConfirmation);
             authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
                     callingUserId);
         }
diff --git a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
index 24865bc..6fa98b8 100644
--- a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
+++ b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
@@ -21,22 +21,6 @@
  * @hide
  */
 public class ConnectivityConstants {
-    // IPC constants
-    public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
-            "android.net.conn.NETWORK_CONDITIONS_MEASURED";
-    public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
-    public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
-    public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
-    public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
-    public static final String EXTRA_CELL_ID = "extra_cellid";
-    public static final String EXTRA_SSID = "extra_ssid";
-    public static final String EXTRA_BSSID = "extra_bssid";
-    /** real time since boot */
-    public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
-    public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
-
-    public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
-            "android.permission.ACCESS_NETWORK_CONDITIONS";
 
     // Penalty applied to scores of Networks that have not been validated.
     public static final int UNVALIDATED_SCORE_PENALTY = 40;
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index b8f057d..d8bb635 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -18,10 +18,9 @@
 
 import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
-import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
 import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
+import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
 import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
 import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
 import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
@@ -35,6 +34,7 @@
 import android.net.Network;
 import android.net.NetworkUtils;
 import android.net.Uri;
+import android.net.shared.PrivateDnsConfig;
 import android.os.Binder;
 import android.os.INetworkManagementService;
 import android.os.UserHandle;
@@ -43,10 +43,7 @@
 import android.util.Pair;
 import android.util.Slog;
 
-import com.android.server.connectivity.MockableSystemProperties;
-
 import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -54,10 +51,8 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
 import java.util.Set;
-import java.util.StringJoiner;
+import java.util.stream.Collectors;
 
 
 /**
@@ -123,43 +118,6 @@
     private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
     private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
 
-    public static class PrivateDnsConfig {
-        public final boolean useTls;
-        public final String hostname;
-        public final InetAddress[] ips;
-
-        public PrivateDnsConfig() {
-            this(false);
-        }
-
-        public PrivateDnsConfig(boolean useTls) {
-            this.useTls = useTls;
-            this.hostname = "";
-            this.ips = new InetAddress[0];
-        }
-
-        public PrivateDnsConfig(String hostname, InetAddress[] ips) {
-            this.useTls = !TextUtils.isEmpty(hostname);
-            this.hostname = useTls ? hostname : "";
-            this.ips = (ips != null) ? ips : new InetAddress[0];
-        }
-
-        public PrivateDnsConfig(PrivateDnsConfig cfg) {
-            useTls = cfg.useTls;
-            hostname = cfg.hostname;
-            ips = cfg.ips;
-        }
-
-        public boolean inStrictMode() {
-            return useTls && !TextUtils.isEmpty(hostname);
-        }
-
-        public String toString() {
-            return PrivateDnsConfig.class.getSimpleName() +
-                    "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
-        }
-    }
-
     public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
         final String mode = getPrivateDnsMode(cr);
 
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 262184b..54c89aa 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,9 +16,8 @@
 
 package com.android.server.connectivity;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
 import android.content.Context;
+import android.net.INetworkMonitor;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -29,7 +28,6 @@
 import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.Messenger;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
@@ -37,11 +35,8 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.WakeupMessage;
 import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkMonitor;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.Objects;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -126,7 +121,6 @@
     public LinkProperties linkProperties;
     // This should only be modified via ConnectivityService.updateCapabilities().
     public NetworkCapabilities networkCapabilities;
-    public final NetworkMonitor networkMonitor;
     public final NetworkMisc networkMisc;
     // Indicates if netd has been told to create this Network. From this point on the appropriate
     // routing rules are setup and routes are added so packets can begin flowing over the Network.
@@ -239,6 +233,9 @@
     // Used by ConnectivityService to keep track of 464xlat.
     public Nat464Xlat clatd;
 
+    // Set after asynchronous creation of the NetworkMonitor.
+    private volatile INetworkMonitor mNetworkMonitor;
+
     private static final String TAG = ConnectivityService.class.getSimpleName();
     private static final boolean VDBG = false;
     private final ConnectivityService mConnService;
@@ -247,7 +244,7 @@
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
-            NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
+            NetworkMisc misc, ConnectivityService connService) {
         this.messenger = messenger;
         asyncChannel = ac;
         network = net;
@@ -258,10 +255,16 @@
         mConnService = connService;
         mContext = context;
         mHandler = handler;
-        networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest);
         networkMisc = misc;
     }
 
+    /**
+     * Inform NetworkAgentInfo that a new NetworkMonitor was created.
+     */
+    public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+        mNetworkMonitor = networkMonitor;
+    }
+
     public ConnectivityService connService() {
         return mConnService;
     }
@@ -278,6 +281,15 @@
         return network;
     }
 
+    /**
+     * Get the INetworkMonitor in this NetworkAgentInfo.
+     *
+     * <p>This will be null before {@link #onNetworkMonitorCreated(INetworkMonitor)} is called.
+     */
+    public INetworkMonitor networkMonitor() {
+        return mNetworkMonitor;
+    }
+
     // Functions for manipulating the requests satisfied by this network.
     //
     // These functions must only called on ConnectivityService's main thread.
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index ea01a0b..6f5a196 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -256,6 +256,41 @@
     static final int USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 1;
     static final int NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 2;
 
+    // Port id to record local active port for Routing Control features
+    // They are used to map to corresponding Inputs
+    // Current interface is only implemented for specific device.
+    // Developers can add more port number and map them to corresponding inputs on demand.
+    @IntDef({
+        CEC_SWITCH_HOME,
+        CEC_SWITCH_HDMI1,
+        CEC_SWITCH_HDMI2,
+        CEC_SWITCH_HDMI3,
+        CEC_SWITCH_HDMI4,
+        CEC_SWITCH_HDMI5,
+        CEC_SWITCH_HDMI6,
+        CEC_SWITCH_HDMI7,
+        CEC_SWITCH_HDMI8,
+        CEC_SWITCH_ARC,
+        CEC_SWITCH_BLUETOOTH,
+        CEC_SWITCH_OPTICAL,
+        CEC_SWITCH_AUX
+    })
+    @interface LocalActivePort {}
+    static final int CEC_SWITCH_HOME = 0;
+    static final int CEC_SWITCH_HDMI1 = 1;
+    static final int CEC_SWITCH_HDMI2 = 2;
+    static final int CEC_SWITCH_HDMI3 = 3;
+    static final int CEC_SWITCH_HDMI4 = 4;
+    static final int CEC_SWITCH_HDMI5 = 5;
+    static final int CEC_SWITCH_HDMI6 = 6;
+    static final int CEC_SWITCH_HDMI7 = 7;
+    static final int CEC_SWITCH_HDMI8 = 8;
+    static final int CEC_SWITCH_ARC = 17;
+    static final int CEC_SWITCH_BLUETOOTH = 18;
+    static final int CEC_SWITCH_OPTICAL = 19;
+    static final int CEC_SWITCH_AUX = 20;
+    static final int CEC_SWITCH_PORT_MAX = 21;
+
     static final String PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM =
             "persist.sys.hdmi.addr.audiosystem";
     static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
@@ -274,15 +309,48 @@
     static final String PROPERTY_SET_MENU_LANGUAGE = "ro.hdmi.set_menu_language";
 
     /**
+     * Property to save the ARC port id on system audio device.
+     * <p>When ARC is initiated, this port will be used to turn on ARC.
+     */
+    static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT =
+            "ro.hdmi.property_sytem_audio_device_arc_port";
+
+    /**
      * Property to disable muting logic in System Audio Control handling. Default is true.
      *
      * <p>True means enabling muting logic.
      * <p>False means never mute device.
      */
-    // TODO(OEM): set to true to disable muting.
     static final String PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE =
             "ro.hdmi.property_system_audio_mode_muting_enable";
 
+    /**
+     * When set to true the HdmiControlService will never request a Logical Address for the
+     * playback device type. Default is false.
+     *
+     * <p> This is useful when HDMI CEC multiple device types is not supported by the cec driver
+     */
+    static final String PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS =
+            "ro.hdmi.property_hdmi_cec_never_claim_playback_logical_address";
+
+    /**
+     * A comma separated list of logical addresses that HdmiControlService
+     * will never assign local CEC devices to.
+     *
+     * <p> This is useful when HDMI CEC hardware module can't assign multiple logical addresses
+     * in the range same range of 0-7 or 8-15.
+     */
+    static final String PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES =
+            "ro.hdmi.property_hdmi_cec_never_assign_logical_addresses";
+
+    /**
+     * Property to indicate if the current device is a cec switch device.
+     *
+     * <p> Default is false.
+     */
+    static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH =
+            "ro.hdmi.property_is_device_hdmi_cec_switch";
+
     // Set to false to allow playback device to go to suspend mode even
     // when it's an active source. True by default.
     static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake";
@@ -323,13 +391,6 @@
             "persist.sys.hdmi.property_sytem_audio_mode_audio_port";
 
     /**
-     * Property to save the ARC port id on system audio device.
-     * <p>When ARC is initiated, this port will be used to turn on ARC.
-     */
-    static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT =
-            "persist.sys.hdmi.property_sytem_audio_device_arc_port";
-
-    /**
      * Property to indicate if a CEC audio device should forward volume keys when system audio mode
      * is off.
      *
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index de6cdd4..e777ce8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -22,20 +22,25 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
+import android.os.SystemProperties;
 import android.util.Slog;
 import android.util.SparseArray;
+
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+
+import libcore.util.EmptyArray;
+
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.function.Predicate;
 import java.util.concurrent.ArrayBlockingQueue;
-import libcore.util.EmptyArray;
+import java.util.function.Predicate;
+
 import sun.util.locale.LanguageTag;
 
 /**
@@ -112,10 +117,18 @@
 
     private final NativeWrapper mNativeWrapperImpl;
 
+    /** List of logical addresses that should not be assigned to the current device.
+     *
+     * <p>Parsed from {@link Constants#PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES}
+     */
+    private final List<Integer> mNeverAssignLogicalAddresses;
+
     // Private constructor.  Use HdmiCecController.create().
     private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) {
         mService = service;
         mNativeWrapperImpl = nativeWrapper;
+        mNeverAssignLogicalAddresses = mService.getIntList(SystemProperties.get(
+            Constants.PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES));
     }
 
     /**
@@ -208,7 +221,8 @@
         for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
             int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
             if (curAddress != Constants.ADDR_UNREGISTERED
-                    && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
+                    && deviceType == HdmiUtils.getTypeFromAddress(curAddress)
+                    && !mNeverAssignLogicalAddresses.contains(curAddress)) {
                 boolean acked = false;
                 for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
                     if (sendPollMessage(curAddress, curAddress, 1)) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
index 11faa56..2da698b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
@@ -252,6 +252,10 @@
         return (HdmiCecLocalDevicePlayback) mSource;
     }
 
+    protected final HdmiCecLocalDeviceSource source() {
+        return (HdmiCecLocalDeviceSource) mSource;
+    }
+
     protected final HdmiCecLocalDeviceTv tv() {
         return (HdmiCecLocalDeviceTv) mSource;
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 7860122..528e0a4 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -27,10 +27,13 @@
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.hdmi.Constants.LocalActivePort;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -51,6 +54,11 @@
     // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior.
     // When it expires, we can assume <User Control Release> is received.
     private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
+    /**
+     * Return value of {@link #getLocalPortFromPhysicalAddress(int)}
+     */
+    private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1;
+    private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0;
 
     protected final HdmiControlService mService;
     protected final int mDeviceType;
@@ -434,10 +442,14 @@
         return true;
     }
 
+    // Audio System device with no Playback device type
+    // needs to refactor this function if it's also a switch
     protected boolean handleRoutingChange(HdmiCecMessage message) {
         return false;
     }
 
+    // Audio System device with no Playback device type
+    // needs to refactor this function if it's also a switch
     protected boolean handleRoutingInformation(HdmiCecMessage message) {
         return false;
     }
@@ -1033,4 +1045,68 @@
         pw.println("mActiveSource: " + mActiveSource);
         pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
     }
+
+    /**
+     * Method to parse target physical address to the port number on the current device.
+     *
+     * <p>This check assumes target address is valid.
+     * @param targetPhysicalAddress is the physical address of the target device
+     * @return
+     * If the target device is under the current device, return the port number of current device
+     * that the target device is connected to.
+     *
+     * <p>If the target device has the same physical address as the current device, return
+     * {@link #TARGET_SAME_PHYSICAL_ADDRESS}.
+     *
+     * <p>If the target device is not under the current device, return
+     * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}.
+     */
+    protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) {
+        int myPhysicalAddress = mService.getPhysicalAddress();
+        if (myPhysicalAddress == targetPhysicalAddress) {
+            return TARGET_SAME_PHYSICAL_ADDRESS;
+        }
+        int finalMask = 0xF000;
+        int mask;
+        int port = 0;
+        for (mask = 0x0F00; mask > 0x000F;  mask >>= 4) {
+            if ((myPhysicalAddress & mask) == 0)  {
+                port = mask & targetPhysicalAddress;
+                break;
+            } else {
+                finalMask |= mask;
+            }
+        }
+        if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) {
+            while (mask != 0x000F) {
+                mask >>= 4;
+                port >>= 4;
+            }
+            return port;
+        }
+        return TARGET_NOT_UNDER_LOCAL_DEVICE;
+    }
+
+    /** Calculates the physical address for {@code activePortId}.
+     *
+     * <p>This method assumes current device physical address is valid.
+     * <p>If the current device is already the leaf of the whole CEC system
+     * and can't have devices under it, will return its own physical address.
+     *
+     * @param activePortId is the local active port Id
+     * @return the calculated physical address of the port
+     */
+    protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) {
+        int myPhysicalAddress = mService.getPhysicalAddress();
+        int finalMask = activePortId << 8;
+        int mask;
+        for (mask = 0x0F00; mask > 0x000F;  mask >>= 4) {
+            if ((myPhysicalAddress & mask) == 0)  {
+                break;
+            } else {
+                finalMask >>= 4;
+            }
+        }
+        return finalMask | myPhysicalAddress;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 7358eaa..048a0e4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -20,10 +20,12 @@
 import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
 
 import android.annotation.Nullable;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioSystem;
+import android.media.tv.TvContract;
 import android.os.SystemProperties;
 import android.provider.Settings.Global;
 
@@ -32,11 +34,13 @@
 import com.android.server.hdmi.Constants.AudioCodec;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 
+import java.util.HashMap;
+
 /**
  * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
  * system.
  */
-public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
+public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
 
     private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
 
@@ -60,16 +64,16 @@
     // AVR as audio receiver.
     @ServiceThreadOnly private boolean mArcEstablished = false;
 
-    /**
-     * Return value of {@link #getLocalPortFromPhysicalAddress(int)}
-     */
-    private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1;
-    private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0;
+    // If the current device uses TvInput for ARC. We assume all other inputs also use TvInput
+    // when ARC is using TvInput.
+    private boolean mArcIntentUsed = SystemProperties
+            .get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, "0").contains("tvinput");
 
-    // Local active port number used for Routing Control.
-    // Default 0 means HOME is the current active path. Temp solution only.
-    // TODO(amyjojo): adding system constants for Atom inputs port and TIF mapping.
-    private int mLocalActivePath = 0;
+    // Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to
+    // accept input switching request from HDMI devices. Requests for which the corresponding
+    // input ID is not yet registered by TV input framework need to be buffered for delayed
+    // processing.
+    private final HashMap<Integer, String> mTvInputs = new HashMap<>();
 
     protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
@@ -77,12 +81,17 @@
         // TODO(amyjojo) make System Audio Control controllable by users
         /*mSystemAudioControlFeatureEnabled =
         mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);*/
-        // TODO(b/80297700): set read-only property in config instead of setting here
-        SystemProperties.set(Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, "false");
         mAutoDeviceOff = mService.readBooleanSetting(
                 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true);
         mAutoTvOff = mService.readBooleanSetting(
                 Global.HDMI_CONTROL_AUTO_TV_OFF_ENABLED, true);
+        // TODO(amyjojo): make the map ro property.
+        mTvInputs.put(Constants.CEC_SWITCH_HDMI1,
+                "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
+        mTvInputs.put(Constants.CEC_SWITCH_HDMI2,
+                "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
+        mTvInputs.put(Constants.CEC_SWITCH_HDMI3,
+                "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
     }
 
     @Override
@@ -143,43 +152,17 @@
             int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
         if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
                 || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
-                        && lastSystemAudioControlStatus)) {
+                && lastSystemAudioControlStatus)) {
             addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
         }
     }
 
-    @ServiceThreadOnly
-    protected boolean handleActiveSource(HdmiCecMessage message) {
-        assertRunOnServiceThread();
-        int logicalAddress = message.getSource();
-        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
-        ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
-        if (!mActiveSource.equals(activeSource)) {
-            setActiveSource(activeSource);
-        }
-        return true;
-    }
-
-    @Override
-    @ServiceThreadOnly
-    protected boolean handleSetStreamPath(HdmiCecMessage message) {
-        assertRunOnServiceThread();
-        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
-        // If current device is the target path, playback device should handle it.
-        // If the path is under the current device, should switch
-        int port = getLocalPortFromPhysicalAddress(physicalAddress);
-        if (port > 0) {
-            routeToPort(port);
-        }
-        return true;
-    }
-
     @Override
     @ServiceThreadOnly
     protected int getPreferredAddress() {
         assertRunOnServiceThread();
         return SystemProperties.getInt(
-                Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
+            Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
     }
 
     @Override
@@ -323,7 +306,7 @@
         for (int i = 0; i < params.length; i++) {
             byte val = params[i];
             audioFormatCodes[i] =
-                    val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
+                val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
         }
         return audioFormatCodes;
     }
@@ -333,7 +316,23 @@
     protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
         assertRunOnServiceThread();
         boolean systemAudioStatusOn = message.getParams().length != 0;
-        if (!setSystemAudioMode(systemAudioStatusOn)) {
+        // Check if the request comes from a non-TV device.
+        // Need to check if TV supports System Audio Control
+        // if non-TV device tries to turn on the feature
+        if (message.getSource() != Constants.ADDR_TV) {
+            if (systemAudioStatusOn) {
+                handleSystemAudioModeOnFromNonTvDevice(message);
+                return true;
+            }
+        } else {
+            // If TV request the feature on
+            // cache TV supporting System Audio Control
+            // until Audio System loses its physical address.
+            setTvSystemAudioModeSupport(true);
+        }
+        // If TV or Audio System does not support the feature,
+        // will send abort command.
+        if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
             return true;
         }
@@ -348,7 +347,8 @@
     @ServiceThreadOnly
     protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
+        if (!checkSupportAndSetSystemAudioMode(
+                HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
         }
         return true;
@@ -358,7 +358,8 @@
     @ServiceThreadOnly
     protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
+        if (!checkSupportAndSetSystemAudioMode(
+                HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
         }
         return true;
@@ -390,7 +391,7 @@
     private void notifyArcStatusToAudioService(boolean enabled) {
         // Note that we don't set any name to ARC.
         mService.getAudioManager()
-                .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
+            .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
     }
 
     private void reportAudioStatus(HdmiCecMessage message) {
@@ -406,7 +407,16 @@
                         mAddress, message.getSource(), scaledVolume, mute));
     }
 
-    protected boolean setSystemAudioMode(boolean newSystemAudioMode) {
+    /**
+     * Method to check if device support System Audio Control. If so, wake up device if necessary.
+     *
+     * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode
+     * @param newSystemAudioMode turning feature on or off. True is on. False is off.
+     * @return true or false.
+     *
+     * <p>False when device does not support the feature. Otherwise returns true.
+     */
+    protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) {
         if (!isSystemAudioControlFeatureEnabled()) {
             HdmiLogger.debug(
                     "Cannot turn "
@@ -422,6 +432,17 @@
         if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) {
             mService.wakeUp();
         }
+        setSystemAudioMode(newSystemAudioMode);
+        return true;
+    }
+
+    /**
+     * Real work to turn on or off System Audio Mode.
+     *
+     * Use {@link #checkSupportAndSetSystemAudioMode(boolean)}
+     * if trying to turn on or off the feature.
+     */
+    private void setSystemAudioMode(boolean newSystemAudioMode) {
         int targetPhysicalAddress = getActiveSource().physicalAddress;
         int port = getLocalPortFromPhysicalAddress(targetPhysicalAddress);
         if (newSystemAudioMode && port >= 0) {
@@ -449,48 +470,13 @@
                 mService.announceSystemAudioModeChange(newSystemAudioMode);
             }
         }
-        return true;
-    }
-
-    /**
-     * Method to parse target physical address to the port number on the current device.
-     *
-     * <p>This check assumes target address is valid.
-     * @param targetPhysicalAddress is the physical address of the target device
-     * @return
-     * <p>If the target device is under the current device, return the port number of current device
-     * that the target device is connected to.
-     *
-     * <p>If the target device has the same physical address as the current device, return
-     * {@link #TARGET_SAME_PHYSICAL_ADDRESS}.
-     *
-     * <p>If the target device is not under the current device, return
-     * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}.
-     */
-    protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) {
-        int myPhysicalAddress = mService.getPhysicalAddress();
-        if (myPhysicalAddress == targetPhysicalAddress) {
-            return TARGET_SAME_PHYSICAL_ADDRESS;
+        // Init arc whenever System Audio Mode is on
+        // Since some TV like LG don't request ARC on with System Audio Mode on request
+        if (newSystemAudioMode
+                && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
+                && !isArcEnabled() && isDirectConnectToTv()) {
+            addAndStartAction(new ArcInitiationActionFromAvr(this));
         }
-        int finalMask = 0xF000;
-        int mask;
-        int port = 0;
-        for (mask = 0x0F00; mask > 0x000F;  mask >>= 4) {
-            if ((myPhysicalAddress & mask) == 0)  {
-                port = mask & targetPhysicalAddress;
-                break;
-            } else {
-                finalMask |= mask;
-            }
-        }
-        if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) {
-            while (mask != 0x000F) {
-                mask >>= 4;
-                port >>= 4;
-            }
-            return port;
-        }
-        return TARGET_NOT_UNDER_LOCAL_DEVICE;
     }
 
     protected void switchToAudioInput() {
@@ -534,7 +520,7 @@
             return;
         }
 
-        if (setSystemAudioMode(false)) {
+        if (checkSupportAndSetSystemAudioMode(false)) {
             // send <Set System Audio Mode> [“Off”]
             mService.sendCecCommand(
                     HdmiCecMessageBuilder.buildSetSystemAudioMode(
@@ -557,6 +543,7 @@
      * <p>The result of the query may be cached until Audio device type is put in standby or loses
      * its physical address.
      */
+    // TODO(amyjojo): making mTvSystemAudioModeSupport null originally and fix the logic.
     void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
         if (!mTvSystemAudioModeSupport) {
             addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
@@ -565,6 +552,37 @@
         }
     }
 
+    /**
+     * Handler of System Audio Mode Request on from non TV device
+     */
+    void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
+        if (!isSystemAudioControlFeatureEnabled()) {
+            HdmiLogger.debug(
+                    "Cannot turn on" + "system audio mode "
+                            + "because the System Audio Control feature is disabled.");
+            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+            return;
+        }
+        // Wake up device if it is still on standby
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+        // Check if TV supports System Audio Control.
+        // Handle broadcasting setSystemAudioMode on or aborting message on callback.
+        queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
+            public void onResult(boolean supported) {
+                if (supported) {
+                    setSystemAudioMode(true);
+                    mService.sendCecCommand(
+                            HdmiCecMessageBuilder.buildSetSystemAudioMode(
+                                    mAddress, Constants.ADDR_BROADCAST, true));
+                } else {
+                    mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+                }
+            }
+        });
+    }
+
     void setTvSystemAudioModeSupport(boolean supported) {
         mTvSystemAudioModeSupport = supported;
     }
@@ -589,13 +607,134 @@
         mAutoDeviceOff = autoDeviceOff;
     }
 
-    private void routeToPort(int portId) {
-        // TODO(AMYJOJO): route to specific input of the port
-        mLocalActivePath = portId;
+    @Override
+    protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
+        int port = getLocalPortFromPhysicalAddress(physicalAddress);
+        // Wake up if the new Active Source is the current device or under it
+        // or if System Audio Control is enabled.
+        if ((isSystemAudioActivated() || port >= 0) && mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+
+        if (isSystemAudioActivated() && port < 0) {
+            // If system audio mode is on and the new active source is not under the current device,
+            // Will switch to ARC input.
+            // TODO(b/115637145): handle system aduio without ARC
+            routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
+        } else if (mIsSwitchDevice && port >= 0) {
+            // If current device is a switch and the new active source is under it,
+            // will switch to the corresponding active path.
+            routeToInputFromPortId(port);
+        }
     }
 
-    @VisibleForTesting
-    protected int getLocalActivePath() {
-        return mLocalActivePath;
+    protected void routeToInputFromPortId(int portId) {
+        if (mArcIntentUsed) {
+            routeToTvInputFromPortId(portId);
+        } else {
+            // TODO(): implement input switching for devices not using TvInput.
+        }
+    }
+
+    protected void routeToTvInputFromPortId(int portId) {
+        if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) {
+            HdmiLogger.debug("Invalid port number for Tv Input switching.");
+            return;
+        }
+        // TODO(amyjojo): handle if switching to the current input
+        if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
+            switchToHomeTvInput();
+        } else if (portId == Constants.CEC_SWITCH_ARC) {
+            switchToTvInput(SystemProperties.get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT));
+            return;
+        } else {
+            String uri = mTvInputs.get(portId);
+            if (uri != null) {
+                switchToTvInput(mTvInputs.get(portId));
+            } else {
+                HdmiLogger.debug("Port number does not match any Tv Input.");
+                return;
+            }
+        }
+
+        setLocalActivePort(portId);
+    }
+
+    // For device to switch to specific TvInput with corresponding URI.
+    private void switchToTvInput(String uri) {
+        mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
+                TvContract.buildChannelUriForPassthroughInput(uri))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+    // For device using TvInput to switch to Home.
+    private void switchToHomeTvInput() {
+        Intent activityIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+                        | Intent.FLAG_ACTIVITY_SINGLE_TOP
+                        | Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+        mService.getContext().startActivity(activityIntent);
+    }
+
+    @Override
+    protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
+        int port = getLocalPortFromPhysicalAddress(physicalAddress);
+        // Routing change or information sent from switches under the current device can be ignored.
+        if (port > 0) {
+            return;
+        }
+        // When other switches route to some other devices not under the current device,
+        // check system audio mode status and do ARC switch if needed.
+        if (port < 0 && isSystemAudioActivated()) {
+            handleRoutingChangeAndInformationForSystemAudio();
+            return;
+        }
+        // When other switches route to the current device
+        // and the current device is also a switch.
+        if (port == 0) {
+            handleRoutingChangeAndInformationForSwitch(message);
+        }
+    }
+
+    // Handle the system audio(ARC) part of the logic on receiving routing change or information.
+    private void handleRoutingChangeAndInformationForSystemAudio() {
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+        // TODO(b/115637145): handle system aduio without ARC
+        routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
+    }
+
+    // Handle the routing control part of the logic on receiving routing change or information.
+    private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+        if (getLocalActivePort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
+            routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
+            if (mService.playback() != null) {
+                mService.playback().setAndBroadcastActiveSource(
+                        message, mService.getPhysicalAddress());
+            } else {
+                setAndBroadcastActiveSource(message, mService.getPhysicalAddress());
+            }
+            return;
+        }
+
+        int routingInformationPath =
+                getActivePathOnSwitchFromActivePortId(getLocalActivePort());
+        // If current device is already the leaf of the whole HDMI system, will do nothing.
+        if (routingInformationPath == mService.getPhysicalAddress()) {
+            HdmiLogger.debug("Current device can't assign valid physical address"
+                    + "to devices under it any more. "
+                    + "It's physical address is " + routingInformationPath);
+            return;
+        }
+        // Otherwise will switch to the current active port and broadcast routing information.
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingInformation(
+                mAddress, routingInformationPath));
+        routeToInputFromPortId(getLocalActivePort());
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d45b00b..379cc16 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -26,6 +26,7 @@
 import android.provider.Settings.Global;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.LocalePicker;
 import com.android.internal.app.LocalePicker.LocaleInfo;
 import com.android.internal.util.IndentingPrintWriter;
@@ -35,12 +36,10 @@
 import java.util.List;
 import java.util.Locale;
 
-import java.util.List;
-
 /**
  * Represent a logical device of type Playback residing in Android system.
  */
-final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
+final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
     private static final String TAG = "HdmiCecLocalDevicePlayback";
 
     private static final boolean WAKE_ON_HOTPLUG =
@@ -62,6 +61,11 @@
     // If true, turn off TV upon standby. False by default.
     private boolean mAutoTvOff;
 
+    // Local active port number used for Routing Control.
+    // Default 0 means HOME is the current active path. Temp solution only.
+    // TODO(amyjojo): adding system constants for input ports to TIF mapping.
+    private int mLocalActivePath = 0;
+
     HdmiCecLocalDevicePlayback(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
 
@@ -100,25 +104,6 @@
     }
 
     @ServiceThreadOnly
-    void oneTouchPlay(IHdmiControlCallback callback) {
-        assertRunOnServiceThread();
-        List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
-        if (!actions.isEmpty()) {
-            Slog.i(TAG, "oneTouchPlay already in progress");
-            actions.get(0).addCallback(callback);
-            return;
-        }
-        OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
-                callback);
-        if (action == null) {
-            Slog.w(TAG, "Cannot initiate oneTouchPlay");
-            invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
-            return;
-        }
-        addAndStartAction(action);
-    }
-
-    @ServiceThreadOnly
     void queryDisplayStatus(IHdmiControlCallback callback) {
         assertRunOnServiceThread();
         List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
@@ -189,7 +174,7 @@
     }
 
     @ServiceThreadOnly
-    void setActiveSource(boolean on) {
+    void setIsActiveSource(boolean on) {
         assertRunOnServiceThread();
         mIsActiveSource = on;
         if (on) {
@@ -227,21 +212,6 @@
         return !getWakeLock().isHeld();
     }
 
-    @Override
-    @ServiceThreadOnly
-    protected boolean handleActiveSource(HdmiCecMessage message) {
-        assertRunOnServiceThread();
-        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
-        mayResetActiveSource(physicalAddress);
-        return true;  // Broadcast message.
-    }
-
-    private void mayResetActiveSource(int physicalAddress) {
-        if (physicalAddress != mService.getPhysicalAddress()) {
-            setActiveSource(false);
-        }
-    }
-
     @ServiceThreadOnly
     protected boolean handleUserControlPressed(HdmiCecMessage message) {
         assertRunOnServiceThread();
@@ -254,10 +224,21 @@
     protected boolean handleSetStreamPath(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
-        maySetActiveSource(physicalAddress);
-        maySendActiveSource(message.getSource());
-        wakeUpIfActiveSource();
-        return true;  // Broadcast message.
+        // If current device is the target path, set to Active Source.
+        // If the path is under the current device, should switch
+        int port = getLocalPortFromPhysicalAddress(physicalAddress);
+        if (port == 0) {
+            setIsActiveSource(true);
+            maySendActiveSource(message.getSource());
+            wakeUpIfActiveSource();
+        } else if (port > 0) {
+            // Wake up the device if the power is in standby mode for routing
+            if (mService.isPowerStandbyOrTransient()) {
+                mService.wakeUp();
+            }
+            routeToPort(port);
+        }
+        return true;
     }
 
     // Samsung model we tested sends <Routing Change> and <Request Active Source>
@@ -282,10 +263,11 @@
     }
 
     private void maySetActiveSource(int physicalAddress) {
-        setActiveSource(physicalAddress == mService.getPhysicalAddress());
+        setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
     }
 
-    private void wakeUpIfActiveSource() {
+    @Override
+    protected void wakeUpIfActiveSource() {
         if (!mIsActiveSource) {
             return;
         }
@@ -296,7 +278,8 @@
         }
     }
 
-    private void maySendActiveSource(int dest) {
+    @Override
+    protected void maySendActiveSource(int dest) {
         if (mIsActiveSource) {
             mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
                     mAddress, mService.getPhysicalAddress()));
@@ -306,14 +289,6 @@
         }
     }
 
-    @Override
-    @ServiceThreadOnly
-    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
-        assertRunOnServiceThread();
-        maySendActiveSource(message.getSource());
-        return true;  // Broadcast message.
-    }
-
     @ServiceThreadOnly
     protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
@@ -361,16 +336,6 @@
 
     @Override
     @ServiceThreadOnly
-    protected void sendStandby(int deviceId) {
-        assertRunOnServiceThread();
-
-        // Playback device can send <Standby> to TV only. Ignore the parameter.
-        int targetAddress = Constants.ADDR_TV;
-        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
-    }
-
-    @Override
-    @ServiceThreadOnly
     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
         super.disableDevice(initiatedByCec, callback);
 
@@ -379,10 +344,20 @@
             mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
                     mAddress, mService.getPhysicalAddress()));
         }
-        setActiveSource(false);
+        setIsActiveSource(false);
         checkIfPendingActionsCleared();
     }
 
+    private void routeToPort(int portId) {
+        // TODO(AMYJOJO): route to specific input of the port
+        mLocalActivePath = portId;
+    }
+
+    @VisibleForTesting
+    protected int getLocalActivePath() {
+        return mLocalActivePath;
+    }
+
     @Override
     protected void dump(final IndentingPrintWriter pw) {
         super.dump(pw);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
new file mode 100644
index 0000000..8d55299
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -0,0 +1,254 @@
+/*
+ * 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.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.hdmi.Constants.LocalActivePort;
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import java.util.List;
+
+/**
+ * Represent a logical source device residing in Android system.
+ */
+abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
+
+    private static final String TAG = "HdmiCecLocalDeviceSource";
+
+    // Indicate if current device is Active Source or not
+    private boolean mIsActiveSource = false;
+
+    // Device has cec switch functionality or not.
+    // Default is false.
+    protected boolean mIsSwitchDevice = SystemProperties.getBoolean(
+            Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
+
+    // Local active port number used for Routing Control.
+    // This records the default active port or the previous valid active port.
+    // Default is HOME input.
+    // Note that we don't save active path here because for source device,
+    // new Active Source physical address might not match the local active path
+    @GuardedBy("mLock")
+    @LocalActivePort
+    private int mLocalActivePort = Constants.CEC_SWITCH_HOME;
+
+    protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
+        super(service, deviceType);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    void onHotplug(int portId, boolean connected) {
+        assertRunOnServiceThread();
+        mCecMessageCache.flushAll();
+        // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected void sendStandby(int deviceId) {
+        assertRunOnServiceThread();
+
+        // Send standby to TV only for now
+        int targetAddress = Constants.ADDR_TV;
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
+    }
+
+    @ServiceThreadOnly
+    void oneTouchPlay(IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
+        if (!actions.isEmpty()) {
+            Slog.i(TAG, "oneTouchPlay already in progress");
+            actions.get(0).addCallback(callback);
+            return;
+        }
+        OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
+                callback);
+        if (action == null) {
+            Slog.w(TAG, "Cannot initiate oneTouchPlay");
+            invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
+            return;
+        }
+        addAndStartAction(action);
+    }
+
+    @ServiceThreadOnly
+    private void invokeCallback(IHdmiControlCallback callback, int result) {
+        assertRunOnServiceThread();
+        try {
+            callback.onComplete(result);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Invoking callback failed:" + e);
+        }
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleActiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int logicalAddress = message.getSource();
+        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+        ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
+        if (!mActiveSource.equals(activeSource)) {
+            setActiveSource(activeSource);
+        }
+        setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
+        switchInputOnReceivingNewActivePath(physicalAddress);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        maySendActiveSource(message.getSource());
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleSetStreamPath(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+        // If current device is the target path, set to Active Source.
+        // If the path is under the current device, should switch
+        if (physicalAddress == mService.getPhysicalAddress() && mService.isPlaybackDevice()) {
+            setAndBroadcastActiveSource(message, physicalAddress);
+        }
+        switchInputOnReceivingNewActivePath(physicalAddress);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleRoutingChange(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
+        // if the current device is a pure playback device
+        if (!mIsSwitchDevice
+                && newPath == mService.getPhysicalAddress()
+                && mService.isPlaybackDevice()) {
+            setAndBroadcastActiveSource(message, newPath);
+        }
+        handleRoutingChangeAndInformation(newPath, message);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleRoutingInformation(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+        // if the current device is a pure playback device
+        if (!mIsSwitchDevice
+                && physicalAddress == mService.getPhysicalAddress()
+                && mService.isPlaybackDevice()) {
+            setAndBroadcastActiveSource(message, physicalAddress);
+        }
+        handleRoutingChangeAndInformation(physicalAddress, message);
+        return true;
+    }
+
+    // Method to switch Input with the new Active Path.
+    // All the devices with Switch functionality should implement this.
+    protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
+        // do nothing
+    }
+
+    // Source device with Switch functionality should implement this method.
+    // TODO(): decide which type will handle the routing when multi device type is supported
+    protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
+        // do nothing
+    }
+
+    // Active source claiming needs to be handled in the parent class
+    // since we decide who will be the active source when the device supports
+    // multiple device types in this method.
+    // This method should only be called when the device can be the active source.
+    protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) {
+        // If the device has both playback and audio system logical addresses,
+        // playback will claim active source. Otherwise audio system will.
+        HdmiCecLocalDevice deviceToBeActiveSource = mService.playback();
+        if (deviceToBeActiveSource == null) {
+            deviceToBeActiveSource = mService.audioSystem();
+        }
+        if (this == deviceToBeActiveSource) {
+            ActiveSource activeSource = ActiveSource.of(mAddress, physicalAddress);
+            setIsActiveSource(true);
+            setActiveSource(activeSource);
+            wakeUpIfActiveSource();
+            maySendActiveSource(message.getSource());
+        }
+    }
+
+    @ServiceThreadOnly
+    void setIsActiveSource(boolean on) {
+        assertRunOnServiceThread();
+        mIsActiveSource = on;
+    }
+
+    @ServiceThreadOnly
+    // Check if current device is the Active Source
+    boolean isActiveSource() {
+        assertRunOnServiceThread();
+        return mIsActiveSource;
+    }
+
+    protected void wakeUpIfActiveSource() {
+        if (!mIsActiveSource) {
+            return;
+        }
+        // Wake up the device if the power is in standby mode
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+        return;
+    }
+
+    protected void maySendActiveSource(int dest) {
+        if (mIsActiveSource) {
+            mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
+                    mAddress, mService.getPhysicalAddress()));
+        }
+    }
+
+    @VisibleForTesting
+    protected void setLocalActivePort(@LocalActivePort int portId) {
+        synchronized (mLock) {
+            mLocalActivePort = portId;
+        }
+    }
+
+    // To get the local active port to switch to
+    // when receivng routing change or information.
+    @LocalActivePort
+    protected int getLocalActivePort() {
+        synchronized (mLock) {
+            return mLocalActivePort;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 941c321..3f949ba 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -365,6 +365,20 @@
     }
 
     /**
+     * Build &lt;Routing Information&gt; command.
+     *
+     * <p>This is a broadcast message sent to all devices on the bus.
+     *
+     * @param src source address of command
+     * @param physicalAddress physical address of the new active routing path
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildRoutingInformation(int src, int physicalAddress) {
+        return buildCommand(src, Constants.ADDR_BROADCAST,
+            Constants.MESSAGE_ROUTING_INFORMATION, physicalAddressToParam(physicalAddress));
+    }
+
+    /**
      * Build &lt;Give Device Power Status&gt; command.
      *
      * @param src source address of command
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e3a4084..903045d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -140,6 +140,10 @@
     static final int STANDBY_SCREEN_OFF = 0;
     static final int STANDBY_SHUTDOWN = 1;
 
+    private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr =
+            SystemProperties.getBoolean(
+                    Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false);
+
     /**
      * Interface to report send result.
      */
@@ -413,7 +417,7 @@
         mSettingsObserver = new SettingsObserver(mHandler);
     }
 
-    private static List<Integer> getIntList(String string) {
+    protected static List<Integer> getIntList(String string) {
         ArrayList<Integer> list = new ArrayList<>();
         TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
         splitter.setString(string);
@@ -639,6 +643,10 @@
         // A container for [Device type, Local device info].
         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
         for (int type : mLocalDevices) {
+            if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
+                    && isHdmiCecNeverClaimPlaybackLogicAddr) {
+                continue;
+            }
             HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
             if (localDevice == null) {
                 localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1003,6 +1011,10 @@
         if (connected && !isTvDevice()) {
             ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
             for (int type : mLocalDevices) {
+                if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
+                        && isHdmiCecNeverClaimPlaybackLogicAddr) {
+                    continue;
+                }
                 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
                 if (localDevice == null) {
                     localDevice = HdmiCecLocalDevice.create(this, type);
@@ -1651,6 +1663,9 @@
                     }
                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
                     if (device == null) {
+                        device = audioSystem();
+                    }
+                    if (device == null) {
                         Slog.w(TAG, "Local device not available");
                         return;
                     }
@@ -1830,9 +1845,13 @@
     @ServiceThreadOnly
     private void oneTouchPlay(final IHdmiControlCallback callback) {
         assertRunOnServiceThread();
-        HdmiCecLocalDevicePlayback source = playback();
+        HdmiCecLocalDeviceSource source = playback();
         if (source == null) {
-            Slog.w(TAG, "Local playback device not available");
+            source = audioSystem();
+        }
+
+        if (source == null) {
+            Slog.w(TAG, "Local source device not available");
             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
             return;
         }
@@ -2095,11 +2114,15 @@
         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
     }
 
+    boolean isPlaybackDevice() {
+        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
+    }
+
     boolean isTvDeviceEnabled() {
         return isTvDevice() && tv() != null;
     }
 
-    private HdmiCecLocalDevicePlayback playback() {
+    protected HdmiCecLocalDevicePlayback playback() {
         return (HdmiCecLocalDevicePlayback)
                 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
     }
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 5c66316..46611dd 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -16,8 +16,8 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -55,7 +55,7 @@
     private int mPowerStatusCounter = 0;
 
     // Factory method. Ensures arguments are valid.
-    static OneTouchPlayAction create(HdmiCecLocalDevicePlayback source,
+    static OneTouchPlayAction create(HdmiCecLocalDeviceSource source,
             int targetAddress, IHdmiControlCallback callback) {
         if (source == null || callback == null) {
             Slog.e(TAG, "Wrong arguments");
@@ -84,8 +84,12 @@
 
     private void broadcastActiveSource() {
         sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath()));
-        // Because only playback device can create this action, it's safe to cast.
-        playback().setActiveSource(true);
+        // Because only source device can create this action, it's safe to cast.
+        HdmiCecLocalDeviceSource source = source();
+        source.setIsActiveSource(true);
+        source.setActiveSource(getSourceAddress(), getSourcePath());
+        // Set local active port to HOME when One Touch Play.
+        source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
     }
 
     private void queryDevicePowerStatus() {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
index 2fdcb51..a6e6965 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
@@ -16,6 +16,7 @@
 package com.android.server.hdmi;
 
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
@@ -91,7 +92,7 @@
                             mSendRequestActiveSourceRetryCount++;
                             sendRequestActiveSource();
                         } else {
-                            audioSystem().setSystemAudioMode(false);
+                            audioSystem().checkSupportAndSetSystemAudioMode(false);
                             finish();
                         }
                     }
@@ -106,7 +107,7 @@
                             mSendSetSystemAudioModeRetryCount++;
                             sendSetSystemAudioMode(on, dest);
                         } else {
-                            audioSystem().setSystemAudioMode(false);
+                            audioSystem().checkSupportAndSetSystemAudioMode(false);
                             finish();
                         }
                     }
@@ -115,7 +116,7 @@
 
     private void handleActiveSourceTimeout() {
         HdmiLogger.debug("Cannot get active source.");
-        audioSystem().setSystemAudioMode(false);
+        audioSystem().checkSupportAndSetSystemAudioMode(false);
         finish();
     }
 
@@ -123,12 +124,12 @@
         audioSystem().queryTvSystemAudioModeSupport(
                 supported -> {
                     if (supported) {
-                        if (audioSystem().setSystemAudioMode(true)) {
+                        if (audioSystem().checkSupportAndSetSystemAudioMode(true)) {
                             sendSetSystemAudioMode(true, Constants.ADDR_BROADCAST);
                         }
                         finish();
                     } else {
-                        audioSystem().setSystemAudioMode(false);
+                        audioSystem().checkSupportAndSetSystemAudioMode(false);
                         finish();
                     }
                 });
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index d96b6cb..8810d1d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -213,8 +213,6 @@
     private static native void nativeSetFocusedApplication(long ptr,
             int displayId, InputApplicationHandle application);
     private static native void nativeSetFocusedDisplay(long ptr, int displayId);
-    private static native boolean nativeTransferTouchFocus(long ptr,
-            InputChannel fromChannel, InputChannel toChannel);
     private static native void nativeSetPointerSpeed(long ptr, int speed);
     private static native void nativeSetShowTouches(long ptr, boolean enabled);
     private static native void nativeSetInteractive(long ptr, boolean interactive);
@@ -1485,29 +1483,6 @@
         nativeSetSystemUiVisibility(mPtr, visibility);
     }
 
-    /**
-     * Atomically transfers touch focus from one window to another as identified by
-     * their input channels.  It is possible for multiple windows to have
-     * touch focus if they support split touch dispatch
-     * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
-     * method only transfers touch focus of the specified window without affecting
-     * other windows that may also have touch focus at the same time.
-     * @param fromChannel The channel of a window that currently has touch focus.
-     * @param toChannel The channel of the window that should receive touch focus in
-     * place of the first.
-     * @return True if the transfer was successful.  False if the window with the
-     * specified channel did not actually have touch focus at the time of the request.
-     */
-    public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
-        if (fromChannel == null) {
-            throw new IllegalArgumentException("fromChannel must not be null.");
-        }
-        if (toChannel == null) {
-            throw new IllegalArgumentException("toChannel must not be null.");
-        }
-        return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
-    }
-
     @Override // Binder call
     public void tryPointerSpeed(int speed) {
         if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
@@ -1951,6 +1926,11 @@
     }
 
     // Native callback.
+    private int getPointerDisplayId() {
+        return mWindowManagerCallbacks.getPointerDisplayId();
+    }
+
+    // Native callback.
     private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
         if (!mSystemReady) {
             return null;
@@ -2017,6 +1997,8 @@
                 KeyEvent event, int policyFlags);
 
         public int getPointerLayer();
+
+        public int getPointerDisplayId();
     }
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 3264790..d4b8eb2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -16,7 +16,15 @@
 
 package com.android.server.inputmethod;
 
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
+import android.view.inputmethod.InputMethodInfo;
+
+import com.android.server.LocalServices;
+
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Input method manager local system service interface.
@@ -39,9 +47,25 @@
     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.
+     * @return A list of {@link InputMethodInfo}.  VR-only IMEs are already excluded.
+     */
+    public abstract List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
+
+    /**
+     * Returns the list of installed input methods that are enabled for the specified user.
+     *
+     * @param userId The user ID to be queried.
+     * @return A list of {@link InputMethodInfo} that are enabled for {@code userId}.
+     */
+    public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
+
+    /**
      * Fake implementation of {@link InputMethodManagerInternal}.  All the methods do nothing.
      */
-    public static final InputMethodManagerInternal NOP =
+    private static final InputMethodManagerInternal NOP =
             new InputMethodManagerInternal() {
                 @Override
                 public void setInteractive(boolean interactive) {
@@ -54,5 +78,25 @@
                 @Override
                 public void startVrInputMethodNoCheck(ComponentName componentName) {
                 }
+
+                @Override
+                public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
+                    return Collections.emptyList();
+                }
+
+                @Override
+                public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+                    return Collections.emptyList();
+                }
             };
+
+    /**
+     * @return Global instance if exists.  Otherwise, a dummy no-op instance.
+     */
+    @NonNull
+    public static InputMethodManagerInternal get() {
+        final InputMethodManagerInternal instance =
+                LocalServices.getService(InputMethodManagerInternal.class);
+        return instance != null ? instance : NOP;
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d45869e..5fa3f52 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.inputmethod.InputMethodSystemProperty.PER_PROFILE_IME_ENABLED;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -1603,7 +1604,7 @@
     // 1) it comes from the system process
     // 2) the calling process' user id is identical to the current user id IMMS thinks.
     @GuardedBy("mMethodMap")
-    private boolean calledFromValidUserLocked() {
+    private boolean calledFromValidUserLocked(boolean allowCrossProfileAccess) {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserId(uid);
         if (DEBUG) {
@@ -1613,7 +1614,13 @@
                     + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
                     + InputMethodUtils.getApiCallStack());
         }
-        if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
+        if (uid == Process.SYSTEM_UID) {
+            return true;
+        }
+        if (userId == mSettings.getCurrentUserId()) {
+            return true;
+        }
+        if (allowCrossProfileAccess && mSettings.isCurrentProfile(userId)) {
             return true;
         }
 
@@ -1674,40 +1681,93 @@
 
     @Override
     public List<InputMethodInfo> getInputMethodList() {
-        return getInputMethodList(false /* isVrOnly */);
+        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(false /* isVrOnly */, resolvedUserIds[0]);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
     }
 
     @Override
     public List<InputMethodInfo> getVrInputMethodList() {
-        return getInputMethodList(true /* isVrOnly */);
-    }
-
-    private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) {
+        final int callingUserId = UserHandle.getCallingUserId();
         synchronized (mMethodMap) {
-            // TODO: Make this work even for non-current users?
-            if (!calledFromValidUserLocked()) {
+            final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+                    mSettings.getCurrentUserId(), null);
+            if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
-            ArrayList<InputMethodInfo> methodList = new ArrayList<>();
-            for (InputMethodInfo info : mMethodList) {
-
-                if (info.isVrOnly() == isVrOnly) {
-                    methodList.add(info);
-                }
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
             }
-            return methodList;
         }
     }
 
     @Override
     public List<InputMethodInfo> getEnabledInputMethodList() {
+        final int callingUserId = UserHandle.getCallingUserId();
         synchronized (mMethodMap) {
-            // TODO: Make this work even for non-current users?
-            if (!calledFromValidUserLocked()) {
+            final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+                    mSettings.getCurrentUserId(), null);
+            if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return getEnabledInputMethodListLocked(resolvedUserIds[0]);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @GuardedBy("mMethodMap")
+    private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly,
+            @UserIdInt int userId) {
+        final ArrayList<InputMethodInfo> methodList;
+        if (userId == mSettings.getCurrentUserId()) {
+            // Create a copy.
+            methodList = new ArrayList<>(mMethodList);
+        } else {
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            methodList = new ArrayList<>();
+            final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+                    new ArrayMap<>();
+            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+            queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+                    methodList);
+        }
+        methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly);
+        return methodList;
+    }
+
+    @GuardedBy("mMethodMap")
+    private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) {
+        if (userId == mSettings.getCurrentUserId()) {
             return mSettings.getEnabledInputMethodListLocked();
         }
+        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+        final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+                new ArrayMap<>();
+        AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+        queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+                methodList);
+        final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
+                mContext.getContentResolver(), methodMap, methodList, userId, true);
+        return settings.getEnabledInputMethodListLocked();
     }
 
     /**
@@ -1717,11 +1777,27 @@
     @Override
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
             boolean allowsImplicitlySelectedSubtypes) {
+        final int callingUserId = UserHandle.getCallingUserId();
         synchronized (mMethodMap) {
-            // TODO: Make this work even for non-current users?
-            if (!calledFromValidUserLocked()) {
+            final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
+                    mSettings.getCurrentUserId(), null);
+            if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return getEnabledInputMethodSubtypeListLocked(imiId,
+                        allowsImplicitlySelectedSubtypes, resolvedUserIds[0]);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @GuardedBy("mMethodMap")
+    private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
+            boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) {
+        if (userId == mSettings.getCurrentUserId()) {
             final InputMethodInfo imi;
             if (imiId == null && mCurMethodId != null) {
                 imi = mMethodMap.get(mCurMethodId);
@@ -1734,6 +1810,21 @@
             return mSettings.getEnabledInputMethodSubtypeListLocked(
                     mContext, imi, allowsImplicitlySelectedSubtypes);
         }
+        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+        final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+                new ArrayMap<>();
+        AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+        queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+                methodList);
+        final InputMethodInfo imi = methodMap.get(imiId);
+        if (imi == null) {
+            return Collections.emptyList();
+        }
+        final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
+                mContext.getContentResolver(), methodMap, methodList, userId, true);
+        return settings.getEnabledInputMethodSubtypeListLocked(
+                mContext, imi, allowsImplicitlySelectedSubtypes);
     }
 
     /**
@@ -2434,7 +2525,7 @@
     @Override
     public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return;
             }
             final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
@@ -2450,7 +2541,7 @@
     @Override
     public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return false;
             }
             final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
@@ -2617,7 +2708,7 @@
             ResultReceiver resultReceiver) {
         int uid = Binder.getCallingUid();
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -2702,7 +2793,7 @@
             ResultReceiver resultReceiver) {
         int uid = Binder.getCallingUid();
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -2809,10 +2900,12 @@
             @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
             IInputContext inputContext, @MissingMethodFlags int missingMethods,
             int unverifiedTargetSdkVersion) {
+        final int userId = UserHandle.getUserId(Binder.getCallingUid());
         InputBindResult res = null;
         synchronized (mMethodMap) {
             // Needs to check the validity before clearing calling identity
-            final boolean calledFromValidUser = calledFromValidUserLocked();
+            // Note that cross-profile access is always allowed here to allow profile-switching.
+            final boolean calledFromValidUser = calledFromValidUserLocked(true);
             final int windowDisplayId =
                     mWindowManagerInternal.getDisplayIdForWindow(windowToken);
             final long ident = Binder.clearCallingIdentity();
@@ -2864,6 +2957,10 @@
                     return InputBindResult.INVALID_USER;
                 }
 
+                if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) {
+                    switchUserLocked(userId);
+                }
+
                 if (mCurFocusedWindow == windowToken) {
                     if (DEBUG) {
                         Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
@@ -3032,7 +3129,7 @@
     public void showInputMethodPickerFromClient(
             IInputMethodClient client, int auxiliarySubtypeMode) {
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return;
             }
             if(!canShowInputMethodPickerLocked(client)) {
@@ -3103,7 +3200,7 @@
             IInputMethodClient client, String inputMethodId) {
         synchronized (mMethodMap) {
             // TODO(yukawa): Should we verify the display ID?
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return;
             }
             executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
@@ -3218,7 +3315,7 @@
     @Override
     public InputMethodSubtype getLastInputMethodSubtype() {
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return null;
             }
             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
@@ -3256,7 +3353,7 @@
             }
         }
         synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return;
             }
             if (!mSystemReady) {
@@ -4102,7 +4199,7 @@
     public InputMethodSubtype getCurrentInputMethodSubtype() {
         synchronized (mMethodMap) {
             // TODO: Make this work even for non-current users?
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return null;
             }
             return getCurrentInputMethodSubtypeLocked();
@@ -4152,7 +4249,7 @@
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
         synchronized (mMethodMap) {
             // TODO: Make this work even for non-current users?
-            if (!calledFromValidUserLocked()) {
+            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
                 return false;
             }
             if (subtype != null && mCurMethodId != null) {
@@ -4167,6 +4264,18 @@
         }
     }
 
+    private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
+        synchronized (mMethodMap) {
+            return getInputMethodListLocked(false, userId);
+        }
+    }
+
+    private List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
+        synchronized (mMethodMap) {
+            return getEnabledInputMethodListLocked(userId);
+        }
+    }
+
     private static final class LocalServiceImpl extends InputMethodManagerInternal {
         @NonNull
         private final InputMethodManagerService mService;
@@ -4192,6 +4301,16 @@
         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);
+        }
+
+        @Override
+        public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+            return mService.getEnabledInputMethodListAsUser(userId);
+        }
     }
 
     @BinderThread
@@ -4545,6 +4664,7 @@
     private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) {
         boolean all = false;
         boolean brief = false;
+        int userIdToBeResolved = UserHandle.USER_CURRENT;
         while (true) {
             final String nextOption = shellCommand.getNextOption();
             if (nextOption == null) {
@@ -4557,19 +4677,34 @@
                 case "-s":
                     brief = true;
                     break;
+                case "-u":
+                case "--user":
+                    userIdToBeResolved = UserHandle.parseUserArg(shellCommand.getNextArgRequired());
+                    break;
             }
         }
-        final List<InputMethodInfo> methods = all ?
-                getInputMethodList() : getEnabledInputMethodList();
-        final PrintWriter pr = shellCommand.getOutPrintWriter();
-        final Printer printer = x -> pr.println(x);
-        final int N = methods.size();
-        for (int i = 0; i < N; ++i) {
-            if (brief) {
-                pr.println(methods.get(i).getId());
-            } else {
-                pr.print(methods.get(i).getId()); pr.println(":");
-                methods.get(i).dump(printer, "  ");
+        synchronized (mMethodMap) {
+            final PrintWriter pr = shellCommand.getOutPrintWriter();
+            final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
+                    mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+            for (int userId : userIds) {
+                final List<InputMethodInfo> methods = all
+                        ? getInputMethodListLocked(false, userId)
+                        : getEnabledInputMethodListLocked(userId);
+                if (userIds.length > 1) {
+                    pr.print("User #");
+                    pr.print(userId);
+                    pr.println(":");
+                }
+                for (InputMethodInfo info : methods) {
+                    if (brief) {
+                        pr.println(info.getId());
+                    } else {
+                        pr.print(info.getId());
+                        pr.println(":");
+                        info.dump(pr::println, "  ");
+                    }
+                }
             }
         }
         return ShellCommandResult.SUCCESS;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 1137bf9..88d1a9c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -29,21 +29,27 @@
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.Pair;
 import android.util.Printer;
 import android.util.Slog;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSystemProperty;
 import android.view.textservice.SpellCheckerInfo;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.StartInputFlags;
+import com.android.server.LocalServices;
 import com.android.server.textservices.TextServicesManagerInternal;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedHashSet;
@@ -1286,4 +1292,62 @@
         return true;
     }
 
+    /**
+     * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a
+     * list of real user IDs.
+     *
+     * <p>This method also converts profile user ID to profile parent user ID unless
+     * {@link InputMethodSystemProperty#PER_PROFILE_IME_ENABLED} is {@code true}.</p>
+     *
+     * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and
+     *                           {@link UserHandle#USER_ALL} are also supported
+     * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT}
+     *                      is specified in {@code userIdToBeResolved}.
+     * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if
+     *                      no debug message is required.
+     * @return An integer array that contain user IDs.
+     */
+    static int[] resolveUserId(@UserIdInt int userIdToBeResolved,
+            @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) {
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+
+        if (userIdToBeResolved == UserHandle.USER_ALL) {
+            if (InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
+                return userManagerInternal.getUserIds();
+            }
+            final IntArray result = new IntArray();
+            for (int userId : userManagerInternal.getUserIds()) {
+                final int parentUserId = userManagerInternal.getProfileParentId(userId);
+                if (result.indexOf(parentUserId) < 0) {
+                    result.add(parentUserId);
+                }
+            }
+            return result.toArray();
+        }
+
+        final int sourceUserId;
+        if (userIdToBeResolved == UserHandle.USER_CURRENT) {
+            sourceUserId = currentUserId;
+        } else if (userIdToBeResolved < 0) {
+            if (warningWriter != null) {
+                warningWriter.print("Pseudo user ID ");
+                warningWriter.print(userIdToBeResolved);
+                warningWriter.println(" is not supported.");
+            }
+            return new int[]{};
+        } else if (userManagerInternal.exists(userIdToBeResolved)) {
+            sourceUserId = userIdToBeResolved;
+        } else {
+            if (warningWriter != null) {
+                warningWriter.print("User #");
+                warningWriter.print(userIdToBeResolved);
+                warningWriter.println(" does not exit.");
+            }
+            return new int[]{};
+        }
+        final int resolvedUserId = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
+                ? sourceUserId : userManagerInternal.getProfileParentId(sourceUserId);
+        return new int[]{resolvedUserId};
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 98ed3ea..f304ceb 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -163,6 +163,18 @@
                         public void startVrInputMethodNoCheck(ComponentName componentName) {
                             reportNotSupported();
                         }
+
+                        @Override
+                        public List<InputMethodInfo> getInputMethodListAsUser(
+                                @UserIdInt int userId) {
+                            return userIdToInputMethodInfoMapper.getAsList(userId);
+                        }
+
+                        @Override
+                        public List<InputMethodInfo> getEnabledInputMethodListAsUser(
+                                @UserIdInt int userId) {
+                            return userIdToInputMethodInfoMapper.getAsList(userId);
+                        }
                     });
         }
 
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index 4c7c420..b3f1018 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -29,7 +29,7 @@
 import java.util.List;
 
 /**
- * Location Manager's interface for location providers.
+ * Location Manager's interface for location providers. Always starts as disabled.
  *
  * @hide
  */
@@ -41,12 +41,6 @@
     public interface LocationProviderManager {
 
         /**
-         * Called on location provider construction to make the location service aware of this
-         * provider and what it's initial enabled/disabled state should be.
-         */
-        void onAttachProvider(AbstractLocationProvider locationProvider, boolean initiallyEnabled);
-
-        /**
          * May be called to inform the location service of a change in this location provider's
          * enabled/disabled state.
          */
@@ -74,13 +68,7 @@
     private final LocationProviderManager mLocationProviderManager;
 
     protected AbstractLocationProvider(LocationProviderManager locationProviderManager) {
-        this(locationProviderManager, true);
-    }
-
-    protected AbstractLocationProvider(LocationProviderManager locationProviderManager,
-            boolean initiallyEnabled) {
         mLocationProviderManager = locationProviderManager;
-        mLocationProviderManager.onAttachProvider(this, initiallyEnabled);
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/GnssConfiguration.java b/services/core/java/com/android/server/location/GnssConfiguration.java
new file mode 100644
index 0000000..29465ad
--- /dev/null
+++ b/services/core/java/com/android/server/location/GnssConfiguration.java
@@ -0,0 +1,365 @@
+/*
+ * 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.location;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.os.SystemProperties;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+/**
+ * A utility class to hold GNSS configuration properties.
+ *
+ * The trigger to load/reload the configuration parameters should be managed by the class
+ * that owns an instance of this class.
+ *
+ * Instances of this class are not thread-safe and should either be used from a single thread
+ * or with external synchronization when used by multiple threads.
+ */
+class GnssConfiguration {
+    private static final String TAG = "GnssConfiguration";
+
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    //TODO(b/33112647): Create gps_debug.conf with commented career parameters.
+    private static final String DEBUG_PROPERTIES_FILE = "/etc/gps_debug.conf";
+
+    // config.xml properties
+    private static final String CONFIG_SUPL_HOST = "SUPL_HOST";
+    private static final String CONFIG_SUPL_PORT = "SUPL_PORT";
+    private static final String CONFIG_C2K_HOST = "C2K_HOST";
+    private static final String CONFIG_C2K_PORT = "C2K_PORT";
+    private static final String CONFIG_SUPL_VER = "SUPL_VER";
+    private static final String CONFIG_SUPL_MODE = "SUPL_MODE";
+    private static final String CONFIG_SUPL_ES = "SUPL_ES";
+    private static final String CONFIG_LPP_PROFILE = "LPP_PROFILE";
+    private static final String CONFIG_A_GLONASS_POS_PROTOCOL_SELECT =
+            "A_GLONASS_POS_PROTOCOL_SELECT";
+    private static final String CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL =
+            "USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL";
+    private static final String CONFIG_GPS_LOCK = "GPS_LOCK";
+    private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC";
+
+    // Limit on NI emergency mode time extension after emergency sessions ends
+    private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300;  // 5 minute maximum
+
+    // Persist property for LPP_PROFILE
+    static final String LPP_PROFILE = "persist.sys.gps.lpp";
+
+    // Represents an HAL interface version. Instances of this class are created in the JNI layer
+    // and returned through native methods.
+    private static class HalInterfaceVersion {
+        final int mMajor;
+        final int mMinor;
+
+        HalInterfaceVersion(int major, int minor) {
+            mMajor = major;
+            mMinor = minor;
+        }
+    }
+
+    /**
+     * Properties loaded from PROPERTIES_FILE.
+     */
+    private Properties mProperties;
+
+    private int mEsExtensionSec = 0;
+
+    private final Context mContext;
+
+    GnssConfiguration(Context context) {
+        mContext = context;
+        mProperties = new Properties();
+    }
+
+    /**
+     * Returns the full set of properties loaded.
+     */
+    Properties getProperties() {
+        return mProperties;
+    }
+
+    /**
+     * Returns the value of config parameter ES_EXTENSION_SEC. The value is range checked
+     * and constrained to min/max limits.
+     */
+    int getEsExtensionSec() {
+        return mEsExtensionSec;
+    }
+
+    /**
+     * Returns the value of config parameter SUPL_HOST or {@code null} if no value is
+     * provided.
+     */
+    String getSuplHost() {
+        return mProperties.getProperty(CONFIG_SUPL_HOST);
+    }
+
+    /**
+     * Returns the value of config parameter SUPL_PORT or {@code defaultPort} if no value is
+     * provided or if there is an error parsing the configured value.
+     */
+    int getSuplPort(int defaultPort) {
+        return getIntConfig(CONFIG_SUPL_PORT, defaultPort);
+    }
+
+    /**
+     * Returns the value of config parameter C2K_HOST or {@code null} if no value is
+     * provided.
+     */
+    String getC2KHost() {
+        return mProperties.getProperty(CONFIG_C2K_HOST);
+    }
+
+    /**
+     * Returns the value of config parameter C2K_PORT or {@code defaultPort} if no value is
+     * provided or if there is an error parsing the configured value.
+     */
+    int getC2KPort(int defaultPort) {
+        return getIntConfig(CONFIG_C2K_PORT, defaultPort);
+    }
+
+    /**
+     * Returns the value of config parameter SUPL_MODE or {@code defaultMode} if no value is
+     * provided or if there is an error parsing the configured value.
+     */
+    int getSuplMode(int defaultMode) {
+        return getIntConfig(CONFIG_SUPL_MODE, defaultMode);
+    }
+
+    /**
+     * Returns the value of config parameter SUPL_ES or {@code defaultSuplEs} if no value is
+     * provided or if there is an error parsing the configured value.
+     */
+    int getSuplEs(int defaulSuplEs) {
+        return getIntConfig(CONFIG_SUPL_ES, defaulSuplEs);
+    }
+
+    /**
+     * Returns the value of config parameter LPP_PROFILE or {@code null} if no value is
+     * provided.
+     */
+    String getLppProfile() {
+        return mProperties.getProperty(CONFIG_LPP_PROFILE);
+    }
+
+    /**
+     * Updates the GNSS HAL satellite blacklist.
+     */
+    void setSatelliteBlacklist(int[] constellations, int[] svids) {
+        native_set_satellite_blacklist(constellations, svids);
+    }
+
+    interface SetCarrierProperty {
+        boolean set(int value);
+    }
+
+    /**
+     * Loads the GNSS properties from carrier config file followed by the properties from
+     * gps debug config file.
+     */
+    void reloadGpsProperties() {
+        if (DEBUG) Log.d(TAG, "Reset GPS properties, previous size = " + mProperties.size());
+        loadPropertiesFromCarrierConfig();
+
+        String lpp_prof = SystemProperties.get(LPP_PROFILE);
+        if (!TextUtils.isEmpty(lpp_prof)) {
+            // override default value of this if lpp_prof is not empty
+            mProperties.setProperty(CONFIG_LPP_PROFILE, lpp_prof);
+        }
+        /*
+         * Overlay carrier properties from a debug configuration file.
+         */
+        loadPropertiesFromGpsDebugConfig(mProperties);
+
+        mEsExtensionSec = getRangeCheckedConfigEsExtensionSec();
+
+        final HalInterfaceVersion gnssConfigurationIfaceVersion =
+                native_get_gnss_configuration_version();
+        if (gnssConfigurationIfaceVersion != null) {
+            // Set to a range checked value.
+            if (isConfigEsExtensionSecSupported(gnssConfigurationIfaceVersion)
+                    && !native_set_es_extension_sec(mEsExtensionSec)) {
+                Log.e(TAG, "Unable to set " + CONFIG_ES_EXTENSION_SEC + ": " + mEsExtensionSec);
+            }
+
+            Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
+                {
+                    put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
+                    put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
+
+                    if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
+                        put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
+                    }
+
+                    put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
+                    put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
+                            GnssConfiguration::native_set_gnss_pos_protocol_select);
+                    put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
+                            GnssConfiguration::native_set_emergency_supl_pdn);
+
+                    if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
+                        put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
+                    }
+                }
+            };
+
+            for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
+                String propertyName = entry.getKey();
+                String propertyValueString = mProperties.getProperty(propertyName);
+                if (propertyValueString != null) {
+                    try {
+                        int propertyValueInt = Integer.decode(propertyValueString);
+                        boolean result = entry.getValue().set(propertyValueInt);
+                        if (!result) {
+                            Log.e(TAG, "Unable to set " + propertyName);
+                        }
+                    } catch (NumberFormatException e) {
+                        Log.e(TAG, "Unable to parse propertyName: " + propertyValueString);
+                    }
+                }
+            }
+        } else if (DEBUG) {
+            Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not"
+                    + " supported");
+        }
+    }
+
+    /**
+     * Loads GNSS properties from carrier config file.
+     */
+    void loadPropertiesFromCarrierConfig() {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager == null) {
+            return;
+        }
+        PersistableBundle configs = configManager.getConfigForSubId(
+                SubscriptionManager.getDefaultDataSubscriptionId());
+        if (configs == null) {
+            if (DEBUG) Log.d(TAG, "SIM not ready, use default carrier config.");
+            configs = CarrierConfigManager.getDefaultConfig();
+        }
+        for (String configKey : configs.keySet()) {
+            if (configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) {
+                String key = configKey
+                        .substring(CarrierConfigManager.Gps.KEY_PREFIX.length())
+                        .toUpperCase();
+                Object value = configs.get(configKey);
+                if (value instanceof String) {
+                    // All GPS properties are of String type; convert so.
+                    if (DEBUG) Log.d(TAG, "Gps config: " + key + " = " + value);
+                    mProperties.setProperty(key, (String) value);
+                }
+            }
+        }
+    }
+
+    private void loadPropertiesFromGpsDebugConfig(Properties properties) {
+        try {
+            File file = new File(DEBUG_PROPERTIES_FILE);
+            FileInputStream stream = null;
+            try {
+                stream = new FileInputStream(file);
+                properties.load(stream);
+            } finally {
+                IoUtils.closeQuietly(stream);
+            }
+        } catch (IOException e) {
+            if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + DEBUG_PROPERTIES_FILE);
+        }
+    }
+
+    private int getRangeCheckedConfigEsExtensionSec() {
+        int emergencyExtensionSeconds = getIntConfig(CONFIG_ES_EXTENSION_SEC, 0);
+        if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
+            Log.w(TAG, CONFIG_ES_EXTENSION_SEC + ": " + emergencyExtensionSeconds
+                    + " too high, reset to " + MAX_EMERGENCY_MODE_EXTENSION_SECONDS);
+            emergencyExtensionSeconds = MAX_EMERGENCY_MODE_EXTENSION_SECONDS;
+        } else if (emergencyExtensionSeconds < 0) {
+            Log.w(TAG, CONFIG_ES_EXTENSION_SEC + ": " + emergencyExtensionSeconds
+                    + " is negative, reset to zero.");
+            emergencyExtensionSeconds = 0;
+        }
+        return emergencyExtensionSeconds;
+    }
+
+    private int getIntConfig(String configParameter, int defaultValue) {
+        String valueString = mProperties.getProperty(configParameter);
+        if (TextUtils.isEmpty(valueString)) {
+            return defaultValue;
+        }
+        try {
+            return Integer.parseInt(valueString);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "Unable to parse config parameter " + configParameter + " value: "
+                    + valueString + ". Using default value: " + defaultValue);
+            return defaultValue;
+        }
+    }
+
+    private static boolean isConfigEsExtensionSecSupported(
+            HalInterfaceVersion gnssConfiguartionIfaceVersion) {
+        // ES_EXTENSION_SEC is introduced in @2.0::IGnssConfiguration.hal
+        return gnssConfiguartionIfaceVersion.mMajor >= 2;
+    }
+
+    private static boolean isConfigSuplEsSupported(
+            HalInterfaceVersion gnssConfiguartionIfaceVersion) {
+        // SUPL_ES is deprecated in @2.0::IGnssConfiguration.hal
+        return gnssConfiguartionIfaceVersion.mMajor < 2;
+    }
+
+    private static boolean isConfigGpsLockSupported(
+            HalInterfaceVersion gnssConfiguartionIfaceVersion) {
+        // GPS_LOCK is deprecated in @2.0::IGnssConfiguration.hal
+        return gnssConfiguartionIfaceVersion.mMajor < 2;
+    }
+
+    private static native HalInterfaceVersion native_get_gnss_configuration_version();
+
+    private static native boolean native_set_supl_version(int version);
+
+    private static native boolean native_set_supl_mode(int mode);
+
+    private static native boolean native_set_supl_es(int es);
+
+    private static native boolean native_set_lpp_profile(int lppProfile);
+
+    private static native boolean native_set_gnss_pos_protocol_select(int gnssPosProtocolSelect);
+
+    private static native boolean native_set_gps_lock(int gpsLock);
+
+    private static native boolean native_set_emergency_supl_pdn(int emergencySuplPdn);
+
+    private static native boolean native_set_satellite_blacklist(int[] constellations, int[] svIds);
+
+    private static native boolean native_set_es_extension_sec(int emergencyExtensionSeconds);
+}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 05d77ab..269767a 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -74,12 +74,7 @@
 import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
 import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
 
-import libcore.io.IoUtils;
-
-import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -87,11 +82,7 @@
 import java.lang.annotation.Target;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
 
 /**
  * A GNSS implementation of LocationProvider used by LocationManager.
@@ -147,7 +138,6 @@
     private static final int LOCATION_HAS_SPEED_ACCURACY = 64;
     private static final int LOCATION_HAS_BEARING_ACCURACY = 128;
 
-
     // IMPORTANT - the GPS_DELETE_* symbols here must match GnssAidingData enum in IGnss.hal
     private static final int GPS_DELETE_EPHEMERIS = 0x0001;
     private static final int GPS_DELETE_ALMANAC = 0x0002;
@@ -200,9 +190,6 @@
     private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1;
     private static final int AGPS_RIL_REQUEST_SETID_MSISDN = 2;
 
-    //TODO(b/33112647): Create gps_debug.conf with commented career parameters.
-    private static final String DEBUG_PROPERTIES_FILE = "/etc/gps_debug.conf";
-
     // ref. location info
     private static final int AGPS_REF_LOCATION_TYPE_GSM_CELLID = 1;
     private static final int AGPS_REF_LOCATION_TYPE_UMTS_CELLID = 2;
@@ -375,7 +362,7 @@
      * Properties loaded from PROPERTIES_FILE.
      * It must be accessed only inside {@link #mHandler}.
      */
-    private Properties mProperties;
+    private GnssConfiguration mGnssConfiguration;
 
     private String mSuplServerHost;
     private int mSuplServerPort = TCP_MIN_PORT;
@@ -411,10 +398,6 @@
     private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP";
     private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT";
 
-    // Persist property for LPP_PROFILE
-    private final static String LPP_PROFILE = "persist.sys.gps.lpp";
-
-
     private final PowerManager mPowerManager;
     private final AlarmManager mAlarmManager;
     private final PendingIntent mWakeupIntent;
@@ -502,7 +485,7 @@
      */
     @Override
     public void onUpdateSatelliteBlacklist(int[] constellations, int[] svids) {
-        mHandler.post(() -> native_set_satellite_blacklist(constellations, svids));
+        mHandler.post(() -> mGnssConfiguration.setSatelliteBlacklist(constellations, svids));
     }
 
     private void subscriptionOrCarrierConfigChanged(Context context) {
@@ -525,17 +508,17 @@
                 }
                 if (isKeepLppProfile) {
                     // load current properties for the carrier
-                    loadPropertiesFromCarrierConfig(context, mProperties);
-                    String lpp_profile = mProperties.getProperty("LPP_PROFILE");
+                    mGnssConfiguration.loadPropertiesFromCarrierConfig();
+                    String lpp_profile = mGnssConfiguration.getLppProfile();
                     // set the persist property LPP_PROFILE for the value
                     if (lpp_profile != null) {
-                        SystemProperties.set(LPP_PROFILE, lpp_profile);
+                        SystemProperties.set(GnssConfiguration.LPP_PROFILE, lpp_profile);
                     }
                 } else {
                     // reset the persist property
-                    SystemProperties.set(LPP_PROFILE, "");
+                    SystemProperties.set(GnssConfiguration.LPP_PROFILE, "");
                 }
-                reloadGpsProperties(context, mProperties);
+                reloadGpsProperties();
                 mNIHandler.setSuplEsEnabled(mSuplEsEnabled);
             }
         } else {
@@ -564,138 +547,19 @@
         return native_is_supported();
     }
 
-    interface SetCarrierProperty {
-        boolean set(int value);
-    }
-
-    private void reloadGpsProperties(Context context, Properties properties) {
-        if (DEBUG) Log.d(TAG, "Reset GPS properties, previous size = " + properties.size());
-        loadPropertiesFromCarrierConfig(context, properties);
-
-        String lpp_prof = SystemProperties.get(LPP_PROFILE);
-        if (!TextUtils.isEmpty(lpp_prof)) {
-            // override default value of this if lpp_prof is not empty
-            properties.setProperty("LPP_PROFILE", lpp_prof);
-        }
-        /*
-         * Overlay carrier properties from a debug configuration file.
-         */
-        loadPropertiesFromGpsDebugConfig(properties);
+    private void reloadGpsProperties() {
+        mGnssConfiguration.reloadGpsProperties();
+        setSuplHostPort();
         // TODO: we should get rid of C2K specific setting.
-        setSuplHostPort(properties.getProperty("SUPL_HOST"),
-                properties.getProperty("SUPL_PORT"));
-        mC2KServerHost = properties.getProperty("C2K_HOST");
-        String portString = properties.getProperty("C2K_PORT");
-        if (mC2KServerHost != null && portString != null) {
-            try {
-                mC2KServerPort = Integer.parseInt(portString);
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "unable to parse C2K_PORT: " + portString);
-            }
-        }
-        if (native_is_gnss_configuration_supported()) {
-            Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
-                {
-                    put("SUPL_VER", GnssLocationProvider::native_set_supl_version);
-                    put("SUPL_MODE", GnssLocationProvider::native_set_supl_mode);
-                    put("SUPL_ES", GnssLocationProvider::native_set_supl_es);
-                    put("LPP_PROFILE", GnssLocationProvider::native_set_lpp_profile);
-                    put("A_GLONASS_POS_PROTOCOL_SELECT",
-                            GnssLocationProvider::native_set_gnss_pos_protocol_select);
-                    put("USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL",
-                            GnssLocationProvider::native_set_emergency_supl_pdn);
-                    put("GPS_LOCK", GnssLocationProvider::native_set_gps_lock);
-                }
-            };
-
-            for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
-                String propertyName = entry.getKey();
-                String propertyValueString = properties.getProperty(propertyName);
-                if (propertyValueString != null) {
-                    try {
-                        int propertyValueInt = Integer.decode(propertyValueString);
-                        boolean result = entry.getValue().set(propertyValueInt);
-                        if (!result) {
-                            Log.e(TAG, "Unable to set " + propertyName);
-                        }
-                    } catch (NumberFormatException e) {
-                        Log.e(TAG, "unable to parse propertyName: " + propertyValueString);
-                    }
-                }
-            }
-        } else if (DEBUG) {
-            Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not"
-                    + " supported");
-        }
-
-        // SUPL_ES configuration.
-        String suplESProperty = mProperties.getProperty("SUPL_ES");
-        if (suplESProperty != null) {
-            try {
-                mSuplEsEnabled = (Integer.parseInt(suplESProperty) == 1);
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "unable to parse SUPL_ES: " + suplESProperty);
-            }
-        }
-
-        String emergencyExtensionSecondsString
-                = properties.getProperty("ES_EXTENSION_SEC", "0");
-        try {
-            int emergencyExtensionSeconds =
-                    Integer.parseInt(emergencyExtensionSecondsString);
-            mNIHandler.setEmergencyExtensionSeconds(emergencyExtensionSeconds);
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "unable to parse ES_EXTENSION_SEC: "
-                    + emergencyExtensionSecondsString);
-        }
-    }
-
-    private void loadPropertiesFromCarrierConfig(Context context, Properties properties) {
-        CarrierConfigManager configManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager == null) {
-            return;
-        }
-        PersistableBundle configs = configManager.getConfigForSubId(
-                SubscriptionManager.getDefaultDataSubscriptionId());
-        if (configs == null) {
-            if (DEBUG) Log.d(TAG, "SIM not ready, use default carrier config.");
-            configs = CarrierConfigManager.getDefaultConfig();
-        }
-        for (String configKey : configs.keySet()) {
-            if (configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) {
-                String key = configKey
-                        .substring(CarrierConfigManager.Gps.KEY_PREFIX.length())
-                        .toUpperCase();
-                Object value = configs.get(configKey);
-                if (value instanceof String) {
-                    // All GPS properties are of String type; convert so.
-                    if (DEBUG) Log.d(TAG, "Gps config: " + key + " = " + value);
-                    properties.setProperty(key, (String) value);
-                }
-            }
-        }
-    }
-
-    private void loadPropertiesFromGpsDebugConfig(Properties properties) {
-        try {
-            File file = new File(DEBUG_PROPERTIES_FILE);
-            FileInputStream stream = null;
-            try {
-                stream = new FileInputStream(file);
-                properties.load(stream);
-            } finally {
-                IoUtils.closeQuietly(stream);
-            }
-
-        } catch (IOException e) {
-            if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + DEBUG_PROPERTIES_FILE);
-        }
+        mC2KServerHost = mGnssConfiguration.getC2KHost();
+        mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT);
+        mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec());
+        mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1;
     }
 
     public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
             Looper looper) {
-        super(locationProviderManager, true);
+        super(locationProviderManager);
 
         mContext = context;
 
@@ -733,7 +597,7 @@
         // relative long time, so the ctor() is kept to create objects needed by this instance,
         // while IO initialization and registration is delegated to our internal handler
         // this approach is just fine because events are posted to our handler anyway
-        mProperties = new Properties();
+        mGnssConfiguration = new GnssConfiguration(mContext);
         sendMessage(INITIALIZE_HANDLER, 0, null);
 
         // Create a GPS net-initiated handler.
@@ -788,6 +652,7 @@
         }, UserHandle.ALL, intentFilter, null, mHandler);
 
         setProperties(PROPERTIES);
+        setEnabled(true);
     }
 
     /**
@@ -918,7 +783,8 @@
         mDownloadXtraWakeLock.acquire(DOWNLOAD_XTRA_DATA_TIMEOUT_MS);
         Log.i(TAG, "WakeLock acquired by handleDownloadXtraData()");
         AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
-            GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mProperties);
+            GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(
+                    mGnssConfiguration.getProperties());
             byte[] data = xtraDownloader.downloadXtraData();
             if (data != null) {
                 if (DEBUG) Log.d(TAG, "calling native_inject_xtra_data");
@@ -963,17 +829,9 @@
         }
     }
 
-    private void setSuplHostPort(String hostString, String portString) {
-        if (hostString != null) {
-            mSuplServerHost = hostString;
-        }
-        if (portString != null) {
-            try {
-                mSuplServerPort = Integer.parseInt(portString);
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
-            }
-        }
+    private void setSuplHostPort() {
+        mSuplServerHost = mGnssConfiguration.getSuplHost();
+        mSuplServerPort = mGnssConfiguration.getSuplPort(TCP_MIN_PORT);
         if (mSuplServerHost != null
                 && mSuplServerPort > TCP_MIN_PORT
                 && mSuplServerPort <= TCP_MAX_PORT) {
@@ -986,23 +844,17 @@
      * Checks what SUPL mode to use, according to the AGPS mode as well as the
      * allowed mode from properties.
      *
-     * @param properties  GPS properties
      * @param agpsEnabled whether AGPS is enabled by settings value
      * @param singleShot  whether "singleshot" is needed
      * @return SUPL mode (MSA vs MSB vs STANDALONE)
      */
-    private int getSuplMode(Properties properties, boolean agpsEnabled, boolean singleShot) {
+    private int getSuplMode(boolean agpsEnabled, boolean singleShot) {
         if (agpsEnabled) {
-            String modeString = properties.getProperty("SUPL_MODE");
-            int suplMode = 0;
-            if (!TextUtils.isEmpty(modeString)) {
-                try {
-                    suplMode = Integer.parseInt(modeString);
-                } catch (NumberFormatException e) {
-                    Log.e(TAG, "unable to parse SUPL_MODE: " + modeString);
-                    return GPS_POSITION_MODE_STANDALONE;
-                }
+            int suplMode = mGnssConfiguration.getSuplMode(0);
+            if (suplMode == 0) {
+                return GPS_POSITION_MODE_STANDALONE;
             }
+
             // MS-Based is the preferred mode for Assisted-GPS position computation, so we favor
             // such mode when it is available
             if (hasCapability(GPS_CAPABILITY_MSB) && (suplMode & AGPS_SUPL_MODE_MSB) != 0) {
@@ -1307,7 +1159,7 @@
             boolean agpsEnabled =
                     (Settings.Global.getInt(mContext.getContentResolver(),
                             Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0);
-            mPositionMode = getSuplMode(mProperties, agpsEnabled, singleShot);
+            mPositionMode = getSuplMode(agpsEnabled, singleShot);
 
             if (DEBUG) {
                 String mode;
@@ -1670,7 +1522,7 @@
                 // re-calls native_init() and other setup.
                 handleEnable();
                 // resend configuration into the restarted HAL service.
-                reloadGpsProperties(mContext, mProperties);
+                reloadGpsProperties();
             }
         });
     }
@@ -2072,7 +1924,7 @@
 
             // load default GPS configuration
             // (this configuration might change in the future based on SIM changes)
-            reloadGpsProperties(mContext, mProperties);
+            reloadGpsProperties();
 
             // TODO: When this object "finishes" we should unregister by invoking
             // SubscriptionManager.getInstance(mContext).unregister
@@ -2227,8 +2079,6 @@
 
     private static native boolean native_is_supported();
 
-    private static native boolean native_is_gnss_configuration_supported();
-
     private static native void native_init_once();
 
     private native boolean native_init();
@@ -2284,21 +2134,4 @@
             int lac, int cid);
 
     private native void native_agps_set_id(int type, String setid);
-
-    // GNSS Configuration
-    private static native boolean native_set_supl_version(int version);
-
-    private static native boolean native_set_supl_mode(int mode);
-
-    private static native boolean native_set_supl_es(int es);
-
-    private static native boolean native_set_lpp_profile(int lppProfile);
-
-    private static native boolean native_set_gnss_pos_protocol_select(int gnssPosProtocolSelect);
-
-    private static native boolean native_set_gps_lock(int gpsLock);
-
-    private static native boolean native_set_emergency_supl_pdn(int emergencySuplPdn);
-
-    private static native boolean native_set_satellite_blacklist(int[] constellations, int[] svIds);
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index dfcef70..a6da8c5 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -101,7 +101,7 @@
     private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager,
             String action, int overlaySwitchResId, int defaultServicePackageNameResId,
             int initialPackageNamesResId) {
-        super(locationProviderManager, false);
+        super(locationProviderManager);
 
         mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId,
                 defaultServicePackageNameResId, initialPackageNamesResId,
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index bfbebf7..86bc9f3 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -43,7 +43,7 @@
 
     public MockProvider(
             LocationProviderManager locationProviderManager, ProviderProperties properties) {
-        super(locationProviderManager, true);
+        super(locationProviderManager);
 
         mEnabled = true;
         mLocation = null;
@@ -52,6 +52,7 @@
         mExtras = null;
 
         setProperties(properties);
+        setEnabled(true);
     }
 
     /** Sets the enabled state of this mock provider. */
@@ -63,8 +64,11 @@
     /** Sets the location to report for this mock provider. */
     public void setLocation(Location l) {
         mLocation = new Location(l);
+        if (!mLocation.isFromMockProvider()) {
+            mLocation.setIsFromMockProvider(true);
+        }
         if (mEnabled) {
-            reportLocation(l);
+            reportLocation(mLocation);
         }
     }
 
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
index 70d64b0..30260b2 100644
--- a/services/core/java/com/android/server/location/PassiveProvider.java
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -43,11 +43,12 @@
     private boolean mReportLocation;
 
     public PassiveProvider(LocationProviderManager locationProviderManager) {
-        super(locationProviderManager, true);
+        super(locationProviderManager);
 
         mReportLocation = false;
 
         setProperties(PROPERTIES);
+        setEnabled(true);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index b70c64e..d611a17 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -19,7 +19,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
@@ -27,14 +26,15 @@
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
+import android.media.session.ControllerCallbackLink;
 import android.media.session.ISession;
-import android.media.session.ISessionCallback;
 import android.media.session.ISessionController;
-import android.media.session.ISessionControllerCallback;
 import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
 import android.media.session.PlaybackState;
+import android.media.session.SessionCallbackLink;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -44,7 +44,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.util.Log;
@@ -55,6 +54,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This is the system implementation of a Session. Apps will interact with the
@@ -84,7 +84,7 @@
     private final Context mContext;
 
     private final Object mLock = new Object();
-    private final ArrayList<ISessionControllerCallbackHolder> mControllerCallbackHolders =
+    private final ArrayList<ControllerCallbackLinkHolder> mControllerCallbackHolders =
             new ArrayList<>();
 
     private long mFlags;
@@ -97,7 +97,7 @@
     // may result in throwing an exception.
     private MediaMetadata mMetadata;
     private PlaybackState mPlaybackState;
-    private ParceledListSlice mQueue;
+    private List<QueueItem> mQueue;
     private CharSequence mQueueTitle;
     private int mRatingType;
     // End TransportPerformer fields
@@ -116,11 +116,11 @@
     private boolean mIsActive = false;
     private boolean mDestroyed = false;
 
-    private long mDuration;
+    private long mDuration = -1;
     private String mMetadataDescription;
 
     public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
-            ISessionCallback cb, String tag, MediaSessionService service, Looper handlerLooper) {
+            SessionCallbackLink cb, String tag, MediaSessionService service, Looper handlerLooper) {
         mOwnerPid = ownerPid;
         mOwnerUid = ownerUid;
         mUserId = userId;
@@ -249,7 +249,7 @@
      * @param useSuggested True to use adjustSuggestedStreamVolume instead of
      */
     public void adjustVolume(String packageName, String opPackageName, int pid, int uid,
-            ISessionControllerCallback caller, boolean asSystemService, int direction, int flags,
+            ControllerCallbackLink caller, boolean asSystemService, int direction, int flags,
             boolean useSuggested) {
         int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
         if (isPlaybackActive() || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
@@ -291,7 +291,7 @@
     }
 
     private void setVolumeTo(String packageName, String opPackageName, int pid, int uid,
-            ISessionControllerCallback caller, int value, int flags) {
+            ControllerCallbackLink caller, int value, int flags) {
         if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
             int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
             final int volumeValue = value;
@@ -440,7 +440,7 @@
         }
     }
 
-    public ISessionCallback getCallback() {
+    public SessionCallbackLink getCallback() {
         return mSessionCb.mCb;
     }
 
@@ -468,7 +468,7 @@
                 + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
         pw.println(indent + "metadata: " + mMetadataDescription);
         pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
-                + (mQueue == null ? 0 : mQueue.getList().size()));
+                + (mQueue == null ? 0 : mQueue.size()));
     }
 
     @Override
@@ -518,7 +518,7 @@
     }
 
     private void logCallbackException(
-            String msg, ISessionControllerCallbackHolder holder, Exception e) {
+            String msg, ControllerCallbackLinkHolder holder, Exception e) {
         Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName
                 + ", exception=" + e);
     }
@@ -529,16 +529,18 @@
                 return;
             }
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+                ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onPlaybackStateChanged(mPlaybackState);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removed dead callback in pushPlaybackStateUpdate",
-                            holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushPlaybackStateUpdate",
-                            holder, e);
+                    holder.mCallback.notifyPlaybackStateChanged(mPlaybackState);
+                } catch (RuntimeException e) {
+                    if (e.getCause() instanceof DeadObjectException) {
+                        mControllerCallbackHolders.remove(i);
+                        logCallbackException("Removing dead callback in pushPlaybackStateUpdate",
+                                holder, e);
+                    } else {
+                        logCallbackException("unexpected exception in pushPlaybackStateUpdate",
+                                holder, e);
+                    }
                 }
             }
         }
@@ -550,14 +552,18 @@
                 return;
             }
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+                ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onMetadataChanged(mMetadata);
-                } catch (DeadObjectException e) {
-                    logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
-                    mControllerCallbackHolders.remove(i);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
+                    holder.mCallback.notifyMetadataChanged(mMetadata);
+                } catch (RuntimeException e) {
+                    if (e.getCause() instanceof DeadObjectException) {
+                        mControllerCallbackHolders.remove(i);
+                        logCallbackException("Removing dead callback in pushMetadataUpdate",
+                                holder, e);
+                    } else {
+                        logCallbackException("unexpected exception in pushMetadataUpdate",
+                                holder, e);
+                    }
                 }
             }
         }
@@ -569,14 +575,17 @@
                 return;
             }
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+                ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onQueueChanged(mQueue);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removed dead callback in pushQueueUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
+                    holder.mCallback.notifyQueueChanged(mQueue);
+                } catch (RuntimeException e) {
+                    if (e.getCause() instanceof DeadObjectException) {
+                        mControllerCallbackHolders.remove(i);
+                        logCallbackException("Removing dead callback in pushQueueUpdate",
+                                holder, e);
+                    } else {
+                        logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
+                    }
                 }
             }
         }
@@ -588,16 +597,18 @@
                 return;
             }
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+                ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onQueueTitleChanged(mQueueTitle);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removed dead callback in pushQueueTitleUpdate",
-                            holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushQueueTitleUpdate",
-                            holder, e);
+                    holder.mCallback.notifyQueueTitleChanged(mQueueTitle);
+                } catch (RuntimeException e) {
+                    if (e.getCause() instanceof DeadObjectException) {
+                        mControllerCallbackHolders.remove(i);
+                        logCallbackException("Removing dead callback in pushQueueTitleUpdate",
+                                holder, e);
+                    } else {
+                        logCallbackException("unexpected exception in pushQueueTitleUpdate",
+                                holder, e);
+                    }
                 }
             }
         }
@@ -609,14 +620,17 @@
                 return;
             }
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+                ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onExtrasChanged(mExtras);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removed dead callback in pushExtrasUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
+                    holder.mCallback.notifyExtrasChanged(mExtras);
+                } catch (RuntimeException e) {
+                    if (e.getCause() instanceof DeadObjectException) {
+                        mControllerCallbackHolders.remove(i);
+                        logCallbackException("Removing dead callback in pushExtrasUpdate",
+                                holder, e);
+                    } else {
+                        logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
+                    }
                 }
             }
         }
@@ -629,14 +643,17 @@
             }
             PlaybackInfo info = mController.getVolumeAttributes();
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+                ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onVolumeInfoChanged(info);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("Unexpected exception in pushVolumeUpdate", holder, e);
+                    holder.mCallback.notifyVolumeInfoChanged(info);
+                } catch (RuntimeException e) {
+                    if (e.getCause() instanceof DeadObjectException) {
+                        mControllerCallbackHolders.remove(i);
+                        logCallbackException("Removing dead callback in pushVolumeUpdate",
+                                holder, e);
+                    } else {
+                        logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
+                    }
                 }
             }
         }
@@ -648,14 +665,16 @@
                 return;
             }
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+                ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onEvent(event, data);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushEvent", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushEvent", holder, e);
+                    holder.mCallback.notifyEvent(event, data);
+                } catch (RuntimeException e) {
+                    if (e.getCause() instanceof DeadObjectException) {
+                        mControllerCallbackHolders.remove(i);
+                        logCallbackException("Removing dead callback in pushEvent", holder, e);
+                    } else {
+                        logCallbackException("unexpected exception in pushEvent", holder, e);
+                    }
                 }
             }
         }
@@ -669,14 +688,18 @@
                 return;
             }
             for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
+                ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i);
                 try {
-                    holder.mCallback.onSessionDestroyed();
-                } catch (DeadObjectException e) {
-                    logCallbackException("Removing dead callback in pushEvent", holder, e);
-                    mControllerCallbackHolders.remove(i);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushEvent", holder, e);
+                    holder.mCallback.notifySessionDestroyed();
+                } catch (RuntimeException e) {
+                    if (e.getCause() instanceof DeadObjectException) {
+                        mControllerCallbackHolders.remove(i);
+                        logCallbackException("Removing dead callback in pushSessionDestroyed",
+                                holder, e);
+                    } else {
+                        logCallbackException("unexpected exception in pushSessionDestroyed",
+                                holder, e);
+                    }
                 }
             }
             // After notifying clear all listeners
@@ -716,10 +739,10 @@
         return result == null ? state : result;
     }
 
-    private int getControllerHolderIndexForCb(ISessionControllerCallback cb) {
-        IBinder binder = cb.asBinder();
+    private int getControllerHolderIndexForCb(ControllerCallbackLink cb) {
+        IBinder binder = cb.getBinder();
         for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-            if (binder.equals(mControllerCallbackHolders.get(i).mCallback.asBinder())) {
+            if (binder.equals(mControllerCallbackHolders.get(i).mCallback.getBinder())) {
                 return i;
             }
         }
@@ -843,7 +866,7 @@
         }
 
         @Override
-        public void setQueue(ParceledListSlice queue) {
+        public void setQueue(List<QueueItem> queue) {
             synchronized (mLock) {
                 mQueue = queue;
             }
@@ -920,9 +943,9 @@
     }
 
     class SessionCb {
-        private final ISessionCallback mCb;
+        private final SessionCallbackLink mCb;
 
-        public SessionCb(ISessionCallback cb) {
+        SessionCb(SessionCallbackLink cb) {
             mCb = cb;
         }
 
@@ -930,224 +953,224 @@
                 boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
             try {
                 if (asSystemService) {
-                    mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
+                    mCb.notifyMediaButton(mContext.getPackageName(), Process.myPid(),
                             Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb);
                 } else {
-                    mCb.onMediaButton(packageName, pid, uid,
+                    mCb.notifyMediaButton(packageName, pid, uid,
                             createMediaButtonIntent(keyEvent), sequenceId, cb);
                 }
                 return true;
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
             }
             return false;
         }
 
         public boolean sendMediaButton(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, boolean asSystemService,
+                ControllerCallbackLink caller, boolean asSystemService,
                 KeyEvent keyEvent) {
             try {
                 if (asSystemService) {
-                    mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
+                    mCb.notifyMediaButton(mContext.getPackageName(), Process.myPid(),
                             Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), 0, null);
                 } else {
-                    mCb.onMediaButtonFromController(packageName, pid, uid, caller,
+                    mCb.notifyMediaButtonFromController(packageName, pid, uid, caller,
                             createMediaButtonIntent(keyEvent));
                 }
                 return true;
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
             }
             return false;
         }
 
         public void sendCommand(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) {
+                ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
             try {
-                mCb.onCommand(packageName, pid, uid, caller, command, args, cb);
-            } catch (RemoteException e) {
+                mCb.notifyCommand(packageName, pid, uid, caller, command, args, cb);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in sendCommand.", e);
             }
         }
 
         public void sendCustomAction(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String action,
+                ControllerCallbackLink caller, String action,
                 Bundle args) {
             try {
-                mCb.onCustomAction(packageName, pid, uid, caller, action, args);
-            } catch (RemoteException e) {
+                mCb.notifyCustomAction(packageName, pid, uid, caller, action, args);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in sendCustomAction.", e);
             }
         }
 
         public void prepare(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             try {
-                mCb.onPrepare(packageName, pid, uid, caller);
-            } catch (RemoteException e) {
+                mCb.notifyPrepare(packageName, pid, uid, caller);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in prepare.", e);
             }
         }
 
         public void prepareFromMediaId(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String mediaId, Bundle extras) {
+                ControllerCallbackLink caller, String mediaId, Bundle extras) {
             try {
-                mCb.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
-            } catch (RemoteException e) {
+                mCb.notifyPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in prepareFromMediaId.", e);
             }
         }
 
         public void prepareFromSearch(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String query, Bundle extras) {
+                ControllerCallbackLink caller, String query, Bundle extras) {
             try {
-                mCb.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
-            } catch (RemoteException e) {
+                mCb.notifyPrepareFromSearch(packageName, pid, uid, caller, query, extras);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in prepareFromSearch.", e);
             }
         }
 
         public void prepareFromUri(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, Uri uri, Bundle extras) {
+                ControllerCallbackLink caller, Uri uri, Bundle extras) {
             try {
-                mCb.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
-            } catch (RemoteException e) {
+                mCb.notifyPrepareFromUri(packageName, pid, uid, caller, uri, extras);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in prepareFromUri.", e);
             }
         }
 
-        public void play(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+        public void play(String packageName, int pid, int uid, ControllerCallbackLink caller) {
             try {
-                mCb.onPlay(packageName, pid, uid, caller);
-            } catch (RemoteException e) {
+                mCb.notifyPlay(packageName, pid, uid, caller);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in play.", e);
             }
         }
 
         public void playFromMediaId(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String mediaId, Bundle extras) {
+                ControllerCallbackLink caller, String mediaId, Bundle extras) {
             try {
-                mCb.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
-            } catch (RemoteException e) {
+                mCb.notifyPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in playFromMediaId.", e);
             }
         }
 
         public void playFromSearch(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, String query, Bundle extras) {
+                ControllerCallbackLink caller, String query, Bundle extras) {
             try {
-                mCb.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
-            } catch (RemoteException e) {
+                mCb.notifyPlayFromSearch(packageName, pid, uid, caller, query, extras);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in playFromSearch.", e);
             }
         }
 
         public void playFromUri(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, Uri uri, Bundle extras) {
+                ControllerCallbackLink caller, Uri uri, Bundle extras) {
             try {
-                mCb.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
-            } catch (RemoteException e) {
+                mCb.notifyPlayFromUri(packageName, pid, uid, caller, uri, extras);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in playFromUri.", e);
             }
         }
 
         public void skipToTrack(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, long id) {
+                ControllerCallbackLink caller, long id) {
             try {
-                mCb.onSkipToTrack(packageName, pid, uid, caller, id);
-            } catch (RemoteException e) {
+                mCb.notifySkipToTrack(packageName, pid, uid, caller, id);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in skipToTrack", e);
             }
         }
 
-        public void pause(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+        public void pause(String packageName, int pid, int uid, ControllerCallbackLink caller) {
             try {
-                mCb.onPause(packageName, pid, uid, caller);
-            } catch (RemoteException e) {
+                mCb.notifyPause(packageName, pid, uid, caller);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in pause.", e);
             }
         }
 
-        public void stop(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+        public void stop(String packageName, int pid, int uid, ControllerCallbackLink caller) {
             try {
-                mCb.onStop(packageName, pid, uid, caller);
-            } catch (RemoteException e) {
+                mCb.notifyStop(packageName, pid, uid, caller);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in stop.", e);
             }
         }
 
-        public void next(String packageName, int pid, int uid, ISessionControllerCallback caller) {
+        public void next(String packageName, int pid, int uid, ControllerCallbackLink caller) {
             try {
-                mCb.onNext(packageName, pid, uid, caller);
-            } catch (RemoteException e) {
+                mCb.notifyNext(packageName, pid, uid, caller);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in next.", e);
             }
         }
 
         public void previous(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             try {
-                mCb.onPrevious(packageName, pid, uid, caller);
-            } catch (RemoteException e) {
+                mCb.notifyPrevious(packageName, pid, uid, caller);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in previous.", e);
             }
         }
 
         public void fastForward(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             try {
-                mCb.onFastForward(packageName, pid, uid, caller);
-            } catch (RemoteException e) {
+                mCb.notifyFastForward(packageName, pid, uid, caller);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in fastForward.", e);
             }
         }
 
         public void rewind(String packageName, int pid, int uid,
-                ISessionControllerCallback caller) {
+                ControllerCallbackLink caller) {
             try {
-                mCb.onRewind(packageName, pid, uid, caller);
-            } catch (RemoteException e) {
+                mCb.notifyRewind(packageName, pid, uid, caller);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in rewind.", e);
             }
         }
 
-        public void seekTo(String packageName, int pid, int uid, ISessionControllerCallback caller,
+        public void seekTo(String packageName, int pid, int uid, ControllerCallbackLink caller,
                 long pos) {
             try {
-                mCb.onSeekTo(packageName, pid, uid, caller, pos);
-            } catch (RemoteException e) {
+                mCb.notifySeekTo(packageName, pid, uid, caller, pos);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in seekTo.", e);
             }
         }
 
-        public void rate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+        public void rate(String packageName, int pid, int uid, ControllerCallbackLink caller,
                 Rating rating) {
             try {
-                mCb.onRate(packageName, pid, uid, caller, rating);
-            } catch (RemoteException e) {
+                mCb.notifyRate(packageName, pid, uid, caller, rating);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in rate.", e);
             }
         }
 
         public void adjustVolume(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, boolean asSystemService, int direction) {
+                ControllerCallbackLink caller, boolean asSystemService, int direction) {
             try {
                 if (asSystemService) {
-                    mCb.onAdjustVolume(mContext.getPackageName(), Process.myPid(),
+                    mCb.notifyAdjustVolume(mContext.getPackageName(), Process.myPid(),
                             Process.SYSTEM_UID, null, direction);
                 } else {
-                    mCb.onAdjustVolume(packageName, pid, uid, caller, direction);
+                    mCb.notifyAdjustVolume(packageName, pid, uid, caller, direction);
                 }
-            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in adjustVolume.", e);
             }
         }
 
         public void setVolumeTo(String packageName, int pid, int uid,
-                ISessionControllerCallback caller, int value) {
+                ControllerCallbackLink caller, int value) {
             try {
-                mCb.onSetVolumeTo(packageName, pid, uid, caller, value);
-            } catch (RemoteException e) {
+                mCb.notifySetVolumeTo(packageName, pid, uid, caller, value);
+            } catch (RuntimeException e) {
                 Slog.e(TAG, "Remote failure in setVolumeTo.", e);
             }
         }
@@ -1161,34 +1184,34 @@
 
     class ControllerStub extends ISessionController.Stub {
         @Override
-        public void sendCommand(String packageName, ISessionControllerCallback caller,
+        public void sendCommand(String packageName, ControllerCallbackLink caller,
                 String command, Bundle args, ResultReceiver cb) {
             mSessionCb.sendCommand(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                     caller, command, args, cb);
         }
 
         @Override
-        public boolean sendMediaButton(String packageName, ISessionControllerCallback cb,
+        public boolean sendMediaButton(String packageName, ControllerCallbackLink cb,
                 boolean asSystemService, KeyEvent keyEvent) {
             return mSessionCb.sendMediaButton(packageName, Binder.getCallingPid(),
                     Binder.getCallingUid(), cb, asSystemService, keyEvent);
         }
 
         @Override
-        public void registerCallbackListener(String packageName, ISessionControllerCallback cb) {
+        public void registerCallbackListener(String packageName, ControllerCallbackLink cb) {
             synchronized (mLock) {
                 // If this session is already destroyed tell the caller and
                 // don't add them.
                 if (mDestroyed) {
                     try {
-                        cb.onSessionDestroyed();
+                        cb.notifySessionDestroyed();
                     } catch (Exception e) {
                         // ignored
                     }
                     return;
                 }
                 if (getControllerHolderIndexForCb(cb) < 0) {
-                    mControllerCallbackHolders.add(new ISessionControllerCallbackHolder(cb,
+                    mControllerCallbackHolders.add(new ControllerCallbackLinkHolder(cb,
                             packageName, Binder.getCallingUid()));
                     if (DEBUG) {
                         Log.d(TAG, "registering controller callback " + cb + " from controller"
@@ -1199,14 +1222,14 @@
         }
 
         @Override
-        public void unregisterCallbackListener(ISessionControllerCallback cb) {
+        public void unregisterCallbackListener(ControllerCallbackLink cb) {
             synchronized (mLock) {
                 int index = getControllerHolderIndexForCb(cb);
                 if (index != -1) {
                     mControllerCallbackHolders.remove(index);
                 }
                 if (DEBUG) {
-                    Log.d(TAG, "unregistering callback " + cb.asBinder());
+                    Log.d(TAG, "unregistering callback " + cb.getBinder());
                 }
             }
         }
@@ -1253,7 +1276,7 @@
 
         @Override
         public void adjustVolume(String packageName, String opPackageName,
-                ISessionControllerCallback caller, boolean asSystemService, int direction,
+                ControllerCallbackLink caller, boolean asSystemService, int direction,
                 int flags) {
             int pid = Binder.getCallingPid();
             int uid = Binder.getCallingUid();
@@ -1268,7 +1291,7 @@
 
         @Override
         public void setVolumeTo(String packageName, String opPackageName,
-                ISessionControllerCallback caller, int value, int flags) {
+                ControllerCallbackLink caller, int value, int flags) {
             int pid = Binder.getCallingPid();
             int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
@@ -1281,110 +1304,110 @@
         }
 
         @Override
-        public void prepare(String packageName, ISessionControllerCallback caller) {
+        public void prepare(String packageName, ControllerCallbackLink caller) {
             mSessionCb.prepare(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void prepareFromMediaId(String packageName, ISessionControllerCallback caller,
+        public void prepareFromMediaId(String packageName, ControllerCallbackLink caller,
                 String mediaId, Bundle extras) {
             mSessionCb.prepareFromMediaId(packageName, Binder.getCallingPid(),
                     Binder.getCallingUid(), caller, mediaId, extras);
         }
 
         @Override
-        public void prepareFromSearch(String packageName, ISessionControllerCallback caller,
+        public void prepareFromSearch(String packageName, ControllerCallbackLink caller,
                 String query, Bundle extras) {
             mSessionCb.prepareFromSearch(packageName, Binder.getCallingPid(),
                     Binder.getCallingUid(), caller, query, extras);
         }
 
         @Override
-        public void prepareFromUri(String packageName, ISessionControllerCallback caller,
+        public void prepareFromUri(String packageName, ControllerCallbackLink caller,
                 Uri uri, Bundle extras) {
             mSessionCb.prepareFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                     caller, uri, extras);
         }
 
         @Override
-        public void play(String packageName, ISessionControllerCallback caller) {
+        public void play(String packageName, ControllerCallbackLink caller) {
             mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void playFromMediaId(String packageName, ISessionControllerCallback caller,
+        public void playFromMediaId(String packageName, ControllerCallbackLink caller,
                 String mediaId, Bundle extras) {
             mSessionCb.playFromMediaId(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                     caller, mediaId, extras);
         }
 
         @Override
-        public void playFromSearch(String packageName, ISessionControllerCallback caller,
+        public void playFromSearch(String packageName, ControllerCallbackLink caller,
                 String query, Bundle extras) {
             mSessionCb.playFromSearch(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                     caller, query, extras);
         }
 
         @Override
-        public void playFromUri(String packageName, ISessionControllerCallback caller,
+        public void playFromUri(String packageName, ControllerCallbackLink caller,
                 Uri uri, Bundle extras) {
             mSessionCb.playFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                     caller, uri, extras);
         }
 
         @Override
-        public void skipToQueueItem(String packageName, ISessionControllerCallback caller,
+        public void skipToQueueItem(String packageName, ControllerCallbackLink caller,
                 long id) {
             mSessionCb.skipToTrack(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                     caller, id);
         }
 
         @Override
-        public void pause(String packageName, ISessionControllerCallback caller) {
+        public void pause(String packageName, ControllerCallbackLink caller) {
             mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void stop(String packageName, ISessionControllerCallback caller) {
+        public void stop(String packageName, ControllerCallbackLink caller) {
             mSessionCb.stop(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void next(String packageName, ISessionControllerCallback caller) {
+        public void next(String packageName, ControllerCallbackLink caller) {
             mSessionCb.next(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void previous(String packageName, ISessionControllerCallback caller) {
+        public void previous(String packageName, ControllerCallbackLink caller) {
             mSessionCb.previous(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                     caller);
         }
 
         @Override
-        public void fastForward(String packageName, ISessionControllerCallback caller) {
+        public void fastForward(String packageName, ControllerCallbackLink caller) {
             mSessionCb.fastForward(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                     caller);
         }
 
         @Override
-        public void rewind(String packageName, ISessionControllerCallback caller) {
+        public void rewind(String packageName, ControllerCallbackLink caller) {
             mSessionCb.rewind(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void seekTo(String packageName, ISessionControllerCallback caller, long pos) {
+        public void seekTo(String packageName, ControllerCallbackLink caller, long pos) {
             mSessionCb.seekTo(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller,
                     pos);
         }
 
         @Override
-        public void rate(String packageName, ISessionControllerCallback caller, Rating rating) {
+        public void rate(String packageName, ControllerCallbackLink caller, Rating rating) {
             mSessionCb.rate(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller,
                     rating);
         }
 
         @Override
-        public void sendCustomAction(String packageName, ISessionControllerCallback caller,
+        public void sendCustomAction(String packageName, ControllerCallbackLink caller,
                 String action, Bundle args) {
             mSessionCb.sendCustomAction(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                     caller, action, args);
@@ -1403,7 +1426,7 @@
         }
 
         @Override
-        public ParceledListSlice getQueue() {
+        public List<QueueItem> getQueue() {
             synchronized (mLock) {
                 return mQueue;
             }
@@ -1432,12 +1455,12 @@
         }
     }
 
-    private class ISessionControllerCallbackHolder {
-        private final ISessionControllerCallback mCallback;
+    private class ControllerCallbackLinkHolder {
+        private final ControllerCallbackLink mCallback;
         private final String mPackageName;
         private final int mUid;
 
-        ISessionControllerCallbackHolder(ISessionControllerCallback callback, String packageName,
+        ControllerCallbackLinkHolder(ControllerCallbackLink callback, String packageName,
                 int uid) {
             mCallback = callback;
             mPackageName = packageName;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 052d579..ce0e72b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -40,20 +40,23 @@
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
+import android.media.MediaController2;
+import android.media.Session2CommandGroup;
 import android.media.Session2Token;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.ICallback;
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession;
-import android.media.session.ISessionCallback;
 import android.media.session.ISessionManager;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
+import android.media.session.SessionCallbackLink;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PowerManager;
@@ -73,6 +76,7 @@
 import android.view.KeyEvent;
 import android.view.ViewConfiguration;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -97,17 +101,24 @@
     private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
 
     private final SessionManagerImpl mSessionManagerImpl;
-
-    // Keeps the full user id for each user.
-    private final SparseIntArray mFullUserIds = new SparseIntArray();
-    private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
-    private final ArrayList<SessionsListenerRecord> mSessionsListeners
-            = new ArrayList<SessionsListenerRecord>();
-    private final Object mLock = new Object();
     private final MessageHandler mHandler = new MessageHandler();
     private final PowerManager.WakeLock mMediaEventWakeLock;
     private final int mLongPressTimeout;
     private final INotificationManager mNotificationManager;
+    private final Object mLock = new Object();
+    // Keeps the full user id for each user.
+    @GuardedBy("mLock")
+    private final SparseIntArray mFullUserIds = new SparseIntArray();
+    @GuardedBy("mLock")
+    private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
+    @GuardedBy("mLock")
+    private final ArrayList<SessionsListenerRecord> mSessionsListeners
+            = new ArrayList<SessionsListenerRecord>();
+    // Map user id as index to list of Session2Tokens
+    // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
+    //       one place.
+    @GuardedBy("mLock")
+    private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
 
     private KeyguardManager mKeyguardManager;
     private IAudioService mAudioService;
@@ -295,10 +306,13 @@
         updateUser();
     }
 
+    // Called when the user with the userId is removed.
     @Override
     public void onStopUser(int userId) {
         if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
         synchronized (mLock) {
+            // TODO: Also handle removing user in updateUser() because adding/switching user is
+            //       handled in updateUser().
             FullUserRecord user = getFullUserRecordLocked(userId);
             if (user != null) {
                 if (user.mFullUserId == userId) {
@@ -308,6 +322,7 @@
                     user.destroySessionsForUserLocked(userId);
                 }
             }
+            mSession2TokensPerUser.remove(userId);
             updateUser();
         }
     }
@@ -353,6 +368,9 @@
                             mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
                         }
                     }
+                    if (mSession2TokensPerUser.get(userInfo.id) == null) {
+                        mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
+                    }
                 }
             }
             // Ensure that the current full user exists.
@@ -362,6 +380,9 @@
                 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
                 mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
                 mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
+                if (mSession2TokensPerUser.get(currentFullUserId) == null) {
+                    mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
+                }
             }
             mFullUserIds.put(currentFullUserId, currentFullUserId);
         }
@@ -415,7 +436,7 @@
         }
 
         try {
-            session.getCallback().asBinder().unlinkToDeath(session, 0);
+            session.getCallback().getBinder().unlinkToDeath(session, 0);
         } catch (Exception e) {
             // ignore exceptions while destroying a session.
         }
@@ -501,7 +522,7 @@
     }
 
     private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
-            String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
+            String callerPackageName, SessionCallbackLink cb, String tag) throws RemoteException {
         synchronized (mLock) {
             return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
         }
@@ -515,7 +536,7 @@
      * 4. It needs to be added to the relevant user record.
      */
     private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
-            String callerPackageName, ISessionCallback cb, String tag) {
+            String callerPackageName, SessionCallbackLink cb, String tag) {
         FullUserRecord user = getFullUserRecordLocked(userId);
         if (user == null) {
             Log.wtf(TAG, "Request from invalid user: " +  userId);
@@ -525,7 +546,7 @@
         final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
                 callerPackageName, cb, tag, this, mHandler.getLooper());
         try {
-            cb.asBinder().linkToDeath(session, 0);
+            cb.getBinder().linkToDeath(session, 0);
         } catch (RemoteException e) {
             throw new RuntimeException("Media Session owner died prematurely.", e);
         }
@@ -722,6 +743,16 @@
             pw.println(indent + "Restored MediaButtonReceiverComponentType: "
                     + mRestoredMediaButtonReceiverComponentType);
             mPriorityStack.dump(pw, indent);
+            pw.println(indent + "Session2Tokens:");
+            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+                List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
+                if (list == null || list.size() == 0) {
+                    continue;
+                }
+                for (Session2Token token : list) {
+                    pw.println(indent + "  " + token);
+                }
+            }
         }
 
         @Override
@@ -876,7 +907,7 @@
         private boolean mVoiceButtonHandled = false;
 
         @Override
-        public ISession createSession(String packageName, ISessionCallback cb, String tag,
+        public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
                 int userId) throws RemoteException {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
@@ -904,7 +935,17 @@
                 if (DEBUG) {
                     Log.d(TAG, "Session2 is created " + sessionToken);
                 }
-                // TODO: Keep the session.
+                if (uid != sessionToken.getUid()) {
+                    throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+                            + " but actually=" + sessionToken.getUid());
+                }
+                Controller2Callback callback = new Controller2Callback(sessionToken);
+                // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
+                //       it's closed.
+                // TODO: Keep controller as well for better readability
+                //       because the GC behavior isn't straightforward.
+                MediaController2 controller = new MediaController2(getContext(), sessionToken,
+                        new HandlerExecutor(mHandler), callback);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -932,6 +973,34 @@
         }
 
         @Override
+        public List<Session2Token> getSession2Tokens(int userId) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            try {
+                // Check that they can make calls on behalf of the user and
+                // get the final user id
+                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+                        true /* allowAll */, true /* requireFull */, "getSession2Tokens",
+                        null /* optional packageName */);
+                List<Session2Token> result = new ArrayList<>();
+                synchronized (mLock) {
+                    if (resolvedUserId == UserHandle.USER_ALL) {
+                        for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+                            result.addAll(mSession2TokensPerUser.valueAt(i));
+                        }
+                    } else {
+                        result.addAll(mSession2TokensPerUser.get(userId));
+                    }
+                }
+                return result;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void addSessionsListener(IActiveSessionsListener listener,
                 ComponentName componentName, int userId) throws RemoteException {
             final int pid = Binder.getCallingPid();
@@ -1930,4 +1999,28 @@
             obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
         }
     }
+
+    private class Controller2Callback extends MediaController2.ControllerCallback {
+        private final Session2Token mToken;
+
+        Controller2Callback(Session2Token token) {
+            mToken = token;
+        }
+
+        @Override
+        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+            synchronized (mLock) {
+                int userId = UserHandle.getUserId(mToken.getUid());
+                mSession2TokensPerUser.get(userId).add(mToken);
+            }
+        }
+
+        @Override
+        public void onDisconnected(MediaController2 controller) {
+            synchronized (mLock) {
+                int userId = UserHandle.getUserId(mToken.getUid());
+                mSession2TokensPerUser.get(userId).remove(mToken);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java
new file mode 100644
index 0000000..e241591
--- /dev/null
+++ b/services/core/java/com/android/server/os/BugreportManagerService.java
@@ -0,0 +1,43 @@
+/*
+ * 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.os;
+
+import android.content.Context;
+
+import com.android.server.SystemService;
+
+/**
+ * Service that provides a privileged API to capture and consume bugreports.
+ *
+ * @hide
+ */
+public class BugreportManagerService extends SystemService {
+    private static final String TAG = "BugreportManagerService";
+
+    private BugreportManagerServiceImpl mService;
+
+    public BugreportManagerService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        mService = new BugreportManagerServiceImpl(getContext());
+        // TODO(b/111441001): Needs sepolicy to be submitted first.
+        // 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
new file mode 100644
index 0000000..faa4714
--- /dev/null
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -0,0 +1,130 @@
+/*
+ * 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.os;
+
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.os.BugreportParams;
+import android.os.IDumpstate;
+import android.os.IDumpstateListener;
+import android.os.IDumpstateToken;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+
+// TODO(b/111441001):
+// 1. Handle the case where another bugreport is in progress
+// 2. Make everything threadsafe
+// 3. Pass validation & other errors on listener
+
+/**
+ * Implementation of the service that provides a privileged API to capture and consume bugreports.
+ *
+ * <p>Delegates the actualy generation to a native implementation of {@code Dumpstate}.
+ */
+class BugreportManagerServiceImpl extends IDumpstate.Stub {
+    private static final String TAG = "BugreportManagerService";
+    private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
+
+    private IDumpstate mDs = null;
+    private final Context mContext;
+
+    BugreportManagerServiceImpl(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public IDumpstateToken setListener(String name, IDumpstateListener listener,
+            boolean getSectionDetails) throws RemoteException {
+        // TODO(b/111441001): Figure out if lazy setting of listener should be allowed
+        // and if so how to handle it.
+        throw new UnsupportedOperationException("setListener is not allowed on this service");
+    }
+
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void startBugreport(FileDescriptor bugreportFd, FileDescriptor screenshotFd,
+            int bugreportMode, IDumpstateListener listener) throws RemoteException {
+
+        validate(bugreportMode);
+
+        mDs = getDumpstateService();
+        if (mDs == null) {
+            Slog.w(TAG, "Unable to get bugreport service");
+            // TODO(b/111441001): pass error on listener
+            return;
+        }
+        mDs.startBugreport(bugreportFd, screenshotFd, bugreportMode, listener);
+    }
+
+    private boolean validate(@BugreportParams.BugreportMode int mode) {
+        if (mode != BugreportParams.BUGREPORT_MODE_FULL
+                && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
+                && mode != BugreportParams.BUGREPORT_MODE_REMOTE
+                && mode != BugreportParams.BUGREPORT_MODE_WEAR
+                && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY
+                && mode != BugreportParams.BUGREPORT_MODE_WIFI) {
+            Slog.w(TAG, "Unknown bugreport mode: " + mode);
+            return false;
+        }
+        return true;
+    }
+
+    /*
+     * Start and get a handle to the native implementation of {@code IDumpstate} which does the
+     * actual bugreport generation.
+     *
+     * <p>Generating bugreports requires root privileges. To limit the footprint
+     * of the root access, the actual generation in Dumpstate binary is accessed as a
+     * oneshot service 'bugreport'.
+     */
+    private IDumpstate getDumpstateService() {
+        // Start bugreport service.
+        SystemProperties.set("ctl.start", "bugreport");
+
+        IDumpstate ds = null;
+        boolean timedOut = false;
+        int totalTimeWaitedMillis = 0;
+        int seedWaitTimeMillis = 500;
+        while (!timedOut) {
+            // Note that the binder service on the native side is "dumpstate".
+            ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
+            if (ds != null) {
+                Slog.i(TAG, "Got bugreport service handle.");
+                break;
+            }
+            SystemClock.sleep(seedWaitTimeMillis);
+            Slog.i(TAG,
+                    "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)");
+            totalTimeWaitedMillis += seedWaitTimeMillis;
+            seedWaitTimeMillis *= 2;
+            timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS;
+        }
+        if (timedOut) {
+            Slog.w(TAG,
+                    "Timed out waiting to get dumpstate service handle ("
+                    + totalTimeWaitedMillis + "ms)");
+        }
+        return ds;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 7ca39df..d0b20e8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -329,6 +329,9 @@
 
                         if (valid) {
                             mSessions.put(session.sessionId, session);
+                            if (session.isStaged()) {
+                                mStagingManager.restoreSession(session);
+                            }
                         } else {
                             // Since this is early during boot we don't send
                             // any observer events about the session, but we
@@ -533,7 +536,7 @@
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mInstallThread.getLooper(), mStagingManager, sessionId, userId,
                 installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
-                false, null, SessionInfo.INVALID_ID);
+                false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR);
 
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
@@ -1131,7 +1134,9 @@
                         }
                     }
                     synchronized (mSessions) {
-                        mSessions.remove(session.sessionId);
+                        if (!session.isStaged() || !success) {
+                            mSessions.remove(session.sessionId);
+                        }
                         addHistoricalSessionLocked(session);
 
                         final File appIconFile = buildAppIconFile(session.sessionId);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5cb6c34..516927f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -151,6 +151,10 @@
     private static final String ATTR_MULTI_PACKAGE = "multiPackage";
     private static final String ATTR_PARENT_SESSION_ID = "parentSessionId";
     private static final String ATTR_STAGED_SESSION = "stagedSession";
+    private static final String ATTR_IS_READY = "isReady";
+    private static final String ATTR_IS_FAILED = "isFailed";
+    private static final String ATTR_IS_APPLIED = "isApplied";
+    private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode";
     private static final String ATTR_MODE = "mode";
     private static final String ATTR_INSTALL_FLAGS = "installFlags";
     private static final String ATTR_INSTALL_LOCATION = "installLocation";
@@ -408,7 +412,8 @@
             int sessionId, int userId,
             String installerPackageName, int installerUid, SessionParams params, long createdMillis,
             File stageDir, String stageCid, boolean prepared, boolean sealed,
-            @Nullable int[] childSessionIds, int parentSessionId) {
+            @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
+            boolean isFailed, boolean isApplied, int stagedSessionErrorCode) {
         mCallback = callback;
         mContext = context;
         mPm = pm;
@@ -438,7 +443,10 @@
         }
 
         mPrepared = prepared;
-
+        mStagedSessionReady = isReady;
+        mStagedSessionFailed = isFailed;
+        mStagedSessionApplied = isApplied;
+        mStagedSessionErrorCode = stagedSessionErrorCode;
         if (sealed) {
             synchronized (mLock) {
                 try {
@@ -2023,8 +2031,9 @@
     private void destroyInternal() {
         synchronized (mLock) {
             mSealed = true;
-            mDestroyed = true;
-
+            if (!params.isStaged) {
+                mDestroyed = true;
+            }
             // Force shut down all bridges
             for (RevocableFileDescriptor fd : mFds) {
                 fd.revoke();
@@ -2131,6 +2140,10 @@
 
             writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage);
             writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged);
+            writeBooleanAttribute(out, ATTR_IS_READY, mStagedSessionReady);
+            writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed);
+            writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied);
+            writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode);
             // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
             //                       we've read all sessions.
             writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId);
@@ -2269,10 +2282,16 @@
             params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
             params.appIconLastModified = appIconFile.lastModified();
         }
+        final boolean isReady = readBooleanAttribute(in, ATTR_IS_READY);
+        final boolean isFailed = readBooleanAttribute(in, ATTR_IS_FAILED);
+        final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
+        final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE);
+
         return new PackageInstallerSession(callback, context, pm, sessionProvider,
                 installerThread, stagingManager, sessionId, userId, installerPackageName,
                 installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed,
-                EMPTY_CHILD_SESSION_ARRAY, parentSessionId);
+                EMPTY_CHILD_SESSION_ARRAY, parentSessionId, isReady, isFailed, isApplied,
+                stagedSessionErrorCode);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3301962..2dc5437 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -13562,7 +13562,8 @@
             }
 
             Signature[] callerSignature;
-            Object obj = mSettings.getSettingLPr(callingUid);
+            final int appId = UserHandle.getAppId(callingUid);
+            final Object obj = mSettings.getSettingLPr(appId);
             if (obj != null) {
                 if (obj instanceof SharedUserSetting) {
                     callerSignature =
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 2329356..c297c62 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.apex.ApexInfo;
+import android.apex.ApexInfoList;
 import android.apex.IApexService;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
@@ -52,9 +53,6 @@
     private final PackageManagerService mPm;
     private final Handler mBgHandler;
 
-    // STOPSHIP: This is a temporary mock implementation of staged sessions. This variable
-    //           shouldn't be needed at all.
-    // TODO(b/118865310): Implement staged sessions logic.
     @GuardedBy("mStagedSessions")
     private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
 
@@ -127,28 +125,55 @@
         return false;
     }
 
-    void commitSession(@NonNull PackageInstallerSession sessionInfo) {
-        updateStoredSession(sessionInfo);
+    private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) {
+        final IApexService apex = IApexService.Stub.asInterface(
+                ServiceManager.getService("apexservice"));
+        boolean success;
+        try {
+            success = apex.submitStagedSession(sessionId, apexInfoList);
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Unable to contact apexservice", re);
+            return false;
+        }
+        return success;
+    }
 
-        mBgHandler.post(() -> {
-            sessionInfo.setStagedSessionReady();
+    void preRebootVerification(@NonNull PackageInstallerSession session) {
+        boolean success = true;
+        if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
 
-            SessionInfo session = sessionInfo.generateInfo(false);
-            // For APEXes, we validate the signature here before we write the package to the
-            // staging directory. For APKs, the signature verification will be done by the package
-            // manager at the point at which it applies the staged install.
-            //
-            // TODO: Decide whether we want to fail fast by detecting signature mismatches right
-            // away.
-            if ((sessionInfo.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
-                if (!validateApexSignatureLocked(session.resolvedBaseCodePath,
-                        session.appPackageName)) {
-                    sessionInfo.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+            final ApexInfoList apexInfoList = new ApexInfoList();
+
+            if (!submitSessionToApexService(session.sessionId, apexInfoList)) {
+                success = false;
+            } else {
+                // For APEXes, we validate the signature here before we mark the session as ready,
+                // so we fail the session early if there is a signature mismatch. For APKs, the
+                // signature verification will be done by the package manager at the point at which
+                // it applies the staged install.
+                //
+                // TODO: Decide whether we want to fail fast by detecting signature mismatches right
+                // away.
+                for (ApexInfo apexPackage : apexInfoList.apexInfos) {
+                    if (!validateApexSignatureLocked(apexPackage.packagePath,
+                            apexPackage.packageName)) {
+                        success = false;
+                        break;
+                    }
                 }
             }
+        }
+        if (success) {
+            session.setStagedSessionReady();
+        } else {
+            session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+        }
+        mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
+    }
 
-            mPm.sendSessionUpdatedBroadcast(sessionInfo.generateInfo(false), sessionInfo.userId);
-        });
+    void commitSession(@NonNull PackageInstallerSession session) {
+        updateStoredSession(session);
+        mBgHandler.post(() -> preRebootVerification(session));
     }
 
     void createSession(@NonNull PackageInstallerSession sessionInfo) {
@@ -163,4 +188,11 @@
             mStagedSessions.remove(sessionInfo.sessionId);
         }
     }
+
+    void restoreSession(@NonNull PackageInstallerSession session) {
+        updateStoredSession(session);
+        // TODO(b/118865310): This method is called when PackageInstaller is re-instantiated, e.g.
+        // at reboot. Staging manager should at this point recover state from apexd and decide what
+        // to do with the session.
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index dd04652..aaa1874 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -620,9 +620,6 @@
                         && callingUid != Process.SYSTEM_UID) {
                     return true;
                 } else if (String.valueOf(Settings.Secure.LOCATION_MODE_OFF).equals(value)) {
-                    // Note LOCATION_MODE will be converted into LOCATION_PROVIDERS_ALLOWED
-                    // in android.provider.Settings.Secure.putStringForUser(), so we shouldn't come
-                    // here normally, but we still protect it here from a direct provider write.
                     return false;
                 }
                 restriction = UserManager.DISALLOW_SHARE_LOCATION;
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index e212b04..437ef73 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -10,7 +10,7 @@
           "include-annotation": "android.platform.test.annotations.Presubmit"
         },
         {
-          "exclude-annotation": "android.support.test.filters.FlakyTest"
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
     },
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index 244ccb6..5cbe74c 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -1,6 +1,4 @@
 michaelwr@google.com
 santoscordon@google.com
 
-per-file BatterySaverPolicy.java=omakoto@google.com
-per-file ShutdownThread.java=fkupolov@google.com
-per-file ThermalManagerService.java=wvw@google.com
\ No newline at end of file
+per-file ThermalManagerService.java=wvw@google.com
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 565bb706..a027873 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -96,6 +96,7 @@
 import com.android.server.lights.LightsManager;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.power.batterysaver.BatterySaverController;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
 import com.android.server.power.batterysaver.BatterySaverStateMachine;
 import com.android.server.power.batterysaver.BatterySavingStats;
 
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 02689a9..7d03d82 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -134,10 +134,14 @@
             if (!halConnected) {
                 mHalWrapper = new ThermalHal20Wrapper();
                 halConnected = mHalWrapper.connectToHal();
-                if (!halConnected) {
-                    mHalWrapper = new ThermalHal11Wrapper();
-                    halConnected = mHalWrapper.connectToHal();
-                }
+            }
+            if (!halConnected) {
+                mHalWrapper = new ThermalHal11Wrapper();
+                halConnected = mHalWrapper.connectToHal();
+            }
+            if (!halConnected) {
+                mHalWrapper = new ThermalHal10Wrapper();
+                halConnected = mHalWrapper.connectToHal();
             }
             mHalWrapper.setCallback(this::onTemperatureChangedCallback);
             if (!halConnected) {
@@ -616,6 +620,81 @@
         }
     }
 
+
+    static class ThermalHal10Wrapper extends ThermalHalWrapper {
+        /** Proxy object for the Thermal HAL 1.0 service. */
+        @GuardedBy("mHalLock")
+        private android.hardware.thermal.V1_0.IThermal mThermalHal10 = null;
+
+        @Override
+        protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
+                int type) {
+            synchronized (mHalLock) {
+                List<Temperature> ret = new ArrayList<>();
+                if (mThermalHal10 == null) {
+                    return ret;
+                }
+                try {
+                    mThermalHal10.getTemperatures(
+                            (ThermalStatus status,
+                                    ArrayList<android.hardware.thermal.V1_0.Temperature>
+                                            temperatures) -> {
+                                if (ThermalStatusCode.SUCCESS == status.code) {
+                                    for (android.hardware.thermal.V1_0.Temperature
+                                            temperature : temperatures) {
+                                        if (shouldFilter && type != temperature.type) {
+                                            continue;
+                                        }
+                                        // Thermal HAL 1.0 doesn't report current throttling status
+                                        ret.add(new Temperature(
+                                                temperature.currentValue, temperature.type,
+                                                temperature.name,
+                                                Temperature.THROTTLING_NONE));
+                                    }
+                                } else {
+                                    Slog.e(TAG,
+                                            "Couldn't get temperatures because of HAL error: "
+                                                    + status.debugMessage);
+                                }
+
+                            });
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e);
+                    connectToHal();
+                }
+                return ret;
+            }
+        }
+
+        @Override
+        protected boolean connectToHal() {
+            synchronized (mHalLock) {
+                try {
+                    mThermalHal10 = android.hardware.thermal.V1_0.IThermal.getService();
+                    mThermalHal10.linkToDeath(new DeathRecipient(),
+                            THERMAL_HAL_DEATH_COOKIE);
+                    Slog.i(TAG,
+                            "Thermal HAL 1.0 service connected, no thermal call back will be "
+                                    + "called due to legacy API.");
+                } catch (NoSuchElementException | RemoteException e) {
+                    Slog.e(TAG,
+                            "Thermal HAL 1.0 service not connected.");
+                    mThermalHal10 = null;
+                }
+                return (mThermalHal10 != null);
+            }
+        }
+
+        @Override
+        protected void dump(PrintWriter pw, String prefix) {
+            synchronized (mHalLock) {
+                pw.print(prefix);
+                pw.println("ThermalHAL 1.0 connected: " + (mThermalHal10 != null ? "yes"
+                        : "no"));
+            }
+        }
+    }
+
     static class ThermalHal11Wrapper extends ThermalHalWrapper {
         /** Proxy object for the Thermal HAL 1.1 service. */
         @GuardedBy("mHalLock")
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 6400c88..1d74350 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -35,13 +35,13 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
-import com.android.server.power.BatterySaverPolicy;
-import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
 import com.android.server.power.PowerManagerService;
+import com.android.server.power.batterysaver.BatterySaverPolicy.BatterySaverPolicyListener;
 import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState;
 import com.android.server.power.batterysaver.BatterySavingStats.DozeState;
 import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState;
@@ -158,10 +158,9 @@
         mBatterySavingStats = batterySavingStats;
 
         // Initialize plugins.
-        final ArrayList<Plugin> plugins = new ArrayList<>();
-        plugins.add(new BatterySaverLocationPlugin(mContext));
-
-        mPlugins = plugins.toArray(new Plugin[plugins.size()]);
+        mPlugins = new Plugin[] {
+                new BatterySaverLocationPlugin(mContext)
+        };
     }
 
     /**
@@ -217,7 +216,7 @@
             super(looper);
         }
 
-        public void postStateChanged(boolean sendBroadcast, int reason) {
+        void postStateChanged(boolean sendBroadcast, int reason) {
             obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
                     ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, reason).sendToTarget();
         }
@@ -244,9 +243,8 @@
         }
     }
 
-    /**
-     * Called by {@link PowerManagerService} to update the battery saver state.
-     */
+    /** Enable or disable full battery saver. */
+    @VisibleForTesting
     public void enableBatterySaver(boolean enable, int reason) {
         synchronized (mLock) {
             if (mEnabled == enable) {
@@ -311,7 +309,7 @@
                     reason);
             mPreviouslyEnabled = mEnabled;
 
-            listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
+            listeners = mListeners.toArray(new LowPowerModeListener[0]);
 
             enabled = mEnabled;
             mIsInteractive = isInteractive;
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
similarity index 98%
rename from services/core/java/com/android/server/power/BatterySaverPolicy.java
rename to services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index cedb548..48a041e 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power;
+package com.android.server.power.batterysaver;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -36,21 +36,19 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ConcurrentUtils;
-import com.android.server.power.batterysaver.BatterySavingStats;
-import com.android.server.power.batterysaver.CpuFrequencies;
+import com.android.server.power.PowerManagerService;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
 /**
- * Class to decide whether to turn on battery saver mode for specific service
+ * Class to decide whether to turn on battery saver mode for specific services.
  *
  * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy.
  * Do not call out with the lock held, such as AccessibilityManager. (Settings provider is okay.)
  *
- * Test:
- atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+ * Test: atest com.android.server.power.batterysaver.BatterySaverPolicyTest.java
  */
 public class BatterySaverPolicy extends ContentObserver {
     private static final String TAG = "BatterySaverPolicy";
@@ -200,8 +198,10 @@
         onChange(true, null);
     }
 
+    @VisibleForTesting
     public void addListener(BatterySaverPolicyListener listener) {
         synchronized (mLock) {
+            // TODO: set this in the constructor instead
             mListeners.add(listener);
         }
     }
@@ -244,7 +244,7 @@
             // Update.
             updateConstantsLocked(setting, deviceSpecificSetting);
 
-            listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]);
+            listeners = mListeners.toArray(new BatterySaverPolicyListener[0]);
         }
 
         // Notify the listeners.
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index a60f16d..b7f28da 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -37,7 +37,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.EventLogTags;
-import com.android.server.power.BatterySaverPolicy;
 import com.android.server.power.BatterySaverStateMachineProto;
 
 import java.io.PrintWriter;
@@ -229,8 +228,8 @@
     }
 
     /**
-     * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable},
-     * it'll be first removed before a new one is posted.
+     * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable} is
+     * already registered, it'll be first removed before being re-posted.
      */
     @VisibleForTesting
     void runOnBgThreadLazy(Runnable r, int delayMillis) {
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
index 8213205..79b44eb 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java
@@ -29,7 +29,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
-import com.android.server.power.BatterySaverPolicy;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index fc21adb..7c1e619 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -598,11 +598,11 @@
     }
 
     @Override
-    public void onBiometricAuthenticated() {
+    public void onBiometricAuthenticated(boolean authenticated) {
         enforceBiometricDialog();
         if (mBar != null) {
             try {
-                mBar.onBiometricAuthenticated();
+                mBar.onBiometricAuthenticated(authenticated);
             } catch (RemoteException ex) {
             }
         }
@@ -641,17 +641,6 @@
         }
     }
 
-    @Override
-    public void showBiometricTryAgain() {
-        enforceBiometricDialog();
-        if (mBar != null) {
-            try {
-                mBar.showBiometricTryAgain();
-            } catch (RemoteException ex) {
-            }
-        }
-    }
-
     // TODO(b/117478341): make it aware of multi-display if needed.
     @Override
     public void disable(int what, IBinder token, String pkg) {
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 2cab63a..ef77140 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -510,7 +510,8 @@
                 Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent());
                 willBind = mContext.bindServiceAsUser(
                         serviceIntent, mConnection,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                                | Context.BIND_RESTRICT_ASSOCIATIONS,
                         UserHandle.of(mUserId));
                 mBinding = willBind;
             } finally {
diff --git a/services/core/java/com/android/server/textservices/TextServicesManagerService.java b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
index 65d5b10..7236d79 100644
--- a/services/core/java/com/android/server/textservices/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
@@ -16,8 +16,6 @@
 
 package com.android.server.textservices;
 
-import static android.view.textservice.TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -43,6 +41,7 @@
 import android.text.TextUtils;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.inputmethod.InputMethodSystemProperty;
 import android.view.textservice.SpellCheckerInfo;
 import android.view.textservice.SpellCheckerSubtype;
 
@@ -334,7 +333,7 @@
         mContext = context;
         mUserManager = mContext.getSystemService(UserManager.class);
         mSpellCheckerOwnerUserIdMap = new LazyIntToIntMap(callingUserId -> {
-            if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
+            if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
                 final long token = Binder.clearCallingIdentity();
                 try {
                     final UserInfo parent = mUserManager.getProfileParent(callingUserId);
@@ -355,7 +354,7 @@
     private void initializeInternalStateLocked(@UserIdInt int userId) {
         // When DISABLE_PER_PROFILE_SPELL_CHECKER is true, we make sure here that work profile users
         // will never have non-null TextServicesData for their user ID.
-        if (DISABLE_PER_PROFILE_SPELL_CHECKER
+        if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
                 && userId != mSpellCheckerOwnerUserIdMap.get(userId)) {
             return;
         }
@@ -780,7 +779,7 @@
     private TextServicesData getDataFromCallingUserIdLocked(@UserIdInt int callingUserId) {
         final int spellCheckerOwnerUserId = mSpellCheckerOwnerUserIdMap.get(callingUserId);
         final TextServicesData data = mUserData.get(spellCheckerOwnerUserId);
-        if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
+        if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
             if (spellCheckerOwnerUserId != callingUserId) {
                 // Calling process is running under child profile.
                 if (data == null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 410f864..fcc8284 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -84,6 +84,7 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.EventLog;
+import android.util.FeatureFlagUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -2219,8 +2220,12 @@
         synchronized (mLock) {
             mInAmbientMode = inAmbientMode;
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            if (data != null && data.connection != null && data.connection.mInfo != null
-                    && data.connection.mInfo.supportsAmbientMode()) {
+            final boolean hasConnection = data != null && data.connection != null;
+            final WallpaperInfo info = hasConnection ? data.connection.mInfo : null;
+
+            // The wallpaper info is null for image wallpaper, also use the engine in this case.
+            if (hasConnection && (info == null && isAodImageWallpaperEnabled()
+                    || info != null && info.supportsAmbientMode())) {
                 // TODO(multi-display) Extends this method with specific display.
                 engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
             } else {
@@ -2237,6 +2242,10 @@
         }
     }
 
+    private boolean isAodImageWallpaperEnabled() {
+        return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+    }
+
     @Override
     public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
         checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6f8f85f..f8f0d1c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -390,6 +390,11 @@
     private boolean mTurnScreenOn;
 
     /**
+     * Current sequencing integer of the configuration, for skipping old activity configurations.
+     */
+    private int mConfigurationSeq;
+
+    /**
      * Temp configs used in {@link #ensureActivityConfiguration(int, boolean)}
      */
     private final Configuration mTmpConfig = new Configuration();
@@ -2568,6 +2573,45 @@
         onRequestedOverrideConfigurationChanged(mTmpConfig);
     }
 
+    @Override
+    void resolveOverrideConfiguration(Configuration newParentConfiguration) {
+        super.resolveOverrideConfiguration(newParentConfiguration);
+
+        // Assign configuration sequence number into hierarchy because there is a different way than
+        // ensureActivityConfiguration() in this class that uses configuration in WindowState during
+        // layout traversals.
+        mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
+        getResolvedOverrideConfiguration().seq = mConfigurationSeq;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        super.onConfigurationChanged(newParentConfig);
+
+        // Configuration's equality doesn't consider seq so if only seq number changes in resolved
+        // override configuration. Therefore ConfigurationContainer doesn't change merged override
+        // configuration, but it's used to push configuration changes so explicitly update that.
+        if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) {
+            onMergedOverrideConfigurationChanged();
+        }
+
+        // TODO(b/80414790): Remove code below after unification.
+        // Same as above it doesn't notify configuration listeners, and consequently AppWindowToken
+        // can't get updated seq number. However WindowState's merged override configuration needs
+        // to have this seq number because that's also used for activity config pushes during layout
+        // traversal. Therefore explicitly update them here.
+        if (mAppWindowToken == null) {
+            return;
+        }
+        final Configuration appWindowTokenRequestedOverrideConfig =
+                mAppWindowToken.getRequestedOverrideConfiguration();
+        if (appWindowTokenRequestedOverrideConfig.seq != getResolvedOverrideConfiguration().seq) {
+            appWindowTokenRequestedOverrideConfig.seq =
+                    getResolvedOverrideConfiguration().seq;
+            mAppWindowToken.onMergedOverrideConfigurationChanged();
+        }
+    }
+
     /** Returns true if the configuration is compatible with this activity. */
     boolean isConfigurationCompatible(Configuration config) {
         final int orientation = mAppWindowToken != null
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 0f286ce..d8644df 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.AppProtoEnums;
 import android.app.IActivityManager;
 import android.app.IApplicationThread;
@@ -479,4 +480,10 @@
     public abstract void setProfilerInfo(ProfilerInfo profilerInfo);
 
     public abstract ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry();
+
+    /**
+     * Gets bitmap snapshot of the provided task id.
+     */
+    public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId,
+            boolean reducedResolution);
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 42121ca..e5bf21a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -245,7 +245,7 @@
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
 import com.android.server.AttributeCache;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -7012,5 +7012,12 @@
                 return mStackSupervisor.getActivityMetricsLogger().getLaunchObserverRegistry();
             }
         }
+
+        @Override
+        public ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
+            synchronized (mGlobalLock) {
+                return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 639ed02..f9c9d33 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -1,5 +1,6 @@
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
@@ -9,7 +10,6 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
-import android.view.InputApplicationHandle;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
@@ -204,6 +204,37 @@
                 + WindowManagerService.TYPE_LAYER_OFFSET;
     }
 
+    /** Callback to get pointer display id. */
+    @Override
+    public int getPointerDisplayId() {
+        synchronized (mService.mGlobalLock) {
+            // If desktop mode is not enabled, show on the default display.
+            if (!mService.mForceDesktopModeOnExternalDisplays) {
+                return DEFAULT_DISPLAY;
+            }
+
+            // Look for the topmost freeform display.
+            int firstExternalDisplayId = DEFAULT_DISPLAY;
+            for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
+                final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
+                // Heuristic solution here. Currently when "Freeform windows" developer option is
+                // enabled we automatically put secondary displays in freeform mode and emulating
+                // "desktop mode". It also makes sense to show the pointer on the same display.
+                if (displayContent.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                    return displayContent.getDisplayId();
+                }
+
+                if (firstExternalDisplayId == DEFAULT_DISPLAY
+                        && displayContent.getDisplayId() != DEFAULT_DISPLAY) {
+                    firstExternalDisplayId = displayContent.getDisplayId();
+                }
+            }
+
+            // Look for the topmost non-default display
+            return firstExternalDisplayId;
+        }
+    }
+
     /** Waits until the built-in input devices have been configured. */
     public boolean waitForInputDevicesReady(long timeoutMillis) {
         synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index b4fe837..c8c5e8f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -107,6 +107,7 @@
         "android.hardware.gnss@1.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@2.0",
+        "android.hardware.input.classifier@1.0",
         "android.hardware.ir@1.0",
         "android.hardware.light@2.0",
         "android.hardware.power@1.0",
@@ -119,6 +120,7 @@
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
+        "android.hardware.vibrator@1.3",
         "android.hardware.vr@1.0",
         "android.frameworks.schedulerservice@1.0",
         "android.frameworks.sensorservice@1.0",
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index defcfd9..63dca62 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -22,6 +22,7 @@
 #include <android/hardware/vibrator/1.1/types.h>
 #include <android/hardware/vibrator/1.2/IVibrator.h>
 #include <android/hardware/vibrator/1.2/types.h>
+#include <android/hardware/vibrator/1.3/IVibrator.h>
 
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
@@ -42,6 +43,7 @@
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_1 = android::hardware::vibrator::V1_1;
 namespace V1_2 = android::hardware::vibrator::V1_2;
+namespace V1_3 = android::hardware::vibrator::V1_3;
 
 namespace android {
 
@@ -136,6 +138,19 @@
     }
 }
 
+static jboolean vibratorSupportsExternalControl(JNIEnv*, jobject) {
+    return halCall(&V1_3::IVibrator::supportsExternalControl).withDefault(false);
+}
+
+static void vibratorSetExternalControl(JNIEnv*, jobject, jboolean enabled) {
+    Status status = halCall(&V1_3::IVibrator::setExternalControl, static_cast<uint32_t>(enabled))
+        .withDefault(Status::UNKNOWN_ERROR);
+    if (status != Status::OK) {
+      ALOGE("Failed to set vibrator external control (%" PRIu32 ").",
+            static_cast<uint32_t>(status));
+    }
+}
+
 static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
     Status status;
     uint32_t lengthMs;
@@ -187,7 +202,9 @@
     { "vibratorOff", "()V", (void*)vibratorOff },
     { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
     { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
-    { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
+    { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect},
+    { "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
+    { "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
 };
 
 int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 43d2dcf..6f105ec 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@
     jmethodID getLongPressTimeout;
     jmethodID getPointerLayer;
     jmethodID getPointerIcon;
+    jmethodID getPointerDisplayId;
     jmethodID getKeyboardLayoutOverlay;
     jmethodID getDeviceAlias;
     jmethodID getTouchCalibrationForInputDevice;
@@ -174,15 +175,6 @@
     loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon);
 }
 
-static void updatePointerControllerFromViewport(
-        sp<PointerController> controller, const DisplayViewport* const viewport) {
-    if (controller != nullptr && viewport != nullptr) {
-        const int32_t width = viewport->logicalRight - viewport->logicalLeft;
-        const int32_t height = viewport->logicalBottom - viewport->logicalTop;
-        controller->setDisplayViewport(width, height, viewport->orientation);
-    }
-}
-
 enum {
     WM_ACTION_PASS_TO_USER = 1,
 };
@@ -310,14 +302,19 @@
 
         // Input devices to be disabled
         SortedVector<int32_t> disabledInputDevices;
+
+        // Associated Pointer controller display.
+        int32_t pointerDisplayId;
     } mLocked GUARDED_BY(mLock);
 
     std::atomic<bool> mInteractive;
 
-    void updateInactivityTimeoutLocked(const sp<PointerController>& controller);
+    void updateInactivityTimeoutLocked();
     void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
     void ensureSpriteControllerLocked();
-
+    const DisplayViewport* findDisplayViewportLocked(int32_t displayId);
+    int32_t getPointerDisplayId();
+    void updatePointerDisplayLocked();
     static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
 
     static inline JNIEnv* jniEnv() {
@@ -342,6 +339,7 @@
         mLocked.pointerGesturesEnabled = true;
         mLocked.showTouches = false;
         mLocked.pointerCapture = false;
+        mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
     }
     mInteractive = true;
 
@@ -391,9 +389,10 @@
     return false;
 }
 
-static const DisplayViewport* findInternalViewport(const std::vector<DisplayViewport>& viewports) {
-    for (const DisplayViewport& v : viewports) {
-        if (v.type == ViewportType::VIEWPORT_INTERNAL) {
+const DisplayViewport* NativeInputManager::findDisplayViewportLocked(int32_t displayId)
+        REQUIRES(mLock) {
+    for (const DisplayViewport& v : mLocked.viewports) {
+        if (v.displayId == displayId) {
             return &v;
         }
     }
@@ -420,20 +419,14 @@
         }
     }
 
-    const DisplayViewport* newInternalViewport = findInternalViewport(viewports);
-    {
+    // Get the preferred pointer controller displayId.
+    int32_t pointerDisplayId = getPointerDisplayId();
+
+    { // acquire lock
         AutoMutex _l(mLock);
-        const DisplayViewport* oldInternalViewport = findInternalViewport(mLocked.viewports);
-        // Internal viewport has changed if there wasn't one earlier, and there is one now, or,
-        // if they are different.
-        const bool internalViewportChanged = (newInternalViewport != nullptr) &&
-                (oldInternalViewport == nullptr || (*oldInternalViewport != *newInternalViewport));
-        if (internalViewportChanged) {
-            sp<PointerController> controller = mLocked.pointerController.promote();
-            updatePointerControllerFromViewport(controller, newInternalViewport);
-        }
         mLocked.viewports = viewports;
-    }
+        mLocked.pointerDisplayId = pointerDisplayId;
+    } // release lock
 
     mInputManager->getReader()->requestRefreshConfiguration(
             InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -556,15 +549,42 @@
 
         controller = new PointerController(this, mLooper, mLocked.spriteController);
         mLocked.pointerController = controller;
-
-        const DisplayViewport* internalViewport = findInternalViewport(mLocked.viewports);
-        updatePointerControllerFromViewport(controller, internalViewport);
-
-        updateInactivityTimeoutLocked(controller);
+        updateInactivityTimeoutLocked();
     }
+
+    updatePointerDisplayLocked();
+
     return controller;
 }
 
+int32_t NativeInputManager::getPointerDisplayId() {
+    JNIEnv* env = jniEnv();
+    jint pointerDisplayId = env->CallIntMethod(mServiceObj,
+            gServiceClassInfo.getPointerDisplayId);
+    if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) {
+        pointerDisplayId = ADISPLAY_ID_DEFAULT;
+    }
+
+    return pointerDisplayId;
+}
+
+void NativeInputManager::updatePointerDisplayLocked() REQUIRES(mLock) {
+    ATRACE_CALL();
+
+    sp<PointerController> controller = mLocked.pointerController.promote();
+    if (controller != nullptr) {
+        const DisplayViewport* viewport = findDisplayViewportLocked(mLocked.pointerDisplayId);
+        if (viewport == nullptr) {
+            ALOGW("Can't find pointer display viewport, fallback to default display.");
+            viewport = findDisplayViewportLocked(ADISPLAY_ID_DEFAULT);
+        }
+
+        if (viewport != nullptr) {
+            controller->setDisplayViewport(*viewport);
+        }
+    }
+}
+
 void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) {
     if (mLocked.spriteController == nullptr) {
         JNIEnv* env = jniEnv();
@@ -821,16 +841,16 @@
 
     if (mLocked.systemUiVisibility != visibility) {
         mLocked.systemUiVisibility = visibility;
-
-        sp<PointerController> controller = mLocked.pointerController.promote();
-        if (controller != nullptr) {
-            updateInactivityTimeoutLocked(controller);
-        }
+        updateInactivityTimeoutLocked();
     }
 }
 
-void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller)
-        REQUIRES(mLock) {
+void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) {
+    sp<PointerController> controller = mLocked.pointerController.promote();
+    if (controller == nullptr) {
+        return;
+    }
+
     bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
     controller->setInactivityTimeout(lightsOut
             ? PointerController::INACTIVITY_TIMEOUT_SHORT
@@ -1480,27 +1500,6 @@
     im->setSystemUiVisibility(visibility);
 }
 
-static jboolean nativeTransferTouchFocus(JNIEnv* env,
-        jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
-    sp<InputChannel> fromChannel =
-            android_view_InputChannel_getInputChannel(env, fromChannelObj);
-    sp<InputChannel> toChannel =
-            android_view_InputChannel_getInputChannel(env, toChannelObj);
-
-    if (fromChannel == nullptr || toChannel == nullptr) {
-        return JNI_FALSE;
-    }
-
-    if (im->getInputManager()->getDispatcher()->
-            transferTouchFocus(fromChannel, toChannel)) {
-        return JNI_TRUE;
-    } else {
-        return JNI_FALSE;
-    }
-}
-
 static void nativeSetPointerSpeed(JNIEnv* /* env */,
         jclass /* clazz */, jlong ptr, jint speed) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1688,8 +1687,6 @@
             (void*) nativeSetInputDispatchMode },
     { "nativeSetSystemUiVisibility", "(JI)V",
             (void*) nativeSetSystemUiVisibility },
-    { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
-            (void*) nativeTransferTouchFocus },
     { "nativeSetPointerSpeed", "(JI)V",
             (void*) nativeSetPointerSpeed },
     { "nativeSetShowTouches", "(JZ)V",
@@ -1824,6 +1821,9 @@
     GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
             "getPointerIcon", "()Landroid/view/PointerIcon;");
 
+    GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz,
+            "getPointerDisplayId", "()I");
+
     GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz,
             "getKeyboardLayoutOverlay",
             "(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;");
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 6d8fc1c..b290bc5 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -135,6 +135,7 @@
 using IGnss_V2_0 = android::hardware::gnss::V2_0::IGnss;
 using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
 using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
+using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
 using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement;
 using IGnssMeasurement_V1_1 = android::hardware::gnss::V1_1::IGnssMeasurement;
 using IGnssMeasurement_V2_0 = android::hardware::gnss::V2_0::IGnssMeasurement;
@@ -179,6 +180,7 @@
 sp<IGnssDebug> gnssDebugIface = nullptr;
 sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
 sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
+sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr;
 sp<IGnssNi> gnssNiIface = nullptr;
 sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
 sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -313,6 +315,15 @@
     }
 }
 
+static jobject createHalInterfaceVersionJavaObject(JNIEnv* env, jint major, jint minor) {
+    jclass versionClass =
+            env->FindClass("com/android/server/location/GnssConfiguration$HalInterfaceVersion");
+    jmethodID versionCtor = env->GetMethodID(versionClass, "<init>", "(II)V");
+    jobject version = env->NewObject(versionClass, versionCtor, major, minor);
+    env->DeleteLocalRef(versionClass);
+    return version;
+}
+
 struct ScopedJniString {
     ScopedJniString(JNIEnv* env, jstring javaString) : mEnv(env), mJavaString(javaString) {
         mNativeString = mEnv->GetStringUTFChars(mJavaString, nullptr);
@@ -1421,10 +1432,19 @@
         gnssNiIface = gnssNi;
     }
 
-    if (gnssHal_V1_1 != nullptr) {
+    if (gnssHal_V2_0 != nullptr) {
+        auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
+        if (!gnssConfiguration.isOk()) {
+            ALOGD("Unable to get a handle to GnssConfiguration_V2_0");
+        } else {
+            gnssConfigurationIface_V2_0 = gnssConfiguration;
+            gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_0;
+            gnssConfigurationIface = gnssConfigurationIface_V2_0;
+        }
+    } else if (gnssHal_V1_1 != nullptr) {
         auto gnssConfiguration = gnssHal_V1_1->getExtensionGnssConfiguration_1_1();
         if (!gnssConfiguration.isOk()) {
-            ALOGD("Unable to get a handle to GnssConfiguration");
+            ALOGD("Unable to get a handle to GnssConfiguration_V1_1");
         } else {
             gnssConfigurationIface_V1_1 = gnssConfiguration;
             gnssConfigurationIface = gnssConfigurationIface_V1_1;
@@ -1463,9 +1483,23 @@
     return (agnssRilIface != nullptr) ? JNI_TRUE : JNI_FALSE;
 }
 
-static jboolean android_location_gpsLocationProvider_is_gnss_configuration_supported(
-        JNIEnv* /* env */, jclass /* jclazz */) {
-    return (gnssConfigurationIface != nullptr) ? JNI_TRUE : JNI_FALSE;
+static jobject android_location_GnssConfiguration_get_gnss_configuration_version(
+        JNIEnv* env, jclass /* jclazz */) {
+    jint major, minor;
+    if (gnssConfigurationIface_V2_0 != nullptr) {
+        major = 2;
+        minor = 0;
+    } else if (gnssConfigurationIface_V1_1 != nullptr) {
+        major = 1;
+        minor = 1;
+    } else if (gnssConfigurationIface != nullptr) {
+        major = 1;
+        minor = 0;
+    } else {
+        return nullptr;
+    }
+
+    return createHalInterfaceVersionJavaObject(env, major, minor);
 }
 
 static jboolean android_location_GnssLocationProvider_init(JNIEnv* env, jobject obj) {
@@ -2278,9 +2312,9 @@
     return boolToJbool(result.isOk());
 }
 
-static jboolean android_location_GnssLocationProvider_set_emergency_supl_pdn(JNIEnv*,
-                                                                    jobject,
-                                                                    jint emergencySuplPdn) {
+static jboolean android_location_GnssConfiguration_set_emergency_supl_pdn(JNIEnv*,
+                                                                          jobject,
+                                                                          jint emergencySuplPdn) {
     if (gnssConfigurationIface == nullptr) {
         ALOGE("no GNSS configuration interface available");
         return JNI_FALSE;
@@ -2294,7 +2328,7 @@
     }
 }
 
-static jboolean android_location_GnssLocationProvider_set_supl_version(JNIEnv*,
+static jboolean android_location_GnssConfiguration_set_supl_version(JNIEnv*,
                                                                     jobject,
                                                                     jint version) {
     if (gnssConfigurationIface == nullptr) {
@@ -2309,9 +2343,14 @@
     }
 }
 
-static jboolean android_location_GnssLocationProvider_set_supl_es(JNIEnv*,
-                                                                    jobject,
-                                                                    jint suplEs) {
+static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*,
+                                                               jobject,
+                                                               jint suplEs) {
+    if (gnssConfigurationIface_V2_0 != nullptr) {
+        ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0.");
+        return JNI_FALSE;
+    }
+
     if (gnssConfigurationIface == nullptr) {
         ALOGE("no GNSS configuration interface available");
         return JNI_FALSE;
@@ -2325,9 +2364,9 @@
     }
 }
 
-static jboolean android_location_GnssLocationProvider_set_supl_mode(JNIEnv*,
-                                                                    jobject,
-                                                                    jint mode) {
+static jboolean android_location_GnssConfiguration_set_supl_mode(JNIEnv*,
+                                                                 jobject,
+                                                                 jint mode) {
     if (gnssConfigurationIface == nullptr) {
         ALOGE("no GNSS configuration interface available");
         return JNI_FALSE;
@@ -2341,9 +2380,14 @@
     }
 }
 
-static jboolean android_location_GnssLocationProvider_set_gps_lock(JNIEnv*,
-                                                                   jobject,
-                                                                   jint gpsLock) {
+static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*,
+                                                                jobject,
+                                                                jint gpsLock) {
+    if (gnssConfigurationIface_V2_0 != nullptr) {
+        ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0.");
+        return JNI_FALSE;
+    }
+
     if (gnssConfigurationIface == nullptr) {
         ALOGE("no GNSS configuration interface available");
         return JNI_FALSE;
@@ -2357,7 +2401,7 @@
     }
 }
 
-static jboolean android_location_GnssLocationProvider_set_lpp_profile(JNIEnv*,
+static jboolean android_location_GnssConfiguration_set_lpp_profile(JNIEnv*,
                                                                    jobject,
                                                                    jint lppProfile) {
     if (gnssConfigurationIface == nullptr) {
@@ -2374,9 +2418,9 @@
     }
 }
 
-static jboolean android_location_GnssLocationProvider_set_gnss_pos_protocol_select(JNIEnv*,
-                                                                   jobject,
-                                                                   jint gnssPosProtocol) {
+static jboolean android_location_GnssConfiguration_set_gnss_pos_protocol_select(JNIEnv*,
+                                                                            jobject,
+                                                                            jint gnssPosProtocol) {
     if (gnssConfigurationIface == nullptr) {
         ALOGE("no GNSS configuration interface available");
         return JNI_FALSE;
@@ -2390,7 +2434,7 @@
     }
 }
 
-static jboolean android_location_GnssLocationProvider_set_satellite_blacklist(
+static jboolean android_location_GnssConfiguration_set_satellite_blacklist(
         JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) {
     if (gnssConfigurationIface_V1_1 == nullptr) {
         ALOGI("No GNSS Satellite Blacklist interface available");
@@ -2431,6 +2475,27 @@
     }
 }
 
+static jboolean android_location_GnssConfiguration_set_es_extension_sec(
+        JNIEnv*, jobject, jint emergencyExtensionSeconds) {
+    if (gnssConfigurationIface == nullptr) {
+        ALOGE("no GNSS configuration interface available");
+        return JNI_FALSE;
+    }
+
+    if (gnssConfigurationIface_V2_0 == nullptr) {
+        ALOGI("Config parameter ES_EXTENSION_SEC is not supported in IGnssConfiguration.hal"
+                " versions earlier than 2.0.");
+        return JNI_FALSE;
+    }
+
+    auto result = gnssConfigurationIface_V2_0->setEsExtensionSec(emergencyExtensionSeconds);
+    if (result.isOk()) {
+        return result;
+    } else {
+        return JNI_FALSE;
+    }
+}
+
 static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass) {
     if (gnssBatchingIface == nullptr) {
         return 0; // batching not supported, size = 0
@@ -2498,17 +2563,14 @@
             android_location_GnssLocationProvider_class_init_native)},
     {"native_is_supported", "()Z", reinterpret_cast<void *>(
             android_location_GnssLocationProvider_is_supported)},
-    {"native_is_gnss_configuration_supported", "()Z",
-            reinterpret_cast<void *>(
-                    android_location_gpsLocationProvider_is_gnss_configuration_supported)},
     {"native_init_once", "()V", reinterpret_cast<void *>(
             android_location_GnssLocationProvider_init_once)},
     {"native_init", "()Z", reinterpret_cast<void *>(android_location_GnssLocationProvider_init)},
     {"native_cleanup", "()V", reinterpret_cast<void *>(
             android_location_GnssLocationProvider_cleanup)},
     {"native_set_position_mode",
-                "(IIIIIZ)Z",
-                reinterpret_cast<void*>(android_location_GnssLocationProvider_set_position_mode)},
+            "(IIIIIZ)Z",
+            reinterpret_cast<void*>(android_location_GnssLocationProvider_set_position_mode)},
     {"native_start", "()Z", reinterpret_cast<void*>(android_location_GnssLocationProvider_start)},
     {"native_stop", "()Z", reinterpret_cast<void*>(android_location_GnssLocationProvider_stop)},
     {"native_delete_aiding_data",
@@ -2545,31 +2607,6 @@
     {"native_get_internal_state",
             "()Ljava/lang/String;",
             reinterpret_cast<void *>(android_location_GnssLocationProvider_get_internal_state)},
-    {"native_set_supl_es",
-            "(I)Z",
-            reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_es)},
-    {"native_set_supl_version",
-            "(I)Z",
-            reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_version)},
-    {"native_set_supl_mode",
-            "(I)Z",
-            reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_mode)},
-    {"native_set_lpp_profile",
-            "(I)Z",
-            reinterpret_cast<void *>(android_location_GnssLocationProvider_set_lpp_profile)},
-    {"native_set_gnss_pos_protocol_select",
-            "(I)Z",
-            reinterpret_cast<void *>(
-                    android_location_GnssLocationProvider_set_gnss_pos_protocol_select)},
-    {"native_set_gps_lock",
-            "(I)Z",
-            reinterpret_cast<void *>(android_location_GnssLocationProvider_set_gps_lock)},
-    {"native_set_emergency_supl_pdn",
-            "(I)Z",
-            reinterpret_cast<void *>(android_location_GnssLocationProvider_set_emergency_supl_pdn)},
-    {"native_set_satellite_blacklist",
-            "([I[I)Z",
-            reinterpret_cast<void *>(android_location_GnssLocationProvider_set_satellite_blacklist)},
 };
 
 static const JNINativeMethod sMethodsBatching[] = {
@@ -2663,6 +2700,42 @@
             reinterpret_cast<void *>(android_location_GnssNetworkConnectivityHandler_agps_data_conn_failed)},
 };
 
+static const JNINativeMethod sConfigurationMethods[] = {
+     /* name, signature, funcPtr */
+    {"native_get_gnss_configuration_version",
+            "()Lcom/android/server/location/GnssConfiguration$HalInterfaceVersion;",
+            reinterpret_cast<void *>(
+                    android_location_GnssConfiguration_get_gnss_configuration_version)},
+    {"native_set_supl_es",
+            "(I)Z",
+            reinterpret_cast<void *>(android_location_GnssConfiguration_set_supl_es)},
+    {"native_set_supl_version",
+            "(I)Z",
+            reinterpret_cast<void *>(android_location_GnssConfiguration_set_supl_version)},
+    {"native_set_supl_mode",
+            "(I)Z",
+            reinterpret_cast<void *>(android_location_GnssConfiguration_set_supl_mode)},
+    {"native_set_lpp_profile",
+            "(I)Z",
+            reinterpret_cast<void *>(android_location_GnssConfiguration_set_lpp_profile)},
+    {"native_set_gnss_pos_protocol_select",
+            "(I)Z",
+            reinterpret_cast<void *>(
+                    android_location_GnssConfiguration_set_gnss_pos_protocol_select)},
+    {"native_set_gps_lock",
+            "(I)Z",
+            reinterpret_cast<void *>(android_location_GnssConfiguration_set_gps_lock)},
+    {"native_set_emergency_supl_pdn",
+            "(I)Z",
+            reinterpret_cast<void *>(android_location_GnssConfiguration_set_emergency_supl_pdn)},
+    {"native_set_satellite_blacklist",
+            "([I[I)Z",
+            reinterpret_cast<void *>(android_location_GnssConfiguration_set_satellite_blacklist)},
+    {"native_set_es_extension_sec",
+            "(I)Z",
+            reinterpret_cast<void *>(android_location_GnssConfiguration_set_es_extension_sec)},
+};
+
 int register_android_server_location_GnssLocationProvider(JNIEnv* env) {
     jniRegisterNativeMethods(
             env,
@@ -2689,6 +2762,11 @@
             "com/android/server/location/GnssNetworkConnectivityHandler",
             sNetworkConnectivityMethods,
             NELEM(sNetworkConnectivityMethods));
+    jniRegisterNativeMethods(
+            env,
+            "com/android/server/location/GnssConfiguration",
+            sConfigurationMethods,
+            NELEM(sConfigurationMethods));
     return jniRegisterNativeMethods(
             env,
             "com/android/server/location/GnssLocationProvider",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7f6895a..76ae5cc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -221,7 +221,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -245,6 +245,7 @@
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.UserRestrictionsUtils;
 import com.android.server.storage.DeviceStorageMonitorInternal;
@@ -5322,6 +5323,7 @@
         }
 
         final int callingUserId = mInjector.userHandleGetCallingUserId();
+        ComponentName adminComponent = null;
         synchronized (getLockObject()) {
             // Make sure the caller has any active admin with the right policy or
             // the required permission.
@@ -5332,8 +5334,7 @@
                     android.Manifest.permission.LOCK_DEVICE);
             final long ident = mInjector.binderClearCallingIdentity();
             try {
-                final ComponentName adminComponent = admin == null ?
-                        null : admin.info.getComponent();
+                adminComponent = admin == null ? null : admin.info.getComponent();
                 if (adminComponent != null) {
                     // For Profile Owners only, callers with only permission not allowed.
                     if ((flags & DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY) != 0) {
@@ -5385,6 +5386,7 @@
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.LOCK_NOW)
+                .setAdmin(adminComponent)
                 .setInt(flags)
                 .write();
     }
@@ -6101,7 +6103,9 @@
         synchronized (getLockObject()) {
             delegates = getDelegatePackagesInternalLocked(scope, userId);
         }
-        if (delegates.size() != 1) {
+        if (delegates.size() == 0) {
+            return null;
+        } else if (delegates.size() > 1) {
             Slog.wtf(LOG_TAG, "More than one delegate holds " + scope);
             return null;
         }
@@ -9188,21 +9192,15 @@
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
 
-        // TODO When InputMethodManager supports per user calls remove
-        //      this restriction.
-        if (!checkCallerIsCurrentUserOrProfile()) {
+        // TODO When InputMethodManager supports per user calls remove this restriction.
+        if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
+                && !checkCallerIsCurrentUserOrProfile()) {
             return false;
         }
-
         final int callingUserId = mInjector.userHandleGetCallingUserId();
         if (packageList != null) {
-            // InputMethodManager fetches input methods for current user.
-            // So this can only be set when calling user is the current user
-            // or parent is current user in case of managed profiles.
-            InputMethodManager inputMethodManager =
-                    mContext.getSystemService(InputMethodManager.class);
-            List<InputMethodInfo> enabledImes = inputMethodManager.getEnabledInputMethodList();
-
+            List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get()
+                    .getEnabledInputMethodListAsUser(callingUserId);
             if (enabledImes != null) {
                 List<String> enabledPackages = new ArrayList<String>();
                 for (InputMethodInfo ime : enabledImes) {
@@ -9250,22 +9248,16 @@
     @Override
     public List getPermittedInputMethodsForCurrentUser() {
         enforceManageUsers();
-        UserInfo currentUser;
-        try {
-            currentUser = mInjector.getIActivityManager().getCurrentUser();
-        } catch (RemoteException e) {
-            Slog.e(LOG_TAG, "Failed to make remote calls to get current user", e);
-            // Activity managed is dead, just allow all IMEs
-            return null;
-        }
 
-        int userId = currentUser.id;
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
         synchronized (getLockObject()) {
             List<String> result = null;
             // If we have multiple profiles we return the intersection of the
             // permitted lists. This can happen in cases where we have a device
             // and profile owner.
-            int[] profileIds = mUserManager.getProfileIdsWithDisabled(userId);
+            int[] profileIds = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
+                    ? new int[]{callingUserId}
+                    : mUserManager.getProfileIdsWithDisabled(callingUserId);
             for (int profileId : profileIds) {
                 // Just loop though all admins, only device or profiles
                 // owners can have permitted lists set.
@@ -9286,9 +9278,8 @@
 
             // If we have a permitted list add all system input methods.
             if (result != null) {
-                InputMethodManager inputMethodManager =
-                        mContext.getSystemService(InputMethodManager.class);
-                List<InputMethodInfo> imes = inputMethodManager.getInputMethodList();
+                List<InputMethodInfo> imes =
+                        InputMethodManagerInternal.get().getInputMethodListAsUser(callingUserId);
                 if (imes != null) {
                     for (InputMethodInfo ime : imes) {
                         ServiceInfo serviceInfo = ime.getServiceInfo();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index d0ec0ee..699bec2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -23,6 +23,7 @@
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.app.admin.DeviceAdminReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -30,15 +31,13 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.ArraySet;
 import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.view.IInputMethodManager;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import java.util.Arrays;
 import java.util.List;
@@ -53,18 +52,38 @@
     protected static final String TAG = "OverlayPackagesProvider";
 
     private final PackageManager mPm;
-    private final IInputMethodManager mIInputMethodManager;
     private final Context mContext;
+    private final Injector mInjector;
 
     public OverlayPackagesProvider(Context context) {
-        this(context, getIInputMethodManager());
+        this(context, new DefaultInjector());
     }
 
     @VisibleForTesting
-    OverlayPackagesProvider(Context context, IInputMethodManager iInputMethodManager) {
+    interface Injector {
+        boolean isPerProfileImeEnabled();
+        @NonNull
+        List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
+    }
+
+    private static final class DefaultInjector implements Injector {
+        @Override
+        public boolean isPerProfileImeEnabled() {
+            return InputMethodSystemProperty.PER_PROFILE_IME_ENABLED;
+        }
+
+        @NonNull
+        @Override
+        public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
+            return InputMethodManagerInternal.get().getInputMethodListAsUser(userId);
+        }
+    }
+
+    @VisibleForTesting
+    OverlayPackagesProvider(Context context, Injector injector) {
         mContext = context;
         mPm = checkNotNull(context.getPackageManager());
-        mIInputMethodManager = checkNotNull(iInputMethodManager);
+        mInjector = checkNotNull(injector);
     }
 
     /**
@@ -89,10 +108,12 @@
         // Newly installed system apps are uninstalled when they are not required and are either
         // disallowed or have a launcher icon.
         nonRequiredApps.removeAll(getRequiredApps(provisioningAction, admin.getPackageName()));
-        // Don't delete the system input method packages in case of Device owner provisioning.
-        if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction)
+        if (mInjector.isPerProfileImeEnabled()) {
+            nonRequiredApps.removeAll(getSystemInputMethods(userId));
+        } else if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction)
                 || ACTION_PROVISION_MANAGED_USER.equals(provisioningAction)) {
-            nonRequiredApps.removeAll(getSystemInputMethods());
+            // Don't delete the system input method packages in case of Device owner provisioning.
+            nonRequiredApps.removeAll(getSystemInputMethods(userId));
         }
         nonRequiredApps.addAll(getDisallowedApps(provisioningAction));
         return nonRequiredApps;
@@ -114,16 +135,8 @@
         return apps;
     }
 
-    private Set<String> getSystemInputMethods() {
-        // InputMethodManager is final so it cannot be mocked.
-        // So, we're using IInputMethodManager directly because it can be mocked.
-        final List<InputMethodInfo> inputMethods;
-        try {
-            inputMethods = mIInputMethodManager.getInputMethodList();
-        } catch (RemoteException e) {
-            // Should not happen
-            return null;
-        }
+    private Set<String> getSystemInputMethods(int userId) {
+        final List<InputMethodInfo> inputMethods = mInjector.getInputMethodListAsUser(userId);
         final Set<String> systemInputMethods = new ArraySet<>();
         for (InputMethodInfo inputMethodInfo : inputMethods) {
             ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo;
@@ -149,11 +162,6 @@
         return disallowedApps;
     }
 
-    private static IInputMethodManager getIInputMethodManager() {
-        final IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
-        return IInputMethodManager.Stub.asInterface(b);
-    }
-
     private Set<String> getRequiredAppsSet(String provisioningAction) {
         final int resId;
         switch (provisioningAction) {
diff --git a/services/ipmemorystore/Android.bp b/services/ipmemorystore/Android.bp
new file mode 100644
index 0000000..013cf56
--- /dev/null
+++ b/services/ipmemorystore/Android.bp
@@ -0,0 +1,4 @@
+java_library_static {
+    name: "services.ipmemorystore",
+    srcs: ["java/**/*.java"],
+}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
new file mode 100644
index 0000000..c9759bf
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -0,0 +1,151 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.IIpMemoryStore;
+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;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+
+/**
+ * Implementation for the IP memory store.
+ * This component offers specialized services for network components to store and retrieve
+ * knowledge about networks, and provides intelligence that groups level 2 networks together
+ * into level 3 networks.
+ *
+ * @hide
+ */
+public class IpMemoryStoreService extends IIpMemoryStore.Stub {
+    final Context mContext;
+
+    public IpMemoryStoreService(@NonNull final Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Store network attributes for a given L2 key.
+     *
+     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+     *              key and only care about grouping can pass a unique ID here like the ones
+     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+     *              relevance of such a network will lead to it being evicted soon if it's not
+     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+     * @param attributes The attributes for this network.
+     * @param listener A listener to inform of the completion of this call, or null if the client
+     *        is not interested in learning about success/failure.
+     * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+     * If the call failed, the L2 key will be null.
+     */
+    @Override
+    public void storeNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final NetworkAttributesParcelable attributes,
+            @Nullable final IOnStatusListener listener) {
+        // TODO : implement this
+    }
+
+    /**
+     * Store a binary blob associated with an L2 key and a name.
+     *
+     * @param l2Key The L2 key for this network.
+     * @param clientId The ID of the client.
+     * @param name The name of this data.
+     * @param data The data to store.
+     * @param listener The listener that will be invoked to return the answer, or null if the
+     *        is not interested in learning about success/failure.
+     * Through the listener, returns a status to indicate success or failure.
+     */
+    @Override
+    public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final Blob data,
+            @Nullable final IOnStatusListener listener) {
+        // TODO : implement this
+    }
+
+    /**
+     * Returns the best L2 key associated with the attributes.
+     *
+     * This will find a record that would be in the same group as the passed attributes. This is
+     * useful to choose the key for storing a sample or private data when the L2 key is not known.
+     * If multiple records are group-close to these attributes, the closest match is returned.
+     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+     * order) L2 key is returned.
+     * If no record matches these attributes, null is returned.
+     *
+     * @param attributes The attributes of the network to find.
+     * @param listener The listener that will be invoked to return the answer.
+     * 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
+    }
+
+    /**
+     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+     * to the same L3 network. Group-closeness is used to determine this.
+     *
+     * @param l2Key1 The key for the first network.
+     * @param l2Key2 The key for the second network.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+     */
+    @Override
+    public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+            @NonNull final IOnSameNetworkResponseListener listener) {
+        // TODO : implement this
+    }
+
+    /**
+     * Retrieve the network attributes for a key.
+     * If no record is present for this key, this will return null attributes.
+     *
+     * @param l2Key The key of the network to query.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the network attributes and the L2 key associated with
+     *         the query.
+     */
+    @Override
+    public void retrieveNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final IOnNetworkAttributesRetrieved listener) {
+        // TODO : implement this.
+    }
+
+    /**
+     * Retrieve previously stored private data.
+     * If no data was stored for this L2 key and name this will return null.
+     *
+     * @param l2Key The L2 key.
+     * @param clientId The id of the client that stored this data.
+     * @param name The name of the data.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the private data if any or null if none, with the L2 key
+     *         and the name of the data associated with the query.
+     */
+    @Override
+    public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
+        // TODO : implement this.
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4326c39..e960fd1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -102,10 +102,12 @@
 import com.android.server.media.projection.MediaProjectionManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
+import com.android.server.net.ipmemorystore.IpMemoryStoreService;
 import com.android.server.net.watchlist.NetworkWatchlistService;
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.oemlock.OemLockService;
 import com.android.server.om.OverlayManagerService;
+import com.android.server.os.BugreportManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.pm.BackgroundDexOptService;
@@ -260,6 +262,10 @@
             "com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
     private static final String ADB_SERVICE_CLASS =
             "com.android.server.adb.AdbService$Lifecycle";
+    private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
+            "com.android.server.appprediction.AppPredictionManagerService";
+    private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
+            "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
 
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
@@ -786,6 +792,11 @@
         traceBeginAndSlog("StartRollbackManagerService");
         mSystemServiceManager.startService(RollbackManagerService.class);
         traceEnd();
+
+        // Service to capture bugreports.
+        traceBeginAndSlog("StartBugreportManagerService");
+        mSystemServiceManager.startService(BugreportManagerService.class);
+        traceEnd();
     }
 
     /**
@@ -1020,6 +1031,18 @@
             Slog.e("System", "************ Failure starting core service", e);
         }
 
+        // Before things start rolling, be sure we have decided whether
+        // we are in safe mode.
+        final boolean safeMode = wm.detectSafeMode();
+        if (safeMode) {
+            // If yes, immediately turn on the global setting for airplane mode.
+            // Note that this does not send broadcasts at this stage because
+            // subsystems are not yet up. We will send broadcasts later to ensure
+            // all listeners have the chance to react with special handling.
+            Settings.Global.putInt(context.getContentResolver(),
+                    Settings.Global.AIRPLANE_MODE_ON, 1);
+        }
+
         StatusBarManagerService statusBar = null;
         INotificationManager notification = null;
         LocationManagerService location = null;
@@ -1153,6 +1176,16 @@
 
             startContentCaptureService(context);
 
+            // App prediction manager service
+            traceBeginAndSlog("StartAppPredictionService");
+            mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
+            traceEnd();
+
+            // Content suggestions manager service
+            traceBeginAndSlog("StartContentSuggestionsService");
+            mSystemServiceManager.startService(CONTENT_SUGGESTIONS_SERVICE_CLASS);
+            traceEnd();
+
             // NOTE: ClipboardService indirectly depends on IntelligenceService
             traceBeginAndSlog("StartClipboardService");
             mSystemServiceManager.startService(ClipboardService.class);
@@ -1167,6 +1200,15 @@
             }
             traceEnd();
 
+            traceBeginAndSlog("StartIpMemoryStoreService");
+            try {
+                ServiceManager.addService(Context.IP_MEMORY_STORE_SERVICE,
+                        new IpMemoryStoreService(context));
+            } catch (Throwable e) {
+                reportWtf("starting IP Memory Store Service", e);
+            }
+            traceEnd();
+
             traceBeginAndSlog("StartIpSecService");
             try {
                 ipSecService = IpSecService.create(context);
@@ -1786,9 +1828,6 @@
         mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
         traceEnd();
 
-        // Before things start rolling, be sure we have decided whether
-        // we are in safe mode.
-        final boolean safeMode = wm.detectSafeMode();
         if (safeMode) {
             traceBeginAndSlog("EnterSafeModeAndDisableJitCompilation");
             mActivityManagerService.enterSafeMode();
@@ -1985,6 +2024,20 @@
                 reportWtf("starting System UI", e);
             }
             traceEnd();
+            // Enable airplane mode in safe mode. setAirplaneMode() cannot be called
+            // earlier as it sends broadcasts to other services.
+            // TODO: This may actually be too late if radio firmware already started leaking
+            // RF before the respective services start. However, fixing this requires changes
+            // to radio firmware and interfaces.
+            if (safeMode) {
+                traceBeginAndSlog("EnableAirplaneModeInSafeMode");
+                try {
+                    connectivityF.setAirplaneMode(true);
+                } catch (Throwable e) {
+                    reportWtf("enabling Airplane Mode during Safe Mode bootup", e);
+                }
+                traceEnd();
+            }
             traceBeginAndSlog("MakeNetworkManagementServiceReady");
             try {
                 if (networkManagementF != null) {
@@ -2179,7 +2232,7 @@
         windowManager.onSystemUiStarted();
     }
 
-    private static void traceBeginAndSlog(String name) {
+    private static void traceBeginAndSlog(@NonNull String name) {
         Slog.i(TAG, name);
         BOOT_TIMINGS_TRACE_LOG.traceBegin(name);
     }
diff --git a/services/net/Android.bp b/services/net/Android.bp
index ae697b7..3b4d6a7 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -11,10 +11,10 @@
     ]
 }
 
-// TODO: move to networking module with IpNeighborMonitor/ConnectivityPacketTracker and remove lib
-java_library {
-    name: "frameworks-net-shared-utils",
+filegroup {
+    name: "services-networkstack-shared-srcs",
     srcs: [
-        "java/android/net/util/FdEventsReader.java",
+        "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient
+        "java/android/net/shared/*.java",
     ]
-}
\ No newline at end of file
+}
diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java
new file mode 100644
index 0000000..463cf2a
--- /dev/null
+++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.net.shared;
+
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.provider.Settings;
+
+/** @hide */
+public class NetworkMonitorUtils {
+
+    // Network conditions broadcast constants
+    public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
+            "android.net.conn.NETWORK_CONDITIONS_MEASURED";
+    public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
+    public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
+    public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
+    public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
+    public static final String EXTRA_CELL_ID = "extra_cellid";
+    public static final String EXTRA_SSID = "extra_ssid";
+    public static final String EXTRA_BSSID = "extra_bssid";
+    /** real time since boot */
+    public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
+    public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
+    public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
+            "android.permission.ACCESS_NETWORK_CONDITIONS";
+
+    // TODO: once the URL is a resource overlay, remove and have the resource define the default
+    private static final String DEFAULT_HTTP_URL =
+            "http://connectivitycheck.gstatic.com/generate_204";
+
+    /**
+     * Get the captive portal server HTTP URL that is configured on the device.
+     */
+    public static String getCaptivePortalServerHttpUrl(Context context) {
+        final String settingUrl = Settings.Global.getString(
+                context.getContentResolver(),
+                Settings.Global.CAPTIVE_PORTAL_HTTP_URL);
+        return settingUrl != null ? settingUrl : DEFAULT_HTTP_URL;
+    }
+
+    /**
+     * Return whether validation is required for a network.
+     * @param dfltNetCap Default requested network capabilities.
+     * @param nc Network capabilities of the network to test.
+     */
+    public static boolean isValidationRequired(
+            NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
+        // TODO: Consider requiring validation for DUN networks.
+        return dfltNetCap.satisfiedByNetworkCapabilities(nc);
+    }
+}
diff --git a/services/net/java/android/net/shared/PrivateDnsConfig.java b/services/net/java/android/net/shared/PrivateDnsConfig.java
new file mode 100644
index 0000000..41e0bad
--- /dev/null
+++ b/services/net/java/android/net/shared/PrivateDnsConfig.java
@@ -0,0 +1,94 @@
+/*
+ * 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 android.net.shared;
+
+import android.net.InetAddresses;
+import android.net.PrivateDnsConfigParcel;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+/** @hide */
+public class PrivateDnsConfig {
+    public final boolean useTls;
+    public final String hostname;
+    public final InetAddress[] ips;
+
+    public PrivateDnsConfig() {
+        this(false);
+    }
+
+    public PrivateDnsConfig(boolean useTls) {
+        this.useTls = useTls;
+        this.hostname = "";
+        this.ips = new InetAddress[0];
+    }
+
+    public PrivateDnsConfig(String hostname, InetAddress[] ips) {
+        this.useTls = !TextUtils.isEmpty(hostname);
+        this.hostname = useTls ? hostname : "";
+        this.ips = (ips != null) ? ips : new InetAddress[0];
+    }
+
+    public PrivateDnsConfig(PrivateDnsConfig cfg) {
+        useTls = cfg.useTls;
+        hostname = cfg.hostname;
+        ips = cfg.ips;
+    }
+
+    /**
+     * Indicates whether this is a strict mode private DNS configuration.
+     */
+    public boolean inStrictMode() {
+        return useTls && !TextUtils.isEmpty(hostname);
+    }
+
+    @Override
+    public String toString() {
+        return PrivateDnsConfig.class.getSimpleName()
+                + "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
+    }
+
+    /**
+     * Create a stable AIDL-compatible parcel from the current instance.
+     */
+    public PrivateDnsConfigParcel toParcel() {
+        final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel();
+        parcel.hostname = hostname;
+
+        final String[] parceledIps = new String[ips.length];
+        for (int i = 0; i < ips.length; i++) {
+            parceledIps[i] = ips[i].getHostAddress();
+        }
+        parcel.ips = parceledIps;
+
+        return parcel;
+    }
+
+    /**
+     * Build a configuration from a stable AIDL-compatible parcel.
+     */
+    public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) {
+        final InetAddress[] ips = new InetAddress[parcel.ips.length];
+        for (int i = 0; i < ips.length; i++) {
+            ips[i] = InetAddresses.parseNumericAddress(parcel.ips[i]);
+        }
+
+        return new PrivateDnsConfig(parcel.hostname, ips);
+    }
+}
diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java
index 8b7b59d..2cdb2b0 100644
--- a/services/net/java/android/net/util/SharedLog.java
+++ b/services/net/java/android/net/util/SharedLog.java
@@ -70,6 +70,10 @@
         mComponent = component;
     }
 
+    public String getTag() {
+        return mTag;
+    }
+
     /**
      * Create a SharedLog based on this log with an additional component prefix on each logged line.
      */
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 5e84ab0..7dac795 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
@@ -71,6 +71,7 @@
 
 import android.annotation.Nullable;
 import android.app.Application;
+import android.app.ApplicationPackageManager;
 import android.app.IBackupAgent;
 import android.app.backup.BackupAgent;
 import android.app.backup.BackupDataInput;
@@ -134,6 +135,8 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
 import org.robolectric.shadows.ShadowLooper;
 import org.robolectric.shadows.ShadowPackageManager;
 import org.robolectric.shadows.ShadowQueuedWork;
@@ -157,6 +160,7 @@
 @Config(
         shadows = {
             FrameworkShadowLooper.class,
+            KeyValueBackupTaskTest.ShadowApplicationPackageManager.class,
             ShadowBackupDataInput.class,
             ShadowBackupDataOutput.class,
             ShadowEventLog.class,
@@ -244,6 +248,7 @@
     @After
     public void tearDown() throws Exception {
         ShadowBackupDataInput.reset();
+        ShadowApplicationPackageManager.reset();
     }
 
     @Test
@@ -2435,7 +2440,8 @@
             mPackageManager.setApplicationEnabledSetting(
                     packageData.packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
             PackageInfo packageInfo = getPackageInfo(packageData);
-            mShadowPackageManager.addPackage(packageInfo);
+            mShadowPackageManager.installPackage(packageInfo);
+            ShadowApplicationPackageManager.setPackageInfo(packageInfo);
             mContext.sendBroadcast(getPackageAddedIntent(packageData));
             // Run the backup looper because on the receiver we post MSG_SCHEDULE_BACKUP_PACKAGE
             mShadowBackupLooper.runToEndOfTasks();
@@ -2848,4 +2854,29 @@
             throw mException;
         }
     }
+
+    /**
+     * Extends {@link org.robolectric.shadows.ShadowApplicationPackageManager} to return the correct
+     * package in user-specific invocations.
+     */
+    @Implements(value = ApplicationPackageManager.class)
+    public static class ShadowApplicationPackageManager
+            extends org.robolectric.shadows.ShadowApplicationPackageManager {
+        private static PackageInfo sPackageInfo;
+
+        static void setPackageInfo(PackageInfo packageInfo) {
+            sPackageInfo = packageInfo;
+        }
+
+        @Override
+        protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) {
+            return sPackageInfo;
+        }
+
+        /** Clear {@link #sPackageInfo}. */
+        @Resetter
+        public static void reset() {
+            sPackageInfo = null;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 6a10ff4..2cc338c 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -70,7 +70,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
 import com.android.server.am.ProcessList.IsolatedUidRange;
 import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
 import com.android.server.wm.ActivityTaskManagerService;
diff --git a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
index 05cb48b..8109b1c 100644
--- a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
@@ -25,7 +25,7 @@
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
index 9626990..cbdc6c3 100644
--- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
@@ -34,7 +34,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
 
 import org.junit.AfterClass;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index 7e0dfcf..65c5781 100644
--- a/services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.appops;
+package com.android.server.appop;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 4ae9bea..36f84d0 100644
--- a/services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server;
+package com.android.server.appop;
 
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
@@ -36,6 +36,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.appop.AppOpsService;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index aac96a1..eb0c627 100644
--- a/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.appop;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 34edd9f..757a046 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -32,7 +32,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
-import android.os.RemoteException;
 import android.test.AndroidTestCase;
 import android.test.mock.MockPackageManager;
 import android.view.inputmethod.InputMethodInfo;
@@ -40,7 +39,6 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.R;
-import com.android.internal.view.IInputMethodManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -61,8 +59,8 @@
 
     private @Mock
     Resources mResources;
-    private @Mock
-    IInputMethodManager mIInputMethodManager;
+    @Mock
+    private OverlayPackagesProvider.Injector mInjector;
     private @Mock
     Context mTestContext;
     private Resources mRealResources;
@@ -81,6 +79,7 @@
                 InstrumentationRegistry.getTargetContext().getCacheDir());
 
         setSystemInputMethods();
+        setIsPerProfileModeEnabled(false);
         setRequiredAppsManagedDevice();
         setVendorRequiredAppsManagedDevice();
         setDisallowedAppsManagedDevice();
@@ -95,7 +94,7 @@
         setVendorDisallowedAppsManagedUser();
 
         mRealResources = InstrumentationRegistry.getTargetContext().getResources();
-        mHelper = new OverlayPackagesProvider(mTestContext, mIInputMethodManager);
+        mHelper = new OverlayPackagesProvider(mTestContext, mInjector);
     }
 
     @Test
@@ -165,6 +164,15 @@
     }
 
     @Test
+    public void testProfileOwnerImesAreRequiredForPerProfileImeMode() {
+        setSystemAppsWithLauncher("app.a", "app.b");
+        setSystemInputMethods("app.a");
+        setIsPerProfileModeEnabled(true);
+
+        verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_PROFILE, "app.b");
+    }
+
+    @Test
     public void testManagedUserImesAreRequired() {
         setSystemAppsWithLauncher("app.a", "app.b");
         setSystemInputMethods("app.a");
@@ -333,11 +341,11 @@
             InputMethodInfo inputMethodInfo = new InputMethodInfo(ri, false, null, null, 0, false);
             inputMethods.add(inputMethodInfo);
         }
-        try {
-            when(mIInputMethodManager.getInputMethodList()).thenReturn(inputMethods);
-        } catch (RemoteException e) {
-            fail(e.toString());
-        }
+        when(mInjector.getInputMethodListAsUser(eq(TEST_USER_ID))).thenReturn(inputMethods);
+    }
+
+    private void setIsPerProfileModeEnabled(boolean enabled) {
+        when(mInjector.isPerProfileImeEnabled()).thenReturn(enabled);
     }
 
     private void setSystemAppsWithLauncher(String... apps) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index bdede33..c442ae8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -17,6 +17,8 @@
 
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
@@ -52,6 +54,7 @@
     private HdmiControlService mHdmiControlService;
     private HdmiCecController mHdmiCecController;
     private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
+    private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
     private FakeNativeWrapper mNativeWrapper;
     private Looper mMyLooper;
     private TestLooper mTestLooper = new TestLooper();
@@ -64,82 +67,86 @@
     @Before
     public void setUp() {
         mHdmiControlService =
-                new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
-                    @Override
-                    AudioManager getAudioManager() {
-                        return new AudioManager() {
-                            @Override
-                            public int getStreamVolume(int streamType) {
-                                switch (streamType) {
-                                    case STREAM_MUSIC:
-                                        return mMusicVolume;
-                                    default:
-                                        return 0;
-                                }
+            new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+                @Override
+                AudioManager getAudioManager() {
+                    return new AudioManager() {
+                        @Override
+                        public int getStreamVolume(int streamType) {
+                            switch (streamType) {
+                                case STREAM_MUSIC:
+                                    return mMusicVolume;
+                                default:
+                                    return 0;
                             }
+                        }
 
-                            @Override
-                            public boolean isStreamMute(int streamType) {
-                                switch (streamType) {
-                                    case STREAM_MUSIC:
-                                        return mMusicMute;
-                                    default:
-                                        return false;
-                                }
+                        @Override
+                        public boolean isStreamMute(int streamType) {
+                            switch (streamType) {
+                                case STREAM_MUSIC:
+                                    return mMusicMute;
+                                default:
+                                    return false;
                             }
+                        }
 
-                            @Override
-                            public int getStreamMaxVolume(int streamType) {
-                                switch (streamType) {
-                                    case STREAM_MUSIC:
-                                        return mMusicMaxVolume;
-                                    default:
-                                        return 100;
-                                }
+                        @Override
+                        public int getStreamMaxVolume(int streamType) {
+                            switch (streamType) {
+                                case STREAM_MUSIC:
+                                    return mMusicMaxVolume;
+                                default:
+                                    return 100;
                             }
+                        }
 
-                            @Override
-                            public void adjustStreamVolume(
-                                    int streamType, int direction, int flags) {
-                                switch (streamType) {
-                                    case STREAM_MUSIC:
-                                        if (direction == AudioManager.ADJUST_UNMUTE) {
-                                            mMusicMute = false;
-                                        } else if (direction == AudioManager.ADJUST_MUTE) {
-                                            mMusicMute = true;
-                                        }
-                                    default:
-                                }
+                        @Override
+                        public void adjustStreamVolume(
+                                int streamType, int direction, int flags) {
+                            switch (streamType) {
+                                case STREAM_MUSIC:
+                                    if (direction == AudioManager.ADJUST_UNMUTE) {
+                                        mMusicMute = false;
+                                    } else if (direction == AudioManager.ADJUST_MUTE) {
+                                        mMusicMute = true;
+                                    }
+                                    break;
+                                default:
                             }
+                        }
 
-                            @Override
-                            public void setWiredDeviceConnectionState(
-                                    int type, int state, String address, String name) {
-                                // Do nothing.
-                            }
-                        };
-                    }
+                        @Override
+                        public void setWiredDeviceConnectionState(
+                                int type, int state, String address, String name) {
+                            // Do nothing.
+                        }
+                    };
+                }
 
-                    @Override
-                    void wakeUp() {}
+                @Override
+                void wakeUp() {}
 
-                    @Override
-                    boolean isControlEnabled() {
-                        return true;
-                    }
-                };
+                @Override
+                boolean isControlEnabled() {
+                    return true;
+                }
+            };
 
         mMyLooper = mTestLooper.getLooper();
         mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+        mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
         mHdmiCecLocalDeviceAudioSystem.init();
+        mHdmiCecLocalDevicePlayback.init();
         mHdmiControlService.setIoLooper(mMyLooper);
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController =
-                HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+            HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
         mHdmiControlService.setCecController(mHdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
         mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
+        mLocalDevices.add(mHdmiCecLocalDevicePlayback);
         mHdmiControlService.initPortInfo();
         // No TV device interacts with AVR so system audio control won't be turned on here
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -170,11 +177,12 @@
     @Test
     public void handleGiveSystemAudioModeStatus_originalOff() throws Exception {
         HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
+                HdmiCecMessageBuilder.buildReportSystemAudioMode(
+                        ADDR_AUDIO_SYSTEM, ADDR_TV, false);
         HdmiCecMessage messageGive =
                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -190,9 +198,9 @@
 
         mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
-                                MESSAGE_REQUEST_SAD_LCPM))
-                .isTrue();
+            mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+                MESSAGE_REQUEST_SAD_LCPM))
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -206,11 +214,11 @@
                         Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
                         Constants.ABORT_NOT_IN_CORRECT_MODE);
 
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
-                                MESSAGE_REQUEST_SAD_LCPM))
-                .isEqualTo(true);
+            mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+                MESSAGE_REQUEST_SAD_LCPM))
+            .isEqualTo(true);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -224,11 +232,11 @@
                         Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
                         Constants.ABORT_UNABLE_TO_DETERMINE);
 
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
-                                MESSAGE_REQUEST_SAD_LCPM))
-                .isEqualTo(true);
+            mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+                MESSAGE_REQUEST_SAD_LCPM))
+            .isEqualTo(true);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -244,17 +252,17 @@
         HdmiCecMessage expectedMessage =
                 HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
         // Check if correctly turned on
         mNativeWrapper.clearResultMessages();
         expectedMessage =
-                HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
+            HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue();
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
         assertThat(mMusicMute).isFalse();
@@ -273,15 +281,15 @@
                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
                         ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
 
         mNativeWrapper.clearResultMessages();
         expectedMessage =
-                HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
+            HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
         assertThat(mMusicMute).isTrue();
@@ -292,7 +300,7 @@
         mHdmiCecLocalDeviceAudioSystem.setAutoDeviceOff(false);
         mHdmiCecLocalDeviceAudioSystem.setAutoTvOff(false);
         // Set system audio control on first
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
         // Check if standby correctly turns off the feature
         mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
@@ -309,9 +317,9 @@
         mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                 Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.getActions(
-                                SystemAudioInitiationActionFromAvr.class))
-                .isNotEmpty();
+            mHdmiCecLocalDeviceAudioSystem.getActions(
+                SystemAudioInitiationActionFromAvr.class))
+            .isNotEmpty();
     }
 
     @Test
@@ -320,9 +328,9 @@
         mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                 Constants.NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.getActions(
-                                SystemAudioInitiationActionFromAvr.class))
-                .isEmpty();
+            mHdmiCecLocalDeviceAudioSystem.getActions(
+                SystemAudioInitiationActionFromAvr.class))
+            .isEmpty();
     }
 
     @Test
@@ -331,9 +339,9 @@
         mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                 Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.getActions(
-                                SystemAudioInitiationActionFromAvr.class))
-                .isEmpty();
+            mHdmiCecLocalDeviceAudioSystem.getActions(
+                SystemAudioInitiationActionFromAvr.class))
+            .isEmpty();
     }
 
     @Test
@@ -342,9 +350,9 @@
         mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                 Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.getActions(
-                                SystemAudioInitiationActionFromAvr.class))
-                .isNotEmpty();
+            mHdmiCecLocalDeviceAudioSystem.getActions(
+                SystemAudioInitiationActionFromAvr.class))
+            .isNotEmpty();
     }
 
     @Test
@@ -354,12 +362,12 @@
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource))
-                .isTrue();
+            .isTrue();
     }
 
     @Test
     public void terminateSystemAudioMode_systemAudioModeOff() throws Exception {
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
         mMusicMute = false;
         HdmiCecMessage message =
@@ -373,7 +381,7 @@
 
     @Test
     public void terminateSystemAudioMode_systemAudioModeOn() throws Exception {
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
         assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue();
         mMusicMute = false;
         HdmiCecMessage expectedMessage =
@@ -458,7 +466,7 @@
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class))
-                .isNotEmpty();
+            .isNotEmpty();
     }
 
     @Test
@@ -473,7 +481,7 @@
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class))
-                .isNotEmpty();
+            .isNotEmpty();
     }
 
     @Test
@@ -521,12 +529,96 @@
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
 
-    @Test
-    public void handleSetStreamPath_underCurrentDevice() {
-        assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(0);
+    public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() {
         HdmiCecMessage message =
-                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetStreamPath(message)).isTrue();
-        assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(1);
+                HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+                        ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
+                        mAvrPhysicalAddress, true);
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                        ADDR_AUDIO_SYSTEM,
+                        ADDR_TUNER_1,
+                        Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
+                        Constants.ABORT_REFUSED);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+    }
+
+    @Test
+    public void handleSystemAudioModeRequest_fromNonTV_tVSupport() {
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+                        ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
+                        mAvrPhysicalAddress, true);
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildSetSystemAudioMode(
+                        ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true);
+        mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true);
+
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+    }
+
+    @Test
+    public void handleActiveSource_activeSourceFromTV_swithToArc() {
+        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+        ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
+            .isEqualTo(expectedActiveSource);
+    }
+
+    @Test
+    public void handleRoutingChange_currentActivePortIsHome() {
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x3000, mAvrPhysicalAddress);
+
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+        ActiveSource expectedActiveSource = ActiveSource.of(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+        int expectedLocalActivePort = Constants.CEC_SWITCH_HOME;
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
+            .isEqualTo(expectedActiveSource);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePort())
+            .isEqualTo(expectedLocalActivePort);
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+    }
+
+    @Test
+    public void handleRoutingInformation_currentActivePortIsHDMI1() {
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x2000);
+        mHdmiCecLocalDeviceAudioSystem.setLocalActivePort(Constants.CEC_SWITCH_HDMI1);
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_AUDIO_SYSTEM, 0x2100);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+    }
+
+    @Test
+    public void handleRoutingChange_homeIsActive_playbackSendActiveSource() {
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x2000);
+
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
new file mode 100644
index 0000000..76f638c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Looper;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(JUnit4.class)
+/** Tests for {@link HdmiCecLocalDevicePlayback} class. */
+public class HdmiCecLocalDevicePlaybackTest {
+
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecController mHdmiCecController;
+    private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
+    private FakeNativeWrapper mNativeWrapper;
+    private Looper mMyLooper;
+    private TestLooper mTestLooper = new TestLooper();
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+    private int mPlaybackPhysicalAddress;
+
+    @Before
+    public void setUp() {
+        mHdmiControlService =
+            new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+                @Override
+                void wakeUp() {
+                }
+
+                @Override
+                boolean isControlEnabled() {
+                    return true;
+                }
+            };
+
+        mMyLooper = mTestLooper.getLooper();
+        mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
+        mHdmiCecLocalDevicePlayback.init();
+        mHdmiControlService.setIoLooper(mMyLooper);
+        mNativeWrapper = new FakeNativeWrapper();
+        mHdmiCecController =
+            HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+        mHdmiControlService.setCecController(mHdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+        mLocalDevices.add(mHdmiCecLocalDevicePlayback);
+        mHdmiControlService.initPortInfo();
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
+        mPlaybackPhysicalAddress = 0x2000;
+        mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
+    }
+
+    @Test
+    public void handleSetStreamPath_underCurrentDevice() {
+        assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(0);
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(1);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
index c7809d3..ef974f2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java
@@ -18,6 +18,7 @@
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -51,6 +52,14 @@
         assertThat(message).isEqualTo(buildMessage("05:A4:06:01"));
     }
 
+    @Test
+    public void buildRoutingInformation() {
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(
+                        ADDR_AUDIO_SYSTEM, 0x2100);
+        assertThat(message).isEqualTo(buildMessage("5F:81:21:00"));
+    }
+
     /**
      * Build a CEC message from a hex byte string with bytes separated by {@code :}.
      *
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 561c61f..0dff03f 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -47,6 +47,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.power.PowerManagerService.Injector;
 import com.android.server.power.PowerManagerService.NativeWrapper;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
 import com.android.server.power.batterysaver.BatterySavingStats;
 
 import org.junit.Rule;
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
rename to services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index acf511e..73eefcf 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power;
+package com.android.server.power.batterysaver;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -31,13 +31,12 @@
 import com.android.frameworks.servicestests.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
-import com.android.server.power.batterysaver.BatterySavingStats;
 
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 /**
- * Tests for {@link com.android.server.power.BatterySaverPolicy}
+ * Tests for {@link com.android.server.power.batterysaver.BatterySaverPolicy}
  */
 public class BatterySaverPolicyTest extends AndroidTestCase {
     private static final boolean BATTERY_SAVER_ON = true;
@@ -62,7 +61,7 @@
     private static final String BATTERY_SAVER_INCORRECT_CONSTANTS = "vi*,!=,,true";
 
     private class BatterySaverPolicyForTest extends BatterySaverPolicy {
-        public BatterySaverPolicyForTest(Object lock, Context context,
+        BatterySaverPolicyForTest(Object lock, Context context,
                 BatterySavingStats batterySavingStats) {
             super(lock, context, batterySavingStats);
         }
@@ -269,15 +268,15 @@
         mBatterySaverPolicy.onChange();
         assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
         assertThat(mBatterySaverPolicy.getFileValues(false).toString())
-                .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, " +
-                "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}");
+                .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, "
+                        + "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}");
 
         mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_3;
 
         mBatterySaverPolicy.onChange();
         assertThat(mBatterySaverPolicy.getFileValues(true).toString())
-                .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, " +
-                        "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}");
+                .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, "
+                        + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}");
         assertThat(mBatterySaverPolicy.getFileValues(false).toString())
                 .isEqualTo("{/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=222}");
 
@@ -287,9 +286,9 @@
 
         mBatterySaverPolicy.onChange();
         assertThat(mBatterySaverPolicy.getFileValues(true).toString())
-                .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, " +
-                        "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, " +
-                        "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}");
+                .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, "
+                        + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, "
+                        + "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}");
         assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
     }
 }
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 b6f1817..8f9d2ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -20,6 +20,9 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
@@ -38,6 +41,7 @@
 import android.app.ActivityOptions;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.PauseActivityItem;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.MutableBoolean;
@@ -210,4 +214,41 @@
         assertNull(mActivity.pendingOptions);
         assertNotNull(activity2.pendingOptions);
     }
+
+    @Test
+    public void testNewOverrideConfigurationIncrementsSeq() {
+        final Configuration newConfig = new Configuration();
+
+        final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
+        mActivity.onRequestedOverrideConfigurationChanged(newConfig);
+        assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+    }
+
+    @Test
+    public void testNewParentConfigurationIncrementsSeq() {
+        final Configuration newConfig = new Configuration(
+                mTask.getRequestedOverrideConfiguration());
+        newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
+                ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+
+        final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
+        mTask.onRequestedOverrideConfigurationChanged(newConfig);
+        assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
+    }
+
+    @Test
+    public void testNotifiesSeqIncrementToAppToken() {
+        final Configuration appWindowTokenRequestedOrientation = mock(Configuration.class);
+        mActivity.mAppWindowToken = mock(AppWindowToken.class);
+        doReturn(appWindowTokenRequestedOrientation).when(mActivity.mAppWindowToken)
+                .getRequestedOverrideConfiguration();
+
+        final Configuration newConfig = new Configuration();
+        newConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
+
+        final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
+        mActivity.onRequestedOverrideConfigurationChanged(newConfig);
+        assertEquals(prevSeq + 1, appWindowTokenRequestedOrientation.seq);
+        verify(mActivity.mAppWindowToken).onMergedOverrideConfigurationChanged();
+    }
 }
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 bd29d2a..5589ca1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -66,7 +66,7 @@
 import android.view.DisplayInfo;
 
 import com.android.internal.app.IVoiceInteractor;
-import com.android.server.AppOpsService;
+import com.android.server.appop.AppOpsService;
 import com.android.server.AttributeCache;
 import com.android.server.ServiceThread;
 import com.android.server.am.ActivityManagerService;
@@ -264,6 +264,8 @@
                     .setOrientation(anyInt(), any(), any());
             doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt());
             doNothing().when(activity).removeWindowContainer();
+            doReturn(mock(Configuration.class)).when(activity.mAppWindowToken)
+                    .getRequestedOverrideConfiguration();
 
             if (mTaskRecord != null) {
                 mTaskRecord.addActivityToTop(activity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index f1c6eab..bb3ab35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -20,11 +20,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -125,7 +123,6 @@
         mDisplayContent = spy(mDisplayContent);
         mWindow = createDropTargetWindow("Drag test window", 0);
         doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
-        when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
 
         synchronized (mWm.mGlobalLock) {
             mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
@@ -176,7 +173,6 @@
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
 
-            assertTrue(mWm.mInputManager.transferTouchFocus(null, null));
             mToken = mTarget.performDrag(
                     new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
                     data);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index c343fe7..8c6ac23 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -18,7 +18,6 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -58,10 +57,6 @@
         assertNotNull(mWm.mTaskPositioningController);
         mTarget = mWm.mTaskPositioningController;
 
-        when(mWm.mInputManager.transferTouchFocus(
-                any(InputChannel.class),
-                any(InputChannel.class))).thenReturn(true);
-
         mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
         mWindow.mInputChannel = new InputChannel();
         synchronized (mWm.mGlobalLock) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 38efc74..3d7cbb5 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -657,9 +657,8 @@
             return;
         }
 
-        if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
-            model.setStopped();
-        }
+        model.setStopped();
+
         try {
             callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
         } catch (DeadObjectException e) {
@@ -802,9 +801,7 @@
             return;
         }
 
-        if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
-            modelData.setStopped();
-        }
+        modelData.setStopped();
 
         try {
             modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2820836..dcaa499 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -239,6 +239,30 @@
             "android.telecom.event.HANDOVER_FAILED";
 
     public static class Details {
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+                prefix = { "DIRECTION_" },
+                value = {DIRECTION_UNKNOWN, DIRECTION_INCOMING, DIRECTION_OUTGOING})
+        public @interface CallDirection {}
+
+        /**
+         * Indicates that the call is neither and incoming nor an outgoing call.  This can be the
+         * case for calls reported directly by a {@link ConnectionService} in special cases such as
+         * call handovers.
+         */
+        public static final int DIRECTION_UNKNOWN = -1;
+
+        /**
+         * Indicates that the call is an incoming call.
+         */
+        public static final int DIRECTION_INCOMING = 0;
+
+        /**
+         * Indicates that the call is an outgoing call.
+         */
+        public static final int DIRECTION_OUTGOING = 1;
+
 
         /** Call can currently be put on hold or unheld. */
         public static final int CAPABILITY_HOLD = 0x00000001;
@@ -519,6 +543,7 @@
         private final Bundle mIntentExtras;
         private final long mCreationTimeMillis;
         private final CallIdentification mCallIdentification;
+        private final @CallDirection int mCallDirection;
 
         /**
          * Whether the supplied capabilities  supports the specified capability.
@@ -838,6 +863,14 @@
             return mCallIdentification;
         }
 
+        /**
+         * Indicates whether the call is an incoming or outgoing call.
+         * @return The call's direction.
+         */
+        public @CallDirection int getCallDirection() {
+            return mCallDirection;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (o instanceof Details) {
@@ -859,7 +892,8 @@
                         areBundlesEqual(mExtras, d.mExtras) &&
                         areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
                         Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
-                        Objects.equals(mCallIdentification, d.mCallIdentification);
+                        Objects.equals(mCallIdentification, d.mCallIdentification) &&
+                        Objects.equals(mCallDirection, d.mCallDirection);
             }
             return false;
         }
@@ -881,7 +915,8 @@
                             mExtras,
                             mIntentExtras,
                             mCreationTimeMillis,
-                            mCallIdentification);
+                            mCallIdentification,
+                            mCallDirection);
         }
 
         /** {@hide} */
@@ -902,7 +937,8 @@
                 Bundle extras,
                 Bundle intentExtras,
                 long creationTimeMillis,
-                CallIdentification callIdentification) {
+                CallIdentification callIdentification,
+                int callDirection) {
             mTelecomCallId = telecomCallId;
             mHandle = handle;
             mHandlePresentation = handlePresentation;
@@ -920,6 +956,7 @@
             mIntentExtras = intentExtras;
             mCreationTimeMillis = creationTimeMillis;
             mCallIdentification = callIdentification;
+            mCallDirection = callDirection;
         }
 
         /** {@hide} */
@@ -941,7 +978,8 @@
                     parcelableCall.getExtras(),
                     parcelableCall.getIntentExtras(),
                     parcelableCall.getCreationTimeMillis(),
-                    parcelableCall.getCallIdentification());
+                    parcelableCall.getCallIdentification(),
+                    parcelableCall.getCallDirection());
         }
 
         @Override
diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java
index 97af06c..87834fd 100644
--- a/telecomm/java/android/telecom/CallIdentification.java
+++ b/telecomm/java/android/telecom/CallIdentification.java
@@ -250,8 +250,8 @@
         mDetails = details;
         mPhoto = photo;
         mNuisanceConfidence = nuisanceConfidence;
-        mCallScreeningAppName = callScreeningPackageName;
-        mCallScreeningPackageName = callScreeningAppName;
+        mCallScreeningAppName = callScreeningAppName;
+        mCallScreeningPackageName = callScreeningPackageName;
     }
 
     private String mName;
@@ -430,4 +430,22 @@
         return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence,
                 mCallScreeningAppName, mCallScreeningPackageName);
     }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[CallId mName=");
+        sb.append(Log.pii(mName));
+        sb.append(", mDesc=");
+        sb.append(mDescription);
+        sb.append(", mDet=");
+        sb.append(mDetails);
+        sb.append(", conf=");
+        sb.append(mNuisanceConfidence);
+        sb.append(", appName=");
+        sb.append(mCallScreeningAppName);
+        sb.append(", pkgName=");
+        sb.append(mCallScreeningPackageName);
+        return sb.toString();
+    }
 }
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index be96b3c..826ad82 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -21,6 +21,7 @@
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -33,8 +34,9 @@
 
 /**
  * This service can be implemented by the default dialer (see
- * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before
- * they are shown to a user.
+ * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
+ * incoming calls before they are shown to a user.  This service can also provide
+ * {@link CallIdentification} information for calls.
  * <p>
  * Below is an example manifest registration for a {@code CallScreeningService}.
  * <pre>
@@ -56,6 +58,34 @@
  *     information about a {@link Call.Details call} which will be shown to the user in the
  *     Dialer app.</li>
  * </ol>
+ * <p>
+ * <h2>Becoming the {@link CallScreeningService}</h2>
+ * Telecom will bind to a single app chosen by the user which implements the
+ * {@link CallScreeningService} API when there are new incoming and outgoing calls.
+ * <p>
+ * The code snippet below illustrates how your app can request that it fills the call screening
+ * role.
+ * <pre>
+ * {@code
+ * private static final int REQUEST_ID = 1;
+ *
+ * public void requestRole() {
+ *     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
+ *     Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING_APP");
+ *     startActivityForResult(intent, REQUEST_ID);
+ * }
+ *
+ * &#64;Override
+ * public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ *     if (requestCode == REQUEST_ID) {
+ *         if (resultCode == android.app.Activity.RESULT_OK) {
+ *             // Your app is now the call screening app
+ *         } else {
+ *             // Your app is not the call screening app
+ *         }
+ *     }
+ * }
+ * </pre>
  */
 public abstract class CallScreeningService extends Service {
     /**
@@ -222,30 +252,46 @@
     }
 
     /**
-     * Called when a new incoming call is added.
-     * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}
-     * should be called to allow or disallow the call.
+     * Called when a new incoming or outgoing call is added which is not in the user's contact list.
+     * <p>
+     * A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by
+     * calling
+     * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}.
+     * Your app can tell if a call is an incoming call by checking to see if
+     * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}.
+     * <p>
+     * For incoming or outgoing calls, the {@link CallScreeningService} can call
+     * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide
+     * {@link CallIdentification} for the call.
      * <p>
      * Note: The {@link Call.Details} instance provided to a call screening service will only have
      * the following properties set.  The rest of the {@link Call.Details} properties will be set to
      * their default value or {@code null}.
      * <ul>
-     *     <li>{@link Call.Details#getState()}</li>
+     *     <li>{@link Call.Details#getCallDirection()}</li>
      *     <li>{@link Call.Details#getConnectTimeMillis()}</li>
      *     <li>{@link Call.Details#getCreationTimeMillis()}</li>
      *     <li>{@link Call.Details#getHandle()}</li>
      *     <li>{@link Call.Details#getHandlePresentation()}</li>
      * </ul>
+     * <p>
+     * Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme}
+     * is {@link PhoneAccount#SCHEME_TEL} are passed for call
+     * screening.  Further, only calls which are not in the user's contacts are passed for
+     * screening.  For outgoing calls, no post-dial digits are passed.
      *
-     * @param callDetails Information about a new incoming call, see {@link Call.Details}.
+     * @param callDetails Information about a new call, see {@link Call.Details}.
      */
     public abstract void onScreenCall(@NonNull Call.Details callDetails);
 
     /**
-     * Responds to the given call, either allowing it or disallowing it.
+     * Responds to the given incoming call, either allowing it or disallowing it.
      * <p>
      * The {@link CallScreeningService} calls this method to inform the system whether the call
      * should be silently blocked or not.
+     * <p>
+     * Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is
+     * {@link Call.Details#DIRECTION_INCOMING}.
      *
      * @param callDetails The call to allow.
      *                    <p>
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 1aeeca7..f5f0af7 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -40,11 +40,30 @@
 import java.util.List;
 
 /**
- * This service is implemented by any app that wishes to provide the user-interface for managing
- * phone calls. Telecom binds to this service while there exists a live (active or incoming) call,
- * and uses it to notify the in-call app of any live and recently disconnected calls. An app must
- * first be set as the default phone app (See {@link TelecomManager#getDefaultDialerPackage()})
- * before the telecom service will bind to its {@code InCallService} implementation.
+ * This service is implemented by an app that wishes to provide functionality for managing
+ * phone calls.
+ * <p>
+ * There are three types of apps which Telecom can bind to when there exists a live (active or
+ * incoming) call:
+ * <ol>
+ *     <li>Default Dialer/Phone app - the default dialer/phone app is one which provides the
+ *     in-call user interface while the device is in a call.  A device is bundled with a system
+ *     provided default dialer/phone app.  The user may choose a single app to take over this role
+ *     from the system app.</li>
+ *     <li>Default Car-mode Dialer/Phone app - the default car-mode dialer/phone app is one which
+ *     provides the in-call user interface while the device is in a call and the device is in car
+ *     mode.  The user may choose a single app to fill this role.</li>
+ *     <li>Call Companion app - a call companion app is one which provides no user interface itself,
+ *     but exposes call information to another display surface, such as a wearable device.  The
+ *     user may choose multiple apps to fill this role.</li>
+ * </ol>
+ * <p>
+ * Apps which wish to fulfill one of the above roles use the {@link android.app.role.RoleManager}
+ * to request that they fill the desired role.
+ *
+ * <h2>Becoming the Default Phone App</h2>
+ * An app filling the role of the default phone app provides a user interface while the device is in
+ * a call, and the device is not in car mode.
  * <p>
  * Below is an example manifest registration for an {@code InCallService}. The meta-data
  * {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular
@@ -82,12 +101,34 @@
  * }
  * </pre>
  * <p>
- * When a user installs your application and runs it for the first time, you should prompt the user
- * to see if they would like your application to be the new default phone app.  See the
- * {@link TelecomManager#ACTION_CHANGE_DEFAULT_DIALER} intent documentation for more information on
- * how to do this.
+ * When a user installs your application and runs it for the first time, you should use the
+ * {@link android.app.role.RoleManager} to prompt the user to see if they would like your app to
+ * be the new default phone app.
+ * <p id="requestRole">
+ * The code below shows how your app can request to become the default phone/dialer app:
+ * <pre>
+ * {@code
+ * private static final int REQUEST_ID = 1;
+ *
+ * public void requestRole() {
+ *     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
+ *     Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
+ *     startActivityForResult(intent, REQUEST_ID);
+ * }
+ *
+ * &#64;Override
+ * public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ *     if (requestCode == REQUEST_ID) {
+ *         if (resultCode == android.app.Activity.RESULT_OK) {
+ *             // Your app is now the default dialer app
+ *         } else {
+ *             // Your app is not the default dialer app
+ *         }
+ *     }
+ * }
+ * </pre>
  * <p id="incomingCallNotification">
- * <h2>Showing the Incoming Call Notification</h2>
+ * <h3>Showing the Incoming Call Notification</h3>
  * When your app receives a new incoming call via {@link InCallService#onCallAdded(Call)}, it is
  * responsible for displaying an incoming call UI for the incoming call.  It should do this using
  * {@link android.app.NotificationManager} APIs to post a new incoming call notification.
@@ -121,7 +162,7 @@
  * heads-up notification if the user is actively using the phone.  When the user is not using the
  * phone, your full-screen incoming call UI is used instead.
  * For example:
- * <pre><code>
+ * <pre><code>{@code
  * // Create an intent which triggers your fullscreen incoming call user interface.
  * Intent intent = new Intent(Intent.ACTION_MAIN, null);
  * intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -151,7 +192,49 @@
  * NotificationManager notificationManager = mContext.getSystemService(
  *     NotificationManager.class);
  * notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build());
- * </code></pre>
+ * }</pre>
+ * <p>
+ * <h2>Becoming the Default Car-mode Phone App</h2>
+ * An app filling the role of the default car-mode dialer/phone app provides a user interface while
+ * the device is in a call, and in car mode.  See
+ * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information about car mode.
+ * When the device is in car mode, Telecom binds to the default car-mode dialer/phone app instead
+ * of the usual dialer/phone app.
+ * <p>
+ * Similar to the requirements for becoming the default dialer/phone app, your app must declare a
+ * manifest entry for its {@link InCallService} implementation.  Your manifest entry should ensure
+ * the following conditions are met:
+ * <ul>
+ *     <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li>
+ *     <li>Set the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} metadata to
+ *     {@code true}<li>
+ *     <li>Your app must request the permission
+ *     {@link android.Manifest.permission.CALL_COMPANION_APP}.</li>
+ * </ul>
+ * <p>
+ * Your app should request to fill the role {@code android.app.role.CAR_MODE_DIALER_APP} in order to
+ * become the default (see <a href="#requestRole">above</a> for how to request your app fills this
+ * role).
+ *
+ * <h2>Becoming a Call Companion App</h2>
+ * An app which fills the companion app role does not directly provide a user interface while the
+ * device is in a call.  Instead, it is typically used to relay information about calls to another
+ * display surface, such as a wearable device.
+ * <p>
+ * Similar to the requirements for becoming the default dialer/phone app, your app must declare a
+ * manifest entry for its {@link InCallService} implementation.  Your manifest entry should
+ * ensure the following conditions are met:
+ * <ul>
+ *     <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li>
+ *     <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI}
+ *     metadata.</li>
+ *     <li>Your app must request the permission
+ *     {@link android.Manifest.permission.CALL_COMPANION_APP}.</li>
+ * </ul>
+ * <p>
+ * Your app should request to fill the role {@code android.app.role.CALL_COMPANION_APP} in order to
+ * become a call companion app (see <a href="#requestRole">above</a> for how to request your app
+ * fills this role).
  */
 public abstract class InCallService extends Service {
 
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 911786e..f7dec83 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -24,6 +24,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.telecom.Call.Details.CallDirection;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -64,6 +65,7 @@
     private final Bundle mExtras;
     private final long mCreationTimeMillis;
     private final CallIdentification mCallIdentification;
+    private final int mCallDirection;
 
     public ParcelableCall(
             String id,
@@ -92,7 +94,8 @@
             Bundle intentExtras,
             Bundle extras,
             long creationTimeMillis,
-            CallIdentification callIdentification) {
+            CallIdentification callIdentification,
+            int callDirection) {
         mId = id;
         mState = state;
         mDisconnectCause = disconnectCause;
@@ -120,6 +123,7 @@
         mExtras = extras;
         mCreationTimeMillis = creationTimeMillis;
         mCallIdentification = callIdentification;
+        mCallDirection = callDirection;
     }
 
     /** The unique ID of the call. */
@@ -318,6 +322,13 @@
         return mCallIdentification;
     }
 
+    /**
+     * Indicates whether the call is an incoming or outgoing call.
+     */
+    public @CallDirection int getCallDirection() {
+        return mCallDirection;
+    }
+
     /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public static final Parcelable.Creator<ParcelableCall> CREATOR =
@@ -356,6 +367,7 @@
             ParcelableRttCall rttCall = source.readParcelable(classLoader);
             long creationTimeMillis = source.readLong();
             CallIdentification callIdentification = source.readParcelable(classLoader);
+            int callDirection = source.readInt();
             return new ParcelableCall(
                     id,
                     state,
@@ -383,7 +395,8 @@
                     intentExtras,
                     extras,
                     creationTimeMillis,
-                    callIdentification);
+                    callIdentification,
+                    callDirection);
         }
 
         @Override
@@ -429,6 +442,7 @@
         destination.writeParcelable(mRttCall, 0);
         destination.writeLong(mCreationTimeMillis);
         destination.writeParcelable(mCallIdentification, 0);
+        destination.writeInt(mCallDirection);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 9fc1b6f..bf9bf9a 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -170,7 +170,8 @@
                     RIL_RADIO_TECHNOLOGY_GSM,
                     RIL_RADIO_TECHNOLOGY_TD_SCDMA,
                     RIL_RADIO_TECHNOLOGY_IWLAN,
-                    RIL_RADIO_TECHNOLOGY_LTE_CA})
+                    RIL_RADIO_TECHNOLOGY_LTE_CA,
+                    RIL_RADIO_TECHNOLOGY_NR})
     public @interface RilRadioTechnology {}
     /**
      * Available radio technologies for GSM, UMTS and CDMA.
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index a1e8b19..51d5ab1 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -174,6 +174,16 @@
     private boolean mIsGroupDisabled = false;
 
     /**
+     * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL
+     * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET.
+     * A profile on the eUICC can be defined as test, operational, provisioning, or unset.
+     * The profile class will be populated from the profile metadata if present. Otherwise,
+     * the profile class defaults to unset if there is no profile metadata or the subscription
+     * is not on an eUICC ({@link #isEmbedded} returns false).
+     */
+    private int mProfileClass;
+
+    /**
      * @hide
      */
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -182,7 +192,8 @@
             @Nullable UiccAccessRule[] accessRules, String cardString) {
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString,
-                false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID);
+                false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID,
+                SubscriptionManager.PROFILE_CLASS_DEFAULT);
     }
 
     /**
@@ -192,10 +203,10 @@
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardString, boolean isOpportunistic,
-            @Nullable String groupUUID, boolean isMetered, int carrierId) {
+            @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) {
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
-                isOpportunistic, groupUUID, isMetered, false, carrierId);
+                isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass);
     }
 
     /**
@@ -206,7 +217,7 @@
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
             boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered,
-            boolean isGroupDisabled, int carrierid) {
+            boolean isGroupDisabled, int carrierid, int profileClass) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -229,6 +240,7 @@
         this.mIsMetered = isMetered;
         this.mIsGroupDisabled = isGroupDisabled;
         this.mCarrierId = carrierid;
+        this.mProfileClass = profileClass;
     }
 
 
@@ -466,6 +478,15 @@
     }
 
     /**
+     * @return the profile class of this subscription.
+     * @hide
+     */
+    @SystemApi
+    public @SubscriptionManager.ProfileClass int getProfileClass() {
+        return this.mProfileClass;
+    }
+
+    /**
      * Checks whether the app with the given context is authorized to manage this subscription
      * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded}
      * returns true).
@@ -590,11 +611,12 @@
             boolean isMetered = source.readBoolean();
             boolean isGroupDisabled = source.readBoolean();
             int carrierid = source.readInt();
+            int profileClass = source.readInt();
 
             return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
                     nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
                     isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID,
-                    isMetered, isGroupDisabled, carrierid);
+                    isMetered, isGroupDisabled, carrierid, profileClass);
         }
 
         @Override
@@ -627,6 +649,7 @@
         dest.writeBoolean(mIsMetered);
         dest.writeBoolean(mIsGroupDisabled);
         dest.writeInt(mCarrierId);
+        dest.writeInt(mProfileClass);
     }
 
     @Override
@@ -662,7 +685,8 @@
                 + " accessRules " + Arrays.toString(mAccessRules)
                 + " cardString=" + cardStringToPrint + " cardId=" + mCardId
                 + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
-                + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled + "}";
+                + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled
+                + " profileClass=" + mProfileClass + "}";
     }
 
     @Override
@@ -670,7 +694,7 @@
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
                 mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc,
                 mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules,
-                mIsGroupDisabled, mCarrierId);
+                mIsGroupDisabled, mCarrierId, mProfileClass);
     }
 
     @Override
@@ -705,6 +729,7 @@
                 && Objects.equals(mCardId, toCompare.mCardId)
                 && TextUtils.equals(mDisplayName, toCompare.mDisplayName)
                 && TextUtils.equals(mCarrierName, toCompare.mCarrierName)
-                && Arrays.equals(mAccessRules, toCompare.mAccessRules);
+                && Arrays.equals(mAccessRules, toCompare.mAccessRules)
+                && mProfileClass == toCompare.mProfileClass;
     }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 2c712a1..34f7abd 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -22,6 +22,7 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -62,6 +63,8 @@
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.internal.telephony.PhoneConstants;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -599,6 +602,73 @@
      * @hide
      */
     public static final String IS_METERED = "is_metered";
+
+    /**
+     * TelephonyProvider column name for the profile class of a subscription
+     * Only present if {@link #IS_EMBEDDED} is 1.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String PROFILE_CLASS = "profile_class";
+
+    /**
+     * Profile class of the subscription
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PROFILE_CLASS_" }, value = {
+            PROFILE_CLASS_TESTING,
+            PROFILE_CLASS_PROVISIONING,
+            PROFILE_CLASS_OPERATIONAL,
+            PROFILE_CLASS_UNSET,
+            PROFILE_CLASS_DEFAULT
+    })
+    public @interface ProfileClass {}
+
+    /**
+     * A testing profile can be pre-loaded or downloaded onto
+     * the eUICC and provides connectivity to test equipment
+     * for the purpose of testing the device and the eUICC. It
+     * is not intended to store any operator credentials.
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_TESTING = 0;
+
+    /**
+     * A provisioning profile is pre-loaded onto the eUICC and
+     * provides connectivity to a mobile network solely for the
+     * purpose of provisioning profiles.
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_PROVISIONING = 1;
+
+    /**
+     * An operational profile can be pre-loaded or downloaded
+     * onto the eUICC and provides services provided by the
+     * operator.
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_OPERATIONAL = 2;
+
+    /**
+     * The profile class is unset. This occurs when profile class
+     * info is not available. The subscription either has no profile
+     * metadata or the profile metadata did not encode profile class.
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_UNSET = -1;
+
+    /**
+     * Default profile class
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+
     /**
      * Broadcast Action: The user has changed one of the default subs related to
      * data, phone calls, or sms</p>
@@ -1102,17 +1172,33 @@
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
-        List<SubscriptionInfo> result = null;
+        return getActiveSubscriptionInfoList(false);
+    }
+
+    /**
+     * This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly
+     * is true, it will filter out the hidden subscriptions.
+     *
+     * @hide
+     */
+    public List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
+        List<SubscriptionInfo> activeList = null;
 
         try {
             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
             if (iSub != null) {
-                result = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
+                activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
             }
         } catch (RemoteException ex) {
             // ignore it
         }
-        return result;
+
+        if (!userVisibleOnly || activeList == null) {
+            return activeList;
+        } else {
+            return activeList.stream().filter(subInfo -> !shouldHideSubscription(subInfo))
+                    .collect(Collectors.toList());
+        }
     }
 
     /**
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index a94b163..a5f56bb 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -232,18 +232,21 @@
     private final String mCountryIso;
     private final String mMnc;
     private final int mEmergencyServiceCategoryBitmask;
+    private final List<String> mEmergencyUrns;
     private final int mEmergencyNumberSourceBitmask;
     private final int mEmergencyCallRouting;
 
     /** @hide */
     public EmergencyNumber(@NonNull String number, @NonNull String countryIso, @NonNull String mnc,
                            @EmergencyServiceCategories int emergencyServiceCategories,
+                           @NonNull List<String> emergencyUrns,
                            @EmergencyNumberSources int emergencyNumberSources,
                            @EmergencyCallRouting int emergencyCallRouting) {
         this.mNumber = number;
         this.mCountryIso = countryIso;
         this.mMnc = mnc;
         this.mEmergencyServiceCategoryBitmask = emergencyServiceCategories;
+        this.mEmergencyUrns = emergencyUrns;
         this.mEmergencyNumberSourceBitmask = emergencyNumberSources;
         this.mEmergencyCallRouting = emergencyCallRouting;
     }
@@ -254,6 +257,7 @@
         mCountryIso = source.readString();
         mMnc = source.readString();
         mEmergencyServiceCategoryBitmask = source.readInt();
+        mEmergencyUrns = source.createStringArrayList();
         mEmergencyNumberSourceBitmask = source.readInt();
         mEmergencyCallRouting = source.readInt();
     }
@@ -265,6 +269,7 @@
         dest.writeString(mCountryIso);
         dest.writeString(mMnc);
         dest.writeInt(mEmergencyServiceCategoryBitmask);
+        dest.writeStringList(mEmergencyUrns);
         dest.writeInt(mEmergencyNumberSourceBitmask);
         dest.writeInt(mEmergencyCallRouting);
     }
@@ -345,6 +350,22 @@
     }
 
     /**
+     * Returns the list of emergency Uniform Resources Names (URN) of the emergency number.
+     *
+     * For example, {@code urn:service:sos} is the generic URN for contacting emergency services
+     * of all type.
+     *
+     * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+     *            RFC 5031
+     *
+     * @return list of emergency Uniform Resources Names (URN) or an empty list if the emergency
+     *         number does not have a specified emergency Uniform Resource Name.
+     */
+    public @NonNull List<String> getEmergencyUrns() {
+        return mEmergencyUrns;
+    }
+
+    /**
      * Checks if the emergency service category is unspecified for the emergency number
      * {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}.
      *
@@ -434,6 +455,7 @@
         return "EmergencyNumber:" + "Number-" + mNumber + "|CountryIso-" + mCountryIso
                 + "|Mnc-" + mMnc
                 + "|ServiceCategories-" + Integer.toBinaryString(mEmergencyServiceCategoryBitmask)
+                + "|Urns-" + mEmergencyUrns
                 + "|Sources-" + Integer.toBinaryString(mEmergencyNumberSourceBitmask)
                 + "|Routing-" + Integer.toBinaryString(mEmergencyCallRouting);
     }
@@ -448,6 +470,7 @@
                 && mCountryIso.equals(other.mCountryIso)
                 && mMnc.equals(other.mMnc)
                 && mEmergencyServiceCategoryBitmask == other.mEmergencyServiceCategoryBitmask
+                && mEmergencyUrns.equals(other.mEmergencyUrns)
                 && mEmergencyNumberSourceBitmask == other.mEmergencyNumberSourceBitmask
                 && mEmergencyCallRouting == other.mEmergencyCallRouting;
     }
@@ -455,7 +478,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mNumber, mCountryIso, mMnc, mEmergencyServiceCategoryBitmask,
-                mEmergencyNumberSourceBitmask, mEmergencyCallRouting);
+                mEmergencyUrns, mEmergencyNumberSourceBitmask, mEmergencyCallRouting);
     }
 
     /**
@@ -584,6 +607,9 @@
                 != second.getEmergencyServiceCategoryBitmask()) {
             return false;
         }
+        if (first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
+            return false;
+        }
         if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) {
             return false;
         }
@@ -605,6 +631,7 @@
         if (areSameEmergencyNumbers(first, second)) {
             return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(),
                     first.getEmergencyServiceCategoryBitmask(),
+                    first.getEmergencyUrns(),
                     first.getEmergencyNumberSourceBitmask()
                             | second.getEmergencyNumberSourceBitmask(),
                     first.getEmergencyCallRouting());
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 6326cc6..cc9befe 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -616,14 +616,13 @@
     /**
      * Update the nickname for the given subscription.
      *
-     * <p>Requires that the calling app has the
-     * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for
-     * internal system use only.
+     * <p>Requires that the calling app has carrier privileges according to the metadata of the
+     * profile to be updated, or the
+     * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
      *
      * @param subscriptionId the ID of the subscription to update.
      * @param nickname the new nickname to apply.
      * @param callbackIntent a PendingIntent to launch when the operation completes.
-     * @hide
      */
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void updateSubscriptionNickname(
@@ -634,7 +633,7 @@
         }
         try {
             getIEuiccController().updateSubscriptionNickname(
-                    subscriptionId, nickname, callbackIntent);
+                    subscriptionId, nickname, mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 9c8d078..525a96a 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -33,6 +33,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Parcelable object to handle IMS call profile.
@@ -323,6 +325,15 @@
             EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
 
     /**
+     * The emergency Uniform Resource Names (URN), only valid if {@link #getServiceType} returns
+     * {@link #SERVICE_TYPE_EMERGENCY}.
+     *
+     * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+     *            3gpp 22.101, Section 10 - Emergency Calls.
+     */
+    private List<String> mEmergencyUrns = new ArrayList<>();
+
+    /**
      * The emergency call routing, only valid if {@link #getServiceType} returns
      * {@link #SERVICE_TYPE_EMERGENCY}
      *
@@ -524,6 +535,7 @@
                 + ", restrictCause=" + mRestrictCause
                 + ", mediaProfile=" + mMediaProfile.toString()
                 + ", emergencyServiceCategories=" + mEmergencyCallRouting
+                + ", emergencyUrns=" + mEmergencyUrns
                 + ", emergencyCallRouting=" + mEmergencyCallRouting + " }";
     }
 
@@ -540,6 +552,7 @@
         out.writeBundle(filteredExtras);
         out.writeParcelable(mMediaProfile, 0);
         out.writeInt(mEmergencyServiceCategories);
+        out.writeStringList(mEmergencyUrns);
         out.writeInt(mEmergencyCallRouting);
     }
 
@@ -549,6 +562,7 @@
         mCallExtras = in.readBundle();
         mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader());
         mEmergencyServiceCategories = in.readInt();
+        mEmergencyUrns = in.createStringArrayList();
         mEmergencyCallRouting = in.readInt();
     }
 
@@ -760,20 +774,21 @@
     }
 
     /**
-     * Set the emergency service categories and emergency call routing. The set value is valid
+     * Set the emergency number information. The set value is valid
      * only if {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY}
      *
      * Reference: 3gpp 23.167, Section 6 - Functional description;
+     *            3gpp 24.503, Section 5.1.6.8.1 - General;
      *            3gpp 22.101, Section 10 - Emergency Calls.
      *
      * @hide
      */
     public void setEmergencyCallInfo(EmergencyNumber num) {
         setEmergencyServiceCategories(num.getEmergencyServiceCategoryBitmask());
+        setEmergencyUrns(num.getEmergencyUrns());
         setEmergencyCallRouting(num.getEmergencyCallRouting());
     }
 
-
     /**
      * Set the emergency service categories. The set value is valid only if
      * {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY}
@@ -800,6 +815,18 @@
     }
 
     /**
+     * Set the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType}
+     * returns {@link #SERVICE_TYPE_EMERGENCY}.
+     *
+     * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+     *            3gpp 22.101, Section 10 - Emergency Calls.
+     */
+    @VisibleForTesting
+    public void setEmergencyUrns(List<String> emergencyUrns) {
+        mEmergencyUrns = emergencyUrns;
+    }
+
+    /**
      * Set the emergency call routing, only valid if {@link #getServiceType} returns
      * {@link #SERVICE_TYPE_EMERGENCY}
      *
@@ -841,6 +868,17 @@
     }
 
     /**
+     * Get the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType}
+     * returns {@link #SERVICE_TYPE_EMERGENCY}.
+     *
+     * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General;
+     *            3gpp 22.101, Section 10 - Emergency Calls.
+     */
+    public List<String> getEmergencyUrns() {
+        return mEmergencyUrns;
+    }
+
+    /**
      * Get the emergency call routing, only valid if {@link #getServiceType} returns
      * {@link #SERVICE_TYPE_EMERGENCY}
      *
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 870a689..dd40d56 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -39,7 +39,7 @@
     oneway void switchToSubscription(int subscriptionId, String callingPackage,
         in PendingIntent callbackIntent);
     oneway void updateSubscriptionNickname(int subscriptionId, String nickname,
-        in PendingIntent callbackIntent);
+        String callingPackage, in PendingIntent callbackIntent);
     oneway void eraseSubscriptions(in PendingIntent callbackIntent);
     oneway void retainSubscriptionsForFactoryReset(in PendingIntent callbackIntent);
 }
\ No newline at end of file
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 37158e5..e1d6e01 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -25,7 +25,8 @@
         "android.test.mock",
     ],
 
+    srcs_lib: "framework",
+    srcs_lib_whitelist_dirs: ["core/java"],
     srcs_lib_whitelist_pkgs: ["android"],
-    metalava_enabled: false,
     compile_dex: true,
 }
diff --git a/tests/PackageWatchdog/Android.mk b/tests/PackageWatchdog/Android.mk
new file mode 100644
index 0000000..b53f27d
--- /dev/null
+++ b/tests/PackageWatchdog/Android.mk
@@ -0,0 +1,34 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# PackageWatchdogTest
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_PACKAGE_NAME := PackageWatchdogTest
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    junit \
+    frameworks-base-testutils \
+    android-support-test \
+    services
+
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner
+
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/PackageWatchdog/AndroidManifest.xml b/tests/PackageWatchdog/AndroidManifest.xml
new file mode 100644
index 0000000..fa89528
--- /dev/null
+++ b/tests/PackageWatchdog/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.tests.packagewatchdog" >
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.tests.packagewatchdog"
+                     android:label="PackageWatchdog Test"/>
+</manifest>
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
new file mode 100644
index 0000000..ec07037
--- /dev/null
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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;
+
+import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.test.TestLooper;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+// TODO(zezeozue): Write test without using PackageWatchdog#getPackages. Just rely on
+// behavior of observers receiving crash notifications or not to determine if it's registered
+/**
+ * Test PackageWatchdog.
+ */
+public class PackageWatchdogTest {
+    private static final String APP_A = "com.package.a";
+    private static final String APP_B = "com.package.b";
+    private static final String OBSERVER_NAME_1 = "observer1";
+    private static final String OBSERVER_NAME_2 = "observer2";
+    private static final String OBSERVER_NAME_3 = "observer3";
+    private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
+    private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
+    private TestLooper mTestLooper;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestLooper = new TestLooper();
+        mTestLooper.startAutoDispatch();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        new File(InstrumentationRegistry.getContext().getFilesDir(),
+                "package-watchdog.xml").delete();
+    }
+
+    /**
+     * Test registration, unregistration, package expiry and duration reduction
+     */
+    @Test
+    public void testRegistration() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+        TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
+
+        // Start observing for observer1 which will be unregistered
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        // Start observing for observer2 which will expire
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+        // Start observing for observer3 which will have expiry duration reduced
+        watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION);
+
+        // Verify packages observed at start
+        // 1
+        assertEquals(1, watchdog.getPackages(observer1).size());
+        assertTrue(watchdog.getPackages(observer1).contains(APP_A));
+        // 2
+        assertEquals(2, watchdog.getPackages(observer2).size());
+        assertTrue(watchdog.getPackages(observer2).contains(APP_A));
+        assertTrue(watchdog.getPackages(observer2).contains(APP_B));
+        // 3
+        assertEquals(1, watchdog.getPackages(observer3).size());
+        assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+
+        // Then unregister observer1
+        watchdog.unregisterHealthObserver(observer1);
+
+        // Verify observer2 and observer3 left
+        // 1
+        assertNull(watchdog.getPackages(observer1));
+        // 2
+        assertEquals(2, watchdog.getPackages(observer2).size());
+        assertTrue(watchdog.getPackages(observer2).contains(APP_A));
+        assertTrue(watchdog.getPackages(observer2).contains(APP_B));
+        // 3
+        assertEquals(1, watchdog.getPackages(observer3).size());
+        assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+
+        // Then advance time a little and run messages in Handlers so observer2 expires
+        Thread.sleep(SHORT_DURATION);
+        mTestLooper.dispatchAll();
+
+        // Verify observer3 left with reduced expiry duration
+        // 1
+        assertNull(watchdog.getPackages(observer1));
+        // 2
+        assertNull(watchdog.getPackages(observer2));
+        // 3
+        assertEquals(1, watchdog.getPackages(observer3).size());
+        assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+
+        // Then advance time some more and run messages in Handlers so observer3 expires
+        Thread.sleep(LONG_DURATION);
+        mTestLooper.dispatchAll();
+
+        // Verify observer3 expired
+        // 1
+        assertNull(watchdog.getPackages(observer1));
+        // 2
+        assertNull(watchdog.getPackages(observer2));
+        // 3
+        assertNull(watchdog.getPackages(observer3));
+    }
+
+    /**
+     * Test package observers are persisted and loaded on startup
+     */
+    @Test
+    public void testPersistence() throws Exception {
+        PackageWatchdog watchdog1 = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+        watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+
+        // Verify 2 observers are registered and saved internally
+        // 1
+        assertEquals(1, watchdog1.getPackages(observer1).size());
+        assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
+        // 2
+        assertEquals(2, watchdog1.getPackages(observer2).size());
+        assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
+        assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
+
+
+        // Then advance time and run IO Handler so file is saved
+        mTestLooper.dispatchAll();
+
+        // Then start a new watchdog
+        PackageWatchdog watchdog2 = createWatchdog();
+
+        // Verify the new watchdog loads observers on startup but nothing registered
+        assertEquals(0, watchdog2.getPackages(observer1).size());
+        assertEquals(0, watchdog2.getPackages(observer2).size());
+        // Verify random observer not saved returns null
+        assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3)));
+
+        // Then regiser observer1
+        watchdog2.registerHealthObserver(observer1);
+        watchdog2.registerHealthObserver(observer2);
+
+        // Verify 2 observers are registered after reload
+        // 1
+        assertEquals(1, watchdog1.getPackages(observer1).size());
+        assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
+        // 2
+        assertEquals(2, watchdog1.getPackages(observer2).size());
+        assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
+        assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
+    }
+
+    /**
+     * Test package failure under threshold does not notify observers
+     */
+    @Test
+    public void testNoPackageFailureBeforeThreshold() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+
+        // Then fail APP_A below the threshold
+        for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) {
+            watchdog.onPackageFailure(new String[]{APP_A});
+        }
+
+        // Verify that observers are not notified
+        assertEquals(0, observer1.mFailedPackages.size());
+        assertEquals(0, observer2.mFailedPackages.size());
+    }
+
+    /**
+     * Test package failure and notifies all observer since none handles the failure
+     */
+    @Test
+    public void testPackageFailureNotifyAll() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+        // Start observing for observer1 and observer2 without handling failures
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+
+        // Then fail APP_A and APP_B above the threshold
+        for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+            watchdog.onPackageFailure(new String[]{APP_A, APP_B});
+        }
+
+        // Verify all observers are notifed of all package failures
+        List<String> observer1Packages = observer1.mFailedPackages;
+        List<String> observer2Packages = observer2.mFailedPackages;
+        assertEquals(2, observer1Packages.size());
+        assertEquals(1, observer2Packages.size());
+        assertEquals(APP_A, observer1Packages.get(0));
+        assertEquals(APP_B, observer1Packages.get(1));
+        assertEquals(APP_A, observer2Packages.get(0));
+    }
+
+    /**
+     * Test package failure and notifies only one observer because it handles the failure
+     */
+    @Test
+    public void testPackageFailureNotifyOne() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, true /* shouldHandle */);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, true /* shouldHandle */);
+
+        // Start observing for observer1 and observer2 with failure handling
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+
+        // Then fail APP_A above the threshold
+        for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+            watchdog.onPackageFailure(new String[]{APP_A});
+        }
+
+        // Verify only one observer is notifed
+        assertEquals(1, observer1.mFailedPackages.size());
+        assertEquals(APP_A, observer1.mFailedPackages.get(0));
+        assertEquals(0, observer2.mFailedPackages.size());
+    }
+
+    private PackageWatchdog createWatchdog() {
+        return new PackageWatchdog(InstrumentationRegistry.getContext(),
+                mTestLooper.getLooper());
+    }
+
+    private static class TestObserver implements PackageHealthObserver {
+        private final String mName;
+        private boolean mShouldHandle;
+        final List<String> mFailedPackages = new ArrayList<>();
+
+        TestObserver(String name) {
+            mName = name;
+        }
+
+        TestObserver(String name, boolean shouldHandle) {
+            mName = name;
+            mShouldHandle = shouldHandle;
+        }
+
+        public boolean onHealthCheckFailed(String packageName) {
+            mFailedPackages.add(packageName);
+            return mShouldHandle;
+        }
+
+        public String getName() {
+            return mName;
+        }
+    }
+}
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index 9d1edbf..f6f35fd 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -18,6 +18,7 @@
     mockito-target-minus-junit4 \
     platform-test-annotations \
     services.core \
+    services.ipmemorystore \
     services.net
 
 LOCAL_JAVA_LIBRARIES := \
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
new file mode 100644
index 0000000..eae9710
--- /dev/null
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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 android.net;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpMemoryStoreTest {
+    @Mock
+    Context mMockContext;
+    @Mock
+    IIpMemoryStore mMockService;
+    IpMemoryStore mStore;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mStore = new IpMemoryStore(mMockContext, mMockService);
+    }
+
+    @Test
+    public void testNetworkAttributes() {
+        // TODO : implement this
+    }
+
+    @Test
+    public void testPrivateData() {
+        // TODO : implement this
+    }
+
+    @Test
+    public void testFindL2Key() {
+        // TODO : implement this
+    }
+
+    @Test
+    public void testIsSameNetwork() {
+        // TODO : implement this
+    }
+
+}
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
index 9695e9a..f82b380 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -53,6 +53,8 @@
     private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
     private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
     private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
+    private static InetAddress PCSCFV6 =  NetworkUtils.numericToInetAddress(
+            "2001:0db8:85a3:0000:0000:8a2e:0370:1");
     private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
     private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
     private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
@@ -86,6 +88,9 @@
         assertTrue(source.isIdenticalValidatedPrivateDnses(target));
         assertTrue(target.isIdenticalValidatedPrivateDnses(source));
 
+        assertTrue(source.isIdenticalPcscfs(target));
+        assertTrue(target.isIdenticalPcscfs(source));
+
         assertTrue(source.isIdenticalRoutes(target));
         assertTrue(target.isIdenticalRoutes(source));
 
@@ -128,6 +133,8 @@
         // set 2 dnses
         source.addDnsServer(DNS1);
         source.addDnsServer(DNS2);
+        // set 1 pcscf
+        source.addPcscfServer(PCSCFV6);
         // set 2 gateways
         source.addRoute(new RouteInfo(GATEWAY1));
         source.addRoute(new RouteInfo(GATEWAY2));
@@ -141,6 +148,7 @@
         target.addLinkAddress(LINKADDRV6);
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
+        target.addPcscfServer(PCSCFV6);
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
@@ -154,6 +162,7 @@
         target.addLinkAddress(LINKADDRV6);
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
+        target.addPcscfServer(PCSCFV6);
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
@@ -167,6 +176,7 @@
         target.addLinkAddress(LINKADDRV6);
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
+        target.addPcscfServer(PCSCFV6);
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
@@ -179,6 +189,21 @@
         // change dnses
         target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
         target.addDnsServer(DNS2);
+        target.addPcscfServer(PCSCFV6);
+        target.addRoute(new RouteInfo(GATEWAY1));
+        target.addRoute(new RouteInfo(GATEWAY2));
+        target.setMtu(MTU);
+        assertFalse(source.equals(target));
+
+        target.clear();
+        target.setInterfaceName(NAME);
+        target.addLinkAddress(LINKADDRV4);
+        target.addLinkAddress(LINKADDRV6);
+        target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+        target.addDnsServer(DNS2);
+        // change pcscf
+        target.addPcscfServer(NetworkUtils.numericToInetAddress(
+            "2001::1"));
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
new file mode 100644
index 0000000..a9f9758
--- /dev/null
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -0,0 +1,113 @@
+/*
+ * 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 android.net.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ParcelableTests {
+    @Test
+    public void testNetworkAttributesParceling() throws Exception {
+        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+        NetworkAttributes in = builder.build();
+        assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+        builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        // groupHint stays null this time around
+        builder.setDnsAddresses(Collections.emptyList());
+        builder.setMtu(18);
+        in = builder.build();
+        assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+        builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9"));
+        builder.setGroupHint("groupHint");
+        builder.setDnsAddresses(Arrays.asList(
+                InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"),
+                InetAddress.getByName("6.7.8.9")));
+        builder.setMtu(1_000_000);
+        in = builder.build();
+        assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+        builder.setMtu(null);
+        in = builder.build();
+        assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+    }
+
+    @Test
+    public void testPrivateDataParceling() throws Exception {
+        final Blob in = new Blob();
+        in.data = new byte[] {89, 111, 108, 111};
+        final Blob out = parcelingRoundTrip(in);
+        // Object.equals on byte[] tests the references
+        assertEquals(in.data.length, out.data.length);
+        assertTrue(Arrays.equals(in.data, out.data));
+    }
+
+    @Test
+    public void testSameL3NetworkResponseParceling() throws Exception {
+        final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+        parcelable.l2Key1 = "key 1";
+        parcelable.l2Key2 = "key 2";
+        parcelable.confidence = 0.43f;
+
+        final SameL3NetworkResponse in = new SameL3NetworkResponse(parcelable);
+        assertEquals("key 1", in.l2Key1);
+        assertEquals("key 2", in.l2Key2);
+        assertEquals(0.43f, in.confidence, 0.01f /* delta */);
+
+        final SameL3NetworkResponse out =
+                new SameL3NetworkResponse(parcelingRoundTrip(in.toParcelable()));
+
+        assertEquals(in, out);
+        assertEquals(in.l2Key1, out.l2Key1);
+        assertEquals(in.l2Key2, out.l2Key2);
+        assertEquals(in.confidence, out.confidence, 0.01f /* delta */);
+    }
+
+    private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception {
+        final Parcel p = Parcel.obtain();
+        in.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        final byte[] marshalledData = p.marshall();
+        p.recycle();
+
+        final Parcel q = Parcel.obtain();
+        q.unmarshall(marshalledData, 0, marshalledData.length);
+        q.setDataPosition(0);
+
+        final Parcelable.Creator<T> creator = (Parcelable.Creator<T>)
+                in.getClass().getField("CREATOR").get(null); // static object, so null receiver
+        final T unmarshalled = (T) creator.createFromParcel(q);
+        q.recycle();
+        return unmarshalled;
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 71529fd..bf39644 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -26,6 +26,8 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
 import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -69,17 +71,19 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -89,7 +93,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
-import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivityManager.PacketKeepalive;
@@ -97,6 +100,8 @@
 import android.net.ConnectivityManager.TooManyRequestsException;
 import android.net.ConnectivityThread;
 import android.net.INetd;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
@@ -114,12 +119,14 @@
 import android.net.NetworkMisc;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.StringNetworkSpecifier;
 import android.net.UidRange;
-import android.net.captiveportal.CaptivePortalProbeResult;
 import android.net.metrics.IpConnectivityLog;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -148,12 +155,9 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.connectivity.ConnectivityConstants;
 import com.android.server.connectivity.DefaultNetworkMetrics;
-import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
-import com.android.server.connectivity.NetworkAgentInfo;
-import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.NetworkPinner;
@@ -168,6 +172,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
 
 import java.net.Inet4Address;
 import java.net.InetAddress;
@@ -230,6 +235,7 @@
     @Mock INetworkStatsService mStatsService;
     @Mock INetworkPolicyManager mNpm;
     @Mock INetd mMockNetd;
+    @Mock NetworkStack mNetworkStack;
 
     private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class);
 
@@ -299,6 +305,7 @@
         public Object getSystemService(String name) {
             if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
             if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
+            if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
             return super.getSystemService(name);
         }
 
@@ -386,7 +393,7 @@
     }
 
     private class MockNetworkAgent {
-        private final WrappedNetworkMonitor mWrappedNetworkMonitor;
+        private final INetworkMonitor mNetworkMonitor;
         private final NetworkInfo mNetworkInfo;
         private final NetworkCapabilities mNetworkCapabilities;
         private final HandlerThread mHandlerThread;
@@ -402,6 +409,26 @@
         // mNetworkStatusReceived.
         private String mRedirectUrl;
 
+        private INetworkMonitorCallbacks mNmCallbacks;
+        private int mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+        private String mNmValidationRedirectUrl = null;
+        private boolean mNmProvNotificationRequested = false;
+
+        void setNetworkValid() {
+            mNmValidationResult = NETWORK_TEST_RESULT_VALID;
+            mNmValidationRedirectUrl = null;
+        }
+
+        void setNetworkInvalid() {
+            mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+            mNmValidationRedirectUrl = null;
+        }
+
+        void setNetworkPortal(String redirectUrl) {
+            setNetworkInvalid();
+            mNmValidationRedirectUrl = redirectUrl;
+        }
+
         MockNetworkAgent(int transport) {
             this(transport, new LinkProperties());
         }
@@ -434,6 +461,29 @@
             }
             mHandlerThread = new HandlerThread("Mock-" + typeName);
             mHandlerThread.start();
+
+            mNetworkMonitor = mock(INetworkMonitor.class);
+            final Answer validateAnswer = inv -> {
+                new Thread(this::onValidationRequested).start();
+                return null;
+            };
+
+            try {
+                doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected();
+                doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt());
+            } catch (RemoteException e) {
+                fail(e.getMessage());
+            }
+
+            final ArgumentCaptor<Network> nmNetworkCaptor =
+                    ArgumentCaptor.forClass(Network.class);
+            final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor =
+                    ArgumentCaptor.forClass(INetworkMonitorCallbacks.class);
+            doNothing().when(mNetworkStack).makeNetworkMonitor(
+                    nmNetworkCaptor.capture(),
+                    any() /* name */,
+                    nmCbCaptor.capture());
+
             mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
                     "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
                     linkProperties, mScore, new NetworkMisc()) {
@@ -465,10 +515,40 @@
                     mPreventReconnectReceived.open();
                 }
             };
+
+            assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId);
+            mNmCallbacks = nmCbCaptor.getValue();
+
+            try {
+                mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
+            } catch (RemoteException e) {
+                fail(e.getMessage());
+            }
+
             // Waits for the NetworkAgent to be registered, which includes the creation of the
             // NetworkMonitor.
             waitForIdle();
-            mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
+        }
+
+        private void onValidationRequested() {
+            try {
+                if (mNmProvNotificationRequested
+                        && mNmValidationResult == NETWORK_TEST_RESULT_VALID) {
+                    mNmCallbacks.hideProvisioningNotification();
+                    mNmProvNotificationRequested = false;
+                }
+
+                mNmCallbacks.notifyNetworkTested(
+                        mNmValidationResult, mNmValidationRedirectUrl);
+
+                if (mNmValidationRedirectUrl != null) {
+                    mNmCallbacks.showProvisioningNotification(
+                            "test_provisioning_notif_action");
+                    mNmProvNotificationRequested = true;
+                }
+            } catch (RemoteException e) {
+                fail(e.getMessage());
+            }
         }
 
         public void adjustScore(int change) {
@@ -539,7 +619,7 @@
             NetworkCallback callback = null;
             final ConditionVariable validatedCv = new ConditionVariable();
             if (validated) {
-                mWrappedNetworkMonitor.gen204ProbeResult = 204;
+                setNetworkValid();
                 NetworkRequest request = new NetworkRequest.Builder()
                         .addTransportType(mNetworkCapabilities.getTransportTypes()[0])
                         .clearCapabilities()
@@ -564,15 +644,14 @@
             if (validated) {
                 // Wait for network to validate.
                 waitFor(validatedCv);
-                mWrappedNetworkMonitor.gen204ProbeResult = 500;
+                setNetworkInvalid();
             }
 
             if (callback != null) mCm.unregisterNetworkCallback(callback);
         }
 
         public void connectWithCaptivePortal(String redirectUrl) {
-            mWrappedNetworkMonitor.gen204ProbeResult = 200;
-            mWrappedNetworkMonitor.gen204ProbeRedirectUrl = redirectUrl;
+            setNetworkPortal(redirectUrl);
             connect(false);
         }
 
@@ -603,10 +682,6 @@
             return mDisconnected;
         }
 
-        public WrappedNetworkMonitor getWrappedNetworkMonitor() {
-            return mWrappedNetworkMonitor;
-        }
-
         public void sendLinkProperties(LinkProperties lp) {
             mNetworkAgent.sendLinkProperties(lp);
         }
@@ -880,28 +955,6 @@
         }
     }
 
-    // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
-    private class WrappedNetworkMonitor extends NetworkMonitor {
-        public final Handler connectivityHandler;
-        // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
-        public int gen204ProbeResult = 500;
-        public String gen204ProbeRedirectUrl = null;
-
-        public WrappedNetworkMonitor(Context context, Handler handler,
-                NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
-                IpConnectivityLog log) {
-            super(context, handler, networkAgentInfo, defaultRequest, log,
-                    NetworkMonitor.Dependencies.DEFAULT);
-            connectivityHandler = handler;
-        }
-
-        @Override
-        protected CaptivePortalProbeResult isCaptivePortal() {
-            if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); }
-            return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null);
-        }
-    }
-
     private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
         public volatile boolean configRestrictsAvoidBadWifi;
         public volatile int configMeteredMultipathPreference;
@@ -923,7 +976,6 @@
 
     private class WrappedConnectivityService extends ConnectivityService {
         public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
-        private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
         private MockableSystemProperties mSystemProperties;
 
         public WrappedConnectivityService(Context context, INetworkManagementService netManager,
@@ -971,15 +1023,6 @@
             }
         }
 
-        @Override
-        public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
-                NetworkAgentInfo nai, NetworkRequest defaultRequest) {
-            final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(
-                    context, handler, nai, defaultRequest, mock(IpConnectivityLog.class));
-            mLastCreatedNetworkMonitor = monitor;
-            return monitor;
-        }
-
         public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
             return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
         }
@@ -1017,10 +1060,6 @@
         protected void registerNetdEventCallback() {
         }
 
-        public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
-            return mLastCreatedNetworkMonitor;
-        }
-
         public void mockVpn(int uid) {
             synchronized (mVpns) {
                 int userId = UserHandle.getUserId(uid);
@@ -2439,7 +2478,7 @@
 
         // Make captive portal disappear then revalidate.
         // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
+        mWiFiNetworkAgent.setNetworkValid();
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
@@ -2448,13 +2487,13 @@
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
+        mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
     }
 
     @Test
-    public void testCaptivePortalApp() {
+    public void testCaptivePortalApp() throws RemoteException {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2477,21 +2516,19 @@
         mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
 
         // Turn into a captive portal.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
+        mWiFiNetworkAgent.setNetworkPortal("http://example.com");
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
-        // Check that startCaptivePortalApp sends the expected intent.
+        // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
         mCm.startCaptivePortalApp(wifiNetwork);
-        Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
-        assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
-        assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK));
+        verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
+                .launchCaptivePortalApp();
 
-        // Have the app report that the captive portal is dismissed, and check that we revalidate.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
-        CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
-        c.reportCaptivePortalDismissed();
+        // Report that the captive portal is dismissed, and check that callbacks are fired
+        mWiFiNetworkAgent.setNetworkValid();
+        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
@@ -2524,20 +2561,6 @@
         waitFor(avoidCv);
 
         assertNoCallbacks(captivePortalCallback, validatedCallback);
-
-        // Now test ignore mode.
-        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
-
-        // Bring up a network with a captive portal.
-        // Since we're ignoring captive portals, the network will validate.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        String secondRedirectUrl = "http://example.com/secondPath";
-        mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
-
-        // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        // But there should be no CaptivePortal callback.
-        captivePortalCallback.assertNoCallback();
     }
 
     private NetworkRequest.Builder newWifiRequestBuilder() {
@@ -3169,7 +3192,7 @@
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+        mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -3213,7 +3236,7 @@
         wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi and expect the dialog to appear.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+        mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -4002,11 +4025,9 @@
         final String TLS_SERVER6 = "2001:db8:53::53";
         final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) };
         final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 };
-        final Handler h = mCellNetworkAgent.getWrappedNetworkMonitor().connectivityHandler;
-        h.sendMessage(h.obtainMessage(
-                NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0,
-                mCellNetworkAgent.getNetwork().netId,
-                new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS)));
+        mCellNetworkAgent.mNmCallbacks.notifyPrivateDnsConfigResolved(
+                new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel());
+
         waitForIdle();
         verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
                 anyInt(), mStringArrayCaptor.capture(), any(), any(),
@@ -4294,6 +4315,12 @@
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
         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));
+        vpnNetworkAgent.setNetworkValid();
+
         vpnNetworkAgent.connect(false);
         mMockVpn.connect();
 
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 01b468a..38322e9 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -17,7 +17,6 @@
 package com.android.server.connectivity;
 
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
 import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
@@ -29,13 +28,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.RouteInfo;
+import android.net.shared.PrivateDnsConfig;
 import android.os.INetworkManagementService;
 import android.provider.Settings;
 import android.support.test.filters.SmallTest;
@@ -43,18 +42,16 @@
 import android.test.mock.MockContentResolver;
 
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
-import com.android.server.connectivity.MockableSystemProperties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.net.InetAddress;
 import java.util.Arrays;
 
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
 /**
  * Tests for {@link DnsManager}.
  *
@@ -133,7 +130,7 @@
                 PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
         Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, "strictmode.com");
         mDnsManager.updatePrivateDns(new Network(TEST_NETID),
-                new DnsManager.PrivateDnsConfig("strictmode.com", new InetAddress[] {
+                new PrivateDnsConfig("strictmode.com", new InetAddress[] {
                     InetAddress.parseNumericAddress("6.6.6.6"),
                     InetAddress.parseNumericAddress("2001:db8:66:66::1")
                     }));
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 354cf2f..4c52d81 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -23,10 +23,10 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.reset;
 
 import android.app.PendingIntent;
 import android.content.Context;
@@ -36,18 +36,18 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
-import android.support.test.runner.AndroidJUnit4;
+import android.net.NetworkStack;
 import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.text.format.DateUtils;
 
 import com.android.internal.R;
 import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkNotificationManager;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 
-import org.junit.runner.RunWith;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -70,13 +70,16 @@
     @Mock NetworkMisc mMisc;
     @Mock NetworkNotificationManager mNotifier;
     @Mock Resources mResources;
+    @Mock NetworkStack mNetworkStack;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mCtx.getResources()).thenReturn(mResources);
         when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity");
-        when(mConnService.createNetworkMonitor(any(), any(), any(), any())).thenReturn(null);
+        when(mCtx.getSystemServiceName(NetworkStack.class))
+                .thenReturn(Context.NETWORK_STACK_SERVICE);
+        when(mCtx.getSystemService(Context.NETWORK_STACK_SERVICE)).thenReturn(mNetworkStack);
 
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
     }
@@ -349,7 +352,7 @@
         caps.addCapability(0);
         caps.addTransportType(transport);
         NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
-                caps, 50, mCtx, null, mMisc, null, mConnService);
+                caps, 50, mCtx, null, mMisc, mConnService);
         nai.everValidated = true;
         return nai;
     }
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
new file mode 100644
index 0000000..859a54d
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link IpMemoryStoreServiceTest}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IpMemoryStoreServiceTest {
+    @Mock
+    Context mMockContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testNetworkAttributes() {
+        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+        // TODO : implement this
+    }
+
+    @Test
+    public void testPrivateData() {
+        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+        // TODO : implement this
+    }
+
+    @Test
+    public void testFindL2Key() {
+        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+        // TODO : implement this
+    }
+
+    @Test
+    public void testIsSameNetwork() {
+        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+        // TODO : implement this
+    }
+}
diff --git a/tools/bit/command.h b/tools/bit/command.h
index fb44900..dd7103e 100644
--- a/tools/bit/command.h
+++ b/tools/bit/command.h
@@ -25,7 +25,7 @@
 
 struct Command
 {
-    Command(const string& prog);
+    explicit Command(const string& prog);
     ~Command();
 
     void AddArg(const string& arg);
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 9968bda..4491a85 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -67,7 +67,7 @@
         case JAVA_TYPE_STRING:
             return "char const*";
         case JAVA_TYPE_BYTE_ARRAY:
-            return "char const*";
+            return "const BytesField&";
         default:
             return "UNKNOWN";
     }
@@ -270,10 +270,6 @@
                                  chainField.name.c_str(), chainField.name.c_str());
                     }
                 }
-            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-                fprintf(out, ", %s arg%d, size_t arg%d_length",
-                        cpp_type_name(*arg), argIndex, argIndex);
-
             } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
                 fprintf(out, ", const std::map<int, int32_t>& arg%d_1, "
                              "const std::map<int, int64_t>& arg%d_2, "
@@ -355,7 +351,8 @@
                     fprintf(out, "    event.end();\n\n");
             } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
                 fprintf(out,
-                        "    event.AppendCharArray(arg%d, arg%d_length);\n",
+                        "    event.AppendCharArray(arg%d.arg, "
+                        "arg%d.arg_length);\n",
                         argIndex, argIndex);
             } else {
                 if (*arg == JAVA_TYPE_STRING) {
@@ -397,10 +394,6 @@
                                 chainField.name.c_str(), chainField.name.c_str());
                    }
                }
-           } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-               fprintf(out, ", %s arg%d, size_t arg%d_length",
-                       cpp_type_name(*arg), argIndex, argIndex);
-
            } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
                fprintf(out,
                        ", const std::map<int, int32_t>& arg%d_1, "
@@ -434,8 +427,6 @@
                                 chainField.name.c_str(), chainField.name.c_str());
                    }
                }
-           } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-               fprintf(out, ", arg%d, arg%d_length", argIndex, argIndex);
            } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
                fprintf(out, ", arg%d_1, arg%d_2, arg%d_3, arg%d_4", argIndex,
                        argIndex, argIndex, argIndex);
@@ -494,7 +485,14 @@
                 fprintf(out, "        arg%d = \"\";\n", argIndex);
                 fprintf(out, "    }\n");
             }
-            fprintf(out, "    event << arg%d;\n", argIndex);
+            if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                fprintf(out,
+                        "    event.AppendCharArray(arg%d.arg, "
+                        "arg%d.arg_length);",
+                        argIndex, argIndex);
+            } else {
+                fprintf(out, "    event << arg%d;\n", argIndex);
+            }
             if (argIndex == 2) {
                 fprintf(out, "    event.end();\n\n");
                 fprintf(out, "    event.end();\n\n");
@@ -577,7 +575,9 @@
 static void write_cpp_usage(
     FILE* out, const string& method_name, const string& atom_code_name,
     const AtomDecl& atom, const AtomDecl &attributionDecl) {
-    fprintf(out, "     * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str());
+    fprintf(out, "     * Usage: %s(StatsLog.%s", method_name.c_str(),
+            atom_code_name.c_str());
+
     for (vector<AtomField>::const_iterator field = atom.fields.begin();
             field != atom.fields.end(); field++) {
         if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -601,11 +601,6 @@
                          field->name.c_str(),
                          field->name.c_str(),
                          field->name.c_str());
-        } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) {
-            fprintf(out, ", %s %s, size_t %s_length",
-                    cpp_type_name(field->javaType), field->name.c_str(),
-                    field->name.c_str());
-
         } else {
             fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
         }
@@ -639,9 +634,6 @@
                              "const std::map<int, char const*>& arg%d_3, "
                              "const std::map<int, float>& arg%d_4",
                              argIndex, argIndex, argIndex, argIndex);
-            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-                fprintf(out, ", %s arg%d, size_t arg%d_length",
-                        cpp_type_name(*arg), argIndex, argIndex);
             } else {
                 fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
             }
@@ -708,6 +700,15 @@
     fprintf(out, "};\n");
     fprintf(out, "\n");
 
+    fprintf(out, "struct BytesField {\n");
+    fprintf(out,
+            "  BytesField(char const* array, size_t len) : arg(array), "
+            "arg_length(len) {}\n");
+    fprintf(out, "  char const* arg;\n");
+    fprintf(out, "  size_t arg_length;\n");
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
     fprintf(out, "struct StateAtomFieldOptions {\n");
     fprintf(out, "  std::vector<int> primaryFields;\n");
     fprintf(out, "  int exclusiveField;\n");
@@ -1183,6 +1184,11 @@
                 fprintf(out, "        str%d = NULL;\n", argIndex);
                 fprintf(out, "    }\n");
 
+                fprintf(out,
+                        "    android::util::BytesField bytesField%d(str%d, "
+                        "str%d_length);",
+                        argIndex, argIndex, argIndex);
+
             } else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
                 hadStringOrChain = true;
                 for (auto chainField : attributionDecl.fields) {
@@ -1240,7 +1246,8 @@
 
         // stats_write call
         argIndex = 1;
-        fprintf(out, "\n    int ret =  android::util::%s(code", cpp_method_name.c_str());
+        fprintf(out, "\n    int ret =  android::util::%s(code",
+                cpp_method_name.c_str());
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
             if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -1255,16 +1262,12 @@
                 }
             } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
                 fprintf(out, ", int32_t_map, int64_t_map, string_map, float_map");
+            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                fprintf(out, ", bytesField%d", argIndex);
             } else {
-                const char* argName = (*arg == JAVA_TYPE_STRING ||
-                                       *arg == JAVA_TYPE_BYTE_ARRAY)
-                                              ? "str"
-                                              : "arg";
+                const char* argName =
+                        (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
                 fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
-
-                if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-                    fprintf(out, ", %s%d_length", argName, argIndex);
-                }
             }
             argIndex++;
         }
diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h
index f14bbfd..bddd981 100644
--- a/tools/streaming_proto/Errors.h
+++ b/tools/streaming_proto/Errors.h
@@ -11,7 +11,7 @@
 struct Error
 {
     Error();
-    explicit Error(const Error& that);
+    Error(const Error& that);
     Error(const string& filename, int lineno, const char* message);
 
     string filename;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b265c40..517bf3b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -189,6 +189,7 @@
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5;
 
+    /** @hide */
     @IntDef(prefix = { "STATUS_NETWORK_SUGGESTIONS_" }, value = {
             STATUS_NETWORK_SUGGESTIONS_SUCCESS,
             STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL,
@@ -234,6 +235,12 @@
     @SystemApi
     public static final int WIFI_CREDENTIAL_FORGOT = 1;
 
+    /** @hide */
+    public static final int PASSPOINT_HOME_NETWORK = 0;
+
+    /** @hide */
+    public static final int PASSPOINT_ROAMING_NETWORK = 1;
+
     /**
      * Broadcast intent action indicating that a Passpoint provider icon has been received.
      *
@@ -1207,26 +1214,30 @@
      * match the ScanResult.
      *
      * @param scanResults a list of scanResult that represents the BSSID
-     * @return List that consists of {@link WifiConfiguration} and corresponding scanResults.
+     * @return List that consists of {@link WifiConfiguration} and corresponding scanResults per
+     * network type({@link #PASSPOINT_HOME_NETWORK} and {@link #PASSPOINT_ROAMING_NETWORK}).
      * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
-    public List<Pair<WifiConfiguration, List<ScanResult>>> getAllMatchingWifiConfigs(
+    public List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> getAllMatchingWifiConfigs(
             @NonNull List<ScanResult> scanResults) {
-        List<Pair<WifiConfiguration, List<ScanResult>>> configs = new ArrayList<>();
+        List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> configs = new ArrayList<>();
         try {
-            Map<String, List<ScanResult>> results = mService.getAllMatchingFqdnsForScanResults(
-                    scanResults);
+            Map<String, Map<Integer, List<ScanResult>>> results =
+                    mService.getAllMatchingFqdnsForScanResults(
+                            scanResults);
             if (results.isEmpty()) {
                 return configs;
             }
             List<WifiConfiguration> wifiConfigurations =
-                    mService.getWifiConfigsForPasspointProfiles(new ArrayList<>(results.keySet()));
+                    mService.getWifiConfigsForPasspointProfiles(
+                            new ArrayList<>(results.keySet()));
             for (WifiConfiguration configuration : wifiConfigurations) {
-                List<ScanResult> scanResultList = results.get(configuration.FQDN);
-                if (scanResultList != null) {
-                    configs.add(Pair.create(configuration, scanResultList));
+                Map<Integer, List<ScanResult>> scanResultsPerNetworkType = results.get(
+                        configuration.FQDN);
+                if (scanResultsPerNetworkType != null) {
+                    configs.add(Pair.create(configuration, scanResultsPerNetworkType));
                 }
             }
         } catch (RemoteException e) {
@@ -1642,7 +1653,7 @@
      * suggestion back using this API.</li>
      *
      * @param networkSuggestions List of network suggestions provided by the app.
-     * @return Status code corresponding to the values in {@link NetworkSuggestionsStatusCode}.
+     * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
      * {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app.
      * @throws {@link SecurityException} if the caller is missing required permissions.
      */
@@ -1663,8 +1674,7 @@
      *
      * @param networkSuggestions List of network suggestions to be removed. Pass an empty list
      *                           to remove all the previous suggestions provided by the app.
-     * @return Status code corresponding to the values in
-     * {@link NetworkSuggestionsStatusCode}.
+     * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
      * Any matching suggestions are removed from the device and will not be considered for any
      * further connection attempts.
      */
diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
similarity index 96%
rename from wifi/java/com/android/server/wifi/AbstractWifiService.java
rename to wifi/java/com/android/server/wifi/BaseWifiService.java
index f06346c..2c96c05 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -38,14 +38,13 @@
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.RemoteException;
 import android.os.WorkSource;
 
 import java.util.List;
 import java.util.Map;
 
 /**
- * Abstract class implementing IWifiManager with stub methods throwing runtime exceptions.
+ * Empty concrete class implementing IWifiManager with stub methods throwing runtime exceptions.
  *
  * This class is meant to be extended by real implementations of IWifiManager in order to facilitate
  * cross-repo changes to WiFi internal APIs, including the introduction of new APIs, the removal of
@@ -56,10 +55,13 @@
  * then given a short grace period to update themselves before the @Deprecated stub is removed for
  * good. If the API scheduled for removal has a replacement or an overload (signature change),
  * these should be introduced before the stub is removed to allow children to migrate.
+ *
+ * When a new API is added to IWifiManager.aidl, a stub should be added in BaseWifiService as
+ * well otherwise compilation will fail.
  */
-public abstract class AbstractWifiService extends IWifiManager.Stub {
+public class BaseWifiService extends IWifiManager.Stub {
 
-    private static final String TAG = AbstractWifiService.class.getSimpleName();
+    private static final String TAG = BaseWifiService.class.getSimpleName();
 
     @Override
     public int getSupportedFeatures() {
@@ -87,7 +89,7 @@
     }
 
     @Override
-    public Map<String, List<ScanResult>> getAllMatchingFqdnsForScanResults(
+    public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingFqdnsForScanResults(
             List<ScanResult> scanResults) {
         throw new UnsupportedOperationException();
     }