Merge "Don't immediately start listening to Prox in DozeSensors."
diff --git a/Android.bp b/Android.bp
index 9ffdd1d..21552695 100644
--- a/Android.bp
+++ b/Android.bp
@@ -203,7 +203,6 @@
     name: "framework-non-updatable-sources",
     srcs: [
         // Java/AIDL sources under frameworks/base
-        ":framework-appsearch-sources",
         ":framework-blobstore-sources",
         ":framework-core-sources",
         ":framework-drm-sources",
@@ -262,6 +261,7 @@
 filegroup {
     name: "framework-updatable-sources",
     srcs: [
+        ":framework-appsearch-sources",
         ":framework-sdkext-sources",
         ":framework-statsd-sources",
         ":updatable-media-srcs",
@@ -427,6 +427,7 @@
     defaults: ["framework-defaults"],
     srcs: [":framework-non-updatable-sources"],
     libs: [
+        "framework-appsearch-stubs",
         // TODO(b/146167933): Use framework-statsd-stubs
         "framework-statsd",
         "framework-wifi-stubs",
@@ -466,6 +467,7 @@
     installable: false, // this lib is a build-only library
     static_libs: [
         "framework-minus-apex",
+        "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
         "framework-sdkext-stubs-systemapi",
         // TODO(b/146167933): Use framework-statsd-stubs instead.
         "framework-statsd",
diff --git a/apex/Android.bp b/apex/Android.bp
index 85d72c7..56f7db2 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -26,6 +26,9 @@
     "--hide Typo " +
     "--hide UnavailableSymbol "
 
+// TODO: remove this server classes are cleaned up.
+mainline_stubs_args += "--hide-package com.android.server "
+
 stubs_defaults {
     name: "framework-module-stubs-defaults-publicapi",
     args: mainline_stubs_args,
diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp
index bcdcc7d..b014fdc 100644
--- a/apex/appsearch/Android.bp
+++ b/apex/appsearch/Android.bp
@@ -14,9 +14,11 @@
 
 apex {
     name: "com.android.appsearch",
-
     manifest: "apex_manifest.json",
-
+    java_libs: [
+        "framework-appsearch",
+        "service-appsearch",
+    ],
     key: "com.android.appsearch.key",
     certificate: ":com.android.appsearch.certificate",
 }
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 0a65f73..3dc5a2c 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -23,17 +23,52 @@
 
 java_library {
   name: "framework-appsearch",
-  installable: false,
-  sdk_version: "core_platform",
-  srcs: [
-    ":framework-appsearch-sources",
-  ],
-  aidl: {
-    export_include_dirs: [
-      "java",
-    ],
-  },
+  installable: true,
+  sdk_version: "core_platform", // TODO(b/146218515) should be core_current
+  srcs: [":framework-appsearch-sources"],
   libs: [
-    "framework-minus-apex",
+    "framework-minus-apex",  // TODO(b/146218515) should be framework-system-stubs
   ],
 }
+
+metalava_appsearch_docs_args =
+    "--hide-package com.android.server " +
+    "--error UnhiddenSystemApi " +
+    "--hide RequiresPermission " +
+    "--hide MissingPermission " +
+    "--hide BroadcastBehavior " +
+    "--hide HiddenSuperclass " +
+    "--hide DeprecationMismatch " +
+    "--hide UnavailableSymbol " +
+    "--hide SdkConstant " +
+    "--hide HiddenTypeParameter " +
+    "--hide Todo --hide Typo " +
+    "--hide HiddenTypedefConstant " +
+    "--show-annotation android.annotation.SystemApi "
+
+droidstubs {
+    name: "framework-appsearch-stubs-srcs",
+    srcs: [
+        ":framework-annotations",
+        ":framework-appsearch-sources",
+    ],
+    aidl: {
+        include_dirs: ["frameworks/base/core/java"],
+    },
+    args: metalava_appsearch_docs_args,
+    sdk_version: "core_current",
+    libs: ["android_system_stubs_current"],
+}
+
+java_library {
+    name: "framework-appsearch-stubs",
+    srcs: [":framework-appsearch-stubs-srcs"],
+    aidl: {
+        export_include_dirs: [
+            "java",
+        ],
+    },
+    sdk_version: "core_current",
+    libs: ["android_system_stubs_current"],
+    installable: false,
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
index fcebe3d..02cc967 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
@@ -15,19 +15,25 @@
  */
 package android.app.appsearch;
 
+import android.annotation.SystemApi;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
 
 /**
- * This is where the AppSearchManagerService wrapper is registered.
+ * Class holding initialization code for the AppSearch module.
  *
- * TODO(b/142567528): add comments when implement this class
  * @hide
  */
+@SystemApi
 public class AppSearchManagerFrameworkInitializer {
+    private AppSearchManagerFrameworkInitializer() {}
 
     /**
-     * TODO(b/142567528): add comments when implement this class
+     * Called by {@link SystemServiceRegistry}'s static initializer and registers all AppSearch
+     * services to {@link Context}, so that {@link Context#getSystemService} can return them.
+     *
+     * @throws IllegalStateException if this is called from anywhere besides
+     *     {@link SystemServiceRegistry}
      */
     public static void initialize() {
         SystemServiceRegistry.registerStaticService(
diff --git a/api/current.txt b/api/current.txt
index 7e7cd9d..4708616 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -35029,6 +35029,7 @@
     method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
     method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
     method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException;
+    method public static int getSuggestedMaxIpcSizeBytes();
     method public boolean isBinderAlive();
     method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException;
     method public boolean pingBinder();
@@ -35225,6 +35226,7 @@
     method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
     method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
     method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+    method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
     method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
     method @Nullable public android.os.PersistableBundle readPersistableBundle();
     method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
@@ -35272,6 +35274,7 @@
     method public void writeNoException();
     method public void writeParcelable(@Nullable android.os.Parcelable, int);
     method public <T extends android.os.Parcelable> void writeParcelableArray(@Nullable T[], int);
+    method public void writeParcelableCreator(@NonNull android.os.Parcelable);
     method public <T extends android.os.Parcelable> void writeParcelableList(@Nullable java.util.List<T>, int);
     method public void writePersistableBundle(@Nullable android.os.PersistableBundle);
     method public void writeSerializable(@Nullable java.io.Serializable);
@@ -38710,6 +38713,10 @@
 
   public final class MediaStore {
     ctor public MediaStore();
+    method @NonNull public static android.app.PendingIntent createDeleteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
+    method @NonNull public static android.app.PendingIntent createFavoriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+    method @NonNull public static android.app.PendingIntent createTrashRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+    method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
     method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
     method public static android.net.Uri getMediaScannerUri();
@@ -38720,9 +38727,6 @@
     method @NonNull public static String getVolumeName(@NonNull android.net.Uri);
     method @Deprecated @NonNull public static android.net.Uri setIncludePending(@NonNull android.net.Uri);
     method @NonNull public static android.net.Uri setRequireOriginal(@NonNull android.net.Uri);
-    method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
-    method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
-    method public static void untrash(@NonNull android.content.Context, @NonNull android.net.Uri);
     field public static final String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
     field public static final String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
     field public static final String ACTION_REVIEW = "android.provider.action.REVIEW";
@@ -44601,6 +44605,7 @@
     method public void notifyConfigChangedForSubId(int);
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
+    field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
     field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
     field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
     field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
@@ -44660,11 +44665,16 @@
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
-    field public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+    field public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING = "config_ims_mmtel_package_override_string";
+    field @Deprecated public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+    field public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING = "config_ims_rcs_package_override_string";
     field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
     field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
     field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
+    field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
     field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+    field public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL = "data_rapid_notification_bool";
+    field public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL = "data_warning_notification_bool";
     field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
     field public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING = "default_vm_number_roaming_and_ims_unregistered_string";
@@ -45224,6 +45234,7 @@
     method public void onDataConnectionStateChanged(int);
     method public void onDataConnectionStateChanged(int, int);
     method public void onMessageWaitingIndicatorChanged(boolean);
+    method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
     method public void onServiceStateChanged(android.telephony.ServiceState);
     method @Deprecated public void onSignalStrengthChanged(int);
     method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
@@ -45238,12 +45249,23 @@
     field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
     field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
     field public static final int LISTEN_NONE = 0; // 0x0
+    field @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
     field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
     field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
     field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
     field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
   }
 
+  public final class PreciseDataConnectionState implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLastCauseCode();
+    method @Nullable public android.net.LinkProperties getLinkProperties();
+    method public int getNetworkType();
+    method public int getState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+  }
+
   public final class RadioAccessSpecifier implements android.os.Parcelable {
     ctor public RadioAccessSpecifier(int, int[], int[]);
     method public int describeContents();
@@ -45756,6 +45778,7 @@
     field public static final int DATA_CONNECTED = 2; // 0x2
     field public static final int DATA_CONNECTING = 1; // 0x1
     field public static final int DATA_DISCONNECTED = 0; // 0x0
+    field public static final int DATA_DISCONNECTING = 4; // 0x4
     field public static final int DATA_SUSPENDED = 3; // 0x3
     field public static final int DATA_UNKNOWN = -1; // 0xffffffff
     field public static final String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
diff --git a/api/removed.txt b/api/removed.txt
index 1a2f434..4e8e325 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -441,6 +441,9 @@
     method @Deprecated public static boolean getIncludePending(@NonNull android.net.Uri);
     method @Deprecated @NonNull public static android.provider.MediaStore.PendingSession openPending(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @Deprecated @NonNull public static android.net.Uri setIncludeTrashed(@NonNull android.net.Uri);
+    method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
+    method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
+    method @Deprecated public static void untrash(@NonNull android.content.Context, @NonNull android.net.Uri);
   }
 
   public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
diff --git a/api/system-current.txt b/api/system-current.txt
index 7504582..b14bf34 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -811,6 +811,14 @@
 
 }
 
+package android.app.appsearch {
+
+  public class AppSearchManagerFrameworkInitializer {
+    method public static void initialize();
+  }
+
+}
+
 package android.app.assist {
 
   public static class AssistStructure.ViewNode {
@@ -8039,6 +8047,7 @@
     field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
     field public static final String KEY_IMPORTANCE = "key_importance";
     field public static final String KEY_PEOPLE = "key_people";
+    field public static final String KEY_RANKING_SCORE = "key_ranking_score";
     field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
     field public static final String KEY_TEXT_REPLIES = "key_text_replies";
     field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -9320,7 +9329,6 @@
     method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
     method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
-    method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
     method public void onRadioPowerStateChanged(int);
     method public void onSrvccStateChanged(int);
     method public void onVoiceActivationStateChanged(int);
@@ -9330,7 +9338,6 @@
     field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
     field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
-    field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
     field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
     field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
@@ -9356,13 +9363,12 @@
   }
 
   public final class PreciseDataConnectionState implements android.os.Parcelable {
-    method public int describeContents();
-    method @Nullable public String getDataConnectionApn();
-    method public int getDataConnectionApnTypeBitMask();
-    method public int getDataConnectionFailCause();
-    method public int getDataConnectionState();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+    method @Deprecated @NonNull public String getDataConnectionApn();
+    method @Deprecated public int getDataConnectionApnTypeBitMask();
+    method @Deprecated public int getDataConnectionFailCause();
+    method @Deprecated @Nullable public android.net.LinkProperties getDataConnectionLinkProperties();
+    method @Deprecated public int getDataConnectionNetworkType();
+    method @Deprecated public int getDataConnectionState();
   }
 
   public final class PreciseDisconnectCause {
diff --git a/api/test-current.txt b/api/test-current.txt
index 3bdd479..1c6bce0 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2832,6 +2832,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
     field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
     field public static final String KEY_IMPORTANCE = "key_importance";
+    field public static final String KEY_RANKING_SCORE = "key_ranking_score";
     field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
     field public static final String KEY_TEXT_REPLIES = "key_text_replies";
     field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -3162,6 +3163,10 @@
     field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
   }
 
+  public final class PreciseDataConnectionState implements android.os.Parcelable {
+    ctor @Deprecated public PreciseDataConnectionState(int, int, int, @NonNull String, @Nullable android.net.LinkProperties, int);
+  }
+
   public class ServiceState implements android.os.Parcelable {
     method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
     method public void setCdmaSystemAndNetworkId(int, int);
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 8b62e2f..d82b151 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -17,6 +17,8 @@
 package android.accessibilityservice;
 
 
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
@@ -58,6 +60,8 @@
 
     /** @hide */
     @IntDef(prefix = { "GESTURE_" }, value = {
+            GESTURE_DOUBLE_TAP,
+            GESTURE_DOUBLE_TAP_AND_HOLD,
             GESTURE_SWIPE_UP,
             GESTURE_SWIPE_UP_AND_LEFT,
             GESTURE_SWIPE_UP_AND_DOWN,
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 47fdcde..0f619c8 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -300,6 +300,18 @@
     public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
 
     /**
+     * The user has performed a double tap gesture on the touch screen.
+     * @hide
+     */
+    public static final int GESTURE_DOUBLE_TAP = 17;
+
+    /**
+     * The user has performed a double tap and hold gesture on the touch screen.
+     * @hide
+     */
+    public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18;
+
+    /**
      * The {@link Intent} that must be declared as handled by the service.
      */
     public static final String SERVICE_INTERFACE =
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index f4e465a..0f10c39 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1943,35 +1943,6 @@
     }
 
     /**
-     * @hide
-     * Removes the shared account.
-     * @param account the account to remove
-     * @param user the user to remove the account from
-     * @return
-     */
-    public boolean removeSharedAccount(final Account account, UserHandle user) {
-        try {
-            boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
-            return val;
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * @hide
-     * @param user
-     * @return
-     */
-    public Account[] getSharedAccounts(UserHandle user) {
-        try {
-            return mService.getSharedAccountsAsUser(user.getIdentifier());
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Confirms that the user knows the password for an account to make extra
      * sure they are the owner of the account.  The user-entered password can
      * be supplied directly, otherwise the authenticator for this account type
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 4cf0a20..0127138 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -80,14 +80,11 @@
         String authTokenType);
 
     /* Shared accounts */
-    Account[] getSharedAccountsAsUser(int userId);
-    boolean removeSharedAccountAsUser(in Account account, int userId);
     void addSharedAccountsFromParentUser(int parentUserId, int userId, String opPackageName);
 
     /* Account renaming. */
     void renameAccount(in IAccountManagerResponse response, in Account accountToRename, String newName);
     String getPreviousName(in Account account);
-    boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
 
     /* Add account in two steps. */
     void startAddAccountSession(in IAccountManagerResponse response, String accountType,
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index 4178309..ffbca16 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -47,7 +47,7 @@
      * TODO get this number from somewhere else. For now set it to a quarter of
      * the 1MB limit.
      */
-    private static final int MAX_IPC_SIZE = IBinder.MAX_IPC_SIZE;
+    private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
 
     private final List<T> mList;
 
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index 515185e..35df474 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -229,6 +229,11 @@
 
     String getOverlayTargetName();
 
+    /**
+     * Map of overlayable name to actor name.
+     */
+    Map<String, String> getOverlayables();
+
     // TODO(b/135203078): Does this and getAppInfoPackageName have to be separate methods?
     //  The refactor makes them the same value with no known consequences, so should be redundant.
     String getPackageName();
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index 0deb2ab..7732316 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -52,6 +52,7 @@
 import android.content.pm.split.DefaultSplitAssetLoader;
 import android.content.pm.split.SplitAssetDependencyLoader;
 import android.content.pm.split.SplitAssetLoader;
+import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -92,6 +93,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /** @hide */
@@ -287,8 +289,23 @@
                                 + result.getErrorMessage());
             }
 
-            return result.getResultAndNull()
-                    .setVolumeUuid(volumeUuid)
+            ParsingPackage pkg = result.getResultAndNull();
+            ApkAssets apkAssets = assets.getApkAssets()[0];
+            if (apkAssets.definesOverlayable()) {
+                SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
+                int size = packageNames.size();
+                for (int index = 0; index < size; index++) {
+                    String packageName = packageNames.get(index);
+                    Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
+                    if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
+                        for (String overlayable : overlayableToActor.keySet()) {
+                            pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable));
+                        }
+                    }
+                }
+            }
+
+            return pkg.setVolumeUuid(volumeUuid)
                     .setApplicationVolumeUuid(volumeUuid)
                     .setSigningDetails(SigningDetails.UNKNOWN);
         } catch (PackageParserException e) {
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index 377279e..0e736d5 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -18,6 +18,8 @@
 
 import static android.os.Build.VERSION_CODES.DONUT;
 
+import static java.util.Collections.emptyMap;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
@@ -55,11 +57,13 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.server.SystemConfig;
 
 import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -126,6 +130,7 @@
     private String overlayCategory;
     private int overlayPriority;
     private boolean overlayIsStatic;
+    private Map<String, String> overlayables = emptyMap();
 
     private String staticSharedLibName;
     private long staticSharedLibVersion;
@@ -475,7 +480,7 @@
 
     @Override
     public Map<String, ArraySet<PublicKey>> getKeySetMapping() {
-        return keySetMapping == null ? Collections.emptyMap() : keySetMapping;
+        return keySetMapping == null ? emptyMap() : keySetMapping;
     }
 
     @Override
@@ -773,6 +778,13 @@
     }
 
     @Override
+    public ParsingPackage addOverlayable(String overlayableName, String actorName) {
+        this.overlayables = CollectionUtils.add(this.overlayables,
+                TextUtils.safeIntern(overlayableName), TextUtils.safeIntern(actorName));
+        return this;
+    }
+
+    @Override
     public PackageImpl addAdoptPermission(String adoptPermission) {
         this.adoptPermissions = ArrayUtils.add(this.adoptPermissions, adoptPermission);
         return this;
@@ -2125,6 +2137,11 @@
     }
 
     @Override
+    public Map<String, String> getOverlayables() {
+        return overlayables;
+    }
+
+    @Override
     public boolean isOverlayIsStatic() {
         return overlayIsStatic;
     }
@@ -2291,7 +2308,7 @@
         appInfo.metaData = appMetaData;
         appInfo.minAspectRatio = minAspectRatio;
         appInfo.minSdkVersion = minSdkVersion;
-        appInfo.name = name;
+        appInfo.name = className;
         if (appInfo.name != null) {
             appInfo.name = appInfo.name.trim();
         }
@@ -2957,6 +2974,7 @@
         dest.writeString(this.overlayCategory);
         dest.writeInt(this.overlayPriority);
         dest.writeBoolean(this.overlayIsStatic);
+        dest.writeMap(this.overlayables);
         dest.writeString(this.staticSharedLibName);
         dest.writeLong(this.staticSharedLibVersion);
         dest.writeStringList(this.libraryNames);
@@ -3100,6 +3118,8 @@
         this.overlayCategory = in.readString();
         this.overlayPriority = in.readInt();
         this.overlayIsStatic = in.readBoolean();
+        this.overlayables = new HashMap<>();
+        in.readMap(overlayables, boot);
         this.staticSharedLibName = TextUtils.safeIntern(in.readString());
         this.staticSharedLibVersion = in.readLong();
         this.libraryNames = in.createStringArrayList();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 43c1f6e..aff1b2e 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -62,6 +62,8 @@
 
     ParsingPackage addOriginalPackage(String originalPackage);
 
+    ParsingPackage addOverlayable(String overlayableName, String actorName);
+
     ParsingPackage addPermission(ParsedPermission permission);
 
     ParsingPackage addPermissionGroup(ParsedPermissionGroup permissionGroup);
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 12bce8a..ed980f3 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -182,6 +182,14 @@
     public static final int MAX_IPC_SIZE = 64 * 1024;
 
     /**
+     * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction
+     * buffer limit.
+     */
+    static int getSuggestedMaxIpcSizeBytes() {
+        return MAX_IPC_SIZE;
+    }
+
+    /**
      * Get the canonical name of the interface supported by this binder.
      */
     public @Nullable String getInterfaceDescriptor() throws RemoteException;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 9eb6445..339397b 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1815,8 +1815,12 @@
         p.writeToParcel(this, parcelableFlags);
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * Flatten the name of the class of the Parcelable into this Parcel.
+     *
+     * @param p The Parcelable object to be written.
+     * @see #readParcelableCreator
+     */
     public final void writeParcelableCreator(@NonNull Parcelable p) {
         String name = p.getClass().getName();
         writeString(name);
@@ -3011,8 +3015,19 @@
         return (T) creator.createFromParcel(this);
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * Read and return a Parcelable.Creator from the parcel. The given class loader will be used to
+     * load the {@link Parcelable.Creator}. If it is null, the default class loader will be used.
+     *
+     * @param loader A ClassLoader from which to instantiate the {@link Parcelable.Creator}
+     * object, or null for the default class loader.
+     * @return the previously written {@link Parcelable.Creator}, or null if a null Creator was
+     * written.
+     * @throws BadParcelableException Throws BadParcelableException if there was an error trying to
+     * read the {@link Parcelable.Creator}.
+     *
+     * @see #writeParcelableCreator
+     */
     @Nullable
     public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
         String name = readString();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 725e0fb..5e478b5 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -44,70 +44,10 @@
  * <p>
  * <b>Device battery life will be significantly affected by the use of this API.</b>
  * Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
- * possible, and be sure to release them as soon as possible.
- * </p><p>
- * The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.
- * This will create a {@link PowerManager.WakeLock} object.  You can then use methods
- * on the wake lock object to control the power state of the device.
- * </p><p>
- * In practice it's quite simple:
- * {@samplecode
- * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
- * wl.acquire();
- *   ..screen will stay on during this section..
- * wl.release();
- * }
- * </p><p>
- * The following wake lock levels are defined, with varying effects on system power.
- * <i>These levels are mutually exclusive - you may only specify one of them.</i>
+ * possible, and be sure to release them as soon as possible. In most cases,
+ * you'll want to use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
  *
- * <table>
- *     <tr><th>Flag Value</th>
- *     <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
- *
- *     <tr><td>{@link #PARTIAL_WAKE_LOCK}</td>
- *         <td>On*</td> <td>Off</td> <td>Off</td>
- *     </tr>
- *
- *     <tr><td>{@link #SCREEN_DIM_WAKE_LOCK}</td>
- *         <td>On</td> <td>Dim</td> <td>Off</td>
- *     </tr>
- *
- *     <tr><td>{@link #SCREEN_BRIGHT_WAKE_LOCK}</td>
- *         <td>On</td> <td>Bright</td> <td>Off</td>
- *     </tr>
- *
- *     <tr><td>{@link #FULL_WAKE_LOCK}</td>
- *         <td>On</td> <td>Bright</td> <td>Bright</td>
- *     </tr>
- * </table>
- * </p><p>
- * *<i>If you hold a partial wake lock, the CPU will continue to run, regardless of any
- * display timeouts or the state of the screen and even after the user presses the power button.
- * In all other wake locks, the CPU will run, but the user can still put the device to sleep
- * using the power button.</i>
- * </p><p>
- * In addition, you can add two more flags, which affect behavior of the screen only.
- * <i>These flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i></p>
- *
- * <table>
- *     <tr><th>Flag Value</th> <th>Description</th></tr>
- *
- *     <tr><td>{@link #ACQUIRE_CAUSES_WAKEUP}</td>
- *         <td>Normal wake locks don't actually turn on the illumination.  Instead, they cause
- *         the illumination to remain on once it turns on (e.g. from user activity).  This flag
- *         will force the screen and/or keyboard to turn on immediately, when the WakeLock is
- *         acquired.  A typical use would be for notifications which are important for the user to
- *         see immediately.</td>
- *     </tr>
- *
- *     <tr><td>{@link #ON_AFTER_RELEASE}</td>
- *         <td>If this flag is set, the user activity timer will be reset when the WakeLock is
- *         released, causing the illumination to remain on a bit longer.  This can be used to
- *         reduce flicker if you are cycling between wake lock conditions.</td>
- *     </tr>
- * </table>
  * <p>
  * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
  * permission in an {@code <uses-permission>} element of the application's manifest.
@@ -931,7 +871,8 @@
      * {@link #FULL_WAKE_LOCK}, {@link #SCREEN_DIM_WAKE_LOCK}
      * and {@link #SCREEN_BRIGHT_WAKE_LOCK}.  Exactly one wake lock level must be
      * specified as part of the {@code levelAndFlags} parameter.
-     * </p><p>
+     * </p>
+     * <p>
      * The wake lock flags are: {@link #ACQUIRE_CAUSES_WAKEUP}
      * and {@link #ON_AFTER_RELEASE}.  Multiple flags can be combined as part of the
      * {@code levelAndFlags} parameters.
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 2fa3386..701ba91 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -32,6 +32,7 @@
 import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
 import android.app.AppGlobals;
+import android.app.PendingIntent;
 import android.content.ClipData;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
@@ -86,6 +87,7 @@
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -162,6 +164,15 @@
     /** {@hide} */
     public static final String SUICIDE_CALL = "suicide";
 
+    /** {@hide} */
+    public static final String CREATE_WRITE_REQUEST_CALL = "create_write_request";
+    /** {@hide} */
+    public static final String CREATE_TRASH_REQUEST_CALL = "create_trash_request";
+    /** {@hide} */
+    public static final String CREATE_FAVORITE_REQUEST_CALL = "create_favorite_request";
+    /** {@hide} */
+    public static final String CREATE_DELETE_REQUEST_CALL = "create_delete_request";
+
     /**
      * Extra used with {@link #SCAN_FILE_CALL} or {@link #SCAN_VOLUME_CALL} to indicate that
      * the file path originated from shell.
@@ -199,6 +210,13 @@
     /** {@hide} */
     public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media";
 
+    /** {@hide} */
+    public static final String EXTRA_CLIP_DATA = "clip_data";
+    /** {@hide} */
+    public static final String EXTRA_CONTENT_VALUES = "content_values";
+    /** {@hide} */
+    public static final String EXTRA_RESULT = "result";
+
     /**
      * This is for internal use by the media scanner only.
      * Name of the (optional) Uri parameter that determines whether to skip deleting
@@ -606,6 +624,10 @@
      * {@link ContentResolver#delete}.
      * <p>
      * By default, trashed items are filtered away from operations.
+     *
+     * @see MediaColumns#IS_TRASHED
+     * @see MediaStore#QUERY_ARG_MATCH_TRASHED
+     * @see MediaStore#createTrashRequest
      */
     @Match
     public static final String QUERY_ARG_MATCH_TRASHED = "android:query-arg-match-trashed";
@@ -620,6 +642,10 @@
      * <p>
      * By default, favorite items are <em>not</em> filtered away from
      * operations.
+     *
+     * @see MediaColumns#IS_FAVORITE
+     * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+     * @see MediaStore#createFavoriteRequest
      */
     @Match
     public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite";
@@ -952,7 +978,9 @@
      * @see MediaStore#setIncludeTrashed(Uri)
      * @see MediaStore#trash(Context, Uri)
      * @see MediaStore#untrash(Context, Uri)
+     * @removed
      */
+    @Deprecated
     public static void trash(@NonNull Context context, @NonNull Uri uri) {
         trash(context, uri, 48 * DateUtils.HOUR_IN_MILLIS);
     }
@@ -970,7 +998,9 @@
      * @see MediaStore#setIncludeTrashed(Uri)
      * @see MediaStore#trash(Context, Uri)
      * @see MediaStore#untrash(Context, Uri)
+     * @removed
      */
+    @Deprecated
     public static void trash(@NonNull Context context, @NonNull Uri uri,
             @DurationMillisLong long timeoutMillis) {
         if (timeoutMillis < 0) {
@@ -992,7 +1022,9 @@
      * @see MediaStore#setIncludeTrashed(Uri)
      * @see MediaStore#trash(Context, Uri)
      * @see MediaStore#untrash(Context, Uri)
+     * @removed
      */
+    @Deprecated
     public static void untrash(@NonNull Context context, @NonNull Uri uri) {
         final ContentValues values = new ContentValues();
         values.put(MediaColumns.IS_TRASHED, 0);
@@ -1010,6 +1042,180 @@
         return uri.buildUpon().authority(MediaStore.AUTHORITY_LEGACY).build();
     }
 
+    private static @NonNull PendingIntent createRequest(@NonNull ContentResolver resolver,
+            @NonNull String method, @NonNull Collection<Uri> uris, @Nullable ContentValues values) {
+        Objects.requireNonNull(resolver);
+        Objects.requireNonNull(uris);
+
+        final Iterator<Uri> it = uris.iterator();
+        final ClipData clipData = ClipData.newRawUri(null, it.next());
+        while (it.hasNext()) {
+            clipData.addItem(new ClipData.Item(it.next()));
+        }
+
+        final Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_CLIP_DATA, clipData);
+        extras.putParcelable(EXTRA_CONTENT_VALUES, values);
+        return resolver.call(AUTHORITY, method, null, extras).getParcelable(EXTRA_RESULT);
+    }
+
+    /**
+     * Create a {@link PendingIntent} that will prompt the user to grant your
+     * app write access for the requested media items.
+     * <p>
+     * This call only generates the request for a prompt; to display the prompt,
+     * call {@link Activity#startIntentSenderForResult} with
+     * {@link PendingIntent#getIntentSender()}. You can then determine if the
+     * user granted your request by testing for {@link Activity#RESULT_OK} in
+     * {@link Activity#onActivityResult}.
+     * <p>
+     * Permissions granted through this mechanism are tied to the lifecycle of
+     * the {@link Activity} that requests them. If you need to retain
+     * longer-term access for background actions, you can place items into a
+     * {@link ClipData} or {@link Intent} which can then be passed to
+     * {@link Context#startService} or
+     * {@link android.app.job.JobInfo.Builder#setClipData}. Be sure to include
+     * any relevant access modes you want to retain, such as
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     * <p>
+     * The displayed prompt will reflect all the media items you're requesting,
+     * including those for which you already hold write access. If you want to
+     * determine if you already hold write access before requesting access, use
+     * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     * <p>
+     * For security and performance reasons this method does not support
+     * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
+     *
+     * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+     *            Typically this value is {@link Context#getContentResolver()},
+     *            but if you need more explicit lifecycle controls, you can
+     *            obtain a {@link ContentProviderClient} and wrap it using
+     *            {@link ContentResolver#wrap(ContentProviderClient)}.
+     * @param uris The set of media items to include in this request. Each item
+     *            must be hosted by {@link MediaStore#AUTHORITY} and must
+     *            reference a specific media item by {@link BaseColumns#_ID}.
+     */
+    public static @NonNull PendingIntent createWriteRequest(@NonNull ContentResolver resolver,
+            @NonNull Collection<Uri> uris) {
+        return createRequest(resolver, CREATE_WRITE_REQUEST_CALL, uris, null);
+    }
+
+    /**
+     * Create a {@link PendingIntent} that will prompt the user to trash the
+     * requested media items. When the user approves this request,
+     * {@link MediaColumns#IS_TRASHED} is set on these items.
+     * <p>
+     * This call only generates the request for a prompt; to display the prompt,
+     * call {@link Activity#startIntentSenderForResult} with
+     * {@link PendingIntent#getIntentSender()}. You can then determine if the
+     * user granted your request by testing for {@link Activity#RESULT_OK} in
+     * {@link Activity#onActivityResult}.
+     * <p>
+     * The displayed prompt will reflect all the media items you're requesting,
+     * including those for which you already hold write access. If you want to
+     * determine if you already hold write access before requesting access, use
+     * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+     *            Typically this value is {@link Context#getContentResolver()},
+     *            but if you need more explicit lifecycle controls, you can
+     *            obtain a {@link ContentProviderClient} and wrap it using
+     *            {@link ContentResolver#wrap(ContentProviderClient)}.
+     * @param uris The set of media items to include in this request. Each item
+     *            must be hosted by {@link MediaStore#AUTHORITY} and must
+     *            reference a specific media item by {@link BaseColumns#_ID}.
+     * @param value The {@link MediaColumns#IS_TRASHED} value to apply.
+     * @see MediaColumns#IS_TRASHED
+     * @see MediaStore#QUERY_ARG_MATCH_TRASHED
+     */
+    public static @NonNull PendingIntent createTrashRequest(@NonNull ContentResolver resolver,
+            @NonNull Collection<Uri> uris, boolean value) {
+        final ContentValues values = new ContentValues();
+        if (value) {
+            values.put(MediaColumns.IS_TRASHED, 1);
+            values.put(MediaColumns.DATE_EXPIRES,
+                    (System.currentTimeMillis() + DateUtils.WEEK_IN_MILLIS) / 1000);
+        } else {
+            values.put(MediaColumns.IS_TRASHED, 0);
+            values.putNull(MediaColumns.DATE_EXPIRES);
+        }
+        return createRequest(resolver, CREATE_TRASH_REQUEST_CALL, uris, values);
+    }
+
+    /**
+     * Create a {@link PendingIntent} that will prompt the user to favorite the
+     * requested media items. When the user approves this request,
+     * {@link MediaColumns#IS_FAVORITE} is set on these items.
+     * <p>
+     * This call only generates the request for a prompt; to display the prompt,
+     * call {@link Activity#startIntentSenderForResult} with
+     * {@link PendingIntent#getIntentSender()}. You can then determine if the
+     * user granted your request by testing for {@link Activity#RESULT_OK} in
+     * {@link Activity#onActivityResult}.
+     * <p>
+     * The displayed prompt will reflect all the media items you're requesting,
+     * including those for which you already hold write access. If you want to
+     * determine if you already hold write access before requesting access, use
+     * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+     *            Typically this value is {@link Context#getContentResolver()},
+     *            but if you need more explicit lifecycle controls, you can
+     *            obtain a {@link ContentProviderClient} and wrap it using
+     *            {@link ContentResolver#wrap(ContentProviderClient)}.
+     * @param uris The set of media items to include in this request. Each item
+     *            must be hosted by {@link MediaStore#AUTHORITY} and must
+     *            reference a specific media item by {@link BaseColumns#_ID}.
+     * @param value The {@link MediaColumns#IS_FAVORITE} value to apply.
+     * @see MediaColumns#IS_FAVORITE
+     * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+     */
+    public static @NonNull PendingIntent createFavoriteRequest(@NonNull ContentResolver resolver,
+            @NonNull Collection<Uri> uris, boolean value) {
+        final ContentValues values = new ContentValues();
+        if (value) {
+            values.put(MediaColumns.IS_FAVORITE, 1);
+        } else {
+            values.put(MediaColumns.IS_FAVORITE, 0);
+        }
+        return createRequest(resolver, CREATE_FAVORITE_REQUEST_CALL, uris, values);
+    }
+
+    /**
+     * Create a {@link PendingIntent} that will prompt the user to permanently
+     * delete the requested media items. When the user approves this request,
+     * {@link ContentResolver#delete} will be called on these items.
+     * <p>
+     * This call only generates the request for a prompt; to display the prompt,
+     * call {@link Activity#startIntentSenderForResult} with
+     * {@link PendingIntent#getIntentSender()}. You can then determine if the
+     * user granted your request by testing for {@link Activity#RESULT_OK} in
+     * {@link Activity#onActivityResult}.
+     * <p>
+     * The displayed prompt will reflect all the media items you're requesting,
+     * including those for which you already hold write access. If you want to
+     * determine if you already hold write access before requesting access, use
+     * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+     *            Typically this value is {@link Context#getContentResolver()},
+     *            but if you need more explicit lifecycle controls, you can
+     *            obtain a {@link ContentProviderClient} and wrap it using
+     *            {@link ContentResolver#wrap(ContentProviderClient)}.
+     * @param uris The set of media items to include in this request. Each item
+     *            must be hosted by {@link MediaStore#AUTHORITY} and must
+     *            reference a specific media item by {@link BaseColumns#_ID}.
+     */
+    public static @NonNull PendingIntent createDeleteRequest(@NonNull ContentResolver resolver,
+            @NonNull Collection<Uri> uris) {
+        return createRequest(resolver, CREATE_DELETE_REQUEST_CALL, uris, null);
+    }
+
     /**
      * Common media metadata columns.
      */
@@ -1127,9 +1333,9 @@
          * Trashed items are retained until they expire as defined by
          * {@link #DATE_EXPIRES}.
          *
+         * @see MediaColumns#IS_TRASHED
          * @see MediaStore#QUERY_ARG_MATCH_TRASHED
-         * @see MediaStore#trash(Context, Uri)
-         * @see MediaStore#untrash(Context, Uri)
+         * @see MediaStore#createTrashRequest
          */
         @Column(Cursor.FIELD_TYPE_INTEGER)
         public static final String IS_TRASHED = "is_trashed";
@@ -1302,7 +1508,9 @@
          * Flag indicating if the media item has been marked as being a
          * "favorite" by the user.
          *
+         * @see MediaColumns#IS_FAVORITE
          * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+         * @see MediaStore#createFavoriteRequest
          */
         @Column(Cursor.FIELD_TYPE_INTEGER)
         public static final String IS_FAVORITE = "is_favorite";
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 8ab687f..c84fbc7 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -124,6 +124,13 @@
     public static final String KEY_IMPORTANCE = "key_importance";
 
     /**
+     * Data type: float, a ranking score from 0 (lowest) to 1 (highest).
+     * Used to rank notifications inside that fall under the same classification (i.e. alerting,
+     * silenced).
+     */
+    public static final String KEY_RANKING_SCORE = "key_ranking_score";
+
+    /**
      * Create a notification adjustment.
      *
      * @param pkg The package of the notification.
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 716a522..e3f11a1 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -35,8 +35,8 @@
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 
-import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IPhoneStateListener;
 
 import dalvik.system.VMRuntime;
 
@@ -196,12 +196,13 @@
     /**
      * Listen for {@link PreciseDataConnectionState} on the data connection (cellular).
      *
-     * @see #onPreciseDataConnectionStateChanged
+     * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * or the calling app has carrier privileges
+     * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * @hide
+     * @see #onPreciseDataConnectionStateChanged
      */
-    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
-    @SystemApi
+    @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
     public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE            = 0x00001000;
 
     /**
@@ -719,8 +720,9 @@
     }
 
     /**
-     * Callback invoked when data connection state changes with precise information
-     * on the registered subscription.
+     * Callback providing update about the default/internet data connection on the registered
+     * subscription.
+     *
      * Note, the registration subId comes from {@link TelephonyManager} object which registers
      * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
      * If this TelephonyManager object was created with
@@ -728,12 +730,13 @@
      * subId. Otherwise, this callback applies to
      * {@link SubscriptionManager#getDefaultSubscriptionId()}.
      *
-     * @param dataConnectionState {@link PreciseDataConnectionState}
+     * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * or the calling app has carrier privileges
+     * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * @hide
+     * @param dataConnectionState {@link PreciseDataConnectionState}
      */
-    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
-    @SystemApi
+    @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
     public void onPreciseDataConnectionStateChanged(
             @NonNull PreciseDataConnectionState dataConnectionState) {
         // default implementation empty
@@ -1042,11 +1045,21 @@
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
 
-            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
-                    () -> {
-                        psl.onDataConnectionStateChanged(state, networkType);
-                        psl.onDataConnectionStateChanged(state);
-                    }));
+            if (state == TelephonyManager.DATA_DISCONNECTING
+                    && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+                Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                        () -> {
+                            psl.onDataConnectionStateChanged(
+                                    TelephonyManager.DATA_CONNECTED, networkType);
+                            psl.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED);
+                        }));
+            } else {
+                Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                        () -> {
+                            psl.onDataConnectionStateChanged(state, networkType);
+                            psl.onDataConnectionStateChanged(state);
+                        }));
+            }
         }
 
         public void onDataActivity(int direction) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9d7b57b..b3702e6 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -21,17 +21,13 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Context;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.CallState;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.DataFailureCause;
-import android.telephony.Annotation.DataState;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.Annotation.PreciseCallStates;
 import android.telephony.Annotation.RadioPowerState;
@@ -357,27 +353,18 @@
      * @param subId for which data connection state changed.
      * @param slotIndex for which data connections state changed. Can be derived from subId except
      * when subId is invalid.
-     * @param state latest data connection state, e.g,
-     * @param isDataConnectivityPossible indicates if data is allowed
-     * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
-     * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
-     * @param linkProperties {@link LinkProperties} associated with this data connection.
-     * @param networkCapabilities {@link NetworkCapabilities} associated with this data connection.
-     * @param networkType associated with this data connection.
-     * @param roaming {@code true} indicates in roaming, {@false} otherwise.
-     * @see TelephonyManager#DATA_DISCONNECTED
-     * @see TelephonyManager#isDataConnectivityPossible()
+     * @param apnType the APN type that triggered this update
+     * @param preciseState the PreciseDataConnectionState
      *
+     * @see android.telephony.PreciseDataConnection
+     * @see TelephonyManager#DATA_DISCONNECTED
      * @hide
      */
-    public void notifyDataConnectionForSubscriber(int slotIndex, int subId, @DataState int state,
-        boolean isDataConnectivityPossible,
-        @ApnType String apn, String apnType, LinkProperties linkProperties,
-        NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
+    public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
+            String apnType, PreciseDataConnectionState preciseState) {
         try {
-            sRegistry.notifyDataConnectionForSubscriber(slotIndex, subId, state,
-                isDataConnectivityPossible,
-                apn, apnType, linkProperties, networkCapabilities, networkType, roaming);
+            sRegistry.notifyDataConnectionForSubscriber(
+                    slotIndex, subId, apnType, preciseState);
         } catch (RemoteException ex) {
             // system process is dead
         }
@@ -662,25 +649,6 @@
     }
 
     /**
-     * Notify data connection failed on certain subscription.
-     *
-     * @param subId for which data connection failed.
-     * @param slotIndex for which data conenction faled. Can be derived from subId except when subId
-     * is invalid.
-     * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN. Note each data
-     * connection can support multiple anyTypes.
-     *
-     * @hide
-     */
-    public void notifyDataConnectionFailed(int subId, int slotIndex, String apnType) {
-        try {
-            sRegistry.notifyDataConnectionFailedForSubscriber(slotIndex, subId, apnType);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
-    }
-
-    /**
      * TODO change from bundle to CellLocation?
      * @hide
      */
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index d44fbd7..3e26f63 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 
 /**
@@ -38,19 +39,19 @@
  * guide.</p>
  *
  * <p><strong>XML attributes</strong></p>
- * <p> 
- * See {@link android.R.styleable#CompoundButton CompoundButton Attributes}, 
- * {@link android.R.styleable#Button Button Attributes}, 
- * {@link android.R.styleable#TextView TextView Attributes}, 
+ * <p>
+ * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
+ * {@link android.R.styleable#Button Button Attributes},
+ * {@link android.R.styleable#TextView TextView Attributes},
  * {@link android.R.styleable#View View Attributes}
  * </p>
  */
 public class RadioButton extends CompoundButton {
-    
+
     public RadioButton(Context context) {
         this(context, null);
     }
-    
+
     public RadioButton(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.radioButtonStyle);
     }
@@ -81,4 +82,20 @@
     public CharSequence getAccessibilityClassName() {
         return RadioButton.class.getName();
     }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        if (getParent() instanceof RadioGroup) {
+            RadioGroup radioGroup = (RadioGroup) getParent();
+            if (radioGroup.getOrientation() == LinearLayout.HORIZONTAL) {
+                info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(0, 1,
+                        radioGroup.getIndexWithinVisibleButtons(this), 1, false, isChecked()));
+            } else {
+                info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(
+                        radioGroup.getIndexWithinVisibleButtons(this), 1, 0, 1,
+                        false, isChecked()));
+            }
+        }
+    }
 }
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index c62c16c..90bc0a3 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IdRes;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -26,6 +27,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStructure;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
 
@@ -93,6 +95,7 @@
         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
         }
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
 
         // retrieve selected radio button as requested by the user in the
         // XML layout file
@@ -475,4 +478,50 @@
         }
         return null;
     }
-}
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        if (this.getOrientation() == HORIZONTAL) {
+            info.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(1,
+                    getVisibleChildCount(), false,
+                    AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+        } else {
+            info.setCollectionInfo(
+                    AccessibilityNodeInfo.CollectionInfo.obtain(getVisibleChildCount(),
+                    1, false,
+                    AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+        }
+    }
+
+    private int getVisibleChildCount() {
+        int count = 0;
+        for (int i = 0; i < getChildCount(); i++) {
+            if (this.getChildAt(i) instanceof RadioButton) {
+                if (((RadioButton) this.getChildAt(i)).getVisibility() == VISIBLE) {
+                    count++;
+                }
+            }
+        }
+        return count;
+    }
+
+    int getIndexWithinVisibleButtons(@Nullable View child) {
+        if (!(child instanceof RadioButton)) {
+            return -1;
+        }
+        int index = 0;
+        for (int i = 0; i < getChildCount(); i++) {
+            if (this.getChildAt(i) instanceof RadioButton) {
+                RadioButton radioButton = (RadioButton) this.getChildAt(i);
+                if (radioButton == child) {
+                    return index;
+                }
+                if (radioButton.getVisibility() == VISIBLE) {
+                    index++;
+                }
+            }
+        }
+        return -1;
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index d8e8d67..1c71b0a 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -24,6 +24,8 @@
 import android.telephony.CellInfo;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.PhoneCapability;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
@@ -60,11 +62,10 @@
     @UnsupportedAppUsage(maxTargetSdk = 28)
     void notifyDataActivity(int state);
     void notifyDataActivityForSubscriber(in int subId, int state);
-    void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
-            boolean isDataConnectivityPossible,
-            String apn, String apnType, in LinkProperties linkProperties,
-            in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
-    void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType);
+    void notifyDataConnectionForSubscriber(
+            int phoneId, int subId, String apnType, in PreciseDataConnectionState preciseState);
+    @UnsupportedAppUsage
+    void notifyDataConnectionFailed(String apnType);
     @UnsupportedAppUsage(maxTargetSdk = 28)
     void notifyCellLocation(in Bundle cellLocation);
     void notifyCellLocationForSubscriber(in int subId, in Bundle cellLocation);
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 4165f20..4dac542 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -308,6 +308,17 @@
     }
 
     /**
+     * @see #add(List, Object)
+     */
+    public static @NonNull <K, V> Map<K, V> add(@Nullable Map<K, V> map, K key, V value) {
+        if (map == null || map == Collections.emptyMap()) {
+            map = new ArrayMap<>();
+        }
+        map.put(key, value);
+        return map;
+    }
+
+    /**
      * Similar to {@link List#remove}, but with support for list values of {@code null} and
      * {@link Collections#emptyList}
      */
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 54f25d3..8a59c99 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -229,7 +229,7 @@
      * Map of system pre-defined, uniquely named actors; keys are namespace,
      * value maps actor name to package name.
      */
-    private ArrayMap<String, ArrayMap<String, String>> mNamedActors = null;
+    private Map<String, Map<String, String>> mNamedActors = null;
 
     public static SystemConfig getInstance() {
         if (!isSystemProcess()) {
@@ -413,7 +413,7 @@
     }
 
     @NonNull
-    public Map<String, ? extends Map<String, String>> getNamedActors() {
+    public Map<String, Map<String, String>> getNamedActors() {
         return mNamedActors != null ? mNamedActors : Collections.emptyMap();
     }
 
@@ -1083,7 +1083,7 @@
                                 mNamedActors = new ArrayMap<>();
                             }
 
-                            ArrayMap<String, String> nameToPkgMap = mNamedActors.get(namespace);
+                            Map<String, String> nameToPkgMap = mNamedActors.get(namespace);
                             if (nameToPkgMap == null) {
                                 nameToPkgMap = new ArrayMap<>();
                                 mNamedActors.put(namespace, nameToPkgMap);
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index bef0ee4..8c1ecae 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -33,6 +33,7 @@
 
 // Static whitelist of open paths that the zygote is allowed to keep open.
 static const char* kPathWhitelist[] = {
+  "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
   "/apex/com.android.conscrypt/javalib/conscrypt.jar",
   "/apex/com.android.ipsec/javalib/ike.jar",
   "/apex/com.android.media/javalib/updatable-media.jar",
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e7c3415..03f8ebd 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2907,10 +2907,6 @@
     <!-- Whether to use voip audio mode for ims call -->
     <bool name="config_use_voip_mode_for_ims">false</bool>
 
-    <!-- ImsService package name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_ims_package"/>
-
     <!-- String array containing numbers that shouldn't be logged. Country-specific. -->
     <string-array name="unloggable_phone_numbers" />
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1631e0f..a8d30c1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -295,7 +295,6 @@
   <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
-  <java-symbol type="string" name="config_ims_package" />
   <java-symbol type="string" name="config_wwan_network_service_package" />
   <java-symbol type="string" name="config_wlan_network_service_package" />
   <java-symbol type="string" name="config_wwan_network_service_class" />
diff --git a/media/Android.bp b/media/Android.bp
index e2bdad1..4b50b7a 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -77,20 +77,13 @@
     path: "apex/java"
 }
 
-metalava_updatable_media_args = " --error UnhiddenSystemApi " +
-    "--hide RequiresPermission " +
-    "--hide MissingPermission --hide BroadcastBehavior " +
-    "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
-    "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
-    "--hide HiddenTypedefConstant --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) "
-
 droidstubs {
     name: "updatable-media-stubs",
     srcs: [
         ":updatable-media-srcs",
         ":framework-media-annotation-srcs",
     ],
-    args: metalava_updatable_media_args,
+    defaults: [ "framework-module-stubs-defaults-systemapi" ],
     aidl: {
         // TODO(b/135922046) remove this
         include_dirs: ["frameworks/base/core/java"],
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 5d084e7..c2cedad 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -30,6 +30,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Slog;
 import android.view.MotionEvent;
 import android.view.View;
@@ -114,11 +115,7 @@
         super.onFinishInflate();
         mLockPatternUtils = new LockPatternUtils(mContext);
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        setOnClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                takeEmergencyCallAction();
-            }
-        });
+        setOnClickListener(v -> takeEmergencyCallAction());
         setOnLongClickListener(new OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
@@ -168,9 +165,9 @@
      */
     public void takeEmergencyCallAction() {
         MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
-        // TODO: implement a shorter timeout once new PowerManager API is ready.
-        // should be the equivalent to the old userActivity(EMERGENCY_CALL_TIMEOUT)
-        mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+        if (mPowerManager != null) {
+            mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+        }
         try {
             ActivityTaskManager.getService().stopSystemLockTaskMode();
         } catch (RemoteException e) {
@@ -182,10 +179,19 @@
                 mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
             }
         } else {
-            Dependency.get(KeyguardUpdateMonitor.class).reportEmergencyCallAction(
-                    true /* bypassHandler */);
+            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+            if (updateMonitor != null) {
+                updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+            } else {
+                Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
+            }
+            TelecomManager telecomManager = getTelecommManager();
+            if (telecomManager == null) {
+                Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+                return;
+            }
             Intent emergencyDialIntent =
-                    getTelecommManager().createLaunchEmergencyDialerIntent(null /* number*/)
+                    telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                                     | Intent.FLAG_ACTIVITY_CLEAR_TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 1277736..c82bc30 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -103,7 +103,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -735,7 +734,7 @@
     @SuppressWarnings("FieldCanBeLocal")
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
-        public void onPendingEntryAdded(NotificationEntry entry) {
+        public void onNotificationAdded(NotificationEntry entry) {
             boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
             boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
             boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 21471ec..4c1cf49 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -32,6 +32,9 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -39,8 +42,11 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.internal.graphics.ColorUtils;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleHubNotificationListenerKt;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -227,15 +233,29 @@
         List<Person> personList = getPeopleFromNotification(entry);
         if (personList.size() > 0) {
             final Person person = personList.get(0);
-
             if (person != null) {
                 icon = person.getIcon();
+                if (icon == null) {
+                    // Lets try and grab the icon constructed by the layout
+                    Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
+                    if (d instanceof  BitmapDrawable) {
+                        icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+                    }
+                }
             }
         }
         if (icon == null) {
-            icon = notification.getLargeIcon() != null
-                    ? notification.getLargeIcon()
-                    : notification.getSmallIcon();
+            boolean shouldTint = notification.getLargeIcon() == null;
+            icon = shouldTint
+                    ? notification.getSmallIcon()
+                    : notification.getLargeIcon();
+            if (shouldTint) {
+                int notifColor = entry.getSbn().getNotification().color;
+                notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
+                notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
+                        true /* findFg */, 3f);
+                icon.setTint(notifColor);
+            }
         }
         if (intent != null) {
             return new Notification.BubbleMetadata.Builder()
@@ -285,7 +305,7 @@
     }
 
     static boolean isShortcutIntent(PendingIntent intent) {
-        return intent.equals(sDummyShortcutIntent);
+        return intent != null && intent.equals(sDummyShortcutIntent);
     }
 
     static List<Person> getPeopleFromNotification(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 9f79785..077d260 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.UserHandle;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -97,7 +98,7 @@
             if (!mReceiverRegistered) {
                 mCurrentUserId = ActivityManager.getCurrentUser();
                 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-                mBroadcastDispatcher.registerReceiver(this, filter);
+                mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
                 mReceiverRegistered = true;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 987b52db..784673e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -249,7 +249,7 @@
     }
 }
 
-private fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
+fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
         entry.row
                 ?.childrenWithId(R.id.expanded)
                 ?.mapNotNull { it as? ViewGroup }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index f3e9b6b..183adeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -37,12 +37,19 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.statusbar.NotificationMediaManager;
 
 import libcore.io.IoUtils;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Objects;
 
 import javax.inject.Inject;
@@ -52,16 +59,15 @@
  * Manages the lockscreen wallpaper.
  */
 @Singleton
-public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable {
+public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
+        Dumpable {
 
     private static final String TAG = "LockscreenWallpaper";
 
-    private final NotificationMediaManager mMediaManager =
-            Dependency.get(NotificationMediaManager.class);
-
+    private final NotificationMediaManager mMediaManager;
     private final WallpaperManager mWallpaperManager;
-    private Handler mH;
     private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final Handler mH;
 
     private boolean mCached;
     private Bitmap mCache;
@@ -74,10 +80,16 @@
     @Inject
     public LockscreenWallpaper(WallpaperManager wallpaperManager,
             @Nullable IWallpaperManager iWallpaperManager,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DumpController dumpController,
+            NotificationMediaManager mediaManager,
+            @MainHandler Handler mainHandler) {
+        dumpController.registerDumpable(getClass().getSimpleName(), this);
         mWallpaperManager = wallpaperManager;
         mCurrentUserId = ActivityManager.getCurrentUser();
         mUpdateMonitor = keyguardUpdateMonitor;
+        mMediaManager = mediaManager;
+        mH = mainHandler;
 
         if (iWallpaperManager != null) {
             // Service is disabled on some devices like Automotive
@@ -89,14 +101,6 @@
         }
     }
 
-    void setHandler(Handler handler) {
-        if (mH != null) {
-            Log.wtfStack(TAG, "Handler has already been set. Trying to double initialize?");
-            return;
-        }
-        mH = handler;
-    }
-
     public Bitmap getBitmap() {
         if (mCached) {
             return mCache;
@@ -227,6 +231,16 @@
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println(getClass().getSimpleName() + ":");
+        IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "  ").increaseIndent();
+        iPw.println("mCached=" + mCached);
+        iPw.println("mCache=" + mCache);
+        iPw.println("mCurrentUserId=" + mCurrentUserId);
+        iPw.println("mSelectedUser=" + mSelectedUser);
+    }
+
     private static class LoaderResult {
         public final boolean success;
         public final Bitmap bitmap;
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 b8aea9b..4c5bbce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -561,7 +561,7 @@
     }
 
     protected void scheduleUpdate() {
-        if (mUpdatePending) return;
+        if (mUpdatePending || mScrimBehind == null) return;
 
         // Make sure that a frame gets scheduled.
         mScrimBehind.invalidate();
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 a9d7601..7e32581 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1061,7 +1061,6 @@
 
         if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
             mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
-            mLockscreenWallpaper.setHandler(mHandler);
         }
 
         mKeyguardIndicationController =
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
new file mode 100644
index 0000000..62ae7b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -0,0 +1,717 @@
+/*
+ * 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.systemui.util.animation
+
+import android.os.Looper
+import android.util.ArrayMap
+import android.util.Log
+import android.view.View
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FlingAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
+import java.util.WeakHashMap
+
+/**
+ * Extension function for all objects which will return a PhysicsAnimator instance for that object.
+ */
+val <T : View> T.physicsAnimator: PhysicsAnimator<T> get() { return getInstance(this) }
+
+private const val TAG = "PhysicsAnimator"
+
+typealias EndAction = () -> Unit
+
+/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
+typealias UpdateMap<T> =
+        ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+/**
+ * Map of the animators associated with a given object. This ensures that only one animator
+ * per object exists.
+ */
+internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
+
+/**
+ * Default spring configuration to use for animations where stiffness and/or damping ratio
+ * were not provided.
+ */
+private val defaultSpring = PhysicsAnimator.SpringConfig(
+        SpringForce.STIFFNESS_MEDIUM,
+        SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+
+/** Default fling configuration to use for animations where friction was not provided. */
+private val defaultFling = PhysicsAnimator.FlingConfig(
+        friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
+
+/** Whether to log helpful debug information about animations. */
+private var verboseLogging = false
+
+/**
+ * Animator that uses physics-based animations to animate properties on views and objects. Physics
+ * animations use real-world physical concepts, such as momentum and mass, to realistically simulate
+ * motion. PhysicsAnimator is heavily inspired by [android.view.ViewPropertyAnimator], and
+ * also uses the builder pattern to configure and start animations.
+ *
+ * The physics animations are backed by [DynamicAnimation].
+ *
+ * @param T The type of the object being animated.
+ */
+class PhysicsAnimator<T> private constructor (val target: T) {
+
+    /** Data class for representing animation frame updates. */
+    data class AnimationUpdate(val value: Float, val velocity: Float)
+
+    /** [DynamicAnimation] instances for the given properties. */
+    private val springAnimations = ArrayMap<FloatPropertyCompat<in T>, SpringAnimation>()
+    private val flingAnimations = ArrayMap<FloatPropertyCompat<in T>, FlingAnimation>()
+
+    /**
+     * Spring and fling configurations for the properties to be animated on the target. We'll
+     * configure and start the DynamicAnimations for these properties according to the provided
+     * configurations.
+     */
+    private val springConfigs = ArrayMap<FloatPropertyCompat<in T>, SpringConfig>()
+    private val flingConfigs = ArrayMap<FloatPropertyCompat<in T>, FlingConfig>()
+
+    /**
+     * Animation listeners for the animation. These will be notified when each property animation
+     * updates or ends.
+     */
+    private val updateListeners = ArrayList<UpdateListener<T>>()
+    private val endListeners = ArrayList<EndListener<T>>()
+
+    /** End actions to run when all animations have completed.  */
+    private val endActions = ArrayList<EndAction>()
+
+    /**
+     * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
+     * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
+     * just one permanent update and end listener to the DynamicAnimations.
+     */
+    internal var internalListeners = ArrayList<InternalListener>()
+
+    /**
+     * Action to run when [start] is called. This can be changed by
+     * [PhysicsAnimatorTestUtils.prepareForTest] to enable animators to run under test and provide
+     * helpful test utilities.
+     */
+    internal var startAction: () -> Unit = ::startInternal
+
+    /**
+     * Springs a property to the given value, using the provided configuration settings.
+     *
+     * Springs are used when you know the exact value to which you want to animate. They can be
+     * configured with a start velocity (typically used when the spring is initiated by a touch
+     * event), but this velocity will be realistically attenuated as forces are applied to move the
+     * property towards the end value.
+     *
+     * If you find yourself repeating the same stiffness and damping ratios many times, consider
+     * storing a single [SpringConfig] instance and passing that in instead of individual values.
+     *
+     * @param property The property to spring to the given value. The property must be an instance
+     * of FloatPropertyCompat&lt;? super T&gt;. For example, if this is a
+     * PhysicsAnimator&lt;FrameLayout&gt;, you can use a FloatPropertyCompat&lt;FrameLayout&gt;, as
+     * well as a FloatPropertyCompat&lt;ViewGroup&gt;, and so on.
+     * @param toPosition The value to spring the given property to.
+     * @param startVelocity The initial velocity to use for the animation.
+     * @param stiffness The stiffness to use for the spring. Higher stiffness values result in
+     * faster animations, while lower stiffness means a slower animation. Reasonable values for
+     * low, medium, and high stiffness can be found as constants in [SpringForce].
+     * @param dampingRatio The damping ratio (bounciness) to use for the spring. Higher values
+     * result in a less 'springy' animation, while lower values allow the animation to bounce
+     * back and forth for a longer time after reaching the final position. Reasonable values for
+     * low, medium, and high damping can be found in [SpringForce].
+     */
+    fun spring(
+        property: FloatPropertyCompat<in T>,
+        toPosition: Float,
+        startVelocity: Float = 0f,
+        stiffness: Float = defaultSpring.stiffness,
+        dampingRatio: Float = defaultSpring.dampingRatio
+    ): PhysicsAnimator<T> {
+        if (verboseLogging) {
+            Log.d(TAG, "Springing ${getReadablePropertyName(property)} to $toPosition.")
+        }
+
+        springConfigs[property] =
+                SpringConfig(stiffness, dampingRatio, startVelocity, toPosition)
+        return this
+    }
+
+    /**
+     * Springs a property to a given value using the provided start velocity and configuration
+     * options.
+     *
+     * @see spring
+     */
+    fun spring(
+        property: FloatPropertyCompat<in T>,
+        toPosition: Float,
+        startVelocity: Float,
+        config: SpringConfig = defaultSpring
+    ): PhysicsAnimator<T> {
+        return spring(
+                property, toPosition, startVelocity, config.stiffness, config.dampingRatio)
+    }
+
+    /**
+     * Springs a property to a given value using the provided configuration options, and a start
+     * velocity of 0f.
+     *
+     * @see spring
+     */
+    fun spring(
+        property: FloatPropertyCompat<in T>,
+        toPosition: Float,
+        config: SpringConfig = defaultSpring
+    ): PhysicsAnimator<T> {
+        return spring(property, toPosition, 0f, config)
+    }
+
+    /**
+     * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+     * the provided configuration settings.
+     *
+     * Flings are used when you have a start velocity, and want the property value to realistically
+     * decrease as friction is applied until the velocity reaches zero. Flings do not have a
+     * deterministic end value. If you are attempting to animate to a specific end value, use
+     * [spring].
+     *
+     * If you find yourself repeating the same friction/min/max values, consider storing a single
+     * [FlingConfig] and passing that in instead.
+     *
+     * @param property The property to fling using the given start velocity.
+     * @param startVelocity The start velocity (in pixels per second) with which to start the fling.
+     * @param friction Friction value applied to slow down the animation over time. Higher values
+     * will more quickly slow the animation. Typical friction values range from 1f to 10f.
+     * @param min The minimum value allowed for the animation. If this value is reached, the
+     * animation will end abruptly.
+     * @param max The maximum value allowed for the animation. If this value is reached, the
+     * animation will end abruptly.
+     */
+    fun fling(
+        property: FloatPropertyCompat<in T>,
+        startVelocity: Float,
+        friction: Float = defaultFling.friction,
+        min: Float = defaultFling.min,
+        max: Float = defaultFling.max
+    ): PhysicsAnimator<T> {
+        if (verboseLogging) {
+            Log.d(TAG, "Flinging ${getReadablePropertyName(property)} " +
+                    "with velocity $startVelocity.")
+        }
+
+        flingConfigs[property] = FlingConfig(friction, min, max, startVelocity)
+        return this
+    }
+
+    /**
+     * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+     * the provided configuration settings.
+     *
+     * @see fling
+     */
+    fun fling(
+        property: FloatPropertyCompat<in T>,
+        startVelocity: Float,
+        config: FlingConfig = defaultFling
+    ): PhysicsAnimator<T> {
+        return fling(property, startVelocity, config.friction, config.min, config.max)
+    }
+
+    /**
+     * Adds a listener that will be called whenever any property on the animated object is updated.
+     * This will be called on every animation frame, with the current value of the animated object
+     * and the new property values.
+     */
+    fun addUpdateListener(listener: UpdateListener<T>): PhysicsAnimator<T> {
+        updateListeners.add(listener)
+        return this
+    }
+
+    /**
+     * Adds a listener that will be called whenever a property's animation ends. This is useful if
+     * you care about a specific property ending, or want to use the end value/end velocity from a
+     * particular property's animation. If you just want to run an action when all property
+     * animations have ended, use [withEndActions].
+     */
+    fun addEndListener(listener: EndListener<T>): PhysicsAnimator<T> {
+        endListeners.add(listener)
+        return this
+    }
+
+    /**
+     * Adds end actions that will be run sequentially when animations for every property involved in
+     * this specific animation have ended (unless they were explicitly canceled). For example, if
+     * you call:
+     *
+     * animator
+     *   .spring(TRANSLATION_X, ...)
+     *   .spring(TRANSLATION_Y, ...)
+     *   .withEndAction(action)
+     *   .start()
+     *
+     * 'action' will be run when both TRANSLATION_X and TRANSLATION_Y end.
+     *
+     * Other properties may still be animating, if those animations were not started in the same
+     * call. For example:
+     *
+     * animator
+     *   .spring(ALPHA, ...)
+     *   .start()
+     *
+     * animator
+     *   .spring(TRANSLATION_X, ...)
+     *   .spring(TRANSLATION_Y, ...)
+     *   .withEndAction(action)
+     *   .start()
+     *
+     * 'action' will still be run as soon as TRANSLATION_X and TRANSLATION_Y end, even if ALPHA is
+     * still animating.
+     *
+     * If you want to run actions as soon as a subset of property animations have ended, you want
+     * access to the animation's end value/velocity, or you want to run these actions even if the
+     * animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
+     * which indicates that all relevant animations have ended.
+     */
+    fun withEndActions(vararg endActions: EndAction): PhysicsAnimator<T> {
+        this.endActions.addAll(endActions)
+        return this
+    }
+
+    /** Starts the animations! */
+    fun start() {
+        startAction()
+    }
+
+    /**
+     * Starts the animations for real! This is typically called immediately by [start] unless this
+     * animator is under test.
+     */
+    internal fun startInternal() {
+        if (!Looper.getMainLooper().isCurrentThread) {
+            Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
+                    "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
+                    "your test setup.")
+        }
+
+        // Add an internal listener that will dispatch animation events to the provided listeners.
+        internalListeners.add(InternalListener(
+                getAnimatedProperties(),
+                ArrayList(updateListeners),
+                ArrayList(endListeners),
+                ArrayList(endActions)))
+
+        for ((property, config) in flingConfigs) {
+            val currentValue = property.getValue(target)
+
+            // If the fling is already out of bounds, don't start it.
+            if (currentValue <= config.min || currentValue >= config.max) {
+                continue
+            }
+
+            val flingAnim = getFlingAnimation(property)
+            config.applyToAnimation(flingAnim)
+            flingAnim.start()
+        }
+
+        for ((property, config) in springConfigs) {
+            val springAnim = getSpringAnimation(property)
+            config.applyToAnimation(springAnim)
+            springAnim.start()
+        }
+
+        clearAnimator()
+    }
+
+    /** Clear the animator's builder variables. */
+    private fun clearAnimator() {
+        springConfigs.clear()
+        flingConfigs.clear()
+
+        updateListeners.clear()
+        endListeners.clear()
+        endActions.clear()
+    }
+
+    /** Retrieves a spring animation for the given property, building one if needed. */
+    private fun getSpringAnimation(property: FloatPropertyCompat<in T>): SpringAnimation {
+        return springAnimations.getOrPut(
+                property,
+                { configureDynamicAnimation(SpringAnimation(target, property), property)
+                        as SpringAnimation })
+    }
+
+    /** Retrieves a fling animation for the given property, building one if needed. */
+    private fun getFlingAnimation(property: FloatPropertyCompat<in T>): FlingAnimation {
+        return flingAnimations.getOrPut(
+                property,
+                { configureDynamicAnimation(FlingAnimation(target, property), property)
+                        as FlingAnimation })
+    }
+
+    /**
+     * Adds update and end listeners to the DynamicAnimation which will dispatch to the internal
+     * listeners.
+     */
+    private fun configureDynamicAnimation(
+        anim: DynamicAnimation<*>,
+        property: FloatPropertyCompat<in T>
+    ): DynamicAnimation<*> {
+        anim.addUpdateListener { _, value, velocity ->
+            for (i in 0 until internalListeners.size) {
+                internalListeners[i].onInternalAnimationUpdate(property, value, velocity)
+            }
+        }
+        anim.addEndListener { _, canceled, value, velocity ->
+            internalListeners.removeAll {
+                it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+        return anim
+    }
+
+    /**
+     * Internal listener class that receives updates from DynamicAnimation listeners, and dispatches
+     * them to the appropriate update/end listeners. This class is also aware of which properties
+     * were being animated when the end listeners were passed in, so that we can provide the
+     * appropriate value for allEnded to [EndListener.onAnimationEnd].
+     */
+    internal inner class InternalListener constructor(
+        private var properties: Set<FloatPropertyCompat<in T>>,
+        private var updateListeners: List<UpdateListener<T>>,
+        private var endListeners: List<EndListener<T>>,
+        private var endActions: List<EndAction>
+    ) {
+
+        /** The number of properties whose animations haven't ended. */
+        private var numPropertiesAnimating = properties.size
+
+        /**
+         * Update values that haven't yet been dispatched because not all property animations have
+         * updated yet.
+         */
+        private val undispatchedUpdates =
+                ArrayMap<FloatPropertyCompat<in T>, AnimationUpdate>()
+
+        /** Called when a DynamicAnimation updates.  */
+        internal fun onInternalAnimationUpdate(
+            property: FloatPropertyCompat<in T>,
+            value: Float,
+            velocity: Float
+        ) {
+
+            // If this property animation isn't relevant to this listener, ignore it.
+            if (!properties.contains(property)) {
+                return
+            }
+
+            undispatchedUpdates[property] = AnimationUpdate(value, velocity)
+            maybeDispatchUpdates()
+        }
+
+        /**
+         * Called when a DynamicAnimation ends.
+         *
+         * @return True if this listener should be removed from the list of internal listeners, so
+         * it no longer receives updates from DynamicAnimations.
+         */
+        internal fun onInternalAnimationEnd(
+            property: FloatPropertyCompat<in T>,
+            canceled: Boolean,
+            finalValue: Float,
+            finalVelocity: Float
+        ): Boolean {
+
+            // If this property animation isn't relevant to this listener, ignore it.
+            if (!properties.contains(property)) {
+                return false
+            }
+
+            // Dispatch updates if we have one for each property.
+            numPropertiesAnimating--
+            maybeDispatchUpdates()
+
+            // If we didn't have an update for each property, dispatch the update for the ending
+            // property. This guarantees that an update isn't sent for this property *after* we call
+            // onAnimationEnd for that property.
+            if (undispatchedUpdates.contains(property)) {
+                updateListeners.forEach { updateListener ->
+                    updateListener.onAnimationUpdateForProperty(
+                            target,
+                            UpdateMap<T>().also { it[property] = undispatchedUpdates[property] })
+                }
+
+                undispatchedUpdates.remove(property)
+            }
+
+            val allEnded = !arePropertiesAnimating(properties)
+            endListeners.forEach {
+                it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+
+            // If all of the animations that this listener cares about have ended, run the end
+            // actions unless the animation was canceled.
+            if (allEnded && !canceled) {
+                endActions.forEach { it() }
+            }
+
+            return allEnded
+        }
+
+        /**
+         * Dispatch undispatched values if we've received an update from each of the animating
+         * properties.
+         */
+        private fun maybeDispatchUpdates() {
+            if (undispatchedUpdates.size >= numPropertiesAnimating &&
+                    undispatchedUpdates.size > 0) {
+                updateListeners.forEach {
+                    it.onAnimationUpdateForProperty(target, ArrayMap(undispatchedUpdates))
+                }
+
+                undispatchedUpdates.clear()
+            }
+        }
+    }
+
+    /** Return true if any animations are running on the object.  */
+    fun isRunning(): Boolean {
+        return arePropertiesAnimating(springAnimations.keys.union(flingAnimations.keys))
+    }
+
+    /** Returns whether the given property is animating.  */
+    fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
+        return springAnimations[property]?.isRunning ?: false
+    }
+
+    /** Returns whether any of the given properties are animating.  */
+    fun arePropertiesAnimating(properties: Set<FloatPropertyCompat<in T>>): Boolean {
+        return properties.any { isPropertyAnimating(it) }
+    }
+
+    /** Return the set of properties that will begin animating upon calling [start]. */
+    internal fun getAnimatedProperties(): Set<FloatPropertyCompat<in T>> {
+        return springConfigs.keys.union(flingConfigs.keys)
+    }
+
+    /** Cancels all in progress animations on all properties. */
+    fun cancel() {
+        for (dynamicAnim in flingAnimations.values.union(springAnimations.values)) {
+            dynamicAnim.cancel()
+        }
+    }
+
+    /**
+     * Container object for spring animation configuration settings. This allows you to store
+     * default stiffness and damping ratio values in a single configuration object, which you can
+     * pass to [spring].
+     */
+    data class SpringConfig internal constructor(
+        internal var stiffness: Float,
+        internal var dampingRatio: Float,
+        internal var startVel: Float = 0f,
+        internal var finalPosition: Float = -Float.MAX_VALUE
+    ) {
+
+        constructor() :
+                this(defaultSpring.stiffness, defaultSpring.dampingRatio)
+
+        constructor(stiffness: Float, dampingRatio: Float) :
+                this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+
+        /** Apply these configuration settings to the given SpringAnimation. */
+        internal fun applyToAnimation(anim: SpringAnimation) {
+            val springForce = anim.spring ?: SpringForce()
+            anim.spring = springForce.apply {
+                stiffness = this@SpringConfig.stiffness
+                dampingRatio = this@SpringConfig.dampingRatio
+                finalPosition = this@SpringConfig.finalPosition
+            }
+
+            if (startVel != 0f) anim.setStartVelocity(startVel)
+        }
+    }
+
+    /**
+     * Container object for fling animation configuration settings. This allows you to store default
+     * friction values (as well as optional min/max values) in a single configuration object, which
+     * you can pass to [fling] and related methods.
+     */
+    data class FlingConfig internal constructor(
+        internal var friction: Float,
+        internal var min: Float,
+        internal var max: Float,
+        internal var startVel: Float
+    ) {
+
+        constructor() : this(defaultFling.friction)
+
+        constructor(friction: Float) :
+                this(friction, defaultFling.min, defaultFling.max)
+
+        constructor(friction: Float, min: Float, max: Float) :
+                this(friction, min, max, startVel = 0f)
+
+        /** Apply these configuration settings to the given FlingAnimation. */
+        internal fun applyToAnimation(anim: FlingAnimation) {
+            anim.apply {
+                friction = this@FlingConfig.friction
+                setMinValue(min)
+                setMaxValue(max)
+                setStartVelocity(startVel)
+            }
+        }
+    }
+
+    /**
+     * Listener for receiving values from in progress animations. Used with
+     * [PhysicsAnimator.addUpdateListener].
+     *
+     * @param <T> The type of the object being animated.
+    </T> */
+    interface UpdateListener<T> {
+
+        /**
+         * Called on each animation frame with the target object, and a map of FloatPropertyCompat
+         * -> AnimationUpdate, containing the latest value and velocity for that property. When
+         * multiple properties are animating together, the map will typically contain one entry for
+         * each property. However, you should never assume that this is the case - when a property
+         * animation ends earlier than the others, you'll receive an UpdateMap containing only that
+         * property's final update. Subsequently, you'll only receive updates for the properties
+         * that are still animating.
+         *
+         * Always check that the map contains an update for the property you're interested in before
+         * accessing it.
+         *
+         * @param target The animated object itself.
+         * @param values Map of property to AnimationUpdate, which contains that property
+         * animation's latest value and velocity. You should never assume that a particular property
+         * is present in this map.
+         */
+        fun onAnimationUpdateForProperty(
+            target: T,
+            values: UpdateMap<T>
+        )
+    }
+
+    /**
+     * Listener for receiving callbacks when animations end.
+     *
+     * @param <T> The type of the object being animated.
+    </T> */
+    interface EndListener<T> {
+
+        /**
+         * Called with the final animation values as each property animation ends. This can be used
+         * to respond to specific property animations concluding (such as hiding a view when ALPHA
+         * ends, even if the corresponding TRANSLATION animations have not ended).
+         *
+         * If you just want to run an action when all of the property animations have ended, you can
+         * use [PhysicsAnimator.withEndActions].
+         *
+         * @param target The animated object itself.
+         * @param property The property whose animation has just ended.
+         * @param canceled Whether the animation was explicitly canceled before it naturally ended.
+         * @param finalValue The final value of the animated property.
+         * @param finalVelocity The final velocity (in pixels per second) of the ended animation.
+         * This is typically zero, unless this was a fling animation which ended abruptly due to
+         * reaching its configured min/max values.
+         * @param allRelevantPropertyAnimsEnded Whether all properties relevant to this end listener
+         * have ended. Relevant properties are those which were animated alongside the
+         * [addEndListener] call where this animator was passed in. For example:
+         *
+         * animator
+         *    .spring(TRANSLATION_X, 100f)
+         *    .spring(TRANSLATION_Y, 200f)
+         *    .withEndListener(firstEndListener)
+         *    .start()
+         *
+         * firstEndListener will be called first for TRANSLATION_X, with allEnded = false,
+         * because TRANSLATION_Y is still running. When TRANSLATION_Y ends, it'll be called with
+         * allEnded = true.
+         *
+         * If a subsequent call to start() is made with other properties, those properties are not
+         * considered relevant and allEnded will still equal true when only TRANSLATION_X and
+         * TRANSLATION_Y end. For example, if immediately after the prior example, while
+         * TRANSLATION_X and TRANSLATION_Y are still animating, we called:
+         *
+         * animator.
+         *    .spring(SCALE_X, 2f, stiffness = 10f) // That will take awhile...
+         *    .withEndListener(secondEndListener)
+         *    .start()
+         *
+         * firstEndListener will still be called with allEnded = true when TRANSLATION_X/Y end, even
+         * though SCALE_X is still animating. Similarly, secondEndListener will be called with
+         * allEnded = true as soon as SCALE_X ends, even if the translation animations are still
+         * running.
+         */
+        fun onAnimationEnd(
+            target: T,
+            property: FloatPropertyCompat<in T>,
+            canceled: Boolean,
+            finalValue: Float,
+            finalVelocity: Float,
+            allRelevantPropertyAnimsEnded: Boolean
+        )
+    }
+
+    companion object {
+
+        /**
+         * Constructor to use to for new physics animator instances in [getInstance]. This is
+         * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
+         * all code using the physics animator is given testable instances instead.
+         */
+        internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
+
+        @JvmStatic
+        fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
+            if (!animators.containsKey(target)) {
+                animators[target] = instanceConstructor(target)
+            }
+
+            return animators[target] as PhysicsAnimator<T>
+        }
+
+        /**
+         * Set whether all physics animators should log a lot of information about animations.
+         * Useful for debugging!
+         */
+        @JvmStatic
+        fun setVerboseLogging(debug: Boolean) {
+            verboseLogging = debug
+        }
+
+        @JvmStatic
+        fun getReadablePropertyName(property: FloatPropertyCompat<*>): String {
+            return when (property) {
+                DynamicAnimation.TRANSLATION_X -> "translationX"
+                DynamicAnimation.TRANSLATION_Y -> "translationY"
+                DynamicAnimation.TRANSLATION_Z -> "translationZ"
+                DynamicAnimation.SCALE_X -> "scaleX"
+                DynamicAnimation.SCALE_Y -> "scaleY"
+                DynamicAnimation.ROTATION -> "rotation"
+                DynamicAnimation.ROTATION_X -> "rotationX"
+                DynamicAnimation.ROTATION_Y -> "rotationY"
+                DynamicAnimation.SCROLL_X -> "scrollX"
+                DynamicAnimation.SCROLL_Y -> "scrollY"
+                DynamicAnimation.ALPHA -> "alpha"
+                else -> "Custom FloatPropertyCompat instance"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
new file mode 100644
index 0000000..a1f74eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -0,0 +1,457 @@
+/*
+ * 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.systemui.util.animation
+
+import android.os.Handler
+import android.os.Looper
+import android.util.ArrayMap
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import java.util.ArrayDeque
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+typealias UpdateMatcher = (PhysicsAnimator.AnimationUpdate) -> Boolean
+typealias UpdateFramesPerProperty<T> =
+        ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>
+
+/**
+ * Utilities for testing code that uses [PhysicsAnimator].
+ *
+ * Start by calling [prepareForTest] at the beginning of each test - this will modify the behavior
+ * of all PhysicsAnimator instances so that they post animations to the main thread (so they don't
+ * crash). It'll also enable the use of the other static helper methods in this class, which you can
+ * use to do things like block the test until animations complete (so you can test end states), or
+ * verify keyframes.
+ */
+object PhysicsAnimatorTestUtils {
+    var timeoutMs: Long = 2000
+    private var startBlocksUntilAnimationsEnd = false
+    private val animationThreadHandler = Handler(Looper.getMainLooper())
+    private val allAnimatedObjects = HashSet<Any>()
+    private val animatorTestHelpers = HashMap<PhysicsAnimator<*>, AnimatorTestHelper<*>>()
+
+    /**
+     * Modifies the behavior of all [PhysicsAnimator] instances so that they post animations to the
+     * main thread, and report all of their
+     */
+    @JvmStatic
+    fun prepareForTest() {
+        val defaultConstructor = PhysicsAnimator.instanceConstructor
+        PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
+            val animator = defaultConstructor(target)
+            allAnimatedObjects.add(target)
+            animatorTestHelpers[animator] = AnimatorTestHelper(animator)
+            return animator
+        }
+
+        timeoutMs = 2000
+        startBlocksUntilAnimationsEnd = false
+        allAnimatedObjects.clear()
+    }
+
+    @JvmStatic
+    fun tearDown() {
+        val latch = CountDownLatch(1)
+        animationThreadHandler.post {
+            animatorTestHelpers.keys.forEach { it.cancel() }
+            latch.countDown()
+        }
+
+        latch.await()
+
+        animatorTestHelpers.clear()
+        animators.clear()
+        allAnimatedObjects.clear()
+    }
+
+    /**
+     * Sets the maximum time (in milliseconds) to block the test thread while waiting for animations
+     * before throwing an exception.
+     */
+    @JvmStatic
+    fun setBlockTimeout(timeoutMs: Long) {
+        this.timeoutMs = timeoutMs
+    }
+
+    /**
+     * Sets whether all animations should block the test thread until they end. This is typically
+     * the desired behavior, since you can invoke code that runs an animation and then assert things
+     * about its end state.
+     */
+    @JvmStatic
+    fun setAllAnimationsBlock(block: Boolean) {
+        startBlocksUntilAnimationsEnd = block
+    }
+
+    /**
+     * Blocks the calling thread until animations of the given property on the target object end.
+     */
+    @JvmStatic
+    @Throws(InterruptedException::class)
+    fun <T : Any> blockUntilAnimationsEnd(
+        animator: PhysicsAnimator<T>,
+        vararg properties: FloatPropertyCompat<in T>
+    ) {
+        val animatingProperties = HashSet<FloatPropertyCompat<in T>>()
+        for (property in properties) {
+            if (animator.isPropertyAnimating(property)) {
+                animatingProperties.add(property)
+            }
+        }
+
+        if (animatingProperties.size > 0) {
+            val latch = CountDownLatch(animatingProperties.size)
+            getAnimationTestHelper(animator).addTestEndListener(
+                    object : PhysicsAnimator.EndListener<T> {
+                override fun onAnimationEnd(
+                    target: T,
+                    property: FloatPropertyCompat<in T>,
+                    canceled: Boolean,
+                    finalValue: Float,
+                    finalVelocity: Float,
+                    allRelevantPropertyAnimsEnded: Boolean
+                ) {
+                    if (animatingProperties.contains(property)) {
+                        latch.countDown()
+                    }
+                }
+            })
+
+            latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+        }
+    }
+
+    /**
+     * Blocks the calling thread until all animations of the given property (on all target objects)
+     * have ended. Useful when you don't have access to the objects being animated, but still need
+     * to wait for them to end so that other testable side effects occur (such as update/end
+     * listeners).
+     */
+    @JvmStatic
+    @Throws(InterruptedException::class)
+    fun <T : Any> blockUntilAnimationsEnd(
+        properties: FloatPropertyCompat<in T>
+    ) {
+        for (target in allAnimatedObjects) {
+            try {
+                blockUntilAnimationsEnd(
+                        PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
+            } catch (e: ClassCastException) {
+                // Keep checking the other objects for ones whose types match the provided
+                // properties.
+            }
+        }
+    }
+
+    /**
+     * Blocks the calling thread until the first animation frame in which predicate returns true. If
+     * the given object isn't animating, returns without blocking.
+     */
+    @JvmStatic
+    @Throws(InterruptedException::class)
+    fun <T : Any> blockUntilFirstAnimationFrameWhereTrue(
+        animator: PhysicsAnimator<T>,
+        predicate: (T) -> Boolean
+    ) {
+        if (animator.isRunning()) {
+            val latch = CountDownLatch(1)
+            getAnimationTestHelper(animator).addTestUpdateListener(object : PhysicsAnimator
+            .UpdateListener<T> {
+                override fun onAnimationUpdateForProperty(
+                    target: T,
+                    values: UpdateMap<T>
+                ) {
+                    if (predicate(target)) {
+                        latch.countDown()
+                    }
+                }
+            })
+
+            latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+        }
+    }
+
+    /**
+     * Verifies that the animator reported animation frame values to update listeners that satisfy
+     * the given matchers, in order. Not all frames need to satisfy a matcher - we'll run through
+     * all animation frames, and check them against the current predicate. If it returns false, we
+     * continue through the frames until it returns true, and then move on to the next matcher.
+     * Verification fails if we run out of frames while unsatisfied matchers remain.
+     *
+     * If verification is successful, all frames to this point are considered 'verified' and will be
+     * cleared. Subsequent calls to this method will start verification at the next animation frame.
+     *
+     * Example: Verify that an animation surpassed x = 50f before going negative.
+     * verifyAnimationUpdateFrames(
+     *    animator, TRANSLATION_X,
+     *    { u -> u.value > 50f },
+     *    { u -> u.value < 0f })
+     *
+     * Example: verify that an animation went backwards at some point while still being on-screen.
+     * verifyAnimationUpdateFrames(
+     *    animator, TRANSLATION_X,
+     *    { u -> u.velocity < 0f && u.value >= 0f })
+     *
+     * This method is intended to help you test longer, more complicated animations where it's
+     * critical that certain values were reached. Using this method to test short animations can
+     * fail due to the animation having fewer frames than provided matchers. For example, an
+     * animation from x = 1f to x = 5f might only have two frames, at x = 3f and x = 5f. The
+     * following would then fail despite it seeming logically sound:
+     *
+     * verifyAnimationUpdateFrames(
+     *    animator, TRANSLATION_X,
+     *    { u -> u.value > 1f },
+     *    { u -> u.value > 2f },
+     *    { u -> u.value > 3f })
+     *
+     * Tests might also fail if your matchers are too granular, such as this example test after an
+     * animation from x = 0f to x = 100f. It's unlikely there was a frame specifically between 2f
+     * and 3f.
+     *
+     * verifyAnimationUpdateFrames(
+     *    animator, TRANSLATION_X,
+     *    { u -> u.value > 2f && u.value < 3f },
+     *    { u -> u.value >= 50f })
+     *
+     * Failures will print a helpful log of all animation frames so you can see what caused the test
+     * to fail.
+     */
+    fun <T : Any> verifyAnimationUpdateFrames(
+        animator: PhysicsAnimator<T>,
+        property: FloatPropertyCompat<in T>,
+        firstUpdateMatcher: UpdateMatcher,
+        vararg additionalUpdateMatchers: UpdateMatcher
+    ) {
+        val updateFrames: UpdateFramesPerProperty<T> = getAnimationUpdateFrames(animator)
+        val matchers = ArrayDeque<UpdateMatcher>(
+                additionalUpdateMatchers.toList())
+        val frameTraceMessage = StringBuilder()
+
+        var curMatcher = firstUpdateMatcher
+
+        // Loop through the updates from the testable animator.
+        for (update in updateFrames[property]
+                ?: error("No frames for given target object and property.")) {
+
+            // Check whether this frame satisfies the current matcher.
+            if (curMatcher(update)) {
+
+                // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
+                // frames and return without failing.
+                if (matchers.size == 0) {
+                    getAnimationUpdateFrames(animator).remove(property)
+                    return
+                }
+
+                frameTraceMessage.append("$update\t(satisfied matcher)\n")
+                curMatcher = matchers.pop() // Get the next matcher and keep going.
+            } else {
+                frameTraceMessage.append("${update}\n")
+            }
+        }
+
+        val readablePropertyName = PhysicsAnimator.getReadablePropertyName(property)
+        getAnimationUpdateFrames(animator).remove(property)
+
+        throw RuntimeException(
+                "Failed to verify animation frames for property $readablePropertyName: " +
+                        "Provided ${additionalUpdateMatchers.size + 1} matchers, " +
+                        "however ${matchers.size + 1} remained unsatisfied.\n\n" +
+                        "All frames:\n$frameTraceMessage")
+    }
+
+    /**
+     * Overload of [verifyAnimationUpdateFrames] that builds matchers for you, from given float
+     * values. For example, to verify that an animations passed from 0f to 50f to 100f back to 50f:
+     *
+     * verifyAnimationUpdateFrames(animator, TRANSLATION_X, 0f, 50f, 100f, 50f)
+     *
+     * This verifies that update frames were received with values of >= 0f, >= 50f, >= 100f, and
+     * <= 50f.
+     *
+     * The same caveats apply: short animations might not have enough frames to satisfy all of the
+     * matchers, and overly specific calls (such as 0f, 1f, 2f, 3f, etc. for an animation from
+     * x = 0f to x = 100f) might fail as the animation only had frames at 0f, 25f, 50f, 75f, and
+     * 100f. As with [verifyAnimationUpdateFrames], failures will print a helpful log of all frames
+     * so you can see what caused the test to fail.
+     */
+    fun <T : Any> verifyAnimationUpdateFrames(
+        animator: PhysicsAnimator<T>,
+        property: FloatPropertyCompat<in T>,
+        startValue: Float,
+        firstTargetValue: Float,
+        vararg additionalTargetValues: Float
+    ) {
+        val matchers = ArrayList<UpdateMatcher>()
+
+        val values = ArrayList<Float>().also {
+            it.add(firstTargetValue)
+            it.addAll(additionalTargetValues.toList())
+        }
+
+        var prevVal = startValue
+        for (value in values) {
+            if (value > prevVal) {
+                matchers.add { update -> update.value >= value }
+            } else {
+                matchers.add { update -> update.value <= value }
+            }
+
+            prevVal = value
+        }
+
+        verifyAnimationUpdateFrames(
+                animator, property, matchers[0], *matchers.drop(0).toTypedArray())
+    }
+
+    /**
+     * Returns all of the values that have ever been reported to update listeners, per property.
+     */
+    fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
+            UpdateFramesPerProperty<T> {
+        return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
+    }
+
+    /**
+     * Clears animation frame updates from the given animator so they aren't used the next time its
+     * passed to [verifyAnimationUpdateFrames].
+     */
+    fun <T : Any> clearAnimationUpdateFrames(animator: PhysicsAnimator<T>) {
+        animatorTestHelpers[animator]?.clearUpdates()
+    }
+
+    private fun <T> getAnimationTestHelper(animator: PhysicsAnimator<T>): AnimatorTestHelper<T> {
+        return animatorTestHelpers[animator] as AnimatorTestHelper<T>
+    }
+
+    /**
+     * Helper class for testing an animator. This replaces the animator's start action with
+     * [startForTest] and adds test listeners to enable other test utility behaviors. We build one
+     * these for each Animator and keep them around so we can access the updates.
+     */
+    class AnimatorTestHelper<T> (private val animator: PhysicsAnimator<T>) {
+
+        /** All updates received for each property animation. */
+        private val allUpdates =
+                ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>()
+
+        private val testEndListeners = ArrayList<PhysicsAnimator.EndListener<T>>()
+        private val testUpdateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>()
+
+        init {
+            animator.startAction = ::startForTest
+        }
+
+        internal fun addTestEndListener(listener: PhysicsAnimator.EndListener<T>) {
+            testEndListeners.add(listener)
+        }
+
+        internal fun addTestUpdateListener(listener: PhysicsAnimator.UpdateListener<T>) {
+            testUpdateListeners.add(listener)
+        }
+
+        internal fun getUpdates(): UpdateFramesPerProperty<T> {
+            return allUpdates
+        }
+
+        internal fun clearUpdates() {
+            allUpdates.clear()
+        }
+
+        private fun startForTest() {
+            // The testable animator needs to block the main thread until super.start() has been
+            // called, since callers expect .start() to be synchronous but we're posting it to a
+            // handler here. We may also continue blocking until all animations end, if
+            // startBlocksUntilAnimationsEnd = true.
+            val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
+
+            animationThreadHandler.post {
+                val animatedProperties = animator.getAnimatedProperties()
+
+                // Add an update listener that dispatches to any test update listeners added by
+                // tests.
+                animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
+                    override fun onAnimationUpdateForProperty(
+                        target: T,
+                        values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+                    ) {
+                        for (listener in testUpdateListeners) {
+                            listener.onAnimationUpdateForProperty(target, values)
+                        }
+                    }
+                })
+
+                // Add an end listener that dispatches to any test end listeners added by tests, and
+                // unblocks the main thread if required.
+                animator.addEndListener(object : PhysicsAnimator.EndListener<T> {
+                    override fun onAnimationEnd(
+                        target: T,
+                        property: FloatPropertyCompat<in T>,
+                        canceled: Boolean,
+                        finalValue: Float,
+                        finalVelocity: Float,
+                        allRelevantPropertyAnimsEnded: Boolean
+                    ) {
+                        for (listener in testEndListeners) {
+                            listener.onAnimationEnd(
+                                    target, property, canceled, finalValue, finalVelocity,
+                                    allRelevantPropertyAnimsEnded)
+                        }
+
+                        if (allRelevantPropertyAnimsEnded) {
+                            testEndListeners.clear()
+                            testUpdateListeners.clear()
+
+                            if (startBlocksUntilAnimationsEnd) {
+                                unblockLatch.countDown()
+                            }
+                        }
+                    }
+                })
+
+                val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
+                    it.add(object : PhysicsAnimator.UpdateListener<T> {
+                        override fun onAnimationUpdateForProperty(
+                            target: T,
+                            values: ArrayMap<FloatPropertyCompat<in T>,
+                                             PhysicsAnimator.AnimationUpdate>
+                        ) {
+                            values.forEach { (property, value) ->
+                                allUpdates.getOrPut(property, { ArrayList() }).add(value)
+                            }
+                        }
+                    })
+                }
+
+                /**
+                 * Add an internal listener at the head of the list that captures update values
+                 * directly from DynamicAnimation. We use this to build a list of all updates so we
+                 * can verify that InternalListener dispatches to the real listeners properly.
+                 */
+                animator.internalListeners.add(0, animator.InternalListener(
+                        animatedProperties,
+                        updateListeners,
+                        ArrayList(),
+                        ArrayList()))
+
+                animator.startInternal()
+                unblockLatch.countDown()
+            }
+
+            unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 2ccecec..2bf855a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -261,7 +261,7 @@
 
     @Test
     public void testRemoveBubble_withDismissedNotif() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
@@ -304,7 +304,7 @@
         assertFalse(mBubbleController.isStackExpanded());
 
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // We should have bubbles & their notifs should not be suppressed
@@ -334,8 +334,8 @@
     @Test
     public void testCollapseAfterChangingExpandedBubble() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow2.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
 
@@ -377,7 +377,7 @@
     @Test
     public void testExpansionRemovesShowInShadeAndDot() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // We should have bubbles & their notifs should not be suppressed
@@ -403,7 +403,7 @@
     @Test
     public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // We should have bubbles & their notifs should not be suppressed
@@ -439,8 +439,8 @@
     @Test
     public void testRemoveLastExpandedCollapses() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow2.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
@@ -483,7 +483,7 @@
                 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion shouldn't change
@@ -501,7 +501,7 @@
                 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion should change
@@ -519,7 +519,7 @@
                 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
 
         // Add the suppress notif bubble
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // Notif should be suppressed because we were foreground
@@ -564,7 +564,7 @@
     public void testExpandStackAndSelectBubble_removedFirst() {
         final String key = mRow.getEntry().getKey();
 
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // Simulate notification cancellation.
@@ -576,7 +576,7 @@
 
     @Test
     public void testMarkNewNotificationAsShowInShade() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 mRow.getEntry().getKey()));
 
@@ -586,7 +586,7 @@
 
     @Test
     public void testAddNotif_notBubble() {
-        mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
+        mEntryListener.onNotificationAdded(mNonBubbleNotifRow.getEntry());
         mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
 
         verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean());
@@ -631,7 +631,7 @@
 
     @Test
     public void testRemoveBubble_succeeds_appCancel() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
@@ -646,7 +646,7 @@
 
     @Test
     public void removeBubble_fails_clearAll()  {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
@@ -669,7 +669,7 @@
 
     @Test
     public void removeBubble_fails_userDismissNotif() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
@@ -692,7 +692,7 @@
 
     @Test
     public void removeBubble_succeeds_userDismissBubble_userDimissNotif() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
new file mode 100644
index 0000000..a39fbc4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -0,0 +1,436 @@
+package com.android.systemui.util.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.util.ArrayMap
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringForce
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.animation.PhysicsAnimator.EndListener
+import com.android.systemui.util.animation.PhysicsAnimator.UpdateListener
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PhysicsAnimatorTest : SysuiTestCase() {
+    private lateinit var viewGroup: ViewGroup
+    private lateinit var testView: View
+    private lateinit var testView2: View
+
+    private lateinit var animator: PhysicsAnimator<View>
+
+    private val springConfig = PhysicsAnimator.SpringConfig(
+            SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+    private val flingConfig = PhysicsAnimator.FlingConfig(2f)
+
+    private lateinit var mockUpdateListener: UpdateListener<View>
+    private lateinit var mockEndListener: EndListener<View>
+    private lateinit var mockEndAction: Runnable
+
+    private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        mockUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+        mockEndListener = mock(EndListener::class.java) as EndListener<View>
+        mockEndAction = mock(Runnable::class.java)
+
+        viewGroup = FrameLayout(context)
+        testView = View(context)
+        testView2 = View(context)
+        viewGroup.addView(testView)
+        viewGroup.addView(testView2)
+
+        PhysicsAnimatorTestUtils.prepareForTest()
+
+        // Most of our tests involve checking the end state of animations, so we want calls that
+        // start animations to block the test thread until the animations have ended.
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+
+        animator = PhysicsAnimator.getInstance(testView)
+    }
+
+    @After
+    fun tearDown() {
+        PhysicsAnimatorTestUtils.tearDown()
+    }
+
+    @Test
+    fun testOneAnimatorPerView() {
+        assertEquals(animator, PhysicsAnimator.getInstance(testView))
+        assertEquals(PhysicsAnimator.getInstance(testView), PhysicsAnimator.getInstance(testView))
+        assertNotEquals(animator, PhysicsAnimator.getInstance(testView2))
+    }
+
+    @Test
+    fun testSpringOneProperty() {
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 50f, springConfig)
+                .start()
+
+        assertEquals(testView.translationX, 50f, 1f)
+    }
+
+    @Test
+    fun testSpringMultipleProperties() {
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+                .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+                .spring(DynamicAnimation.SCALE_Y, 1.1f, springConfig)
+                .start()
+
+        assertEquals(10f, testView.translationX, 1f)
+        assertEquals(50f, testView.translationY, 1f)
+        assertEquals(1.1f, testView.scaleY, 0.01f)
+    }
+
+    @Test
+    fun testFling() {
+        val startTime = System.currentTimeMillis()
+
+        animator
+                .fling(DynamicAnimation.TRANSLATION_X, 1000f /* startVelocity */, flingConfig)
+                .fling(DynamicAnimation.TRANSLATION_Y, 500f, flingConfig)
+                .start()
+
+        val elapsedTimeSeconds = (System.currentTimeMillis() - startTime) / 1000f
+
+        // If the fling worked, the view should be somewhere between its starting position and the
+        // and the theoretical no-friction maximum of startVelocity (in pixels per second)
+        // multiplied by elapsedTimeSeconds. We can't calculate an exact expected location for a
+        // fling, so this is close enough.
+        assertTrue(testView.translationX > 0f)
+        assertTrue(testView.translationX < 1000f * elapsedTimeSeconds)
+        assertTrue(testView.translationY > 0f)
+        assertTrue(testView.translationY < 500f * elapsedTimeSeconds)
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun testEndListenersAndActions() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+                .spring(DynamicAnimation.TRANSLATION_Y, 500f, springConfig)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+        // Once TRANSLATION_X is done, the view should be at x = 10...
+        assertEquals(10f, testView.translationX, 1f)
+
+        // / ...TRANSLATION_Y should still be running...
+        assertTrue(animator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+
+        // ...and our end listener should have been called with x = 10, velocity = 0, and allEnded =
+        // false since TRANSLATION_Y is still running.
+        verify(mockEndListener).onAnimationEnd(
+                testView,
+                DynamicAnimation.TRANSLATION_X,
+                canceled = false,
+                finalValue = 10f,
+                finalVelocity = 0f,
+                allRelevantPropertyAnimsEnded = false)
+        verifyNoMoreInteractions(mockEndListener)
+
+        // The end action should not have been run yet.
+        verify(mockEndAction, times(0)).run()
+
+        // Block until TRANSLATION_Y finishes.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+        // The view should have been moved.
+        assertEquals(10f, testView.translationX, 1f)
+        assertEquals(500f, testView.translationY, 1f)
+
+        // The end listener should have been called, this time with TRANSLATION_Y, y = 50, and
+        // allEnded = true.
+        verify(mockEndListener).onAnimationEnd(
+                testView,
+                DynamicAnimation.TRANSLATION_Y,
+                canceled = false,
+                finalValue = 500f,
+                finalVelocity = 0f,
+                allRelevantPropertyAnimsEnded = true)
+        verifyNoMoreInteractions(mockEndListener)
+
+        // Now that all properties are done animating, the end action should have been called.
+        verify(mockEndAction, times(1)).run()
+    }
+
+    @Test
+    fun testUpdateListeners() {
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 100f, springConfig)
+                .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+                .addUpdateListener(object : UpdateListener<View> {
+                    override fun onAnimationUpdateForProperty(
+                        target: View,
+                        values: UpdateMap<View>
+                    ) {
+                        mockUpdateListener.onAnimationUpdateForProperty(target, values)
+                    }
+                })
+                .start()
+
+        verifyUpdateListenerCalls(animator, mockUpdateListener)
+    }
+
+    @Test
+    fun testListenersNotCalledOnSubsequentAnimations() {
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        verifyUpdateListenerCalls(animator, mockUpdateListener)
+        verify(mockEndListener, times(1)).onAnimationEnd(
+                eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
+                eq(true))
+        verify(mockEndAction, times(1)).run()
+
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 0f, springConfig)
+                .start()
+
+        // We didn't pass any of the listeners/actions to the subsequent animation, so they should
+        // never have been called.
+        verifyNoMoreInteractions(mockUpdateListener)
+        verifyNoMoreInteractions(mockEndListener)
+        verifyNoMoreInteractions(mockEndAction)
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun testAnimationsUpdatedWhileInMotion() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+        // Spring towards x = 100f.
+        animator
+                .spring(
+                        DynamicAnimation.TRANSLATION_X,
+                        100f,
+                        springConfig)
+                .start()
+
+        // Block until it reaches x = 50f.
+        PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+                animator) { view -> view.translationX > 50f }
+
+        // Translation X value at the time of reversing the animation to spring to x = 0f.
+        val reversalTranslationX = testView.translationX
+
+        // Spring back towards 0f.
+        animator
+                .spring(
+                        DynamicAnimation.TRANSLATION_X,
+                        0f,
+                        // Lower the stiffness to ensure the update listener receives at least one
+                        // update frame where the view has continued to move to the right.
+                        springConfig.apply { stiffness = SpringForce.STIFFNESS_LOW })
+                .start()
+
+        // Wait for TRANSLATION_X.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+        // Verify that the animation continued past the X value at the time of reversal, before
+        // springing back. This ensures the change in direction was not abrupt.
+        verifyAnimationUpdateFrames(
+                animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value > reversalTranslationX },
+                { u -> u.value < reversalTranslationX })
+
+        // Verify that the view is where it should be.
+        assertEquals(0f, testView.translationX, 1f)
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun testAnimationsUpdatedWhileInMotion_originalListenersStillCalled() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+        // Spring TRANSLATION_X to 100f, with an update and end listener provided.
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 100f, springConfig)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .start()
+
+        // Wait until the animation is halfway there.
+        PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+                animator) { view -> view.translationX > 50f }
+
+        // The end listener shouldn't have been called since the animation hasn't ended.
+        verifyNoMoreInteractions(mockEndListener)
+
+        // Make sure we called the update listener with appropriate values.
+        verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value > 0f },
+                { u -> u.value >= 50f })
+
+        // Mock a second end listener.
+        val secondEndListener = mock(EndListener::class.java) as EndListener<View>
+        val secondUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+
+        // Start a new animation that springs both TRANSLATION_X and TRANSLATION_Y, and provide it
+        // the second end listener. This new end listener should be called for the end of
+        // TRANSLATION_X and TRANSLATION_Y, with allEnded = true when both have ended.
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 200f, springConfig)
+                .spring(DynamicAnimation.TRANSLATION_Y, 4000f, springConfig)
+                .addUpdateListener(secondUpdateListener)
+                .addEndListener(secondEndListener)
+                .start()
+
+        // Wait for TRANSLATION_X to end.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+        // The update listener provided to the initial animation call (the one that only animated
+        // TRANSLATION_X) should have been called with values on the way to x = 200f. This is
+        // because the second animation call updated the original TRANSLATION_X animation.
+        verifyAnimationUpdateFrames(
+                animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value > 100f }, { u -> u.value >= 200f })
+
+        // The original end listener should also have been called, with allEnded = true since it was
+        // provided to an animator that animated only TRANSLATION_X.
+        verify(mockEndListener, times(1))
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+        verifyNoMoreInteractions(mockEndListener)
+
+        // The second end listener should have been called, but with allEnded = false since it was
+        // provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
+        verify(secondEndListener, times(1))
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+        verifyNoMoreInteractions(secondEndListener)
+
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+        // The original end listener shouldn't receive any callbacks because it was not provided to
+        // an animator that animated TRANSLATION_Y.
+        verifyNoMoreInteractions(mockEndListener)
+
+        verify(secondEndListener, times(1))
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+        verifyNoMoreInteractions(secondEndListener)
+    }
+
+    @Test
+    fun testFlingRespectsMinMax() {
+        animator
+                .fling(DynamicAnimation.TRANSLATION_X,
+                        startVelocity = 1000f,
+                        friction = 1.1f,
+                        max = 10f)
+                .addEndListener(mockEndListener)
+                .start()
+
+        // Ensure that the view stopped at x = 10f, and the end listener was called once with that
+        // value.
+        assertEquals(10f, testView.translationX, 1f)
+        verify(mockEndListener, times(1))
+                .onAnimationEnd(
+                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
+                        anyFloat(), eq(true))
+
+        animator
+                .fling(
+                        DynamicAnimation.TRANSLATION_X,
+                        startVelocity = -1000f,
+                        friction = 1.1f,
+                        min = -5f)
+                .addEndListener(mockEndListener)
+                .start()
+
+        // Ensure that the view stopped at x = -5f, and the end listener was called once with that
+        // value.
+        assertEquals(-5f, testView.translationX, 1f)
+        verify(mockEndListener, times(1))
+                .onAnimationEnd(
+                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
+                        anyFloat(), eq(true))
+    }
+
+    @Test
+    fun testExtensionProperty() {
+        testView
+                .physicsAnimator
+                .spring(DynamicAnimation.TRANSLATION_X, 200f)
+                .start()
+
+        assertEquals(200f, testView.translationX, 1f)
+    }
+
+    /**
+     * Verifies that the calls to the mock update listener match the animation update frames
+     * reported by the test internal listener, in order.
+     */
+    private fun <T : Any> verifyUpdateListenerCalls(
+        animator: PhysicsAnimator<T>,
+        mockUpdateListener: UpdateListener<T>
+    ) {
+        val updates = getAnimationUpdateFrames(animator)
+
+        for (invocation in Mockito.mockingDetails(mockUpdateListener).invocations) {
+
+            // Grab the update map of Property -> AnimationUpdate that was passed to the mock update
+            // listener.
+            val updateMap = invocation.arguments[1]
+                    as ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+            //
+            for ((property, update) in updateMap) {
+                val updatesForProperty = updates[property]!!
+
+                // This update should be the next one in the list for this property.
+                if (update != updatesForProperty[0]) {
+                    Assert.fail("The update listener was called with an unexpected value: $update.")
+                }
+
+                updatesForProperty.remove(update)
+            }
+
+            // Mark this invocation verified.
+            verify(mockUpdateListener).onAnimationUpdateForProperty(animator.target, updateMap)
+        }
+
+        verifyNoMoreInteractions(mockUpdateListener)
+
+        // Since we were removing values as matching invocations were found, there should no longer
+        // be any values remaining. If there are, it means the update listener wasn't notified when
+        // it should have been.
+        assertEquals(0,
+                updates.values.fold(0, { count, propertyUpdates -> count + propertyUpdates.size }))
+
+        clearAnimationUpdateFrames(animator)
+    }
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
deleted file mode 100644
index 3dfe59e..0000000
--- a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
+++ /dev/null
@@ -1,640 +0,0 @@
-/*
- ** Copyright 2015, The Android Open Source Project
- **
- ** Licensed under the Apache License, Version 2.0 (the "License");
- ** you may not use this file except in compliance with the License.
- ** You may obtain a copy of the License at
- **
- **     http://www.apache.org/licenses/LICENSE-2.0
- **
- ** Unless required by applicable law or agreed to in writing, software
- ** distributed under the License is distributed on an "AS IS" BASIS,
- ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ** See the License for the specific language governing permissions and
- ** limitations under the License.
- */
-
-package com.android.server.accessibility.gestures;
-
-import android.accessibilityservice.AccessibilityGestureEvent;
-import android.accessibilityservice.AccessibilityService;
-import android.content.Context;
-import android.gesture.GesturePoint;
-import android.graphics.PointF;
-import android.util.Slog;
-import android.util.TypedValue;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-
-/**
- * This class handles gesture detection for the Touch Explorer.  It collects
- * touch events and determines when they match a gesture, as well as when they
- * won't match a gesture.  These state changes are then surfaced to mListener.
- */
-class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListener {
-
-    private static final boolean DEBUG = false;
-
-    // Tag for logging received events.
-    private static final String LOG_TAG = "AccessibilityGestureDetector";
-
-    // Constants for sampling motion event points.
-    // We sample based on a minimum distance between points, primarily to improve accuracy by
-    // reducing noisy minor changes in direction.
-    private static final float MIN_INCHES_BETWEEN_SAMPLES = 0.1f;
-    private final float mMinPixelsBetweenSamplesX;
-    private final float mMinPixelsBetweenSamplesY;
-
-    // Constants for separating gesture segments
-    private static final float ANGLE_THRESHOLD = 0.0f;
-
-    // Constants for line segment directions
-    private static final int LEFT = 0;
-    private static final int RIGHT = 1;
-    private static final int UP = 2;
-    private static final int DOWN = 3;
-    private static final int[][] DIRECTIONS_TO_GESTURE_ID = {
-        {
-            AccessibilityService.GESTURE_SWIPE_LEFT,
-            AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT,
-            AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP,
-            AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN
-        },
-        {
-            AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT,
-            AccessibilityService.GESTURE_SWIPE_RIGHT,
-            AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP,
-            AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN
-        },
-        {
-            AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT,
-            AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT,
-            AccessibilityService.GESTURE_SWIPE_UP,
-            AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN
-        },
-        {
-            AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT,
-            AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT,
-            AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP,
-            AccessibilityService.GESTURE_SWIPE_DOWN
-        }
-    };
-
-
-    /**
-     * Listener functions are called as a result of onMoveEvent().  The current
-     * MotionEvent in the context of these functions is the event passed into
-     * onMotionEvent.
-     */
-    public interface Listener {
-        /**
-         * Called when the user has performed a double tap and then held down
-         * the second tap.
-         *
-         * @param event The most recent MotionEvent received.
-         * @param policyFlags The policy flags of the most recent event.
-         */
-        void onDoubleTapAndHold(MotionEvent event, int policyFlags);
-
-        /**
-         * Called when the user lifts their finger on the second tap of a double
-         * tap.
-         *
-         * @param event The most recent MotionEvent received.
-         * @param policyFlags The policy flags of the most recent event.
-         *
-         * @return true if the event is consumed, else false
-         */
-        boolean onDoubleTap(MotionEvent event, int policyFlags);
-
-        /**
-         * Called when the system has decided the event stream is a gesture.
-         *
-         * @return true if the event is consumed, else false
-         */
-        boolean onGestureStarted();
-
-        /**
-         * Called when an event stream is recognized as a gesture.
-         *
-         * @param gestureEvent Information about the gesture.
-         *
-         * @return true if the event is consumed, else false
-         */
-        boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
-
-        /**
-         * Called when the system has decided an event stream doesn't match any
-         * known gesture.
-         *
-         * @param event The most recent MotionEvent received.
-         * @param policyFlags The policy flags of the most recent event.
-         *
-         * @return true if the event is consumed, else false
-         */
-        public boolean onGestureCancelled(MotionEvent event, int policyFlags);
-    }
-
-    private final Listener mListener;
-    private final Context mContext;  // Retained for on-demand construction of GestureDetector.
-    private final GestureDetector mGestureDetector;  // Double-tap detector.
-
-    // Indicates that a single tap has occurred.
-    private boolean mFirstTapDetected;
-
-    // Indicates that the down event of a double tap has occured.
-    private boolean mDoubleTapDetected;
-
-    // Indicates that motion events are being collected to match a gesture.
-    private boolean mRecognizingGesture;
-
-    // Indicates that we've collected enough data to be sure it could be a
-    // gesture.
-    private boolean mGestureStarted;
-
-    // Indicates that motion events from the second pointer are being checked
-    // for a double tap.
-    private boolean mSecondFingerDoubleTap;
-
-    // Tracks the most recent time where ACTION_POINTER_DOWN was sent for the
-    // second pointer.
-    private long mSecondPointerDownTime;
-
-    // Policy flags of the previous event.
-    private int mPolicyFlags;
-
-    // These values track the previous point that was saved to use for gesture
-    // detection.  They are only updated when the user moves more than the
-    // recognition threshold.
-    private float mPreviousGestureX;
-    private float mPreviousGestureY;
-
-    // These values track the previous point that was used to determine if there
-    // was a transition into or out of gesture detection.  They are updated when
-    // the user moves more than the detection threshold.
-    private float mBaseX;
-    private float mBaseY;
-    private long mBaseTime;
-
-    // This is the calculated movement threshold used track if the user is still
-    // moving their finger.
-    private final float mGestureDetectionThreshold;
-
-    // Buffer for storing points for gesture detection.
-    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
-
-    // The minimal delta between moves to add a gesture point.
-    private static final int TOUCH_TOLERANCE = 3;
-
-    // The minimal score for accepting a predicted gesture.
-    private static final float MIN_PREDICTION_SCORE = 2.0f;
-
-    // Distance a finger must travel before we decide if it is a gesture or not.
-    private static final int GESTURE_CONFIRM_MM = 10;
-
-    // Time threshold used to determine if an interaction is a gesture or not.
-    // If the first movement of 1cm takes longer than this value, we assume it's
-    // a slow movement, and therefore not a gesture.
-    //
-    // This value was determined by measuring the time for the first 1cm
-    // movement when gesturing, and touch exploring.  Based on user testing,
-    // all gestures started with the initial movement taking less than 100ms.
-    // When touch exploring, the first movement almost always takes longer than
-    // 200ms.
-    private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
-
-    // Time threshold used to determine if a gesture should be cancelled.  If
-    // the finger takes more than this time to move 1cm, the ongoing gesture is
-    // cancelled.
-    private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
-
-    /**
-     * Construct the gesture detector for {@link TouchExplorer}.
-     *
-     * @see #AccessibilityGestureDetector(Context, Listener, GestureDetector)
-     */
-    AccessibilityGestureDetector(Context context, Listener listener) {
-        this(context, listener, null);
-    }
-
-    /**
-     * Construct the gesture detector for {@link TouchExplorer}.
-     *
-     * @param context A context handle for accessing resources.
-     * @param listener A listener to callback with gesture state or information.
-     * @param detector The gesture detector to handle touch event. If null the default one created
-     *                 in place, or for testing purpose.
-     */
-    AccessibilityGestureDetector(Context context, Listener listener, GestureDetector detector) {
-        mListener = listener;
-        mContext = context;
-
-        // Break the circular dependency between constructors and let the class to be testable
-        if (detector == null) {
-            mGestureDetector = new GestureDetector(context, this);
-        } else {
-            mGestureDetector = detector;
-        }
-        mGestureDetector.setOnDoubleTapListener(this);
-        mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
-                context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
-
-        // Calculate minimum gesture velocity
-        final float pixelsPerInchX = context.getResources().getDisplayMetrics().xdpi;
-        final float pixelsPerInchY = context.getResources().getDisplayMetrics().ydpi;
-        mMinPixelsBetweenSamplesX = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchX;
-        mMinPixelsBetweenSamplesY = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchY;
-    }
-
-    /**
-     * Handle a motion event.  If an action is completed, the appropriate
-     * callback on mListener is called, and the return value of the callback is
-     * passed to the caller.
-     *
-     * @param event The transformed motion event to be handled.
-     * @param rawEvent The raw motion event.  It's important that this be the raw
-     * event, before any transformations have been applied, so that measurements
-     * can be made in physical units.
-     * @param policyFlags Policy flags for the event.
-     *
-     * @return true if the event is consumed, else false
-     */
-    public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        // The accessibility gesture detector is interested in the movements in physical space,
-        // so it uses the rawEvent to ignore magnification and other transformations.
-        final float x = rawEvent.getX();
-        final float y = rawEvent.getY();
-        final long time = rawEvent.getEventTime();
-
-        mPolicyFlags = policyFlags;
-        switch (rawEvent.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mDoubleTapDetected = false;
-                mSecondFingerDoubleTap = false;
-                mRecognizingGesture = true;
-                mGestureStarted = false;
-                mPreviousGestureX = x;
-                mPreviousGestureY = y;
-                mStrokeBuffer.clear();
-                mStrokeBuffer.add(new GesturePoint(x, y, time));
-
-                mBaseX = x;
-                mBaseY = y;
-                mBaseTime = time;
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                if (mRecognizingGesture) {
-                    final float deltaX = mBaseX - x;
-                    final float deltaY = mBaseY - y;
-                    final double moveDelta = Math.hypot(deltaX, deltaY);
-                    if (moveDelta > mGestureDetectionThreshold) {
-                        // If the pointer has moved more than the threshold,
-                        // update the stored values.
-                        mBaseX = x;
-                        mBaseY = y;
-                        mBaseTime = time;
-
-                        // Since the pointer has moved, this is not a double
-                        // tap.
-                        mFirstTapDetected = false;
-                        mDoubleTapDetected = false;
-
-                        // If this hasn't been confirmed as a gesture yet, send
-                        // the event.
-                        if (!mGestureStarted) {
-                            mGestureStarted = true;
-                            return mListener.onGestureStarted();
-                        }
-                    } else if (!mFirstTapDetected) {
-                        // The finger may not move if they are double tapping.
-                        // In that case, we shouldn't cancel the gesture.
-                        final long timeDelta = time - mBaseTime;
-                        final long threshold = mGestureStarted ?
-                            CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS :
-                            CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;
-
-                        // If the pointer hasn't moved for longer than the
-                        // timeout, cancel gesture detection.
-                        if (timeDelta > threshold) {
-                            cancelGesture();
-                            return mListener.onGestureCancelled(rawEvent, policyFlags);
-                        }
-                    }
-
-                    final float dX = Math.abs(x - mPreviousGestureX);
-                    final float dY = Math.abs(y - mPreviousGestureY);
-                    if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
-                        mPreviousGestureX = x;
-                        mPreviousGestureY = y;
-                        mStrokeBuffer.add(new GesturePoint(x, y, time));
-                    }
-                }
-                break;
-
-            case MotionEvent.ACTION_UP:
-                if (mDoubleTapDetected) {
-                    return finishDoubleTap(rawEvent, policyFlags);
-                }
-                if (mGestureStarted) {
-                    final float dX = Math.abs(x - mPreviousGestureX);
-                    final float dY = Math.abs(y - mPreviousGestureY);
-                    if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
-                        mStrokeBuffer.add(new GesturePoint(x, y, time));
-                    }
-                    return recognizeGesture(rawEvent, policyFlags);
-                }
-                break;
-
-            case MotionEvent.ACTION_POINTER_DOWN:
-                // Once a second finger is used, we're definitely not
-                // recognizing a gesture.
-                cancelGesture();
-
-                if (rawEvent.getPointerCount() == 2) {
-                    // If this was the second finger, attempt to recognize double
-                    // taps on it.
-                    mSecondFingerDoubleTap = true;
-                    mSecondPointerDownTime = time;
-                } else {
-                    // If there are more than two fingers down, stop watching
-                    // for a double tap.
-                    mSecondFingerDoubleTap = false;
-                }
-                break;
-
-            case MotionEvent.ACTION_POINTER_UP:
-                // If we're detecting taps on the second finger, see if we
-                // should finish the double tap.
-                if (mSecondFingerDoubleTap && mDoubleTapDetected) {
-                    return finishDoubleTap(rawEvent, policyFlags);
-                }
-                break;
-
-            case MotionEvent.ACTION_CANCEL:
-                clear();
-                break;
-        }
-
-        // If we're detecting taps on the second finger, map events from the
-        // finger to the first finger.
-        if (mSecondFingerDoubleTap) {
-            MotionEvent newEvent = mapSecondPointerToFirstPointer(rawEvent);
-            if (newEvent == null) {
-                return false;
-            }
-            boolean handled = mGestureDetector.onTouchEvent(newEvent);
-            newEvent.recycle();
-            return handled;
-        }
-
-        if (!mRecognizingGesture) {
-            return false;
-        }
-
-        // Pass the transformed event on to the standard gesture detector.
-        return mGestureDetector.onTouchEvent(event);
-    }
-
-    public void clear() {
-        mFirstTapDetected = false;
-        mDoubleTapDetected = false;
-        mSecondFingerDoubleTap = false;
-        mGestureStarted = false;
-        mGestureDetector.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_CANCEL,
-                0.0f, 0.0f, 0));
-        cancelGesture();
-    }
-
-
-    @Override
-    public void onLongPress(MotionEvent e) {
-        maybeSendLongPress(e, mPolicyFlags);
-    }
-
-    @Override
-    public boolean onSingleTapUp(MotionEvent event) {
-        mFirstTapDetected = true;
-        return false;
-    }
-
-    @Override
-    public boolean onSingleTapConfirmed(MotionEvent event) {
-        clear();
-        return false;
-    }
-
-    @Override
-    public boolean onDoubleTap(MotionEvent event) {
-        // The processing of the double tap is deferred until the finger is
-        // lifted, so that we can detect a long press on the second tap.
-        mDoubleTapDetected = true;
-        return false;
-    }
-
-    private void maybeSendLongPress(MotionEvent event, int policyFlags) {
-        if (!mDoubleTapDetected) {
-            return;
-        }
-
-        clear();
-
-        mListener.onDoubleTapAndHold(event, policyFlags);
-    }
-
-    private boolean finishDoubleTap(MotionEvent event, int policyFlags) {
-        clear();
-
-        return mListener.onDoubleTap(event, policyFlags);
-    }
-
-    private void cancelGesture() {
-        mRecognizingGesture = false;
-        mGestureStarted = false;
-        mStrokeBuffer.clear();
-    }
-
-    /**
-     * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
-     * Listener callbacks for success or failure.
-     *
-     * @param event The raw motion event to pass to the listener callbacks.
-     * @param policyFlags Policy flags for the event.
-     *
-     * @return true if the event is consumed, else false
-     */
-    private boolean recognizeGesture(MotionEvent event, int policyFlags) {
-        if (mStrokeBuffer.size() < 2) {
-            return mListener.onGestureCancelled(event, policyFlags);
-        }
-
-        // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
-        // direction change.
-        // Method: for each sampled motion event, check the angle of the most recent motion vector
-        // versus the preceding motion vector, and segment the line if the angle is about
-        // 90 degrees.
-
-        ArrayList<PointF> path = new ArrayList<>();
-        PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
-        path.add(lastDelimiter);
-
-        float dX = 0;  // Sum of unit vectors from last delimiter to each following point
-        float dY = 0;
-        int count = 0;  // Number of points since last delimiter
-        float length = 0;  // Vector length from delimiter to most recent point
-
-        PointF next = new PointF();
-        for (int i = 1; i < mStrokeBuffer.size(); ++i) {
-            next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
-            if (count > 0) {
-                // Average of unit vectors from delimiter to following points
-                float currentDX = dX / count;
-                float currentDY = dY / count;
-
-                // newDelimiter is a possible new delimiter, based on a vector with length from
-                // the last delimiter to the previous point, but in the direction of the average
-                // unit vector from delimiter to previous points.
-                // Using the averaged vector has the effect of "squaring off the curve",
-                // creating a sharper angle between the last motion and the preceding motion from
-                // the delimiter. In turn, this sharper angle achieves the splitting threshold
-                // even in a gentle curve.
-                PointF newDelimiter = new PointF(length * currentDX + lastDelimiter.x,
-                    length * currentDY + lastDelimiter.y);
-
-                // Unit vector from newDelimiter to the most recent point
-                float nextDX = next.x - newDelimiter.x;
-                float nextDY = next.y - newDelimiter.y;
-                float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
-                nextDX = nextDX / nextLength;
-                nextDY = nextDY / nextLength;
-
-                // Compare the initial motion direction to the most recent motion direction,
-                // and segment the line if direction has changed by about 90 degrees.
-                float dot = currentDX * nextDX + currentDY * nextDY;
-                if (dot < ANGLE_THRESHOLD) {
-                    path.add(newDelimiter);
-                    lastDelimiter = newDelimiter;
-                    dX = 0;
-                    dY = 0;
-                    count = 0;
-                }
-            }
-
-            // Vector from last delimiter to most recent point
-            float currentDX = next.x - lastDelimiter.x;
-            float currentDY = next.y - lastDelimiter.y;
-            length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
-
-            // Increment sum of unit vectors from delimiter to each following point
-            count = count + 1;
-            dX = dX + currentDX / length;
-            dY = dY + currentDY / length;
-        }
-
-        path.add(next);
-        Slog.i(LOG_TAG, "path=" + path.toString());
-
-        // Classify line segments, and call Listener callbacks.
-        return recognizeGesturePath(event, policyFlags, path);
-    }
-
-    /**
-     * Classifies a pair of line segments, by direction.
-     * Calls Listener callbacks for success or failure.
-     *
-     * @param event The raw motion event to pass to the listener's onGestureCanceled method.
-     * @param policyFlags Policy flags for the event.
-     * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
-     *
-     * @return true if the event is consumed, else false
-     */
-    private boolean recognizeGesturePath(MotionEvent event, int policyFlags,
-            ArrayList<PointF> path) {
-
-        final int displayId = event.getDisplayId();
-        if (path.size() == 2) {
-            PointF start = path.get(0);
-            PointF end = path.get(1);
-
-            float dX = end.x - start.x;
-            float dY = end.y - start.y;
-            int direction = toDirection(dX, dY);
-            switch (direction) {
-                case LEFT:
-                    return mListener.onGestureCompleted(
-                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_LEFT,
-                                    displayId));
-                case RIGHT:
-                    return mListener.onGestureCompleted(
-                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_RIGHT,
-                                    displayId));
-                case UP:
-                    return mListener.onGestureCompleted(
-                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_UP,
-                                    displayId));
-                case DOWN:
-                    return mListener.onGestureCompleted(
-                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_DOWN,
-                                    displayId));
-                default:
-                    // Do nothing.
-            }
-
-        } else if (path.size() == 3) {
-            PointF start = path.get(0);
-            PointF mid = path.get(1);
-            PointF end = path.get(2);
-
-            float dX0 = mid.x - start.x;
-            float dY0 = mid.y - start.y;
-
-            float dX1 = end.x - mid.x;
-            float dY1 = end.y - mid.y;
-
-            int segmentDirection0 = toDirection(dX0, dY0);
-            int segmentDirection1 = toDirection(dX1, dY1);
-            int gestureId = DIRECTIONS_TO_GESTURE_ID[segmentDirection0][segmentDirection1];
-            return mListener.onGestureCompleted(
-                    new AccessibilityGestureEvent(gestureId, displayId));
-        }
-        // else if (path.size() < 2 || 3 < path.size()) then no gesture recognized.
-        return mListener.onGestureCancelled(event, policyFlags);
-    }
-
-    /** Maps a vector to a dominant direction in set {LEFT, RIGHT, UP, DOWN}. */
-    private static int toDirection(float dX, float dY) {
-        if (Math.abs(dX) > Math.abs(dY)) {
-            // Horizontal
-            return (dX < 0) ? LEFT : RIGHT;
-        } else {
-            // Vertical
-            return (dY < 0) ? UP : DOWN;
-        }
-    }
-
-    private MotionEvent mapSecondPointerToFirstPointer(MotionEvent event) {
-        // Only map basic events when two fingers are down.
-        if (event.getPointerCount() != 2 ||
-                (event.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN &&
-                 event.getActionMasked() != MotionEvent.ACTION_POINTER_UP &&
-                 event.getActionMasked() != MotionEvent.ACTION_MOVE)) {
-            return null;
-        }
-
-        int action = event.getActionMasked();
-
-        if (action == MotionEvent.ACTION_POINTER_DOWN) {
-            action = MotionEvent.ACTION_DOWN;
-        } else if (action == MotionEvent.ACTION_POINTER_UP) {
-            action = MotionEvent.ACTION_UP;
-        }
-
-        // Map the information from the second pointer to the first.
-        return MotionEvent.obtain(mSecondPointerDownTime, event.getEventTime(), action,
-                event.getX(1), event.getY(1), event.getPressure(1), event.getSize(1),
-                event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
-                event.getDeviceId(), event.getEdgeFlags());
-    }
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
new file mode 100644
index 0000000..9b7adc8
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -0,0 +1,232 @@
+/*
+ * 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.accessibility.gestures;
+
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+
+import static com.android.server.accessibility.gestures.Swipe.DOWN;
+import static com.android.server.accessibility.gestures.Swipe.LEFT;
+import static com.android.server.accessibility.gestures.Swipe.RIGHT;
+import static com.android.server.accessibility.gestures.Swipe.UP;
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class coordinates a series of individual gesture matchers to serve as a unified gesture
+ * detector. Gesture matchers are tied to a single gesture. It calls listener callback functions
+ * when a gesture starts or completes.
+ */
+class GestureManifold implements GestureMatcher.StateChangeListener {
+
+    private static final String LOG_TAG = "GestureManifold";
+
+    private final List<GestureMatcher> mGestures = new ArrayList<>();
+    private final Context mContext;
+    // Handler for performing asynchronous operations.
+    private final Handler mHandler;
+    // Listener to be notified of gesture start and end.
+    private Listener mListener;
+    // Shared state information.
+    private TouchState mState;
+
+    GestureManifold(Context context, Listener listener, TouchState state) {
+        mContext = context;
+        mHandler = new Handler(context.getMainLooper());
+        mListener = listener;
+        mState = state;
+        // Set up gestures.
+        // Start with double tap.
+        mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
+        mGestures.add(new MultiTapAndHold(context, 2, GESTURE_DOUBLE_TAP_AND_HOLD, this));
+        // One-direction swipes.
+        mGestures.add(new Swipe(context, RIGHT, GESTURE_SWIPE_RIGHT, this));
+        mGestures.add(new Swipe(context, LEFT, GESTURE_SWIPE_LEFT, this));
+        mGestures.add(new Swipe(context, UP, GESTURE_SWIPE_UP, this));
+        mGestures.add(new Swipe(context, DOWN, GESTURE_SWIPE_DOWN, this));
+        // Two-direction swipes.
+        mGestures.add(new Swipe(context, LEFT, RIGHT, GESTURE_SWIPE_LEFT_AND_RIGHT, this));
+        mGestures.add(new Swipe(context, LEFT, UP, GESTURE_SWIPE_LEFT_AND_UP, this));
+        mGestures.add(new Swipe(context, LEFT, DOWN, GESTURE_SWIPE_LEFT_AND_DOWN, this));
+        mGestures.add(new Swipe(context, RIGHT, UP, GESTURE_SWIPE_RIGHT_AND_UP, this));
+        mGestures.add(new Swipe(context, RIGHT, DOWN, GESTURE_SWIPE_RIGHT_AND_DOWN, this));
+        mGestures.add(new Swipe(context, RIGHT, LEFT, GESTURE_SWIPE_RIGHT_AND_LEFT, this));
+        mGestures.add(new Swipe(context, DOWN, UP, GESTURE_SWIPE_DOWN_AND_UP, this));
+        mGestures.add(new Swipe(context, DOWN, LEFT, GESTURE_SWIPE_DOWN_AND_LEFT, this));
+        mGestures.add(new Swipe(context, DOWN, RIGHT, GESTURE_SWIPE_DOWN_AND_RIGHT, this));
+        mGestures.add(new Swipe(context, UP, DOWN, GESTURE_SWIPE_UP_AND_DOWN, this));
+        mGestures.add(new Swipe(context, UP, LEFT, GESTURE_SWIPE_UP_AND_LEFT, this));
+        mGestures.add(new Swipe(context, UP, RIGHT, GESTURE_SWIPE_UP_AND_RIGHT, this));
+    }
+
+    /**
+     * Processes a motion event.
+     *
+     * @param event The event as received from the previous entry in the event stream.
+     * @param rawEvent The event without any transformations e.g. magnification.
+     * @param policyFlags
+     * @return True if the event has been appropriately handled by the gesture manifold and related
+     *     callback functions, false if it should be handled further by the calling function.
+     */
+    boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mState.isClear()) {
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                // Sanity safeguard: if touch state is clear, then matchers should always be clear
+                // before processing the next down event.
+                clear();
+            } else {
+                // If for some reason other events come through while in the clear state they could
+                // compromise the state of particular matchers, so we just ignore them.
+                return false;
+            }
+        }
+        for (GestureMatcher matcher : mGestures) {
+            if (matcher.getState() != GestureMatcher.STATE_GESTURE_CANCELED) {
+                if (DEBUG) {
+                    Slog.d(LOG_TAG, matcher.toString());
+                }
+                matcher.onMotionEvent(event, rawEvent, policyFlags);
+                if (DEBUG) {
+                    Slog.d(LOG_TAG, matcher.toString());
+                }
+                if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
+                    // Here we just clear and return. The actual gesture dispatch is done in
+                    // onStateChanged().
+                    clear();
+                    // No need to process this event any further.
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public void clear() {
+        for (GestureMatcher matcher : mGestures) {
+            matcher.clear();
+        }
+    }
+
+    /**
+     * Listener that receives notifications of the state of the gesture detector. Listener functions
+     * are called as a result of onMotionEvent(). The current MotionEvent in the context of these
+     * functions is the event passed into onMotionEvent.
+     */
+    public interface Listener {
+        /**
+         * Called when the user has performed a double tap and then held down the second tap.
+         */
+        void onDoubleTapAndHold();
+
+        /**
+         * Called when the user lifts their finger on the second tap of a double tap.
+         * @return true if the event is consumed, else false
+         */
+        boolean onDoubleTap();
+
+        /**
+         * Called when the system has decided the event stream is a gesture.
+         *
+         * @return true if the event is consumed, else false
+         */
+        boolean onGestureStarted();
+
+        /**
+         * Called when an event stream is recognized as a gesture.
+         *
+         * @param gestureEvent Information about the gesture.
+         * @return true if the event is consumed, else false
+         */
+        boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
+
+        /**
+         * Called when the system has decided an event stream doesn't match any known gesture.
+         *
+         * @param event The most recent MotionEvent received.
+         * @param policyFlags The policy flags of the most recent event.
+         * @return true if the event is consumed, else false
+         */
+        boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+    }
+
+    @Override
+    public void onStateChanged(
+            int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (state == GestureMatcher.STATE_GESTURE_STARTED && !mState.isGestureDetecting()) {
+            mListener.onGestureStarted();
+        } else if (state == GestureMatcher.STATE_GESTURE_COMPLETED) {
+            onGestureCompleted(gestureId);
+        } else if (state == GestureMatcher.STATE_GESTURE_CANCELED && mState.isGestureDetecting()) {
+            // We only want to call the cancelation callback if there are no other pending
+            // detectors.
+            for (GestureMatcher matcher : mGestures) {
+                if (matcher.getState() == GestureMatcher.STATE_GESTURE_STARTED) {
+                    return;
+                }
+            }
+            if (DEBUG) {
+                Slog.d(LOG_TAG, "Cancelling.");
+            }
+            mListener.onGestureCancelled(event, rawEvent, policyFlags);
+        }
+    }
+
+    private void onGestureCompleted(int gestureId) {
+        MotionEvent event = mState.getLastReceivedEvent();
+        // Note that gestures that complete immediately call clear() from onMotionEvent.
+        // Gestures that complete on a delay call clear() here.
+        switch (gestureId) {
+            case GESTURE_DOUBLE_TAP:
+                mListener.onDoubleTap();
+                clear();
+                break;
+            case GESTURE_DOUBLE_TAP_AND_HOLD:
+                mListener.onDoubleTapAndHold();
+                clear();
+                break;
+            default:
+                AccessibilityGestureEvent gestureEvent =
+                        new AccessibilityGestureEvent(gestureId, event.getDisplayId());
+                mListener.onGestureCompleted(gestureEvent);
+                break;
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
new file mode 100644
index 0000000..0b30ff57
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
@@ -0,0 +1,371 @@
+/*
+ * 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.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class describes a common base for gesture matchers. A gesture matcher checks a series of
+ * motion events against a single gesture. Coordinating the individual gesture matchers is done by
+ * the GestureManifold. To create a new Gesture, extend this class and override the onDown, onMove,
+ * onUp, etc methods as necessary. If you don't override a method your matcher will do nothing in
+ * response to that type of event. Finally, be sure to give your gesture a name by overriding
+ * getGestureName().
+ */
+abstract class GestureMatcher {
+    // Potential states for this individual gesture matcher.
+    // In STATE_CLEAR, this matcher is accepting new motion events but has not formally signaled
+    // that there is enough data to judge that a gesture has started.
+    static final int STATE_CLEAR = 0;
+    // In STATE_GESTURE_STARTED, this matcher continues to accept motion events and it has signaled
+    // to the gesture manifold that what looks like the specified gesture has started.
+    static final int STATE_GESTURE_STARTED = 1;
+    // In STATE_GESTURE_COMPLETED, this matcher has successfully matched the specified gesture. and
+    // will not accept motion events until it is cleared.
+    static final int STATE_GESTURE_COMPLETED = 2;
+    // In STATE_GESTURE_CANCELED, this matcher will not accept new motion events because it is
+    // impossible that this set of motion events will match the specified gesture.
+    static final int STATE_GESTURE_CANCELED = 3;
+
+    @IntDef({STATE_CLEAR, STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELED})
+    public @interface State {}
+
+    @State private int mState = STATE_CLEAR;
+    // The id number of the gesture that gets passed to accessibility services.
+    private final int mGestureId;
+    // handler for asynchronous operations like timeouts
+    private final Handler mHandler;
+
+    private final StateChangeListener mListener;
+
+    // Use this to transition to new states after a delay.
+    // e.g. cancel or complete after some timeout.
+    // Convenience functions for tapTimeout and doubleTapTimeout are already defined here.
+    protected final DelayedTransition mDelayedTransition;
+
+    GestureMatcher(int gestureId, Handler handler, StateChangeListener listener) {
+        mGestureId = gestureId;
+        mHandler = handler;
+        mDelayedTransition = new DelayedTransition();
+        mListener = listener;
+    }
+
+    /**
+     * Resets all state information for this matcher. Subclasses that include their own state
+     * information should override this method to reset their own state information and call
+     * super.clear().
+     */
+    protected void clear() {
+        mState = STATE_CLEAR;
+        cancelPendingTransitions();
+    }
+
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Transitions to a new state and notifies any listeners. Note that any pending transitions are
+     * canceled.
+     */
+    private void setState(
+            @State int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        mState = state;
+        cancelPendingTransitions();
+        mListener.onStateChanged(mGestureId, mState, event, rawEvent, policyFlags);
+    }
+
+    /** Indicates that there is evidence to suggest that this gesture has started. */
+    protected final void startGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        setState(STATE_GESTURE_STARTED, event, rawEvent, policyFlags);
+    }
+
+    /** Indicates this stream of motion events can no longer match this gesture. */
+    protected final void cancelGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+    }
+
+    /** Indicates this gesture is completed. */
+    protected final void completeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        setState(STATE_GESTURE_COMPLETED, event, rawEvent, policyFlags);
+    }
+
+    public int getGestureId() {
+        return mGestureId;
+    }
+
+    /**
+     * Process a motion event and attempt to match it to this gesture.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     * @return the state of this matcher.
+     */
+    public final int onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mState == STATE_GESTURE_CANCELED || mState == STATE_GESTURE_COMPLETED) {
+            return mState;
+        }
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                onDown(event, rawEvent, policyFlags);
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                onPointerDown(event, rawEvent, policyFlags);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                onMove(event, rawEvent, policyFlags);
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                onPointerUp(event, rawEvent, policyFlags);
+                break;
+            case MotionEvent.ACTION_UP:
+                onUp(event, rawEvent, policyFlags);
+                break;
+            default:
+                // Cancel because of invalid event.
+                setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+                break;
+        }
+        return mState;
+    }
+
+    /**
+     * Matchers override this method to respond to ACTION_DOWN events. ACTION_DOWN events indicate
+     * the first finger has touched the screen. If not overridden the default response is to do
+     * nothing.
+     */
+    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /**
+     * Matchers override this method to respond to ACTION_POINTER_DOWN events. ACTION_POINTER_DOWN
+     * indicates that more than one finger has touched the screen. If not overridden the default
+     * response is to do nothing.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     */
+    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /**
+     * Matchers override this method to respond to ACTION_MOVE events. ACTION_MOVE indicates that
+     * one or fingers has moved. If not overridden the default response is to do nothing.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     */
+    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /**
+     * Matchers override this method to respond to ACTION_POINTER_UP events. ACTION_POINTER_UP
+     * indicates that a finger has lifted from the screen but at least one finger continues to touch
+     * the screen. If not overridden the default response is to do nothing.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     */
+    protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /**
+     * Matchers override this method to respond to ACTION_UP events. ACTION_UP indicates that there
+     * are no more fingers touching the screen. If not overridden the default response is to do
+     * nothing.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     */
+    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /** Cancels this matcher after the tap timeout. Any pending state transitions are removed. */
+    protected void cancelAfterTapTimeout(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+    }
+
+    /** Cancels this matcher after the double tap timeout. Any pending cancelations are removed. */
+    protected final void cancelAfterDoubleTapTimeout(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+    }
+
+    /**
+     * Cancels this matcher after the specified timeout. Any pending cancelations are removed. Used
+     * to prevent this matcher from accepting motion events until it is cleared.
+     */
+    protected final void cancelAfter(
+            long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        mDelayedTransition.cancel();
+        mDelayedTransition.post(STATE_GESTURE_CANCELED, timeout, event, rawEvent, policyFlags);
+    }
+
+    /** Cancels any delayed transitions between states scheduled for this matcher. */
+    protected final void cancelPendingTransitions() {
+        mDelayedTransition.cancel();
+    }
+
+    /**
+     * Signals that this gesture has been completed after the tap timeout has expired. Used to
+     * ensure that there is no conflict with another gesture or for gestures that explicitly require
+     * a hold.
+     */
+    protected final void completeAfterLongPressTimeout(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        completeAfter(ViewConfiguration.getLongPressTimeout(), event, rawEvent, policyFlags);
+    }
+
+    /**
+     * Signals that this gesture has been completed after the tap timeout has expired. Used to
+     * ensure that there is no conflict with another gesture or for gestures that explicitly require
+     * a hold.
+     */
+    protected final void completeAfterTapTimeout(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        completeAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+    }
+
+    /**
+     * Signals that this gesture has been completed after the specified timeout has expired. Used to
+     * ensure that there is no conflict with another gesture or for gestures that explicitly require
+     * a hold.
+     */
+    protected final void completeAfter(
+            long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        mDelayedTransition.cancel();
+        mDelayedTransition.post(STATE_GESTURE_COMPLETED, timeout, event, rawEvent, policyFlags);
+    }
+
+    /**
+     * Signals that this gesture has been completed after the double-tap timeout has expired. Used
+     * to ensure that there is no conflict with another gesture or for gestures that explicitly
+     * require a hold.
+     */
+    protected final void completeAfterDoubleTapTimeout(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        completeAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+    }
+
+    public static String getStateSymbolicName(@State int state) {
+        switch (state) {
+            case STATE_CLEAR:
+                return "STATE_CLEAR";
+            case STATE_GESTURE_STARTED:
+                return "STATE_GESTURE_STARTED";
+            case STATE_GESTURE_COMPLETED:
+                return "STATE_GESTURE_COMPLETED";
+            case STATE_GESTURE_CANCELED:
+                return "STATE_GESTURE_CANCELED";
+            default:
+                return "Unknown state: " + state;
+        }
+    }
+
+    /**
+     * Returns a readable name for this matcher that can be displayed to the user and in system
+     * logs.
+     */
+    abstract String getGestureName();
+
+    /**
+     * Returns a String representation of this matcher. Each matcher can override this method to add
+     * extra state information to the string representation.
+     */
+    public String toString() {
+        return getGestureName() + ":" + getStateSymbolicName(mState);
+    }
+
+    /** This class allows matchers to transition between states on a delay. */
+    protected final class DelayedTransition implements Runnable {
+
+        private static final String LOG_TAG = "GestureMatcher.DelayedTransition";
+        int mTargetState;
+        MotionEvent mEvent;
+        MotionEvent mRawEvent;
+        int mPolicyFlags;
+
+        public void cancel() {
+            // Avoid meaningless debug messages.
+            if (DEBUG && isPending()) {
+                Slog.d(
+                        LOG_TAG,
+                        getGestureName()
+                                + ": canceling delayed transition to "
+                                + getStateSymbolicName(mTargetState));
+            }
+            mHandler.removeCallbacks(this);
+        }
+
+        public void post(
+                int state, long delay, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            mTargetState = state;
+            mEvent = event;
+            mRawEvent = rawEvent;
+            mPolicyFlags = policyFlags;
+            mHandler.postDelayed(this, delay);
+            if (DEBUG) {
+                Slog.d(
+                        LOG_TAG,
+                        getGestureName()
+                                + ": posting delayed transition to "
+                                + getStateSymbolicName(mTargetState));
+            }
+        }
+
+        public boolean isPending() {
+            return mHandler.hasCallbacks(this);
+        }
+
+        public void forceSendAndRemove() {
+            if (isPending()) {
+                run();
+                cancel();
+            }
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG) {
+                Slog.d(
+                        LOG_TAG,
+                        getGestureName()
+                                + ": executing delayed transition to "
+                                + getStateSymbolicName(mTargetState));
+            }
+            setState(mTargetState, mEvent, mRawEvent, mPolicyFlags);
+        }
+    }
+
+    /** Interface to allow a class to listen for state changes in a specific gesture matcher */
+    interface StateChangeListener {
+
+        void onStateChanged(
+                int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags);
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
new file mode 100644
index 0000000..2891c6c
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -0,0 +1,145 @@
+/*
+ * 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.accessibility.gestures;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class matches multi-tap gestures. The number of taps for each instance is specified in the
+ * constructor.
+ */
+class MultiTap extends GestureMatcher {
+
+    // Maximum reasonable number of taps.
+    public static final int MAX_TAPS = 10;
+    final int mTargetTaps;
+    // The acceptable distance between two taps
+    int mDoubleTapSlop;
+    // The acceptable distance the pointer can move and still count as a tap.
+    int mTouchSlop;
+    int mTapTimeout;
+    int mDoubleTapTimeout;
+    int mCurrentTaps;
+    float mBaseX;
+    float mBaseY;
+
+    MultiTap(Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+        super(gesture, new Handler(context.getMainLooper()), listener);
+        mTargetTaps = taps;
+        mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mTapTimeout = ViewConfiguration.getTapTimeout();
+        mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+        clear();
+    }
+
+    @Override
+    protected void clear() {
+        mCurrentTaps = 0;
+        mBaseX = Float.NaN;
+        mBaseY = Float.NaN;
+        super.clear();
+    }
+
+    @Override
+    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfterTapTimeout(event, rawEvent, policyFlags);
+        if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+            mBaseX = event.getX();
+            mBaseY = event.getY();
+        }
+        if (!isInsideSlop(rawEvent, mDoubleTapSlop)) {
+            cancelGesture(event, rawEvent, policyFlags);
+        }
+        mBaseX = event.getX();
+        mBaseY = event.getY();
+    }
+
+    @Override
+    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+        if (!isInsideSlop(rawEvent, mTouchSlop)) {
+            cancelGesture(event, rawEvent, policyFlags);
+        }
+        if (getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) {
+            mCurrentTaps++;
+            if (mCurrentTaps == mTargetTaps) {
+                // Done.
+                completeAfterTapTimeout(event, rawEvent, policyFlags);
+                return;
+            }
+            // Needs more taps.
+            cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+        } else {
+            // Either too many taps or nonsensical event stream.
+            cancelGesture(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (!isInsideSlop(rawEvent, mTouchSlop)) {
+            cancelGesture(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelGesture(event, rawEvent, policyFlags);
+    }
+
+    @Override
+    protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelGesture(event, rawEvent, policyFlags);
+    }
+
+    @Override
+    public String getGestureName() {
+        switch (mTargetTaps) {
+            case 2:
+                return "Double Tap";
+            case 3:
+                return "Triple Tap";
+            default:
+                return Integer.toString(mTargetTaps) + " Taps";
+        }
+    }
+
+    private boolean isInsideSlop(MotionEvent rawEvent, int slop) {
+        final float deltaX = mBaseX - rawEvent.getX();
+        final float deltaY = mBaseY - rawEvent.getY();
+        if (deltaX == 0 && deltaY == 0) {
+            return true;
+        }
+        final double moveDelta = Math.hypot(deltaX, deltaY);
+        return moveDelta <= slop;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString()
+                + ", Taps:"
+                + mCurrentTaps
+                + ", mBaseX: "
+                + Float.toString(mBaseX)
+                + ", mBaseY: "
+                + Float.toString(mBaseY);
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
new file mode 100644
index 0000000..6a1f1a5
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
@@ -0,0 +1,51 @@
+/*
+ * 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.accessibility.gestures;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * This class matches gestures of the form multi-tap and hold. The number of taps for each instance
+ * is specified in the constructor.
+ */
+class MultiTapAndHold extends MultiTap {
+    MultiTapAndHold(
+            Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+        super(context, taps, gesture, listener);
+    }
+
+    @Override
+    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        super.onDown(event, rawEvent, policyFlags);
+        if (mCurrentTaps + 1 == mTargetTaps) {
+            completeAfterLongPressTimeout(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    public String getGestureName() {
+        switch (mTargetTaps) {
+            case 2:
+                return "Double Tap and Hold";
+            case 3:
+                return "Triple Tap and Hold";
+            default:
+                return Integer.toString(mTargetTaps) + " Taps and Hold";
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
new file mode 100644
index 0000000..b246c67
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
@@ -0,0 +1,430 @@
+/*
+ * 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.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.content.Context;
+import android.gesture.GesturePoint;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+
+/**
+ * This class is responsible for matching one-finger swipe gestures. Each instance matches one swipe
+ * gesture. A swipe is specified as a series of one or more directions e.g. left, left and up, etc.
+ * At this time swipes with more than two directions are not supported.
+ */
+class Swipe extends GestureMatcher {
+
+    // Direction constants.
+    public static final int LEFT = 0;
+    public static final int RIGHT = 1;
+    public static final int UP = 2;
+    public static final int DOWN = 3;
+    // This is the calculated movement threshold used track if the user is still
+    // moving their finger.
+    private final float mGestureDetectionThreshold;
+
+    // Buffer for storing points for gesture detection.
+    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+    // The minimal delta between moves to add a gesture point.
+    private static final int TOUCH_TOLERANCE_PIX = 3;
+
+    // The minimal score for accepting a predicted gesture.
+    private static final float MIN_PREDICTION_SCORE = 2.0f;
+
+    // Distance a finger must travel before we decide if it is a gesture or not.
+    private static final int GESTURE_CONFIRM_CM = 1;
+
+    // Time threshold used to determine if an interaction is a gesture or not.
+    // If the first movement of 1cm takes longer than this value, we assume it's
+    // a slow movement, and therefore not a gesture.
+    //
+    // This value was determined by measuring the time for the first 1cm
+    // movement when gesturing, and touch exploring.  Based on user testing,
+    // all gestures started with the initial movement taking less than 100ms.
+    // When touch exploring, the first movement almost always takes longer than
+    // 200ms.
+    private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
+
+    // Time threshold used to determine if a gesture should be cancelled.  If
+    // the finger takes more than this time to move 1cm, the ongoing gesture is
+    // cancelled.
+    private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
+
+    private int[] mDirections;
+    private float mBaseX;
+    private float mBaseY;
+    private long mBaseTime;
+    private float mPreviousGestureX;
+    private float mPreviousGestureY;
+    // Constants for sampling motion event points.
+    // We sample based on a minimum distance between points, primarily to improve accuracy by
+    // reducing noisy minor changes in direction.
+    private static final float MIN_CM_BETWEEN_SAMPLES = 0.25f;
+    private final float mMinPixelsBetweenSamplesX;
+    private final float mMinPixelsBetweenSamplesY;
+
+    // Constants for separating gesture segments
+    private static final float ANGLE_THRESHOLD = 0.0f;
+
+    Swipe(
+            Context context,
+            int direction,
+            int gesture,
+            GestureMatcher.StateChangeListener listener) {
+        this(context, new int[] {direction}, gesture, listener);
+    }
+
+    Swipe(
+            Context context,
+            int direction1,
+            int direction2,
+            int gesture,
+            GestureMatcher.StateChangeListener listener) {
+        this(context, new int[] {direction1, direction2}, gesture, listener);
+    }
+
+    private Swipe(
+            Context context,
+            int[] directions,
+            int gesture,
+            GestureMatcher.StateChangeListener listener) {
+        super(gesture, new Handler(context.getMainLooper()), listener);
+        mDirections = directions;
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        mGestureDetectionThreshold =
+                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10, displayMetrics)
+                        * GESTURE_CONFIRM_CM;
+        // Calculate minimum gesture velocity
+        final float pixelsPerCmX = displayMetrics.xdpi / 2.54f;
+        final float pixelsPerCmY = displayMetrics.ydpi / 2.54f;
+        mMinPixelsBetweenSamplesX = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmX;
+        mMinPixelsBetweenSamplesY = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmY;
+        clear();
+    }
+
+    @Override
+    protected void clear() {
+        mBaseX = Float.NaN;
+        mBaseY = Float.NaN;
+        mBaseTime = 0;
+        mStrokeBuffer.clear();
+        super.clear();
+    }
+
+    @Override
+    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfterDelay(event, rawEvent, policyFlags);
+        if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+            mBaseX = rawEvent.getX();
+            mBaseY = rawEvent.getY();
+            mBaseTime = event.getEventTime();
+            mPreviousGestureX = mBaseX;
+            mPreviousGestureY = mBaseY;
+        }
+        // Otherwise do nothing because this event doesn't make sense in the middle of a gesture.
+    }
+
+    @Override
+    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        final float x = rawEvent.getX();
+        final float y = rawEvent.getY();
+        final long time = event.getEventTime();
+        final float dX = Math.abs(x - mPreviousGestureX);
+        final float dY = Math.abs(y - mPreviousGestureY);
+        final long timeDelta = time - mBaseTime;
+        final double moveDelta = Math.hypot(Math.abs(x - mBaseX), Math.abs(y - mBaseY));
+        if (DEBUG) {
+            Slog.d(
+                    getGestureName(),
+                    "moveDelta:"
+                            + Double.toString(moveDelta)
+                            + " mGestureDetectionThreshold: "
+                            + Float.toString(mGestureDetectionThreshold));
+        }
+        if (getState() == STATE_CLEAR) {
+            if (mStrokeBuffer.size() == 0) {
+                // First, make sure the pointer is going in the right direction.
+                cancelAfterDelay(event, rawEvent, policyFlags);
+                int direction = toDirection(x - mBaseX, y - mBaseY);
+                if (direction != mDirections[0]) {
+                    cancelGesture(event, rawEvent, policyFlags);
+                    return;
+                } else {
+                    // This is confirmed to be some kind of swipe so start tracking points.
+                    mStrokeBuffer.add(new GesturePoint(mBaseX, mBaseY, mBaseTime));
+                }
+            }
+            if (moveDelta > mGestureDetectionThreshold) {
+                // If the pointer has moved more than the threshold,
+                // update the stored values.
+                mBaseX = x;
+                mBaseY = y;
+                mBaseTime = time;
+                if (getState() == STATE_CLEAR) {
+                    startGesture(event, rawEvent, policyFlags);
+                    cancelAfterDelay(event, rawEvent, policyFlags);
+                }
+            }
+        }
+        if (getState() == STATE_GESTURE_STARTED) {
+            if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+                mPreviousGestureX = x;
+                mPreviousGestureY = y;
+                mStrokeBuffer.add(new GesturePoint(x, y, time));
+                cancelAfterDelay(event, rawEvent, policyFlags);
+            }
+        }
+    }
+
+    @Override
+    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (getState() != STATE_GESTURE_STARTED) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+
+        final float x = rawEvent.getX();
+        final float y = rawEvent.getY();
+        final long time = event.getEventTime();
+        final float dX = Math.abs(x - mPreviousGestureX);
+        final float dY = Math.abs(y - mPreviousGestureY);
+        if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+            mStrokeBuffer.add(new GesturePoint(x, y, time));
+        }
+        recognizeGesture(event, rawEvent, policyFlags);
+    }
+
+    @Override
+    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelGesture(event, rawEvent, policyFlags);
+    }
+
+    @Override
+    protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelGesture(event, rawEvent, policyFlags);
+    }
+
+    /**
+     * queues a transition to STATE_GESTURE_CANCEL based on the current state. If we have
+     * transitioned to STATE_GESTURE_STARTED the delay is longer.
+     */
+    private void cancelAfterDelay(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelPendingTransitions();
+        switch (getState()) {
+            case STATE_CLEAR:
+                cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS, event, rawEvent, policyFlags);
+                break;
+            case STATE_GESTURE_STARTED:
+                cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS, event, rawEvent, policyFlags);
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
+     * Listener callbacks for success or failure.
+     *
+     * @param event The raw motion event to pass to the listener callbacks.
+     * @param policyFlags Policy flags for the event.
+     * @return true if the event is consumed, else false
+     */
+    private void recognizeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mStrokeBuffer.size() < 2) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+
+        // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
+        // direction change.
+        // Method: for each sampled motion event, check the angle of the most recent motion vector
+        // versus the preceding motion vector, and segment the line if the angle is about
+        // 90 degrees.
+
+        ArrayList<PointF> path = new ArrayList<>();
+        PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
+        path.add(lastDelimiter);
+
+        float dX = 0; // Sum of unit vectors from last delimiter to each following point
+        float dY = 0;
+        int count = 0; // Number of points since last delimiter
+        float length = 0; // Vector length from delimiter to most recent point
+
+        PointF next = new PointF();
+        for (int i = 1; i < mStrokeBuffer.size(); ++i) {
+            next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
+            if (count > 0) {
+                // Average of unit vectors from delimiter to following points
+                float currentDX = dX / count;
+                float currentDY = dY / count;
+
+                // newDelimiter is a possible new delimiter, based on a vector with length from
+                // the last delimiter to the previous point, but in the direction of the average
+                // unit vector from delimiter to previous points.
+                // Using the averaged vector has the effect of "squaring off the curve",
+                // creating a sharper angle between the last motion and the preceding motion from
+                // the delimiter. In turn, this sharper angle achieves the splitting threshold
+                // even in a gentle curve.
+                PointF newDelimiter =
+                        new PointF(
+                                length * currentDX + lastDelimiter.x,
+                                length * currentDY + lastDelimiter.y);
+
+                // Unit vector from newDelimiter to the most recent point
+                float nextDX = next.x - newDelimiter.x;
+                float nextDY = next.y - newDelimiter.y;
+                float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
+                nextDX = nextDX / nextLength;
+                nextDY = nextDY / nextLength;
+
+                // Compare the initial motion direction to the most recent motion direction,
+                // and segment the line if direction has changed by about 90 degrees.
+                float dot = currentDX * nextDX + currentDY * nextDY;
+                if (dot < ANGLE_THRESHOLD) {
+                    path.add(newDelimiter);
+                    lastDelimiter = newDelimiter;
+                    dX = 0;
+                    dY = 0;
+                    count = 0;
+                }
+            }
+
+            // Vector from last delimiter to most recent point
+            float currentDX = next.x - lastDelimiter.x;
+            float currentDY = next.y - lastDelimiter.y;
+            length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
+
+            // Increment sum of unit vectors from delimiter to each following point
+            count = count + 1;
+            dX = dX + currentDX / length;
+            dY = dY + currentDY / length;
+        }
+
+        path.add(next);
+        if (DEBUG) {
+            Slog.d(getGestureName(), "path=" + path.toString());
+        }
+        // Classify line segments, and call Listener callbacks.
+        recognizeGesturePath(event, rawEvent, policyFlags, path);
+    }
+
+    /**
+     * Classifies a pair of line segments, by direction. Calls Listener callbacks for success or
+     * failure.
+     *
+     * @param event The raw motion event to pass to the listener's onGestureCanceled method.
+     * @param policyFlags Policy flags for the event.
+     * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
+     * @return true if the event is consumed, else false
+     */
+    private void recognizeGesturePath(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags, ArrayList<PointF> path) {
+
+        final int displayId = event.getDisplayId();
+        if (path.size() != mDirections.length + 1) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        for (int i = 0; i < path.size() - 1; ++i) {
+            PointF start = path.get(i);
+            PointF end = path.get(i + 1);
+
+            float dX = end.x - start.x;
+            float dY = end.y - start.y;
+            int direction = toDirection(dX, dY);
+            if (direction != mDirections[i]) {
+                if (DEBUG) {
+                    Slog.d(
+                            getGestureName(),
+                            "Found direction "
+                                    + directionToString(direction)
+                                    + " when expecting "
+                                    + directionToString(mDirections[i]));
+                }
+                cancelGesture(event, rawEvent, policyFlags);
+                return;
+            }
+        }
+        if (DEBUG) {
+            Slog.d(getGestureName(), "Completed.");
+        }
+        completeGesture(event, rawEvent, policyFlags);
+    }
+
+    private static int toDirection(float dX, float dY) {
+        if (Math.abs(dX) > Math.abs(dY)) {
+            // Horizontal
+            return (dX < 0) ? LEFT : RIGHT;
+        } else {
+            // Vertical
+            return (dY < 0) ? UP : DOWN;
+        }
+    }
+
+    public static String directionToString(int direction) {
+        switch (direction) {
+            case LEFT:
+                return "left";
+            case RIGHT:
+                return "right";
+            case UP:
+                return "up";
+            case DOWN:
+                return "down";
+            default:
+                return "Unknown Direction";
+        }
+    }
+
+    @Override
+    String getGestureName() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Swipe ").append(directionToString(mDirections[0]));
+        for (int i = 1; i < mDirections.length; ++i) {
+            builder.append(" and ").append(directionToString(mDirections[i]));
+        }
+        return builder.toString();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder(super.toString());
+        if (getState() != STATE_GESTURE_CANCELED) {
+            builder.append(", mBaseX: ")
+                    .append(mBaseX)
+                    .append(", mBaseY: ")
+                    .append(mBaseY)
+                    .append(", mGestureDetectionThreshold:")
+                    .append(mGestureDetectionThreshold)
+                    .append(", mMinPixelsBetweenSamplesX:")
+                    .append(mMinPixelsBetweenSamplesX)
+                    .append(", mMinPixelsBetweenSamplesY:")
+                    .append(mMinPixelsBetweenSamplesY);
+        }
+        return builder.toString();
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index b62e260..5f41638 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -59,7 +59,7 @@
  * @hide
  */
 public class TouchExplorer extends BaseEventStreamTransformation
-        implements AccessibilityGestureDetector.Listener {
+        implements GestureManifold.Listener {
 
     static final boolean DEBUG = false;
 
@@ -104,7 +104,7 @@
     private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;
 
     // Helper to detect gestures.
-    private final AccessibilityGestureDetector mGestureDetector;
+    private final GestureManifold  mGestureDetector;
 
     // Helper class to track received pointers.
     private final TouchState.ReceivedPointerTracker mReceivedPointerTracker;
@@ -142,7 +142,7 @@
      *                one created in place, or for testing purpose.
      */
     public TouchExplorer(Context context, AccessibilityManagerService service,
-            AccessibilityGestureDetector detector) {
+            GestureManifold detector) {
         mContext = context;
         mAms = service;
         mState = new TouchState();
@@ -161,7 +161,7 @@
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
                 mDetermineUserIntentTimeout);
         if (detector == null) {
-            mGestureDetector = new AccessibilityGestureDetector(context, this);
+            mGestureDetector = new GestureManifold(context, this, mState);
         } else {
             mGestureDetector = detector;
         }
@@ -285,7 +285,7 @@
     }
 
     @Override
-    public void onDoubleTapAndHold(MotionEvent event, int policyFlags) {
+    public void onDoubleTapAndHold() {
         // Ignore the event if we aren't touch interacting.
         if (!mState.isTouchInteracting()) {
             return;
@@ -303,7 +303,7 @@
     }
 
     @Override
-    public boolean onDoubleTap(MotionEvent event, int policyFlags) {
+    public boolean onDoubleTap() {
         if (!mState.isTouchInteracting()) {
             return false;
         }
@@ -319,7 +319,7 @@
 
         // Announce the end of a new touch interaction.
         mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
-
+        mSendTouchInteractionEndDelayed.cancel();
         // Try to use the standard accessibility API to click
         if (!mAms.performActionOnAccessibilityFocusedItem(
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
@@ -356,7 +356,7 @@
     }
 
     @Override
-    public boolean onGestureCancelled(MotionEvent event, int policyFlags) {
+    public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (mState.isGestureDetecting()) {
             endGestureDetection(event.getActionMasked() == MotionEvent.ACTION_UP);
             return true;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index f463260..d23dbbe 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -71,7 +71,10 @@
     // Helper class to track received pointers.
     // Todo: collapse or hide this class so multiple classes don't modify it.
     private final ReceivedPointerTracker mReceivedPointerTracker;
+    // The most recently received motion event.
     private MotionEvent mLastReceivedEvent;
+    // The accompanying raw event without any transformations.
+    private MotionEvent mLastReceivedRawEvent;
 
     public TouchState() {
         mReceivedPointerTracker = new ReceivedPointerTracker();
@@ -97,6 +100,9 @@
         if (mLastReceivedEvent != null) {
             mLastReceivedEvent.recycle();
         }
+        if (mLastReceivedRawEvent != null) {
+            mLastReceivedRawEvent.recycle();
+        }
         mLastReceivedEvent = MotionEvent.obtain(rawEvent);
         mReceivedPointerTracker.onMotionEvent(rawEvent);
     }
@@ -246,7 +252,6 @@
         // or if it goes up the next one that most recently went down.
         private int mPrimaryPointerId;
 
-
         ReceivedPointerTracker() {
             clear();
         }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4a0c7a3..601d314 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -31,7 +31,6 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -260,7 +259,9 @@
 
     private final LocalLog mListenLog = new LocalLog(100);
 
-    private PreciseDataConnectionState[] mPreciseDataConnectionState;
+    // Per-phoneMap of APN Type to DataConnectionState
+    private List<Map<String, PreciseDataConnectionState>> mPreciseDataConnectionStates =
+            new ArrayList<Map<String, PreciseDataConnectionState>>();
 
     // Nothing here yet, but putting it here in case we want to add more in the future.
     static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0;
@@ -415,7 +416,6 @@
         mCallQuality = copyOf(mCallQuality, mNumPhones);
         mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
         mCallAttributes = copyOf(mCallAttributes, mNumPhones);
-        mPreciseDataConnectionState = copyOf(mPreciseDataConnectionState, mNumPhones);
         mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
         mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
 
@@ -423,6 +423,7 @@
         if (mNumPhones < oldNumPhones) {
             cutListToSize(mCellInfo, mNumPhones);
             cutListToSize(mImsReasonInfo, mNumPhones);
+            cutListToSize(mPreciseDataConnectionStates, mNumPhones);
             return;
         }
 
@@ -454,7 +455,7 @@
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
-            mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
+            mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
         }
 
         // Note that location can be null for non-phone builds like
@@ -516,7 +517,7 @@
         mCallQuality = new CallQuality[numPhones];
         mCallNetworkType = new int[numPhones];
         mCallAttributes = new CallAttributes[numPhones];
-        mPreciseDataConnectionState = new PreciseDataConnectionState[numPhones];
+        mPreciseDataConnectionStates = new ArrayList<>();
         mCellInfo = new ArrayList<>();
         mImsReasonInfo = new ArrayList<>();
         mEmergencyNumberList = new HashMap<>();
@@ -549,7 +550,7 @@
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
-            mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
+            mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
         }
 
         // Note that location can be null for non-phone builds like
@@ -922,8 +923,10 @@
                     }
                     if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
                         try {
-                            r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionState[phoneId]);
+                            for (PreciseDataConnectionState pdcs
+                                    : mPreciseDataConnectionStates.get(phoneId).values()) {
+                                r.callback.onPreciseDataConnectionStateChanged(pdcs);
+                            }
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
@@ -937,7 +940,8 @@
                     }
                     if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) !=0) {
                         try {
-                            r.callback.onVoiceActivationStateChanged(mVoiceActivationState[phoneId]);
+                            r.callback.onVoiceActivationStateChanged(
+                                    mVoiceActivationState[phoneId]);
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
@@ -1496,20 +1500,38 @@
         }
     }
 
-    public void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
-                                                  boolean isDataAllowed,
-                                                  String apn, String apnType,
-            LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
-            int networkType, boolean roaming) {
+    /**
+     * Send a notification to registrants that the data connection state has changed.
+     *
+     * @param phoneId the phoneId carrying the data connection
+     * @param subId the subscriptionId for the data connection
+     * @param apnType the APN type that triggered a change in the data connection
+     * @param preciseState a PreciseDataConnectionState that has info about the data connection
+     */
+    public void notifyDataConnectionForSubscriber(
+            int phoneId, int subId, String apnType, PreciseDataConnectionState preciseState) {
         if (!checkNotifyPermission("notifyDataConnection()" )) {
             return;
         }
+
+        String apn = "";
+        int state = TelephonyManager.DATA_UNKNOWN;
+        int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        LinkProperties linkProps = null;
+
+        if (preciseState != null) {
+            apn = preciseState.getDataConnectionApn();
+            state = preciseState.getState();
+            networkType = preciseState.getNetworkType();
+            linkProps = preciseState.getDataConnectionLinkProperties();
+        }
         if (VDBG) {
             log("notifyDataConnectionForSubscriber: subId=" + subId
-                + " state=" + state + " isDataAllowed=" + isDataAllowed
-                + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
-                + " mRecords.size()=" + mRecords.size());
+                    + " state=" + state + "' apn='" + apn
+                    + "' apnType=" + apnType + " networkType=" + networkType
+                    + "' preciseState=" + preciseState);
         }
+
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 // We only call the callback when the change is for default APN type.
@@ -1541,30 +1563,48 @@
                     mDataConnectionState[phoneId] = state;
                     mDataConnectionNetworkType[phoneId] = networkType;
                 }
-                mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
-                        state, networkType,
-                        ApnSetting.getApnTypesBitmaskFromString(apnType), apn,
-                        linkProperties, DataFailCause.NONE);
-                for (Record r : mRecords) {
-                    if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
-                            && idMatch(r.subId, subId, phoneId)) {
-                        try {
-                            r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionState[phoneId]);
-                        } catch (RemoteException ex) {
-                            mRemoveList.add(r.binder);
+
+                boolean needsNotify = false;
+                // State has been cleared for this APN Type
+                if (preciseState == null) {
+                    // We try clear the state and check if the state was previously not cleared
+                    needsNotify = mPreciseDataConnectionStates.get(phoneId).remove(apnType) != null;
+                } else {
+                    // We need to check to see if the state actually changed
+                    PreciseDataConnectionState oldPreciseState =
+                            mPreciseDataConnectionStates.get(phoneId).put(apnType, preciseState);
+                    needsNotify = !preciseState.equals(oldPreciseState);
+                }
+
+                if (needsNotify) {
+                    for (Record r : mRecords) {
+                        if (r.matchPhoneStateListenerEvent(
+                                PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
+                                && idMatch(r.subId, subId, phoneId)) {
+                            try {
+                                r.callback.onPreciseDataConnectionStateChanged(preciseState);
+                            } catch (RemoteException ex) {
+                                mRemoveList.add(r.binder);
+                            }
                         }
                     }
                 }
             }
             handleRemoveListLocked();
         }
-        broadcastDataConnectionStateChanged(state, isDataAllowed, apn, apnType, linkProperties,
-                networkCapabilities, roaming, subId);
+
+        broadcastDataConnectionStateChanged(state, apn, apnType, subId);
     }
 
-    public void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
+    /**
+     * Stub to satisfy the ITelephonyRegistry aidl interface; do not use this function.
+     * @see #notifyDataConnectionFailedForSubscriber
+     */
+    public void notifyDataConnectionFailed(String apnType) {
+        loge("This function should not be invoked");
+    }
+
+    private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
         if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
             return;
         }
@@ -1574,17 +1614,20 @@
         }
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
-                mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
-                        TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                        ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
-                        DataFailCause.NONE);
+                mPreciseDataConnectionStates.get(phoneId).put(
+                        apnType,
+                        new PreciseDataConnectionState(
+                                TelephonyManager.DATA_UNKNOWN,
+                                TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                                ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+                                DataFailCause.NONE));
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
                             PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionState[phoneId]);
+                                    mPreciseDataConnectionStates.get(phoneId).get(apnType));
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
@@ -1772,25 +1815,32 @@
         if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
             return;
         }
+
+        // precise notify invokes imprecise notify
+        notifyDataConnectionFailedForSubscriber(phoneId, subId, apnType);
+
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
-                mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
-                        TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                        ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
+                mPreciseDataConnectionStates.get(phoneId).put(
+                        apnType,
+                        new PreciseDataConnectionState(
+                                TelephonyManager.DATA_UNKNOWN,
+                                TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                                ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+                                failCause));
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
                             PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionState[phoneId]);
+                                    mPreciseDataConnectionStates.get(phoneId).get(apnType));
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
                     }
                 }
             }
-
             handleRemoveListLocked();
         }
     }
@@ -2048,7 +2098,6 @@
         }
     }
 
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -2087,7 +2136,7 @@
                 pw.println("mCallQuality=" + mCallQuality[i]);
                 pw.println("mCallAttributes=" + mCallAttributes[i]);
                 pw.println("mCallNetworkType=" + mCallNetworkType[i]);
-                pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState[i]);
+                pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
                 pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
                 pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
                 pw.decreaseIndent();
@@ -2247,29 +2296,13 @@
         }
     }
 
-    private void broadcastDataConnectionStateChanged(int state, boolean isDataAllowed, String apn,
-                                                     String apnType, LinkProperties linkProperties,
-                                                     NetworkCapabilities networkCapabilities,
-                                                     boolean roaming, int subId) {
+    private void broadcastDataConnectionStateChanged(int state, String apn,
+                                                     String apnType, int subId) {
         // Note: not reporting to the battery stats service here, because the
         // status bar takes care of that after taking into account all of the
         // required info.
         Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
         intent.putExtra(TelephonyManager.EXTRA_STATE, dataStateToString(state));
-        if (!isDataAllowed) {
-            intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
-        }
-        if (linkProperties != null) {
-            intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
-            String iface = linkProperties.getInterfaceName();
-            if (iface != null) {
-                intent.putExtra(PhoneConstants.DATA_IFACE_NAME_KEY, iface);
-            }
-        }
-        if (networkCapabilities != null) {
-            intent.putExtra(PhoneConstants.DATA_NETWORK_CAPABILITIES_KEY, networkCapabilities);
-        }
-        if (roaming) intent.putExtra(PhoneConstants.DATA_NETWORK_ROAMING_KEY, true);
 
         intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
         intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e101fe0..5996b7d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4409,7 +4409,6 @@
         return true;
     }
 
-    @Override
     public boolean renameSharedAccountAsUser(Account account, String newName, int userId) {
         userId = handleIncomingUser(userId);
         UserAccounts accounts = getUserAccounts(userId);
@@ -4425,7 +4424,6 @@
         return r > 0;
     }
 
-    @Override
     public boolean removeSharedAccountAsUser(Account account, int userId) {
         return removeSharedAccountAsUser(account, userId, getCallingUid());
     }
@@ -4443,7 +4441,6 @@
         return deleted;
     }
 
-    @Override
     public Account[] getSharedAccountsAsUser(int userId) {
         userId = handleIncomingUser(userId);
         UserAccounts accounts = getUserAccounts(userId);
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 232bc08e..fc67e24 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -77,21 +77,28 @@
     private static final String LOG_TAG = "AttentionManagerService";
     private static final boolean DEBUG = false;
 
-    /** Default value in absence of {@link DeviceConfig} override. */
-    private static final boolean DEFAULT_SERVICE_ENABLED = true;
-
     /** Service will unbind if connection is not used for that amount of time. */
     private static final long CONNECTION_TTL_MILLIS = 60_000;
 
-    /** If the check attention called within that period - cached value will be returned. */
-    private static final long STALE_AFTER_MILLIS = 5_000;
+    /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
+    private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    private static final boolean DEFAULT_SERVICE_ENABLED = true;
+
+    /**
+     * DeviceConfig flag name, describes how much time we consider a result fresh; if the check
+     * attention called within that period - cached value will be returned.
+     */
+    @VisibleForTesting static final String KEY_STALE_AFTER_MILLIS = "stale_after_millis";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    @VisibleForTesting static final long DEFAULT_STALE_AFTER_MILLIS = 1_000;
 
     /** The size of the buffer that stores recent attention check results. */
     @VisibleForTesting
     protected static final int ATTENTION_CACHE_BUFFER_SIZE = 5;
 
-    /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
-    private static final String SERVICE_ENABLED = "service_enabled";
     private static String sTestAttentionServicePackage;
     private final Context mContext;
     private final PowerManager mPowerManager;
@@ -160,11 +167,29 @@
 
     @VisibleForTesting
     protected boolean isServiceEnabled() {
-        return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, SERVICE_ENABLED,
+        return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_SERVICE_ENABLED,
                 DEFAULT_SERVICE_ENABLED);
     }
 
     /**
+     * How much time we consider a result fresh; if the check attention called within that period -
+     * cached value will be returned.
+     */
+    @VisibleForTesting
+    protected long getStaleAfterMillis() {
+        final long millis = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS,
+                DEFAULT_STALE_AFTER_MILLIS);
+
+        if (millis < 0 || millis > 10_000) {
+            Slog.w(LOG_TAG, "Bad flag value supplied for: " + KEY_STALE_AFTER_MILLIS);
+            return DEFAULT_STALE_AFTER_MILLIS;
+        }
+
+        return millis;
+    }
+
+    /**
      * Checks whether user attention is at the screen and calls in the provided callback.
      *
      * Calling this multiple times quickly in a row will result in either a) returning a cached
@@ -199,7 +224,7 @@
             // throttle frequent requests
             final AttentionCheckCache cache = userState.mAttentionCheckCacheBuffer == null ? null
                     : userState.mAttentionCheckCacheBuffer.getLast();
-            if (cache != null && now < cache.mLastComputed + STALE_AFTER_MILLIS) {
+            if (cache != null && now < cache.mLastComputed + getStaleAfterMillis()) {
                 callbackInternal.onSuccess(cache.mResult, cache.mTimestamp);
                 return true;
             }
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 7098435..a7e40cb 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -59,6 +59,12 @@
             return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
         }
 
+        // If a score has been assigned by notification assistant service, use this service
+        // rank results within each bucket instead of this comparator implementation.
+        if (left.getRankingScore() != right.getRankingScore()) {
+            return -1 * Float.compare(left.getRankingScore(), right.getRankingScore());
+        }
+
         // first all colorized notifications
         boolean leftImportantColorized = isImportantColorized(left);
         boolean rightImportantColorized = isImportantColorized(right);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e968fb70..c8afcc9 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -144,6 +144,7 @@
     private int mSystemImportance = IMPORTANCE_UNSPECIFIED;
     private int mAssistantImportance = IMPORTANCE_UNSPECIFIED;
     private int mImportance = IMPORTANCE_UNSPECIFIED;
+    private float mRankingScore = 0f;
     // Field used in global sort key to bypass normal notifications
     private int mCriticality = CriticalNotificationExtractor.NORMAL;
     // A MetricsEvent.NotificationImportanceExplanation, tracking source of mImportance.
@@ -655,6 +656,9 @@
                     importance = Math.min(IMPORTANCE_HIGH, importance);
                     setAssistantImportance(importance);
                 }
+                if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
+                    mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
+                }
                 if (!signals.isEmpty() && adjustment.getIssuer() != null) {
                     mAdjustmentIssuer = adjustment.getIssuer();
                 }
@@ -772,6 +776,10 @@
         return mImportance;
     }
 
+    public float getRankingScore() {
+        return mRankingScore;
+    }
+
     public CharSequence getImportanceExplanation() {
         switch (mImportanceExplanationCode) {
             case MetricsEvent.IMPORTANCE_EXPLANATION_UNKNOWN:
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index e055116..ac3bf9a 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -26,6 +26,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -44,6 +45,38 @@
 
     private final VerifyCallback mVerifyCallback;
 
+    /**
+     * @return nullable actor result with {@link ActorState} failure status
+     */
+    static Pair<String, ActorState> getPackageNameForActor(String actorUriString,
+            Map<String, Map<String, String>> namedActors) {
+        if (namedActors.isEmpty()) {
+            return Pair.create(null, ActorState.NO_NAMED_ACTORS);
+        }
+
+        Uri actorUri = Uri.parse(actorUriString);
+
+        String actorScheme = actorUri.getScheme();
+        List<String> actorPathSegments = actorUri.getPathSegments();
+        if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
+            return Pair.create(null, ActorState.INVALID_OVERLAYABLE_ACTOR_NAME);
+        }
+
+        String actorNamespace = actorUri.getAuthority();
+        Map<String, String> namespace = namedActors.get(actorNamespace);
+        if (namespace == null) {
+            return Pair.create(null, ActorState.MISSING_NAMESPACE);
+        }
+
+        String actorName = actorPathSegments.get(0);
+        String packageName = namespace.get(actorName);
+        if (TextUtils.isEmpty(packageName)) {
+            return Pair.create(null, ActorState.MISSING_ACTOR_NAME);
+        }
+
+        return Pair.create(packageName, ActorState.ALLOWED);
+    }
+
     public OverlayActorEnforcer(@NonNull VerifyCallback verifyCallback) {
         mVerifyCallback = verifyCallback;
     }
@@ -141,31 +174,14 @@
             }
         }
 
-        Map<String, ? extends Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
-        if (namedActors.isEmpty()) {
-            return ActorState.NO_NAMED_ACTORS;
+        Map<String, Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
+        Pair<String, ActorState> actorUriPair = getPackageNameForActor(actor, namedActors);
+        ActorState actorUriState = actorUriPair.second;
+        if (actorUriState != ActorState.ALLOWED) {
+            return actorUriState;
         }
 
-        Uri actorUri = Uri.parse(actor);
-
-        String actorScheme = actorUri.getScheme();
-        List<String> actorPathSegments = actorUri.getPathSegments();
-        if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
-            return ActorState.INVALID_OVERLAYABLE_ACTOR_NAME;
-        }
-
-        String actorNamespace = actorUri.getAuthority();
-        Map<String, String> namespace = namedActors.get(actorNamespace);
-        if (namespace == null) {
-            return ActorState.MISSING_NAMESPACE;
-        }
-
-        String actorName = actorPathSegments.get(0);
-        String packageName = namespace.get(actorName);
-        if (TextUtils.isEmpty(packageName)) {
-            return ActorState.MISSING_ACTOR_NAME;
-        }
-
+        String packageName = actorUriPair.first;
         PackageInfo packageInfo = mVerifyCallback.getPackageInfo(packageName, userId);
         if (packageInfo == null) {
             return ActorState.MISSING_APP_INFO;
@@ -192,7 +208,7 @@
      * For easier logging/debugging, a set of all possible failure/success states when running
      * enforcement.
      */
-    private enum ActorState {
+    enum ActorState {
         ALLOWED,
         INVALID_ACTOR,
         MISSING_NAMESPACE,
@@ -244,7 +260,7 @@
          * value maps actor name to package name
          */
         @NonNull
-        Map<String, ? extends Map<String, String>> getNamedActors();
+        Map<String, Map<String, String>> getNamedActors();
 
         /**
          * @return true if the target package has declared an overlayable
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 8b69946..f1947ac 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1073,8 +1073,6 @@
             mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         }
 
-        // TODO(b/143096091): Remove PackageInfo cache so that PackageManager is always queried
-        //  to enforce visibility/other permission checks
         public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
                 final boolean useCache) {
             if (useCache) {
@@ -1097,18 +1095,12 @@
 
         @Override
         public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
-            // TODO(b/143096091): Remove clearing calling ID
-            long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                return getPackageInfo(packageName, userId, true);
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
-            }
+            return getPackageInfo(packageName, userId, true);
         }
 
         @NonNull
         @Override
-        public Map<String, ? extends Map<String, String>> getNamedActors() {
+        public Map<String, Map<String, String>> getNamedActors() {
             return SystemConfig.getInstance().getNamedActors();
         }
 
@@ -1136,57 +1128,45 @@
         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
                 @Nullable String targetOverlayableName, int userId)
                 throws IOException {
-            // TODO(b/143096091): Remove clearing calling ID
-            long callingIdentity = Binder.clearCallingIdentity();
+            PackageInfo packageInfo = getPackageInfo(packageName, userId);
+            if (packageInfo == null) {
+                throw new IOException("Unable to get target package");
+            }
+
+            String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+
+            ApkAssets apkAssets = null;
             try {
-                PackageInfo packageInfo = getPackageInfo(packageName, userId);
-                if (packageInfo == null) {
-                    throw new IOException("Unable to get target package");
-                }
-
-                String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-
-                ApkAssets apkAssets = null;
-                try {
-                    apkAssets = ApkAssets.loadFromPath(baseCodePath);
-                    return apkAssets.getOverlayableInfo(targetOverlayableName);
-                } finally {
-                    if (apkAssets != null) {
-                        try {
-                            apkAssets.close();
-                        } catch (Throwable ignored) {
-                        }
+                apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                return apkAssets.getOverlayableInfo(targetOverlayableName);
+            } finally {
+                if (apkAssets != null) {
+                    try {
+                        apkAssets.close();
+                    } catch (Throwable ignored) {
                     }
                 }
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
             }
         }
 
         @Override
         public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
                 throws RemoteException, IOException {
-            // TODO(b/143096091): Remove clearing calling ID
-            long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
-                        userId);
-                String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+            PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
+                    userId);
+            String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
 
-                ApkAssets apkAssets = null;
-                try {
-                    apkAssets = ApkAssets.loadFromPath(baseCodePath);
-                    return apkAssets.definesOverlayable();
-                } finally {
-                    if (apkAssets != null) {
-                        try {
-                            apkAssets.close();
-                        } catch (Throwable ignored) {
-                        }
+            ApkAssets apkAssets = null;
+            try {
+                apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                return apkAssets.definesOverlayable();
+            } finally {
+                if (apkAssets != null) {
+                    try {
+                        apkAssets.close();
+                    } catch (Throwable ignored) {
                     }
                 }
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
             }
         }
 
@@ -1229,16 +1209,10 @@
         @Nullable
         @Override
         public String[] getPackagesForUid(int uid) {
-            // TODO(b/143096091): Remove clearing calling ID
-            long callingIdentity = Binder.clearCallingIdentity();
             try {
-                try {
-                    return mPackageManager.getPackagesForUid(uid);
-                } catch (RemoteException ignored) {
-                    return null;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
+                return mPackageManager.getPackagesForUid(uid);
+            } catch (RemoteException ignored) {
+                return null;
             }
         }
 
diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
new file mode 100644
index 0000000..8bea119
--- /dev/null
+++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
@@ -0,0 +1,375 @@
+/*
+ * 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.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.parsing.AndroidPackage;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.PackageSetting;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Track visibility of a targets and overlays to actors.
+ *
+ * 4 cases to handle:
+ * <ol>
+ *     <li>Target adds/changes an overlayable to add a reference to an actor
+ *         <ul>
+ *             <li>Must expose target to actor</li>
+ *             <li>Must expose any overlays that pointed to that overlayable name to the actor</li>
+ *         </ul>
+ *     </li>
+ *     <li>Target removes/changes an overlayable to remove a reference to an actor
+ *         <ul>
+ *             <li>If this target has no other overlayables referencing the actor, hide the
+ *             target</li>
+ *             <li>For all overlays targeting this overlayable, if the overlay is only visible to
+ *             the actor through this overlayable, hide the overlay</li>
+ *         </ul>
+ *     </li>
+ *     <li>Overlay adds/changes an overlay tag to add a reference to an overlayable name
+ *         <ul>
+ *             <li>Expose this overlay to the actor defined by the target overlayable</li>
+ *         </ul>
+ *     </li>
+ *     <li>Overlay removes/changes an overlay tag to remove a reference to an overlayable name
+ *         <ul>
+ *             <li>If this overlay is only visible to an actor through this overlayable name's
+ *             target's actor</li>
+ *         </ul>
+ *     </li>
+ * </ol>
+ *
+ * In this class, the names "actor", "target", and "overlay" all refer to the ID representations.
+ * All other use cases are named appropriate. "actor" is actor name, "target" is target package
+ * name, and "overlay" is overlay package name.
+ */
+public class OverlayReferenceMapper {
+
+    private final Object mLock = new Object();
+
+    /**
+     * Keys are actors, values are maps which map target to a set of overlays targeting it.
+     * The presence of a target in the value map means the actor and targets are connected, even
+     * if the corresponding target's set is empty.
+     * See class comment for specific types.
+     */
+    @GuardedBy("mLock")
+    private final Map<String, Map<String, Set<String>>> mActorToTargetToOverlays = new HashMap<>();
+
+    /**
+     * Keys are actor package names, values are generic package names the actor should be able
+     * to see.
+     */
+    @GuardedBy("mLock")
+    private final Map<String, Set<String>> mActorPkgToPkgs = new HashMap<>();
+
+    @GuardedBy("mLock")
+    private boolean mDeferRebuild;
+
+    @NonNull
+    private final Provider mProvider;
+
+    /**
+     * @param deferRebuild whether or not to defer rebuild calls on add/remove until first get call;
+     *                     useful during boot when multiple packages are added in rapid succession
+     *                     and queries in-between are not expected
+     */
+    public OverlayReferenceMapper(boolean deferRebuild, @Nullable Provider provider) {
+        this.mDeferRebuild = deferRebuild;
+        this.mProvider = provider != null ? provider : new Provider() {
+            @Nullable
+            @Override
+            public String getActorPkg(String actor) {
+                Map<String, Map<String, String>> namedActors = SystemConfig.getInstance()
+                        .getNamedActors();
+
+                Pair<String, OverlayActorEnforcer.ActorState> actorPair =
+                        OverlayActorEnforcer.getPackageNameForActor(actor, namedActors);
+                return actorPair.first;
+            }
+
+            @NonNull
+            @Override
+            public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) {
+                String target = pkg.getOverlayTarget();
+                if (TextUtils.isEmpty(target)) {
+                    return Collections.emptyMap();
+                }
+
+                String overlayable = pkg.getOverlayTargetName();
+                Map<String, Set<String>> targetToOverlayables = new HashMap<>();
+                Set<String> overlayables = new HashSet<>();
+                overlayables.add(overlayable);
+                targetToOverlayables.put(target, overlayables);
+                return targetToOverlayables;
+            }
+        };
+    }
+
+    /**
+     * @return mapping of actor package to a set of packages it can view
+     */
+    @NonNull
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public Map<String, Set<String>> getActorPkgToPkgs() {
+        return mActorPkgToPkgs;
+    }
+
+    public boolean isValidActor(@NonNull String targetName, @NonNull String actorPackageName) {
+        synchronized (mLock) {
+            assertMapBuilt();
+            Set<String> validSet = mActorPkgToPkgs.get(actorPackageName);
+            return validSet != null && validSet.contains(targetName);
+        }
+    }
+
+    /**
+     * Add a package to be considered for visibility. Currently supports adding as a target and/or
+     * an overlay. Adding an actor is not supported. Those are configured as part of
+     * {@link SystemConfig#getNamedActors()}.
+     *
+     * @param pkg the package to add
+     * @param otherPkgs map of other packages to consider, excluding {@param pkg}
+     */
+    public void addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
+        synchronized (mLock) {
+            if (!pkg.getOverlayables().isEmpty()) {
+                addTarget(pkg, otherPkgs);
+            }
+
+            // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
+            if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
+                addOverlay(pkg, otherPkgs);
+            }
+
+            if (!mDeferRebuild) {
+                rebuild();
+            }
+        }
+    }
+
+    /**
+     * Removes a package to be considered for visibility. Currently supports removing as a target
+     * and/or an overlay. Removing an actor is not supported. Those are staticly configured as part
+     * of {@link SystemConfig#getNamedActors()}.
+     *
+     * @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)}
+     */
+    public void removePkg(String pkgName) {
+        synchronized (mLock) {
+            removeTarget(pkgName);
+            removeOverlay(pkgName);
+
+            if (!mDeferRebuild) {
+                rebuild();
+            }
+        }
+    }
+
+    private void removeTarget(String target) {
+        synchronized (mLock) {
+            Iterator<Map<String, Set<String>>> iterator =
+                    mActorToTargetToOverlays.values().iterator();
+            while (iterator.hasNext()) {
+                Map<String, Set<String>> next = iterator.next();
+                next.remove(target);
+                if (next.isEmpty()) {
+                    iterator.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Associate an actor with an association of a new target to overlays for that target.
+     *
+     * If a target overlays itself, it will not be associated with itself, as only one half of the
+     * relationship needs to exist for visibility purposes.
+     */
+    private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs) {
+        synchronized (mLock) {
+            String target = targetPkg.getPackageName();
+            removeTarget(target);
+
+            Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
+            for (String overlayable : overlayablesToActors.keySet()) {
+                String actor = overlayablesToActors.get(overlayable);
+                addTargetToMap(actor, target);
+
+                for (AndroidPackage overlayPkg : otherPkgs.values()) {
+                    Map<String, Set<String>> targetToOverlayables =
+                            mProvider.getTargetToOverlayables(overlayPkg);
+                    Set<String> overlayables = targetToOverlayables.get(target);
+                    if (CollectionUtils.isEmpty(overlayables)) {
+                        continue;
+                    }
+
+                    if (overlayables.contains(overlayable)) {
+                        addOverlayToMap(actor, target, overlayPkg.getPackageName());
+                    }
+                }
+            }
+        }
+    }
+
+    private void removeOverlay(String overlay) {
+        synchronized (mLock) {
+            for (Map<String, Set<String>> targetToOverlays : mActorToTargetToOverlays.values()) {
+                for (Set<String> overlays : targetToOverlays.values()) {
+                    overlays.remove(overlay);
+                }
+            }
+        }
+    }
+
+    /**
+     * Associate an actor with an association of targets to overlays for a new overlay.
+     *
+     * If an overlay targets itself, it will not be associated with itself, as only one half of the
+     * relationship needs to exist for visibility purposes.
+     */
+    private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs) {
+        synchronized (mLock) {
+            String overlay = overlayPkg.getPackageName();
+            removeOverlay(overlay);
+
+            Map<String, Set<String>> targetToOverlayables =
+                    mProvider.getTargetToOverlayables(overlayPkg);
+            for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) {
+                String target = entry.getKey();
+                Set<String> overlayables = entry.getValue();
+                AndroidPackage targetPkg = otherPkgs.get(target);
+                if (targetPkg == null) {
+                    continue;
+                }
+
+                String targetPkgName = targetPkg.getPackageName();
+                Map<String, String> overlayableToActor = targetPkg.getOverlayables();
+                for (String overlayable : overlayables) {
+                    String actor = overlayableToActor.get(overlayable);
+                    if (TextUtils.isEmpty(actor)) {
+                        continue;
+                    }
+                    addOverlayToMap(actor, targetPkgName, overlay);
+                }
+            }
+        }
+    }
+
+    public void rebuildIfDeferred() {
+        synchronized (mLock) {
+            if (mDeferRebuild) {
+                rebuild();
+                mDeferRebuild = false;
+            }
+        }
+    }
+
+    private void assertMapBuilt() {
+        if (mDeferRebuild) {
+            throw new IllegalStateException("The actor map must be built by calling "
+                    + "rebuildIfDeferred before it is queried");
+        }
+    }
+
+    private void rebuild() {
+        synchronized (mLock) {
+            mActorPkgToPkgs.clear();
+            for (String actor : mActorToTargetToOverlays.keySet()) {
+                String actorPkg = mProvider.getActorPkg(actor);
+                if (TextUtils.isEmpty(actorPkg)) {
+                    continue;
+                }
+
+                Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+                Set<String> pkgs = new HashSet<>();
+
+                for (String target : targetToOverlays.keySet()) {
+                    Set<String> overlays = targetToOverlays.get(target);
+                    pkgs.add(target);
+                    pkgs.addAll(overlays);
+                }
+
+                mActorPkgToPkgs.put(actorPkg, pkgs);
+            }
+        }
+    }
+
+    private void addTargetToMap(String actor, String target) {
+        Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+        if (targetToOverlays == null) {
+            targetToOverlays = new HashMap<>();
+            mActorToTargetToOverlays.put(actor, targetToOverlays);
+        }
+
+        Set<String> overlays = targetToOverlays.get(target);
+        if (overlays == null) {
+            overlays = new HashSet<>();
+            targetToOverlays.put(target, overlays);
+        }
+    }
+
+    private void addOverlayToMap(String actor, String target, String overlay) {
+        synchronized (mLock) {
+            Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+            if (targetToOverlays == null) {
+                targetToOverlays = new HashMap<>();
+                mActorToTargetToOverlays.put(actor, targetToOverlays);
+            }
+
+            Set<String> overlays = targetToOverlays.get(target);
+            if (overlays == null) {
+                overlays = new HashSet<>();
+                targetToOverlays.put(target, overlays);
+            }
+
+            overlays.add(overlay);
+        }
+    }
+
+    public interface Provider {
+
+        /**
+         * Given the actor string from an overlayable definition, return the actor's package name.
+         */
+        @Nullable
+        String getActorPkg(String actor);
+
+        /**
+         * Mock response of multiple overlay tags.
+         *
+         * TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests
+         */
+        @NonNull
+        Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 8374ee6..c4bcf80 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -16,8 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.content.pm.PackageParser.Component;
-import static android.content.pm.PackageParser.IntentInfo;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
 
@@ -26,7 +24,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.parsing.AndroidPackage;
 import android.content.pm.parsing.ComponentParseUtils;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
@@ -50,9 +47,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.FgThread;
+import com.android.server.om.OverlayReferenceMapper;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -109,11 +106,16 @@
 
     private final FeatureConfig mFeatureConfig;
 
+    private final OverlayReferenceMapper mOverlayReferenceMapper;
+
     AppsFilter(FeatureConfig featureConfig, String[] forceQueryableWhitelist,
-            boolean systemAppsQueryable) {
+            boolean systemAppsQueryable,
+            @Nullable OverlayReferenceMapper.Provider overlayProvider) {
         mFeatureConfig = featureConfig;
         mForceQueryableByDevicePackageNames = forceQueryableWhitelist;
         mSystemAppsQueryable = systemAppsQueryable;
+        mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/,
+                overlayProvider);
     }
 
     public interface FeatureConfig {
@@ -193,7 +195,7 @@
             }
         }
         return new AppsFilter(featureConfig, forcedQueryablePackageNames,
-                forceSystemAppsQueryable);
+                forceSystemAppsQueryable, null);
     }
 
     /** Returns true if the querying package may query for the potential target package */
@@ -282,6 +284,7 @@
 
     public void onSystemReady() {
         mFeatureConfig.onSystemReady();
+        mOverlayReferenceMapper.rebuildIfDeferred();
     }
 
     /**
@@ -338,6 +341,16 @@
                     }
                 }
             }
+
+            int existingSize = existingSettings.size();
+            ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize);
+            for (int index = 0; index < existingSize; index++) {
+                PackageSetting pkgSetting = existingSettings.valueAt(index);
+                if (pkgSetting.pkg != null) {
+                    existingPkgs.put(pkgSetting.name, pkgSetting.pkg);
+                }
+            }
+            mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -381,6 +394,8 @@
                 addPackage(setting.sharedUser.packages.valueAt(i), existingSettings);
             }
         }
+
+        mOverlayReferenceMapper.removePkg(setting.name);
     }
 
     /**
@@ -397,8 +412,7 @@
             PackageSetting targetPkgSetting, int userId) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication");
         try {
-            if (!shouldFilterApplicationInternal(callingUid, callingSetting,
-                    targetPkgSetting,
+            if (!shouldFilterApplicationInternal(callingUid, callingSetting, targetPkgSetting,
                     userId)) {
                 return false;
             }
@@ -412,8 +426,8 @@
         }
     }
 
-    private boolean shouldFilterApplicationInternal(int callingUid,
-            SettingBase callingSetting, PackageSetting targetPkgSetting, int userId) {
+    private boolean shouldFilterApplicationInternal(int callingUid, SettingBase callingSetting,
+            PackageSetting targetPkgSetting, int userId) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal");
         try {
             final boolean featureEnabled = mFeatureConfig.isGloballyEnabled();
@@ -530,6 +544,29 @@
                     }
                 }
             }
+
+            if (callingSharedPkgSettings != null) {
+                int size = callingSharedPkgSettings.size();
+                for (int index = 0; index < size; index++) {
+                    PackageSetting pkgSetting = callingSharedPkgSettings.valueAt(index);
+                    if (mOverlayReferenceMapper.isValidActor(targetName, pkgSetting.name)) {
+                        if (DEBUG_LOGGING) {
+                            log(callingPkgSetting, targetPkgSetting,
+                                    "matches shared user of package that acts on target of "
+                                            + "overlay");
+                        }
+                        return false;
+                    }
+                }
+            } else {
+                if (mOverlayReferenceMapper.isValidActor(targetName, callingPkgSetting.name)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingPkgSetting, targetPkgSetting, "acts on target of overlay");
+                    }
+                    return false;
+                }
+            }
+
             return true;
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
similarity index 83%
rename from services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
index b707912..538e2d5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
@@ -24,22 +24,21 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
-import android.util.DisplayMetrics;
-import android.view.GestureDetector;
 import android.view.MotionEvent;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.Before;
 import org.junit.Test;
 
 import java.util.ArrayList;
 
 /**
- * Tests for AccessibilityGestureDetector
+ * Tests for GestureManifold
  */
-public class AccessibilityGestureDetectorTest {
+public class GestureManifoldTest {
 
     // Constants for testRecognizeGesturePath()
     private static final PointF PATH_START = new PointF(300f, 300f);
@@ -47,24 +46,21 @@
     private static final long PATH_STEP_MILLISEC = 100;
 
     // Data used by all tests
-    private AccessibilityGestureDetector mDetector;
-    private AccessibilityGestureDetector.Listener mResultListener;
+    private GestureManifold mManifold;
+    private TouchState mState;
+    private GestureManifold.Listener mResultListener;
 
     @Before
     public void setUp() {
-        // Construct a mock Context.
-        DisplayMetrics displayMetricsMock = mock(DisplayMetrics.class);
-        displayMetricsMock.xdpi = 500;
-        displayMetricsMock.ydpi = 500;
-        Resources mockResources = mock(Resources.class);
-        when(mockResources.getDisplayMetrics()).thenReturn(displayMetricsMock);
-        Context contextMock = mock(Context.class);
-        when(contextMock.getResources()).thenReturn(mockResources);
+        Context context = InstrumentationRegistry.getContext();
+        // Construct a testable GestureManifold.
+        mResultListener = mock(GestureManifold.Listener.class);
+        mState = new TouchState();
+        mManifold = new GestureManifold(context, mResultListener, mState);
+        // Play the role of touch explorer in updating the shared state.
+        when(mResultListener.onGestureStarted()).thenReturn(onGestureStarted());
 
-        // Construct a testable AccessibilityGestureDetector.
-        mResultListener = mock(AccessibilityGestureDetector.Listener.class);
-        GestureDetector doubleTapDetectorMock = mock(GestureDetector.class);
-        mDetector = new AccessibilityGestureDetector(contextMock, mResultListener, doubleTapDetectorMock);
+
     }
 
 
@@ -141,8 +137,8 @@
         // For each path step from start (non-inclusive) to end ... add a motion point.
         for (int step = 1; step < numSteps; ++step) {
             path.add(new PointF(
-                (start.x + (stepX * (float) step)),
-                (start.y + (stepY * (float) step))));
+                    (start.x + (stepX * (float) step)),
+                    (start.y + (stepY * (float) step))));
         }
     }
 
@@ -170,12 +166,22 @@
                     point.x, point.y, 0);
 
             // Send event.
-            mDetector.onMotionEvent(event, event, policyFlags);
+            mState.onReceivedMotionEvent(event);
+            mManifold.onMotionEvent(event, event, policyFlags);
             eventTimeMs += PATH_STEP_MILLISEC;
+            if (mState.isClear()) {
+                mState.startTouchInteracting();
+            }
         }
 
+        mState.clear();
         // Check that correct gesture was recognized.
         verify(mResultListener).onGestureCompleted(
                 argThat(gestureEvent -> gestureEvent.getGestureId() == gestureId));
     }
+
+    private boolean onGestureStarted() {
+        mState.startGestureDetecting();
+        return false;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 4b1ec6f..a4ceadb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -74,7 +74,7 @@
     private TouchExplorer mTouchExplorer;
     private long mLastDownTime = Integer.MIN_VALUE;
 
-    // mock package-private AccessibilityGestureDetector class
+    // mock package-private GestureManifold class
     @Rule
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
@@ -108,7 +108,7 @@
     public void setUp() {
         Context context = InstrumentationRegistry.getContext();
         AccessibilityManagerService ams = new AccessibilityManagerService(context);
-        AccessibilityGestureDetector detector = mock(AccessibilityGestureDetector.class);
+        GestureManifold detector = mock(GestureManifold.class);
         mCaptor = new EventCaptor();
         mTouchExplorer = new TouchExplorer(context, ams, detector);
         mTouchExplorer.setNext(mCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index a47a567..e90cb46 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -16,7 +16,11 @@
 
 package com.android.server.attention;
 
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
 import static com.android.server.attention.AttentionManagerService.ATTENTION_CACHE_BUFFER_SIZE;
+import static com.android.server.attention.AttentionManagerService.DEFAULT_STALE_AFTER_MILLIS;
+import static com.android.server.attention.AttentionManagerService.KEY_STALE_AFTER_MILLIS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -35,6 +39,7 @@
 import android.os.IPowerManager;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.provider.DeviceConfig;
 import android.service.attention.IAttentionCallback;
 import android.service.attention.IAttentionService;
 
@@ -180,6 +185,45 @@
         assertThat(buffer.get(0)).isEqualTo(cache);
     }
 
+    @Test
+    public void testGetStaleAfterMillis_handlesGoodFlagValue() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "123", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(123);
+    }
+
+    @Test
+    public void testGetStaleAfterMillis_handlesBadFlagValue_1() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "-123", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+                DEFAULT_STALE_AFTER_MILLIS);
+    }
+
+    @Test
+    public void testGetStaleAfterMillis_handlesBadFlagValue_2() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "15000", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+                DEFAULT_STALE_AFTER_MILLIS);
+    }
+
+    @Test
+    public void testGetStaleAfterMillis_handlesBadFlagValue_3() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "abracadabra", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+                DEFAULT_STALE_AFTER_MILLIS);
+    }
+
+    @Test
+    public void testGetStaleAfterMillis_handlesBadFlagValue_4() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "15_000L", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+                DEFAULT_STALE_AFTER_MILLIS);
+    }
+
     private class MockIAttentionService implements IAttentionService {
         public void checkAttention(IAttentionCallback callback) throws RemoteException {
             callback.onSuccess(0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
new file mode 100644
index 0000000..0f915db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.om
+
+import org.mockito.Answers
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.stubbing.Answer
+import org.mockito.stubbing.Stubber
+
+// TODO(chiuwinson): Move this entire file to a shared utility module
+// TODO(b/135203078): De-dupe utils added for overlays vs package refactor
+object MockitoUtils {
+    val ANSWER_THROWS = Answer<Any?> {
+        when (val name = it.method.name) {
+            "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+            else -> {
+                val arguments = it.arguments
+                        ?.takeUnless { it.isEmpty() }
+                        ?.joinToString()
+                        ?.let {
+                            "with $it"
+                        }
+                        .orEmpty()
+
+                throw UnsupportedOperationException("${it.mock::class.java.simpleName}#$name " +
+                        "$arguments should not be called")
+            }
+        }
+    }
+}
+
+inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
+
+fun <Type> Stubber.whenever(mock: Type) = Mockito.`when`(mock)
+fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock)
+
+@Suppress("UNCHECKED_CAST")
+fun <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Any?) =
+        Mockito.`when`(mock).thenAnswer { block(it) }
+
+fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { }
+
+inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T {
+    val swappingAnswer = object : Answer<Any?> {
+        var delegate: Answer<*> = Answers.RETURNS_DEFAULTS
+
+        override fun answer(invocation: InvocationOnMock?): Any? {
+            return delegate.answer(invocation)
+        }
+    }
+
+    return Mockito.mock(T::class.java, swappingAnswer).apply(block)
+            .also {
+                // To allow when() usage inside block, only swap to throwing afterwards
+                swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS
+            }
+}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
new file mode 100644
index 0000000..ef12948
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.om
+
+import android.content.pm.parsing.AndroidPackage
+import android.net.Uri
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.testng.Assert.assertThrows
+
+@RunWith(Parameterized::class)
+class OverlayReferenceMapperTests {
+
+    companion object {
+        private const val TARGET_PACKAGE_NAME = "com.test.target"
+        private const val OVERLAY_PACKAGE_NAME = "com.test.overlay"
+        private const val ACTOR_PACKAGE_NAME = "com.test.actor"
+        private const val ACTOR_NAME = "overlay://test/actorName"
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "deferRebuild {0}")
+        fun parameters() = arrayOf(true, false)
+    }
+
+    private lateinit var mapper: OverlayReferenceMapper
+
+    @JvmField
+    @Parameterized.Parameter(0)
+    var deferRebuild = false
+
+    @Before
+    fun initMapper() {
+        mapper = mapper()
+    }
+
+    @Test
+    fun targetWithOverlay() {
+        val target = mockTarget()
+        val overlay = mockOverlay()
+        val existing = mapper.addInOrder(overlay)
+        assertEmpty()
+        mapper.addInOrder(target, existing = existing)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
+        mapper.remove(target)
+        assertEmpty()
+    }
+
+    @Test
+    fun targetWithMultipleOverlays() {
+        val target = mockTarget()
+        val overlay0 = mockOverlay(0)
+        val overlay1 = mockOverlay(1)
+        mapper = mapper(
+                overlayToTargetToOverlayables = mapOf(
+                        overlay0.packageName to mapOf(
+                                target.packageName to target.overlayables.keys
+                        ),
+                        overlay1.packageName to mapOf(
+                                target.packageName to target.overlayables.keys
+                        )
+                )
+        )
+        val existing = mapper.addInOrder(overlay0, overlay1)
+        assertEmpty()
+        mapper.addInOrder(target, existing = existing)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay0, overlay1))
+        mapper.remove(overlay0)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay1))
+        mapper.remove(target)
+        assertEmpty()
+    }
+
+    @Test
+    fun targetWithoutOverlay() {
+        val target = mockTarget()
+        mapper.addInOrder(target)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+        mapper.remove(target)
+        assertEmpty()
+    }
+
+    @Test
+    fun overlayWithTarget() {
+        val target = mockTarget()
+        val overlay = mockOverlay()
+        val existing = mapper.addInOrder(target)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+        mapper.addInOrder(overlay, existing = existing)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
+        mapper.remove(overlay)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+    }
+
+    @Test
+    fun overlayWithMultipleTargets() {
+        val target0 = mockTarget(0)
+        val target1 = mockTarget(1)
+        val overlay = mockOverlay()
+        mapper = mapper(
+                overlayToTargetToOverlayables = mapOf(
+                        overlay.packageName to mapOf(
+                                target0.packageName to target0.overlayables.keys,
+                                target1.packageName to target1.overlayables.keys
+                        )
+                )
+        )
+        mapper.addInOrder(target0, target1, overlay)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay))
+        mapper.remove(target0)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay))
+        mapper.remove(target1)
+        assertEmpty()
+    }
+
+    @Test
+    fun overlayWithoutTarget() {
+        val overlay = mockOverlay()
+        mapper.addInOrder(overlay)
+        // An overlay can only have visibility exposed through its target
+        assertEmpty()
+        mapper.remove(overlay)
+        assertEmpty()
+    }
+
+    private fun OverlayReferenceMapper.addInOrder(
+        vararg pkgs: AndroidPackage,
+        existing: MutableMap<String, AndroidPackage> = mutableMapOf()
+    ) = pkgs.fold(existing) { map, pkg ->
+        addPkg(pkg, map)
+        map[pkg.packageName] = pkg
+        return@fold map
+    }
+
+    private fun OverlayReferenceMapper.remove(pkg: AndroidPackage) = removePkg(pkg.packageName)
+
+    private fun assertMapping(vararg pairs: Pair<String, Set<AndroidPackage>>) {
+        val expected = pairs.associate { it }
+                .mapValues { pair -> pair.value.map { it.packageName }.toSet() }
+
+        // This validates the API exposed for querying the relationships
+        expected.forEach { (actorPkgName, expectedPkgNames) ->
+            expectedPkgNames.forEach { expectedPkgName ->
+                if (deferRebuild) {
+                    assertThrows(IllegalStateException::class.java) {
+                        mapper.isValidActor(expectedPkgName, actorPkgName)
+                    }
+                    mapper.rebuildIfDeferred()
+                    deferRebuild = false
+                }
+
+                assertThat(mapper.isValidActor(expectedPkgName, actorPkgName)).isTrue()
+            }
+        }
+
+        // This asserts no other relationships are defined besides those tested above
+        assertThat(mapper.actorPkgToPkgs).containsExactlyEntriesIn(expected)
+    }
+
+    private fun assertEmpty() = assertMapping()
+
+    private fun mapper(
+        namedActors: Map<String, Map<String, String>> = Uri.parse(ACTOR_NAME).run {
+            mapOf(authority!! to mapOf(pathSegments.first() to ACTOR_PACKAGE_NAME))
+        },
+        overlayToTargetToOverlayables: Map<String, Map<String, Set<String>>> = mapOf(
+                mockOverlay().packageName to mapOf(
+                        mockTarget().run { packageName to overlayables.keys }
+                )
+        )
+    ) = OverlayReferenceMapper(deferRebuild, object : OverlayReferenceMapper.Provider {
+        override fun getActorPkg(actor: String?) =
+                OverlayActorEnforcer.getPackageNameForActor(actor, namedActors).first
+
+        override fun getTargetToOverlayables(pkg: AndroidPackage) =
+                overlayToTargetToOverlayables[pkg.packageName] ?: emptyMap()
+    })
+
+    private fun mockTarget(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
+        whenever(packageName) { "$TARGET_PACKAGE_NAME$increment" }
+        whenever(overlayables) { mapOf("overlayableName$increment" to ACTOR_NAME) }
+        whenever(toString()) { "Package{$packageName}" }
+    }
+
+    private fun mockOverlay(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
+        whenever(packageName) { "$OVERLAY_PACKAGE_NAME$increment" }
+        whenever(overlayables) { emptyMap<String, String>() }
+        whenever(toString()) { "Package{$packageName}" }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 4fc625a..82bbdcb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -35,6 +35,11 @@
 import android.os.Build;
 import android.os.Process;
 import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.om.OverlayReferenceMapper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,11 +48,18 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
 @RunWith(JUnit4.class)
 public class AppsFilterTest {
 
     private static final int DUMMY_CALLING_UID = 10345;
     private static final int DUMMY_TARGET_UID = 10556;
+    private static final int DUMMY_ACTOR_UID = 10656;
+    private static final int DUMMY_OVERLAY_UID = 10756;
+    private static final int DUMMY_ACTOR_TWO_UID = 10856;
 
     @Mock
     AppsFilter.FeatureConfig mFeatureConfigMock;
@@ -117,7 +129,7 @@
     @Test
     public void testSystemReadyPropogates() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
         appsFilter.onSystemReady();
         verify(mFeatureConfigMock).onSystemReady();
     }
@@ -125,7 +137,8 @@
     @Test
     public void testQueriesAction_FilterMatches() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_UID);
@@ -138,7 +151,8 @@
     @Test
     public void testQueriesAction_NoMatchingAction_Filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -151,7 +165,8 @@
     @Test
     public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -169,7 +184,8 @@
     @Test
     public void testNoQueries_Filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -182,7 +198,8 @@
     @Test
     public void testForceQueryable_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                         pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID);
@@ -195,7 +212,8 @@
     @Test
     public void testForceQueryableByDevice_SystemCaller_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID,
@@ -209,7 +227,8 @@
     @Test
     public void testForceQueryableByDevice_NonSystemCaller_Filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -224,7 +243,8 @@
     public void testSystemQueryable_DoesntFilter() {
         final AppsFilter appsFilter =
                 new AppsFilter(mFeatureConfigMock, new String[]{},
-                        true /* system force queryable */);
+                        true /* system force queryable */, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID,
@@ -238,7 +258,8 @@
     @Test
     public void testQueriesPackage_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -253,7 +274,8 @@
         when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
                 .thenReturn(false);
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(
                 appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -266,20 +288,22 @@
     @Test
     public void testSystemUid_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
 
         assertFalse(appsFilter.shouldFilterApplication(0, null, target, 0));
-        assertFalse(appsFilter.shouldFilterApplication(
-                Process.FIRST_APPLICATION_UID - 1, null, target, 0));
+        assertFalse(appsFilter.shouldFilterApplication(Process.FIRST_APPLICATION_UID - 1,
+                null, target, 0));
     }
 
     @Test
     public void testNonSystemUid_NoCallingSetting_Filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -290,7 +314,8 @@
     @Test
     public void testNoTargetPackage_filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = new PackageSettingBuilder()
                 .setName("com.some.package")
@@ -304,6 +329,127 @@
         assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
     }
 
+    @Test
+    public void testActsOnTargetOfOverlay() {
+        final String actorName = "overlay://test/actorName";
+
+        ParsingPackage target = pkg("com.some.package.target")
+                .addOverlayable("overlayableName", actorName);
+        ParsingPackage overlay = pkg("com.some.package.overlay")
+                .setIsOverlay(true)
+                .setOverlayTarget(target.getPackageName())
+                .setOverlayTargetName("overlayableName");
+        ParsingPackage actor = pkg("com.some.package.actor");
+
+        final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false,
+                new OverlayReferenceMapper.Provider() {
+                    @Nullable
+                    @Override
+                    public String getActorPkg(String actorString) {
+                        if (actorName.equals(actorString)) {
+                            return actor.getPackageName();
+                        }
+                        return null;
+                    }
+
+                    @NonNull
+                    @Override
+                    public Map<String, Set<String>> getTargetToOverlayables(
+                            @NonNull AndroidPackage pkg) {
+                        if (overlay.getPackageName().equals(pkg.getPackageName())) {
+                            Map<String, Set<String>> map = new ArrayMap<>();
+                            Set<String> set = new ArraySet<>();
+                            set.add(overlay.getOverlayTargetName());
+                            map.put(overlay.getOverlayTarget(), set);
+                            return map;
+                        }
+                        return Collections.emptyMap();
+                    }
+                });
+        appsFilter.onSystemReady();
+
+        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID);
+        PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID);
+        PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_UID);
+
+        // Actor can see both target and overlay
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting,
+                targetSetting, 0));
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting,
+                overlaySetting, 0));
+
+        // But target/overlay can't see each other
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting,
+                overlaySetting, 0));
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting,
+                targetSetting, 0));
+
+        // And can't see the actor
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting,
+                actorSetting, 0));
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting,
+                actorSetting, 0));
+    }
+
+    @Test
+    public void testActsOnTargetOfOverlayThroughSharedUser() {
+        final String actorName = "overlay://test/actorName";
+
+        ParsingPackage target = pkg("com.some.package.target")
+                .addOverlayable("overlayableName", actorName);
+        ParsingPackage overlay = pkg("com.some.package.overlay")
+                .setIsOverlay(true)
+                .setOverlayTarget(target.getPackageName())
+                .setOverlayTargetName("overlayableName");
+        ParsingPackage actorOne = pkg("com.some.package.actor.one");
+        ParsingPackage actorTwo = pkg("com.some.package.actor.two");
+
+        final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false,
+                new OverlayReferenceMapper.Provider() {
+                    @Nullable
+                    @Override
+                    public String getActorPkg(String actorString) {
+                        // Only actorOne is mapped as a valid actor
+                        if (actorName.equals(actorString)) {
+                            return actorOne.getPackageName();
+                        }
+                        return null;
+                    }
+
+                    @NonNull
+                    @Override
+                    public Map<String, Set<String>> getTargetToOverlayables(
+                            @NonNull AndroidPackage pkg) {
+                        if (overlay.getPackageName().equals(pkg.getPackageName())) {
+                            Map<String, Set<String>> map = new ArrayMap<>();
+                            Set<String> set = new ArraySet<>();
+                            set.add(overlay.getOverlayTargetName());
+                            map.put(overlay.getOverlayTarget(), set);
+                            return map;
+                        }
+                        return Collections.emptyMap();
+                    }
+                });
+        appsFilter.onSystemReady();
+
+        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID);
+        PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID);
+        PackageSetting actorOneSetting = simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_UID);
+        PackageSetting actorTwoSetting = simulateAddPackage(appsFilter, actorTwo,
+                DUMMY_ACTOR_TWO_UID);
+
+        SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser",
+                actorOneSetting.pkgFlags, actorOneSetting.pkgPrivateFlags);
+        actorSharedSetting.addPackage(actorOneSetting);
+        actorSharedSetting.addPackage(actorTwoSetting);
+
+        // actorTwo can see both target and overlay
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting,
+                targetSetting, 0));
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting,
+                overlaySetting, 0));
+    }
+
     private interface WithSettingBuilder {
         PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 5baeede..2473997 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -22,7 +22,7 @@
 
 import java.io.File;
 
-class PackageSettingBuilder {
+public class PackageSettingBuilder {
     private String mName;
     private String mRealName;
     private String mCodePath;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 0b4760d..a328568 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.spy;
 
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -271,6 +272,18 @@
     }
 
     @Test
+    public void testRankingScoreOverrides() {
+        NotificationComparator comp = new NotificationComparator(mContext);
+        NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
+        assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
+
+        when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f);
+        assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
+        assertTrue(comp.compare(mRecordCheater, recordMinCallNonInterruptive) > 0);
+        assertTrue(comp.compare(mRecordColorizedCall, recordMinCallNonInterruptive) < 0);
+    }
+
+    @Test
     public void testMessaging() {
         NotificationComparator comp = new NotificationComparator(mContext);
         assertTrue(comp.isImportantMessaging(mRecordInlineReply));
diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java
index 86016bb..095e8e9 100644
--- a/services/usb/java/com/android/server/usb/UsbSerialReader.java
+++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java
@@ -75,12 +75,14 @@
         if (uid != Process.SYSTEM_UID) {
             enforcePackageBelongsToUid(uid, packageName);
 
+            UserHandle user = Binder.getCallingUserHandle();
             int packageTargetSdkVersion;
             long token = Binder.clearCallingIdentity();
             try {
                 PackageInfo pkg;
                 try {
-                    pkg = mContext.getPackageManager().getPackageInfo(packageName, 0);
+                    pkg = mContext.getPackageManager()
+                            .getPackageInfoAsUser(packageName, 0, user.getIdentifier());
                 } catch (PackageManager.NameNotFoundException e) {
                     throw new RemoteException("package " + packageName + " cannot be found");
                 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 001888c..c0a73cc 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -419,12 +419,33 @@
             KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
 
     /**
-     * Override the device's configuration for the ImsService to use for this SIM card.
+     * The package name containing the ImsService that will be bound to the telephony framework to
+     * support both IMS MMTEL and RCS feature functionality instead of the device default
+     * ImsService for this subscription.
+     * @deprecated Use {@link #KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING} and
+     * {@link #KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING} instead to configure these values
+     * separately. If any of those values are not empty, they will override this value.
      */
     public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING =
             "config_ims_package_override_string";
 
     /**
+     * The package name containing the ImsService that will be bound to the telephony framework to
+     * support IMS MMTEL feature functionality instead of the device default ImsService for this
+     * subscription.
+     */
+    public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING =
+            "config_ims_mmtel_package_override_string";
+
+    /**
+     * The package name containing the ImsService that will be bound to the telephony framework to
+     * support IMS RCS feature functionality instead of the device default ImsService for this
+     * subscription.
+     */
+    public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING =
+            "config_ims_rcs_package_override_string";
+
+    /**
      * Override the package that will manage {@link SubscriptionPlan}
      * information instead of the {@link CarrierService} that defines this
      * value.
@@ -2176,7 +2197,7 @@
      * the start of the next month.
      * <p>
      * This setting may be still overridden by explicit user choice. By default,
-     * the platform value will be used.
+     * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
      */
     public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT =
             "monthly_data_cycle_day_int";
@@ -2185,10 +2206,7 @@
      * When {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG},
      * or {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} are set to this value, the platform default
      * value will be used for that key.
-     *
-     * @hide
      */
-    @Deprecated
     public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1;
 
     /**
@@ -2212,8 +2230,8 @@
      * If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data usage warning will
      * be disabled.
      * <p>
-     * This setting may be overridden by explicit user choice. By default, the platform value
-     * will be used.
+     * This setting may be overridden by explicit user choice. By default,
+     * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
      */
     public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG =
             "data_warning_threshold_bytes_long";
@@ -2221,8 +2239,7 @@
     /**
      * Controls if the device should automatically notify the user as they reach
      * their cellular data warning. When set to {@code false} the carrier is
-     * expected to have implemented their own notification mechanism.
-     * @hide
+     * expected to have implemented their own notification mechanism. {@code true} by default.
      */
     public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL =
             "data_warning_notification_bool";
@@ -2244,8 +2261,8 @@
      * phone. If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data limit will be
      * disabled.
      * <p>
-     * This setting may be overridden by explicit user choice. By default, the platform value
-     * will be used.
+     * This setting may be overridden by explicit user choice. By default,
+     * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
      */
     public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG =
             "data_limit_threshold_bytes_long";
@@ -2253,8 +2270,7 @@
     /**
      * Controls if the device should automatically notify the user as they reach
      * their cellular data limit. When set to {@code false} the carrier is
-     * expected to have implemented their own notification mechanism.
-     * @hide
+     * expected to have implemented their own notification mechanism. {@code true} by default.
      */
     public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL =
             "data_limit_notification_bool";
@@ -2262,8 +2278,7 @@
     /**
      * Controls if the device should automatically notify the user when rapid
      * cellular data usage is observed. When set to {@code false} the carrier is
-     * expected to have implemented their own notification mechanism.
-     * @hide
+     * expected to have implemented their own notification mechanism.  {@code true} by default.
      */
     public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL =
             "data_rapid_notification_bool";
@@ -3486,6 +3501,8 @@
         sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
+        sDefaults.putString(KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING, null);
+        sDefaults.putString(KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING, null);
         sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 257d634..78ad5c5 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -19,8 +19,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.net.LinkProperties;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Annotation.ApnType;
@@ -29,6 +31,8 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.data.ApnSetting;
 
+import dalvik.system.VMRuntime;
+
 import java.util.Objects;
 
 
@@ -46,35 +50,62 @@
  *   <li>Data connection fail cause.
  * </ul>
  *
- * @hide
  */
-@SystemApi
 public final class PreciseDataConnectionState implements Parcelable {
 
     private @DataState int mState = TelephonyManager.DATA_UNKNOWN;
     private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
     private @DataFailureCause int mFailCause = DataFailCause.NONE;
-    private @ApnType int mAPNTypes = ApnSetting.TYPE_NONE;
-    private String mAPN = "";
+    private @ApnType int mApnTypes = ApnSetting.TYPE_NONE;
+    private String mApn = "";
     private LinkProperties mLinkProperties = null;
+    private ApnSetting mApnSetting = null;
 
     /**
      * Constructor
      *
+     * @deprecated this constructor has been superseded and should not be used.
      * @hide
      */
-    @UnsupportedAppUsage
+    @TestApi
+    @Deprecated
+    @UnsupportedAppUsage // (maxTargetSdk = Build.VERSION_CODES.Q)
+    // FIXME: figure out how to remove the UnsupportedAppUsage and delete this constructor
     public PreciseDataConnectionState(@DataState int state,
                                       @NetworkType int networkType,
-                                      @ApnType int apnTypes, String apn,
-                                      LinkProperties linkProperties,
+                                      @ApnType int apnTypes, @NonNull String apn,
+                                      @Nullable LinkProperties linkProperties,
                                       @DataFailureCause int failCause) {
+        this(state, networkType, apnTypes, apn, linkProperties, failCause, null);
+    }
+
+
+    /**
+     * Constructor
+     *
+     * @param state the state of the data connection
+     * @param networkType the access network that is/would carry this data connection
+     * @param apnTypes the APN types that this data connection carries
+     * @param apnSetting if there is a valid APN for this Data Connection, then the APN Settings;
+     *        if there is no valid APN setting for the specific type, then this will be null
+     * @param linkProperties if the data connection is connected, the properties of the connection
+     * @param failCause in case a procedure related to this data connection fails, a non-zero error
+     *        code indicating the cause of the failure.
+     * @hide
+     */
+    public PreciseDataConnectionState(@DataState int state,
+                                      @NetworkType int networkType,
+                                      @ApnType int apnTypes, @NonNull String apn,
+                                      @Nullable LinkProperties linkProperties,
+                                      @DataFailureCause int failCause,
+                                      @Nullable ApnSetting apnSetting) {
         mState = state;
         mNetworkType = networkType;
-        mAPNTypes = apnTypes;
-        mAPN = apn;
+        mApnTypes = apnTypes;
+        mApn = apn;
         mLinkProperties = linkProperties;
         mFailCause = failCause;
+        mApnSetting = apnSetting;
     }
 
     /**
@@ -93,76 +124,160 @@
     private PreciseDataConnectionState(Parcel in) {
         mState = in.readInt();
         mNetworkType = in.readInt();
-        mAPNTypes = in.readInt();
-        mAPN = in.readString();
-        mLinkProperties = (LinkProperties)in.readParcelable(null);
+        mApnTypes = in.readInt();
+        mApn = in.readString();
+        mLinkProperties = (LinkProperties) in.readParcelable(null);
         mFailCause = in.readInt();
+        mApnSetting = (ApnSetting) in.readParcelable(null);
     }
 
     /**
      * Returns the state of data connection that supported the apn types returned by
      * {@link #getDataConnectionApnTypeBitMask()}
+     *
+     * @deprecated use {@link #getState()}
+     * @hide
      */
+    @Deprecated
+    @SystemApi
     public @DataState int getDataConnectionState() {
+        if (mState == TelephonyManager.DATA_DISCONNECTING
+                && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+            return TelephonyManager.DATA_CONNECTED;
+        }
+
+        return mState;
+    }
+
+    /**
+     * Returns the high-level state of this data connection.
+     */
+    public @DataState int getState() {
         return mState;
     }
 
     /**
      * Returns the network type associated with this data connection.
+     *
+     * @deprecated use {@link getNetworkType()}
      * @hide
      */
+    @Deprecated
+    @SystemApi
     public @NetworkType int getDataConnectionNetworkType() {
         return mNetworkType;
     }
 
     /**
-     * Returns the data connection APN types supported by this connection and triggers
-     * {@link PreciseDataConnectionState} change.
+     * Returns the network type associated with this data connection.
+     *
+     * Return the current/latest (radio) bearer technology that carries this data connection.
+     * For a variety of reasons, the network type can change during the life of the data
+     * connection, and this information is not reliable unless the physical link is currently
+     * active; (there is currently no mechanism to know whether the physical link is active at
+     * any given moment). Thus, this value is generally correct but may not be relied-upon to
+     * represent the status of the radio bearer at any given moment.
      */
-    public @ApnType int getDataConnectionApnTypeBitMask() {
-        return mAPNTypes;
+    public @NetworkType int getNetworkType() {
+        return mNetworkType;
     }
 
     /**
-     * Returns APN {@link ApnSetting} of this data connection.
+     * Returns the APN types mapped to this data connection.
+     *
+     * @deprecated use {@link #getApnSetting()}
+     * @hide
      */
-    @Nullable
+    @Deprecated
+    @SystemApi
+    public @ApnType int getDataConnectionApnTypeBitMask() {
+        return mApnTypes;
+    }
+
+    /**
+     * Returns APN of this data connection.
+     *
+     * @deprecated use {@link #getApnSetting()}
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @Deprecated
     public String getDataConnectionApn() {
-        return mAPN;
+        return mApn;
     }
 
     /**
      * Get the properties of the network link {@link LinkProperties}.
+     *
+     * @deprecated use {@link #getLinkProperties()}
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @SystemApi
+    @Nullable
     public LinkProperties getDataConnectionLinkProperties() {
         return mLinkProperties;
     }
 
     /**
-     * Returns data connection fail cause, in case there was a failure.
+     * Get the properties of the network link {@link LinkProperties}.
      */
-    public @Annotation.DataFailureCause int getDataConnectionFailCause() {
+    @Nullable
+    public LinkProperties getLinkProperties() {
+        return mLinkProperties;
+    }
+
+    /**
+     * Returns the cause code generated by the most recent state change.
+     *
+     * @deprecated use {@link #getLastCauseCode()}
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    public int getDataConnectionFailCause() {
         return mFailCause;
     }
 
+    /**
+     * Returns the cause code generated by the most recent state change.
+     *
+     * Return the cause code for the most recent change in {@link #getState}. In the event of an
+     * error, this cause code will be non-zero.
+     */
+    // FIXME(b144774287): some of these cause codes should have a prescribed meaning.
+    public int getLastCauseCode() {
+        return mFailCause;
+    }
+
+    /**
+     * Return the APN Settings for this data connection.
+     *
+     * Returns the ApnSetting that was used to configure this data connection.
+     */
+    // FIXME: This shouldn't be nullable; update once the ApnSetting is supplied correctly
+    @Nullable ApnSetting getApnSetting() {
+        return mApnSetting;
+    }
+
     @Override
     public int describeContents() {
         return 0;
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeInt(mState);
         out.writeInt(mNetworkType);
-        out.writeInt(mAPNTypes);
-        out.writeString(mAPN);
+        out.writeInt(mApnTypes);
+        out.writeString(mApn);
         out.writeParcelable(mLinkProperties, flags);
         out.writeInt(mFailCause);
+        out.writeParcelable(mApnSetting, flags);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
+    public static final @NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
             = new Parcelable.Creator<PreciseDataConnectionState>() {
 
         public PreciseDataConnectionState createFromParcel(Parcel in) {
@@ -176,8 +291,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mState, mNetworkType, mAPNTypes, mAPN, mLinkProperties,
-                mFailCause);
+        return Objects.hash(mState, mNetworkType, mApnTypes, mApn, mLinkProperties,
+                mFailCause, mApnSetting);
     }
 
     @Override
@@ -188,11 +303,12 @@
         }
 
         PreciseDataConnectionState other = (PreciseDataConnectionState) obj;
-        return Objects.equals(mAPN, other.mAPN) && mAPNTypes == other.mAPNTypes
+        return Objects.equals(mApn, other.mApn) && mApnTypes == other.mApnTypes
                 && mFailCause == other.mFailCause
                 && Objects.equals(mLinkProperties, other.mLinkProperties)
                 && mNetworkType == other.mNetworkType
-                && mState == other.mState;
+                && mState == other.mState
+                && Objects.equals(mApnSetting, other.mApnSetting);
     }
 
     @NonNull
@@ -202,10 +318,11 @@
 
         sb.append("Data Connection state: " + mState);
         sb.append(", Network type: " + mNetworkType);
-        sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mAPNTypes));
-        sb.append(", APN: " + mAPN);
+        sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mApnTypes));
+        sb.append(", APN: " + mApn);
         sb.append(", Link properties: " + mLinkProperties);
         sb.append(", Fail cause: " + DataFailCause.toString(mFailCause));
+        sb.append(", Apn Setting: " + mApnSetting);
 
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b1dd2d8..78c5e43 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -801,18 +801,6 @@
     public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY;
 
     /**
-     * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
-     * for an String representation of the data interface.
-     *
-     * <p class="note">
-     * Retrieve with
-     * {@link android.content.Intent#getParcelableExtra(String name)}.
-     *
-     * @hide
-     */
-    public static final String EXTRA_DATA_LINK_PROPERTIES_KEY = PhoneConstants.DATA_LINK_PROPERTIES_KEY;
-
-    /**
      * Broadcast intent action for letting the default dialer to know to show voicemail
      * notification.
      *
@@ -5129,6 +5117,7 @@
             DATA_CONNECTING,
             DATA_CONNECTED,
             DATA_SUSPENDED,
+            DATA_DISCONNECTING,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DataState{}
@@ -5145,6 +5134,12 @@
      * traffic is temporarily unavailable. For example, in a 2G network,
      * data activity may be suspended when a voice call arrives. */
     public static final int DATA_SUSPENDED      = 3;
+    /**
+     * Data connection state: Disconnecting.
+     *
+     * IP traffic may be available but will cease working imminently.
+     */
+    public static final int DATA_DISCONNECTING = 4;
 
     /**
      * Returns a constant indicating the current data connection state
@@ -5154,14 +5149,21 @@
      * @see #DATA_CONNECTING
      * @see #DATA_CONNECTED
      * @see #DATA_SUSPENDED
+     * @see #DATA_DISCONNECTING
      */
     public int getDataState() {
         try {
             ITelephony telephony = getITelephony();
             if (telephony == null)
                 return DATA_DISCONNECTED;
-            return telephony.getDataStateForSubId(
+            int state = telephony.getDataStateForSubId(
                     getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
+            if (state == TelephonyManager.DATA_DISCONNECTING
+                    && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+                return TelephonyManager.DATA_CONNECTED;
+            }
+
+            return state;
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return DATA_DISCONNECTED;
@@ -5182,6 +5184,7 @@
             case DATA_CONNECTING: return "CONNECTING";
             case DATA_CONNECTED: return "CONNECTED";
             case DATA_SUSPENDED: return "SUSPENDED";
+            case DATA_DISCONNECTING: return "DISCONNECTING";
         }
         return "UNKNOWN(" + state + ")";
     }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ec54ec..97b24ae 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -888,12 +888,13 @@
     /**
     *  @return true if the ImsService to bind to for the slot id specified was set, false otherwise.
     */
-    boolean setImsService(int slotId, boolean isCarrierImsService, String packageName);
+    boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
+            in int[] featureTypes, in String packageName);
 
     /**
     * @return the package name of the carrier/device ImsService associated with this slot.
     */
-    String getImsService(int slotId, boolean isCarrierImsService);
+    String getBoundImsServicePackage(int slotIndex, boolean isCarrierImsService, int featureType);
 
     /**
      * Get the MmTelFeature state attached to this subscription id.
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
index f7f0f29..8640acc 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
@@ -16,14 +16,10 @@
 
 package com.android.internal.telephony;
 
-import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
-import android.telephony.PreciseCallState;
 
 import com.android.internal.telephony.PhoneConstants;
 
-import java.util.List;
-
 public class PhoneConstantConversions {
     /**
      * Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
@@ -67,6 +63,8 @@
                 return TelephonyManager.DATA_CONNECTED;
             case SUSPENDED:
                 return TelephonyManager.DATA_SUSPENDED;
+            case DISCONNECTING:
+                return TelephonyManager.DATA_DISCONNECTING;
             default:
                 return TelephonyManager.DATA_DISCONNECTED;
         }
@@ -84,6 +82,8 @@
                 return PhoneConstants.DataState.CONNECTED;
             case TelephonyManager.DATA_SUSPENDED:
                 return PhoneConstants.DataState.SUSPENDED;
+            case TelephonyManager.DATA_DISCONNECTING:
+                return PhoneConstants.DataState.DISCONNECTING;
             default:
                 return PhoneConstants.DataState.DISCONNECTED;
         }
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index fde2c5a..fadb573 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -46,6 +46,7 @@
       * <ul>
       * <li>CONNECTED = IP traffic should be available</li>
       * <li>CONNECTING = Currently setting up data connection</li>
+      * <li>DISCONNECTING = IP temporarily available</li>
       * <li>DISCONNECTED = IP not available</li>
       * <li>SUSPENDED = connection is created but IP traffic is
       *                 temperately not available. i.e. voice call is in place
@@ -55,10 +56,15 @@
     @UnsupportedAppUsage(implicitMember =
             "values()[Lcom/android/internal/telephony/PhoneConstants$DataState;")
     public enum DataState {
-        @UnsupportedAppUsage CONNECTED,
-        @UnsupportedAppUsage CONNECTING,
-        @UnsupportedAppUsage DISCONNECTED,
-        @UnsupportedAppUsage SUSPENDED;
+        @UnsupportedAppUsage
+        CONNECTED,
+        @UnsupportedAppUsage
+        CONNECTING,
+        @UnsupportedAppUsage
+        DISCONNECTED,
+        @UnsupportedAppUsage
+        SUSPENDED,
+        DISCONNECTING;
     };
 
     public static final String STATE_KEY = "state";
@@ -91,20 +97,12 @@
 
     public static final String PHONE_NAME_KEY = "phoneName";
     public static final String DATA_NETWORK_TYPE_KEY = "networkType";
-    public static final String DATA_FAILURE_CAUSE_KEY = "failCause";
     public static final String DATA_APN_TYPE_KEY = "apnType";
     public static final String DATA_APN_KEY = "apn";
-    public static final String DATA_LINK_PROPERTIES_KEY = "linkProperties";
-    public static final String DATA_NETWORK_CAPABILITIES_KEY = "networkCapabilities";
 
-    public static final String DATA_IFACE_NAME_KEY = "iface";
-    public static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
-    public static final String DATA_NETWORK_ROAMING_KEY = "networkRoaming";
     public static final String PHONE_IN_ECM_STATE = "phoneinECMState";
     public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall";
 
-    public static final String REASON_LINK_PROPERTIES_CHANGED = "linkPropertiesChanged";
-
     /**
      * Return codes for supplyPinReturnResult and
      * supplyPukReturnResult APIs
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 26064cb..08115ec 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -58,21 +58,6 @@
     }
 }
 
-metalava_wifi_docs_args =
-    "--hide-package com.android.server " +
-    "--error UnhiddenSystemApi " +
-    "--hide RequiresPermission " +
-    "--hide MissingPermission " +
-    "--hide BroadcastBehavior " +
-    "--hide HiddenSuperclass " +
-    "--hide DeprecationMismatch " +
-    "--hide UnavailableSymbol " +
-    "--hide SdkConstant " +
-    "--hide HiddenTypeParameter " +
-    "--hide Todo --hide Typo " +
-    "--hide HiddenTypedefConstant " +
-    "--show-annotation android.annotation.SystemApi "
-
 droidstubs {
     name: "framework-wifi-stubs-srcs",
     srcs: [
@@ -82,7 +67,7 @@
     aidl: {
         include_dirs: ["frameworks/base/core/java"],
     },
-    args: metalava_wifi_docs_args,
+    defaults: [ "framework-module-stubs-defaults-systemapi" ],
     sdk_version: "core_current",
     libs: ["android_system_stubs_current"],
 }