Merge "Fix StrictModeTest#testNonSdkApiUsage" into qt-dev
diff --git a/api/test-current.txt b/api/test-current.txt
index dd11b83..1a912a1c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -645,6 +645,7 @@
     method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
     method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
     field public static final String BUGREPORT_SERVICE = "bugreport";
+    field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
     field public static final String ROLLBACK_SERVICE = "rollback";
     field public static final String STATUS_BAR_SERVICE = "statusbar";
     field public static final String TEST_NETWORK_SERVICE = "test_network";
@@ -2196,6 +2197,7 @@
 package android.permission {
 
   public final class PermissionControllerManager {
+    method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
     method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
     field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
     field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
@@ -2203,11 +2205,25 @@
     field public static final int REASON_MALWARE = 1; // 0x1
   }
 
+  public static interface PermissionControllerManager.OnGetAppPermissionResultCallback {
+    method public void onGetAppPermissions(@NonNull java.util.List<android.permission.RuntimePermissionPresentationInfo>);
+  }
+
   public abstract static class PermissionControllerManager.OnRevokeRuntimePermissionsCallback {
     ctor public PermissionControllerManager.OnRevokeRuntimePermissionsCallback();
     method public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>);
   }
 
+  public final class RuntimePermissionPresentationInfo implements android.os.Parcelable {
+    ctor public RuntimePermissionPresentationInfo(@NonNull CharSequence, boolean, boolean);
+    method public int describeContents();
+    method @NonNull public CharSequence getLabel();
+    method public boolean isGranted();
+    method public boolean isStandard();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionPresentationInfo> CREATOR;
+  }
+
 }
 
 package android.print {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index fc0048c..2899d49 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -294,8 +294,6 @@
         MediametricsMediadrmReported mediametrics_mediadrm_reported = 198;
         MediametricsNuPlayerReported mediametrics_nuplayer_reported = 199;
         MediametricsRecorderReported mediametrics_recorder_reported = 200;
-        VehicleMapServicePacketReported vms_packet_reported = 201;
-        VehicleMapServicePacketFailureReported vms_packet_failure_reported = 202;
         CarPowerStateChanged car_power_state_changed = 203;
         GarageModeInfo garage_mode_info = 204;
         TestAtomReported test_atom_reported = 205 [(log_from_module) = "cts"];
@@ -6421,42 +6419,6 @@
 }
 
 /**
- * Logs information about Vehicle Map Service packets. It specifies the layer
- * type, subtype, data version and size of the packet.
- */
-message VehicleMapServicePacketReported {
-  // Identifies the type of data being transported by a Vehicle Maps Service
-  // packet.
-  optional int32 layer_type = 1;
-  // Identifies the subtype of the layer.
-  optional int32 layer_subtype = 2;
-  // Identifies the version of the data being transported by a Vehicle Maps
-  // Service packet.
-  optional int32 version = 3;
-  // Size in bytes of the packet.
-  optional int64 size_in_bytes = 4;
-}
-
-/**
- * Logs that a Vehicle Map Service packet failed to be delivered.
- */
-message VehicleMapServicePacketFailureReported {
-  // Identifies the type of data being transported by a Vehicle Map Service
-  // packet.
-  optional int32 layer_type = 1;
-  // Identifies the subtype of the layer.
-  optional int32 layer_subtype = 2;
-  // Identifies the version of the data being transported by a Vehicle Map
-  // Service packet.
-  optional int32 version = 3;
-  // Identifies the package name of the publisher of the data.
-  optional string publisher_name = 4;
-  // Identifies the package name of the subscriber of the data. An empty string
-  // signifies that there are zero subscribers for the packet.
-  optional string subscriber_name = 5;
-}
-
-/**
  * Logs when Car Power state changed.
  *
  * Logged from:
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 654e698..941eda8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4090,6 +4090,7 @@
      * @hide
      * @see #getSystemService(String)
      */
+    @TestApi
     public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b09eada..cb939f0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -807,7 +807,7 @@
      *
      * @hide
      */
-    public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00000200;
+    public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
 
     /** {@hide} */
     public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200;
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 41be38a..58aacc2 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -153,14 +153,21 @@
     public static final int CLONE_REMOVE_RES_NAMES = 1 << 3;
 
     /** @hide */
+    public static final int CLONE_REMOVE_PERSON = 1 << 4;
+
+    /** @hide */
     public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON | CLONE_REMOVE_RES_NAMES;
 
     /** @hide */
     public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT
-            | CLONE_REMOVE_RES_NAMES;
+            | CLONE_REMOVE_RES_NAMES | CLONE_REMOVE_PERSON;
 
     /** @hide */
     public static final int CLONE_REMOVE_FOR_LAUNCHER_APPROVAL = CLONE_REMOVE_INTENT
+            | CLONE_REMOVE_RES_NAMES | CLONE_REMOVE_PERSON;
+
+    /** @hide */
+    public static final int CLONE_REMOVE_FOR_APP_PREDICTION = CLONE_REMOVE_ICON
             | CLONE_REMOVE_RES_NAMES;
 
     /** @hide */
@@ -169,8 +176,11 @@
             CLONE_REMOVE_INTENT,
             CLONE_REMOVE_NON_KEY_INFO,
             CLONE_REMOVE_RES_NAMES,
+            CLONE_REMOVE_PERSON,
             CLONE_REMOVE_FOR_CREATOR,
-            CLONE_REMOVE_FOR_LAUNCHER
+            CLONE_REMOVE_FOR_LAUNCHER,
+            CLONE_REMOVE_FOR_LAUNCHER_APPROVAL,
+            CLONE_REMOVE_FOR_APP_PREDICTION
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CloneFlags {}
@@ -548,7 +558,9 @@
             mDisabledMessage = source.mDisabledMessage;
             mDisabledMessageResId = source.mDisabledMessageResId;
             mCategories = cloneCategories(source.mCategories);
-            mPersons = clonePersons(source.mPersons);
+            if ((cloneFlags & CLONE_REMOVE_PERSON) == 0) {
+                mPersons = clonePersons(source.mPersons);
+            }
             if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
                 mIntents = cloneIntents(source.mIntents);
                 mIntentPersistableExtrases =
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 6e83e5a..3ab5c51 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -173,6 +173,7 @@
      *
      * @hide
      */
+    @TestApi
     public interface OnGetAppPermissionResultCallback {
         /**
          * The result for {@link #getAppPermissions(String, OnGetAppPermissionResultCallback,
@@ -385,6 +386,7 @@
      *
      * @hide
      */
+    @TestApi
     @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
     public void getAppPermissions(@NonNull String packageName,
             @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) {
diff --git a/core/java/android/permission/RuntimePermissionPresentationInfo.java b/core/java/android/permission/RuntimePermissionPresentationInfo.java
index 4fce14c..d696fea 100644
--- a/core/java/android/permission/RuntimePermissionPresentationInfo.java
+++ b/core/java/android/permission/RuntimePermissionPresentationInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -32,6 +33,7 @@
  *
  * @hide
  */
+@TestApi
 @SystemApi
 public final class RuntimePermissionPresentationInfo implements Parcelable {
     private static final int FLAG_GRANTED = 1 << 0;
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index e81ce7f..aa11445 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -16,6 +16,7 @@
 package android.service.notification;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -48,6 +49,7 @@
     private final CharSequence mExplanation;
     private final Bundle mSignals;
     private final int mUser;
+    @Nullable private String mIssuer;
 
     /** @hide */
     @StringDef (prefix = { "KEY_" }, value = {
@@ -183,6 +185,7 @@
         }
         mSignals = in.readBundle();
         mUser = in.readInt();
+        mIssuer = in.readString();
     }
 
     public static final @android.annotation.NonNull Creator<Adjustment> CREATOR = new Creator<Adjustment>() {
@@ -251,6 +254,7 @@
         }
         dest.writeBundle(mSignals);
         dest.writeInt(mUser);
+        dest.writeString(mIssuer);
     }
 
     @Override
@@ -259,4 +263,14 @@
                 + "mSignals=" + mSignals
                 + '}';
     }
+
+    /** @hide */
+    public void setIssuer(@Nullable String issuer) {
+        mIssuer = issuer;
+    }
+
+    /** @hide */
+    public @Nullable String getIssuer() {
+        return mIssuer;
+    }
 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index cafeb87..12d3228 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -236,6 +236,7 @@
     public final void adjustNotification(@NonNull Adjustment adjustment) {
         if (!isBound()) return;
         try {
+            setAdjustmentIssuer(adjustment);
             getNotificationInterface().applyEnqueuedAdjustmentFromAssistant(mWrapper, adjustment);
         } catch (android.os.RemoteException ex) {
             Log.v(TAG, "Unable to contact notification manager", ex);
@@ -253,6 +254,9 @@
     public final void adjustNotifications(@NonNull List<Adjustment> adjustments) {
         if (!isBound()) return;
         try {
+            for (Adjustment adjustment : adjustments) {
+                setAdjustmentIssuer(adjustment);
+            }
             getNotificationInterface().applyAdjustmentsFromAssistant(mWrapper, adjustments);
         } catch (android.os.RemoteException ex) {
             Log.v(TAG, "Unable to contact notification manager", ex);
@@ -366,6 +370,12 @@
         }
     }
 
+    private void setAdjustmentIssuer(@Nullable Adjustment adjustment) {
+        if (adjustment != null) {
+            adjustment.setIssuer(getOpPackageName() + "/" + getClass().getName());
+        }
+    }
+
     private final class MyHandler extends Handler {
         public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1;
         public static final int MSG_ON_NOTIFICATION_SNOOZED = 2;
@@ -389,6 +399,7 @@
                     NotificationChannel channel = (NotificationChannel) args.arg2;
                     args.recycle();
                     Adjustment adjustment = onNotificationEnqueued(sbn, channel);
+                    setAdjustmentIssuer(adjustment);
                     if (adjustment != null) {
                         if (!isBound()) {
                             Log.w(TAG, "MSG_ON_NOTIFICATION_ENQUEUED: service not bound, skip.");
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index a88c51a..fca97fe 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -185,7 +185,7 @@
     private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
 
     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
-    private static final int WATCHDOG_TIMEOUT_MILLIS = 3000;
+    private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
 
     private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
     private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -252,6 +252,123 @@
     // Sorted list of DisplayResolveInfos for the alphabetical app section.
     private List<ResolverActivity.DisplayResolveInfo> mSortedList = new ArrayList<>();
 
+    private ContentPreviewCoordinator mPreviewCoord;
+
+    private class ContentPreviewCoordinator {
+        private static final int IMAGE_LOAD_TIMEOUT_MILLIS = 300;
+        private static final int IMAGE_FADE_IN_MILLIS = 150;
+        private static final int IMAGE_LOAD_TIMEOUT = 1;
+        private static final int IMAGE_LOAD_INTO_VIEW = 2;
+
+        private final View mParentView;
+        private boolean mHideParentOnFail;
+        private boolean mAtLeastOneLoaded = false;
+
+        class LoadUriTask {
+            public final Uri mUri;
+            public final int mImageResourceId;
+            public final int mExtraCount;
+            public final Bitmap mBmp;
+
+            LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
+                this.mImageResourceId = imageResourceId;
+                this.mUri = uri;
+                this.mExtraCount = extraCount;
+                this.mBmp = bmp;
+            }
+        }
+
+        // If at least one image loads within the timeout period, allow other
+        // loads to continue. Otherwise terminate and optionally hide
+        // the parent area
+        private final Handler mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case IMAGE_LOAD_TIMEOUT:
+                        maybeHideContentPreview();
+                        break;
+
+                    case IMAGE_LOAD_INTO_VIEW:
+                        if (isFinishing()) break;
+
+                        LoadUriTask task = (LoadUriTask) msg.obj;
+                        RoundedRectImageView imageView = mParentView.findViewById(
+                                task.mImageResourceId);
+                        if (task.mBmp == null) {
+                            imageView.setVisibility(View.GONE);
+                            maybeHideContentPreview();
+                            return;
+                        }
+
+                        mAtLeastOneLoaded = true;
+                        imageView.setVisibility(View.VISIBLE);
+                        imageView.setAlpha(0.0f);
+                        imageView.setImageBitmap(task.mBmp);
+
+                        ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
+                                1.0f);
+                        fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
+                        fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
+                        fadeAnim.start();
+
+                        if (task.mExtraCount > 0) {
+                            imageView.setExtraImageCount(task.mExtraCount);
+                        }
+                }
+            }
+        };
+
+        ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
+            super();
+
+            this.mParentView = parentView;
+            this.mHideParentOnFail = hideParentOnFail;
+        }
+
+        private void loadUriIntoView(final int imageResourceId, final Uri uri,
+                final int extraImages) {
+            mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, IMAGE_LOAD_TIMEOUT_MILLIS);
+
+            AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
+                final Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
+                final Message msg = Message.obtain();
+                msg.what = IMAGE_LOAD_INTO_VIEW;
+                msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
+                mHandler.sendMessage(msg);
+            });
+        }
+
+        private void cancelLoads() {
+            mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
+            mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
+        }
+
+        private void maybeHideContentPreview() {
+            if (!mAtLeastOneLoaded && mHideParentOnFail) {
+                Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
+                        + " within " + IMAGE_LOAD_TIMEOUT_MILLIS + "ms.");
+                collapseParentView();
+                if (mChooserRowAdapter != null) {
+                    mChooserRowAdapter.hideContentPreview();
+                }
+                mHideParentOnFail = false;
+            }
+        }
+
+        private void collapseParentView() {
+            // This will effectively hide the content preview row by forcing the height
+            // to zero. It is faster than forcing a relayout of the listview
+            final View v = mParentView;
+            int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
+            int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
+            v.measure(widthSpec, heightSpec);
+            v.getLayoutParams().height = 0;
+            v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
+            v.invalidate();
+        }
+    }
+
     private final Handler mChooserHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -621,14 +738,15 @@
     private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
             Intent targetIntent, LayoutInflater layoutInflater, ViewGroup convertView,
             ViewGroup parent) {
+        if (convertView != null) return convertView;
+
         switch (previewType) {
             case CONTENT_PREVIEW_TEXT:
-                return displayTextContentPreview(targetIntent, layoutInflater, convertView, parent);
+                return displayTextContentPreview(targetIntent, layoutInflater, parent);
             case CONTENT_PREVIEW_IMAGE:
-                return displayImageContentPreview(targetIntent, layoutInflater, convertView,
-                        parent);
+                return displayImageContentPreview(targetIntent, layoutInflater, parent);
             case CONTENT_PREVIEW_FILE:
-                return displayFileContentPreview(targetIntent, layoutInflater, convertView, parent);
+                return displayFileContentPreview(targetIntent, layoutInflater, parent);
             default:
                 Log.e(TAG, "Unexpected content preview type: " + previewType);
         }
@@ -637,10 +755,9 @@
     }
 
     private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
-            ViewGroup convertView, ViewGroup parent) {
-        ViewGroup contentPreviewLayout =
-                convertView != null ? convertView : (ViewGroup) layoutInflater.inflate(
-                        R.layout.chooser_grid_preview_text, parent, false);
+            ViewGroup parent) {
+        ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
+                R.layout.chooser_grid_preview_text, parent, false);
 
         contentPreviewLayout.findViewById(R.id.copy_button).setOnClickListener(
                 this::onCopyButtonClicked);
@@ -677,12 +794,8 @@
             if (previewThumbnail == null) {
                 previewThumbnailView.setVisibility(View.GONE);
             } else {
-                Bitmap bmp = loadThumbnail(previewThumbnail, new Size(100, 100));
-                if (bmp == null) {
-                    previewThumbnailView.setVisibility(View.GONE);
-                } else {
-                    previewThumbnailView.setImageBitmap(bmp);
-                }
+                mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
+                mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
             }
         }
 
@@ -690,15 +803,15 @@
     }
 
     private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
-            ViewGroup convertView, ViewGroup parent) {
-        ViewGroup contentPreviewLayout =
-                convertView != null ? convertView : (ViewGroup) layoutInflater.inflate(
-                        R.layout.chooser_grid_preview_image, parent, false);
+            ViewGroup parent) {
+        ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
+                R.layout.chooser_grid_preview_image, parent, false);
+        mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
 
         String action = targetIntent.getAction();
         if (Intent.ACTION_SEND.equals(action)) {
             Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
-            loadUriIntoView(R.id.content_preview_image_1_large, uri, contentPreviewLayout);
+            mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
         } else {
             ContentResolver resolver = getContentResolver();
 
@@ -717,21 +830,16 @@
                 return contentPreviewLayout;
             }
 
-            loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0),
-                    contentPreviewLayout);
+            mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
 
             if (imageUris.size() == 2) {
-                loadUriIntoView(R.id.content_preview_image_2_large, imageUris.get(1),
-                        contentPreviewLayout);
+                mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
+                        imageUris.get(1), 0);
             } else if (imageUris.size() > 2) {
-                loadUriIntoView(R.id.content_preview_image_2_small, imageUris.get(1),
-                        contentPreviewLayout);
-                RoundedRectImageView imageView = loadUriIntoView(
-                        R.id.content_preview_image_3_small, imageUris.get(2), contentPreviewLayout);
-
-                if (imageUris.size() > 3) {
-                    imageView.setExtraImageCount(imageUris.size() - 3);
-                }
+                mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
+                        imageUris.get(1), 0);
+                mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
+                        imageUris.get(2), imageUris.size() - 3);
             }
         }
 
@@ -803,11 +911,10 @@
     }
 
     private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
-            ViewGroup convertView, ViewGroup parent) {
+            ViewGroup parent) {
 
-        ViewGroup contentPreviewLayout =
-                convertView != null ? convertView : (ViewGroup) layoutInflater.inflate(
-                        R.layout.chooser_grid_preview_file, parent, false);
+        ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
+                R.layout.chooser_grid_preview_file, parent, false);
 
         // TODO(b/120417119): Disable file copy until after moving to sysui,
         // due to permissions issues
@@ -839,6 +946,10 @@
                         R.id.content_preview_filename);
                 fileNameView.setText(fileName);
 
+                View thumbnailView = contentPreviewLayout.findViewById(
+                        R.id.content_preview_file_thumbnail);
+                thumbnailView.setVisibility(View.GONE);
+
                 ImageView fileIconView = contentPreviewLayout.findViewById(
                         R.id.content_preview_file_icon);
                 fileIconView.setVisibility(View.VISIBLE);
@@ -849,32 +960,25 @@
         return contentPreviewLayout;
     }
 
-    private void loadFileUriIntoView(Uri uri, View parent) {
+    private void loadFileUriIntoView(final Uri uri, final View parent) {
         FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
 
         TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
         fileNameView.setText(fileInfo.name);
 
         if (fileInfo.hasThumbnail) {
-            loadUriIntoView(R.id.content_preview_file_thumbnail, uri, parent);
+            mPreviewCoord = new ContentPreviewCoordinator(parent, false);
+            mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
         } else {
+            View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
+            thumbnailView.setVisibility(View.GONE);
+
             ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
             fileIconView.setVisibility(View.VISIBLE);
             fileIconView.setImageResource(R.drawable.chooser_file_generic);
         }
     }
 
-    private RoundedRectImageView loadUriIntoView(int imageResourceId, Uri uri, View parent) {
-        RoundedRectImageView imageView = parent.findViewById(imageResourceId);
-        Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
-        if (bmp != null) {
-            imageView.setVisibility(View.VISIBLE);
-            imageView.setImageBitmap(bmp);
-        }
-
-        return imageView;
-    }
-
     @VisibleForTesting
     protected boolean isImageType(String mimeType) {
         return mimeType != null && mimeType.startsWith("image/");
@@ -944,6 +1048,9 @@
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
         mChooserHandler.removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
         mChooserHandler.removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
+
+        if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
+
         if (mAppPredictor != null) {
             mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
             mAppPredictor.destroy();
@@ -2036,9 +2143,12 @@
         }
 
         int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
-        if (mChooserRowAdapter.calculateChooserTargetWidth(availableWidth)
+        if (mChooserRowAdapter.consumeLayoutRequest()
+                || mChooserRowAdapter.calculateChooserTargetWidth(availableWidth)
                 || mAdapterView.getAdapter() == null) {
-            mAdapterView.setAdapter(mChooserRowAdapter);
+            if (mAdapterView.getAdapter() == null) {
+                mAdapterView.setAdapter(mChooserRowAdapter);
+            }
 
             getMainThreadHandler().post(() -> {
                 if (mResolverDrawerLayout == null || mChooserRowAdapter == null) {
@@ -2589,6 +2699,9 @@
         private int mChooserTargetWidth = 0;
         private boolean mShowAzLabelIfPoss;
 
+        private boolean mHideContentPreview = false;
+        private boolean mLayoutRequested = false;
+
         private static final int VIEW_TYPE_DIRECT_SHARE = 0;
         private static final int VIEW_TYPE_NORMAL = 1;
         private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
@@ -2651,6 +2764,18 @@
             return maxTargets;
         }
 
+        public void hideContentPreview() {
+            mHideContentPreview = true;
+            mLayoutRequested = true;
+            notifyDataSetChanged();
+        }
+
+        public boolean consumeLayoutRequest() {
+            boolean oldValue = mLayoutRequested;
+            mLayoutRequested = false;
+            return oldValue;
+        }
+
         @Override
         public boolean areAllItemsEnabled() {
             return false;
@@ -2684,7 +2809,8 @@
                 return 0;
             }
 
-            if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) {
+            if (mHideContentPreview || mChooserListAdapter == null
+                    || mChooserListAdapter.getCount() == 0) {
                 return 0;
             }
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index a5daa0a..538c81d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1319,7 +1319,8 @@
             buttonLayout.setVisibility(View.VISIBLE);
             int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
             buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
-                    buttonLayout.getPaddingRight(), buttonLayout.getPaddingBottom() + inset);
+                    buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+                        R.dimen.resolver_button_bar_spacing) + inset);
 
             mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
             mSettingsButton = (Button) buttonLayout.findViewById(R.id.button_app_settings);
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index f9f28da..daa6347 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1301,18 +1301,6 @@
     lpTrack->setParameters(param.toString());
 }
 
-static void android_media_AudioTrack_set_eos(JNIEnv *env,  jobject thiz) {
-    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
-    if (lpTrack == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                          "AudioTrack not initialized");
-        return;
-    }
-    AudioParameter param = AudioParameter();
-    param.addInt(String8("EOS"), 1);
-    lpTrack->setParameters(param.toString());
-}
-
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 static const JNINativeMethod gMethods[] = {
@@ -1389,7 +1377,6 @@
     {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation},
     {"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id},
     {"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding},
-    {"native_set_eos",        "()V",    (void *)android_media_AudioTrack_set_eos},
 };
 
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bff954d..ab7aed3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1697,6 +1697,7 @@
          @hide -->
     <permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"
         android:protectionLevel="signature|privileged" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
 
     <!-- @SystemApi Allows an internal user to set signal strength in NetworkRequest. This kind of
          request will wake up device when signal strength meets the given value.
diff --git a/core/res/res/layout/chooser_grid_preview_file.xml b/core/res/res/layout/chooser_grid_preview_file.xml
index 27c6041..f7d60c9 100644
--- a/core/res/res/layout/chooser_grid_preview_file.xml
+++ b/core/res/res/layout/chooser_grid_preview_file.xml
@@ -44,8 +44,7 @@
           android:adjustViewBounds="true"
           android:layout_gravity="center_vertical"
           android:gravity="center"
-          android:scaleType="centerCrop"
-          android:visibility="gone"/>
+          android:scaleType="centerCrop"/>
     <ImageView
         android:id="@+id/content_preview_file_icon"
         android:layout_width="36dp"
diff --git a/core/res/res/layout/chooser_grid_preview_image.xml b/core/res/res/layout/chooser_grid_preview_image.xml
index ad31e0d..79a0de4 100644
--- a/core/res/res/layout/chooser_grid_preview_image.xml
+++ b/core/res/res/layout/chooser_grid_preview_image.xml
@@ -33,7 +33,6 @@
 
     <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
           android:id="@+id/content_preview_image_1_large"
-          android:visibility="gone"
           android:layout_width="120dp"
           android:layout_height="140dp"
           android:layout_alignParentTop="true"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 0697b0e..aeaccfd 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -100,8 +100,8 @@
         android:layoutDirection="locale"
         android:measureWithLargestChild="true"
         android:background="?attr/colorBackgroundFloating"
-        android:paddingTop="8dp"
-        android:paddingBottom="8dp"
+        android:paddingTop="@dimen/resolver_button_bar_spacing"
+        android:paddingBottom="@dimen/resolver_button_bar_spacing"
         android:paddingStart="12dp"
         android:paddingEnd="12dp"
         android:elevation="8dp">
diff --git a/packages/SystemUI/res/values-mcc310-mnc030/config.xml b/core/res/res/values-mcc310-mnc030/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc030/config.xml
rename to core/res/res/values-mcc310-mnc030/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc070/config.xml b/core/res/res/values-mcc310-mnc070/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc070/config.xml
rename to core/res/res/values-mcc310-mnc070/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc170/config.xml b/core/res/res/values-mcc310-mnc170/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc170/config.xml
rename to core/res/res/values-mcc310-mnc170/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc280/config.xml b/core/res/res/values-mcc310-mnc280/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc280/config.xml
rename to core/res/res/values-mcc310-mnc280/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc380/config.xml b/core/res/res/values-mcc310-mnc380/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc380/config.xml
rename to core/res/res/values-mcc310-mnc380/config.xml
diff --git a/core/res/res/values-mcc310-mnc410/config.xml b/core/res/res/values-mcc310-mnc410/config.xml
index 00ab712..3fb3f0f 100644
--- a/core/res/res/values-mcc310-mnc410/config.xml
+++ b/core/res/res/values-mcc310-mnc410/config.xml
@@ -48,4 +48,8 @@
         <item>"#8"</item>
         <item>"#9"</item>
     </string-array>
+
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+
 </resources>
diff --git a/packages/SystemUI/res/values-mcc310-mnc560/config.xml b/core/res/res/values-mcc310-mnc560/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc560/config.xml
rename to core/res/res/values-mcc310-mnc560/config.xml
diff --git a/packages/SystemUI/res/values-mcc310-mnc950/config.xml b/core/res/res/values-mcc310-mnc950/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc310-mnc950/config.xml
rename to core/res/res/values-mcc310-mnc950/config.xml
diff --git a/packages/SystemUI/res/values-mcc311-mnc180/config.xml b/core/res/res/values-mcc311-mnc180/config.xml
similarity index 100%
rename from packages/SystemUI/res/values-mcc311-mnc180/config.xml
rename to core/res/res/values-mcc311-mnc180/config.xml
diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml
index db2f8d0..336e30e 100755
--- a/core/res/res/values-mcc311-mnc480/config.xml
+++ b/core/res/res/values-mcc311-mnc480/config.xml
@@ -40,4 +40,7 @@
 
     <bool name="config_use_sim_language_file">true</bool>
 
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2de5397..fb58569 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4121,4 +4121,9 @@
     even after user setup is complete. The defined component should be used for supervision purposes
     only. The component must be part of a system app. -->
     <string name="config_defaultSupervisionProfileOwnerComponent" translatable="false"></string>
+
+    <!-- Whether to artificially interpret all signal strengths as
+         one bar higher than they actually are -->
+    <bool name="config_inflateSignalStrength">false</bool>
+
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 6f11432..e0ab6c8 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -742,6 +742,7 @@
     <dimen name="chooser_preview_image_border">1dp</dimen>
     <dimen name="chooser_preview_width">-1px</dimen>
     <dimen name="resolver_icon_size">42dp</dimen>
+    <dimen name="resolver_button_bar_spacing">8dp</dimen>
     <dimen name="resolver_badge_size">18dp</dimen>
     <dimen name="chooser_target_width">90dp</dimen>
     <dimen name="chooser_header_scroll_elevation">4dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3a348f0..3d2d969 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3775,6 +3775,7 @@
   <java-symbol type="drawable" name="iconfactory_adaptive_icon_drawable_wrapper"/>
   <java-symbol type="dimen" name="resolver_icon_size"/>
   <java-symbol type="dimen" name="resolver_badge_size"/>
+  <java-symbol type="dimen" name="resolver_button_bar_spacing"/>
 
   <!-- For DropBox -->
   <java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" />
@@ -3796,4 +3797,5 @@
   <java-symbol type="style" name="Animation.DeviceDefault.Activity.Resolver" />
   
   <java-symbol type="string" name="config_defaultSupervisionProfileOwnerComponent" />
+  <java-symbol type="bool" name="config_inflateSignalStrength" />
 </resources>
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
index dbb79bc..e030478 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationChunkedStreamer.java
@@ -162,15 +162,15 @@
             }
 
             if ((opResult.output != null) && (opResult.output.length > 0)) {
-                if (inputLength > 0) {
+                if (inputLength + mBufferedLength > 0) {
                     // More output might be produced in this loop -- buffer the current output
                     if (bufferedOutput == null) {
                         bufferedOutput = new ByteArrayOutputStream();
-                        try {
-                            bufferedOutput.write(opResult.output);
-                        } catch (IOException e) {
-                            throw new ProviderException("Failed to buffer output", e);
-                        }
+                    }
+                    try {
+                        bufferedOutput.write(opResult.output);
+                    } catch (IOException e) {
+                        throw new ProviderException("Failed to buffer output", e);
                     }
                 } else {
                     // No more output will be produced in this loop
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index d9d614f..e29e569 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -101,6 +101,20 @@
     public static final int PLAYSTATE_PAUSED  = 2;  // matches SL_PLAYSTATE_PAUSED
     /** indicates AudioTrack state is playing */
     public static final int PLAYSTATE_PLAYING = 3;  // matches SL_PLAYSTATE_PLAYING
+    /**
+      * @hide
+      * indicates AudioTrack state is stopping waiting for NATIVE_EVENT_STREAM_END to
+      * transition to PLAYSTATE_STOPPED.
+      * Only valid for offload mode.
+      */
+    private static final int PLAYSTATE_STOPPING = 4;
+    /**
+      * @hide
+      * indicates AudioTrack state is paused from stopping state. Will transition to
+      * PLAYSTATE_STOPPING if play() is called.
+      * Only valid for offload mode.
+      */
+    private static final int PLAYSTATE_PAUSED_STOPPING = 5;
 
     // keep these values in sync with android_media_AudioTrack.cpp
     /**
@@ -303,6 +317,14 @@
      * One of PLAYSTATE_STOPPED, PLAYSTATE_PAUSED, or PLAYSTATE_PLAYING.
      */
     private int mPlayState = PLAYSTATE_STOPPED;
+
+    /**
+     * Indicates that we are expecting an end of stream callback following a call
+     * to setOffloadEndOfStream() in a gapless track transition context. The native track
+     * will be restarted automatically.
+     */
+    private boolean mOffloadEosPending = false;
+
     /**
      * Lock to ensure mPlayState updates reflect the actual state of the object.
      */
@@ -1073,6 +1095,10 @@
      * Declares that the last write() operation on this track provided the last buffer of this
      * stream.
      * After the end of stream, previously set padding and delay values are ignored.
+     * Can only be called only if the AudioTrack is opened in offload mode
+     * {@see Builder#setOffloadedPlayback(boolean)}.
+     * Can only be called only if the AudioTrack is in state {@link #PLAYSTATE_PLAYING}
+     * {@see #getPlaystate()}.
      * Use this method in the same thread as any write() operation.
      */
     public void setOffloadEndOfStream() {
@@ -1082,7 +1108,20 @@
         if (mState == STATE_UNINITIALIZED) {
             throw new IllegalStateException("Uninitialized track");
         }
-        native_set_eos();
+        if (mPlayState != PLAYSTATE_PLAYING) {
+            throw new IllegalStateException("EOS not supported if not playing");
+        }
+        synchronized (mStreamEventCbLock) {
+            if (mStreamEventCbInfoList.size() == 0) {
+                throw new IllegalStateException("EOS not supported without StreamEventCallback");
+            }
+        }
+
+        synchronized (mPlayStateLock) {
+            native_stop();
+            mOffloadEosPending = true;
+            mPlayState = PLAYSTATE_STOPPING;
+        }
     }
 
     /**
@@ -1366,7 +1405,11 @@
         }
         baseRelease();
         native_release();
-        mState = STATE_UNINITIALIZED;
+        synchronized (mPlayStateLock) {
+            mState = STATE_UNINITIALIZED;
+            mPlayState = PLAYSTATE_STOPPED;
+            mPlayStateLock.notify();
+        }
     }
 
     @Override
@@ -1525,7 +1568,14 @@
      */
     public int getPlayState() {
         synchronized (mPlayStateLock) {
-            return mPlayState;
+            switch (mPlayState) {
+                case PLAYSTATE_STOPPING:
+                    return PLAYSTATE_PLAYING;
+                case PLAYSTATE_PAUSED_STOPPING:
+                    return PLAYSTATE_PAUSED;
+                default:
+                    return mPlayState;
+            }
         }
     }
 
@@ -2260,7 +2310,12 @@
         synchronized(mPlayStateLock) {
             baseStart();
             native_start();
-            mPlayState = PLAYSTATE_PLAYING;
+            if (mPlayState == PLAYSTATE_PAUSED_STOPPING) {
+                mPlayState = PLAYSTATE_STOPPING;
+            } else {
+                mPlayState = PLAYSTATE_PLAYING;
+                mOffloadEosPending = false;
+            }
         }
     }
 
@@ -2282,9 +2337,15 @@
         synchronized(mPlayStateLock) {
             native_stop();
             baseStop();
-            mPlayState = PLAYSTATE_STOPPED;
-            mAvSyncHeader = null;
-            mAvSyncBytesRemaining = 0;
+            if (mOffloaded && mPlayState != PLAYSTATE_PAUSED_STOPPING) {
+                mPlayState = PLAYSTATE_STOPPING;
+            } else {
+                mPlayState = PLAYSTATE_STOPPED;
+                mOffloadEosPending = false;
+                mAvSyncHeader = null;
+                mAvSyncBytesRemaining = 0;
+                mPlayStateLock.notify();
+            }
         }
     }
 
@@ -2305,7 +2366,11 @@
         synchronized(mPlayStateLock) {
             native_pause();
             basePause();
-            mPlayState = PLAYSTATE_PAUSED;
+            if (mPlayState == PLAYSTATE_STOPPING) {
+                mPlayState = PLAYSTATE_PAUSED_STOPPING;
+            } else {
+                mPlayState = PLAYSTATE_PAUSED;
+            }
         }
     }
 
@@ -2434,6 +2499,9 @@
             return ERROR_BAD_VALUE;
         }
 
+        if (!blockUntilOffloadDrain(writeMode)) {
+            return 0;
+        }
 
         final int ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat,
                 writeMode == WRITE_BLOCKING);
@@ -2544,6 +2612,10 @@
             return ERROR_BAD_VALUE;
         }
 
+        if (!blockUntilOffloadDrain(writeMode)) {
+            return 0;
+        }
+
         final int ret = native_write_short(audioData, offsetInShorts, sizeInShorts, mAudioFormat,
                 writeMode == WRITE_BLOCKING);
 
@@ -2632,6 +2704,10 @@
             return ERROR_BAD_VALUE;
         }
 
+        if (!blockUntilOffloadDrain(writeMode)) {
+            return 0;
+        }
+
         final int ret = native_write_float(audioData, offsetInFloats, sizeInFloats, mAudioFormat,
                 writeMode == WRITE_BLOCKING);
 
@@ -2706,6 +2782,10 @@
             return ERROR_BAD_VALUE;
         }
 
+        if (!blockUntilOffloadDrain(writeMode)) {
+            return 0;
+        }
+
         int ret = 0;
         if (audioData.isDirect()) {
             ret = native_write_native_bytes(audioData,
@@ -2790,6 +2870,10 @@
             return ERROR_BAD_VALUE;
         }
 
+        if (!blockUntilOffloadDrain(writeMode)) {
+            return 0;
+        }
+
         // create timestamp header if none exists
         if (mAvSyncHeader == null) {
             mAvSyncHeader = ByteBuffer.allocate(mOffset);
@@ -2859,6 +2943,25 @@
         return native_reload_static();
     }
 
+    /**
+     * When an AudioTrack in offload mode is in STOPPING play state, wait until event STREAM_END is
+     * received if blocking write or return with 0 frames written if non blocking mode.
+     */
+    private boolean blockUntilOffloadDrain(int writeMode) {
+        synchronized (mPlayStateLock) {
+            while (mPlayState == PLAYSTATE_STOPPING || mPlayState == PLAYSTATE_PAUSED_STOPPING) {
+                if (writeMode == WRITE_NON_BLOCKING) {
+                    return false;
+                }
+                try {
+                    mPlayStateLock.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+            return true;
+        }
+    }
+
     //--------------------------------------------------------------------------
     // Audio effects management
     //--------------------
@@ -3293,6 +3396,22 @@
         public void handleMessage(Message msg) {
             final LinkedList<StreamEventCbInfo> cbInfoList;
             synchronized (mStreamEventCbLock) {
+                if (msg.what == NATIVE_EVENT_STREAM_END) {
+                    synchronized (mPlayStateLock) {
+                        if (mPlayState == PLAYSTATE_STOPPING) {
+                            if (mOffloadEosPending) {
+                                native_start();
+                                mPlayState = PLAYSTATE_PLAYING;
+                            } else {
+                                mAvSyncHeader = null;
+                                mAvSyncBytesRemaining = 0;
+                                mPlayState = PLAYSTATE_STOPPED;
+                            }
+                            mOffloadEosPending = false;
+                            mPlayStateLock.notify();
+                        }
+                    }
+                }
                 if (mStreamEventCbInfoList.size() == 0) {
                     return;
                 }
@@ -3560,7 +3679,6 @@
     private native int native_getPortId();
 
     private native void native_set_delay_padding(int delayInFrames, int paddingInFrames);
-    private native void native_set_eos();
 
     //---------------------------------------------------------
     // Utility methods
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
index 1c25866..c5d7728 100644
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
@@ -30,4 +30,5 @@
         android:strokeWidth="1"
         android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
   </group>
-</vector>
\ No newline at end of file
+</vector>
+
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
index e2fd862..25d1af3 100644
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
@@ -30,4 +30,4 @@
         android:strokeWidth="1"
         android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
   </group>
-</vector>
\ No newline at end of file
+</vector>
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index 6e7be06..095e2e9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -35,7 +35,7 @@
  */
 class CarNavigationBarView extends LinearLayout {
     private View mNavButtons;
-    private View mNotificationsButton;
+    private CarNavigationButton mNotificationsButton;
     private CarStatusBar mCarStatusBar;
     private Context mContext;
     private View mLockScreenButtons;
@@ -151,10 +151,20 @@
      * Nav buttons will be shown.
      */
     public void hideKeyguardButtons() {
-        if (mLockScreenButtons == null) {
-            return;
-        }
+        if (mLockScreenButtons == null) return;
+
         mNavButtons.setVisibility(View.VISIBLE);
         mLockScreenButtons.setVisibility(View.GONE);
     }
+
+    /**
+     * Toggles the notification unseen indicator on/off.
+     *
+     * @param hasUnseen true if the unseen notification count is great than 0.
+     */
+    void toggleNotificationUnseenIndicator(Boolean hasUnseen) {
+        if (mNotificationsButton == null) return;
+
+        mNotificationsButton.setUnseen(hasUnseen);
+    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
index e640baa..8879742 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
@@ -34,12 +34,17 @@
  * code.
  */
 public class CarNavigationButton extends com.android.keyguard.AlphaOptimizedImageButton {
-
     private static final String TAG = "CarNavigationButton";
+
+    private static final int UNSEEN_ICON_RESOURCE_ID = R.drawable.car_ic_notification_unseen;
+    private static final int UNSEEN_SELECTED_ICON_RESOURCE_ID =
+            R.drawable.car_ic_notification_selected_unseen;
+
     private Context mContext;
     private String mIntent;
     private String mLongIntent;
     private boolean mBroadcastIntent;
+    private boolean mHasUnseen = false;
     private boolean mSelected = false;
     private float mSelectedAlpha = 1f;
     private float mUnselectedAlpha = 1f;
@@ -50,6 +55,8 @@
     public CarNavigationButton(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
+
+        // CarNavigationButton attrs
         TypedArray typedArray = context.obtainStyledAttributes(
                 attrs, R.styleable.CarNavigationButton);
         mIntent = typedArray.getString(R.styleable.CarNavigationButton_intent);
@@ -59,10 +66,15 @@
                 R.styleable.CarNavigationButton_selectedAlpha, mSelectedAlpha);
         mUnselectedAlpha = typedArray.getFloat(
                 R.styleable.CarNavigationButton_unselectedAlpha, mUnselectedAlpha);
-        mIconResourceId = typedArray.getResourceId(
-                com.android.internal.R.styleable.ImageView_src, 0);
         mSelectedIconResourceId = typedArray.getResourceId(
                 R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
+        typedArray.recycle();
+
+        // ImageView attrs
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.ImageView);
+        mIconResourceId = a.getResourceId(com.android.internal.R.styleable.ImageView_src, 0);
+        a.recycle();
     }
 
 
@@ -119,6 +131,23 @@
         super.setSelected(selected);
         mSelected = selected;
         setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
-        setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+        updateImage();
+    }
+
+    /**
+     * @param hasUnseen true if should indicate if this is a Unseen state, false otherwise.
+     */
+    public void setUnseen(boolean hasUnseen) {
+        mHasUnseen = hasUnseen;
+        updateImage();
+    }
+
+    private void updateImage() {
+        if (mHasUnseen) {
+            setImageResource(mSelected ? UNSEEN_SELECTED_ICON_RESOURCE_ID
+                    : UNSEEN_ICON_RESOURCE_ID);
+        } else {
+            setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+        }
     }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 54e468ee..b6b34c7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -311,7 +311,6 @@
         return result;
     }
 
-
     @Override
     public void showKeyguard() {
         super.showKeyguard();
@@ -451,9 +450,21 @@
         mNotificationDataManager = new NotificationDataManager();
         mNotificationDataManager.setOnUnseenCountUpdateListener(
                 () -> {
-                    // TODO: Update Notification Icon based on unseen count
-                    Log.d(TAG, "unseen count: " +
-                            mNotificationDataManager.getUnseenNotificationCount());
+                    if (mNavigationBarView != null && mNotificationDataManager != null) {
+                        Boolean hasUnseen =
+                                mNotificationDataManager.getUnseenNotificationCount() > 0;
+                        if (mNavigationBarView != null) {
+                            mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+                        }
+
+                        if (mLeftNavigationBarView != null) {
+                            mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+                        }
+
+                        if (mRightNavigationBarView != null) {
+                            mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
+                        }
+                    }
                 });
 
         CarHeadsUpNotificationManager carHeadsUpNotificationManager =
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
index ad2bae8..55ab8d4 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
@@ -60,7 +60,6 @@
  */
 public class IpMemoryStoreService extends IIpMemoryStore.Stub {
     private static final String TAG = IpMemoryStoreService.class.getSimpleName();
-    private static final int MAX_CONCURRENT_THREADS = 4;
     private static final int DATABASE_SIZE_THRESHOLD = 10 * 1024 * 1024; //10MB
     private static final int MAX_DROP_RECORD_TIMES = 500;
     private static final int MIN_DELETE_NUM = 5;
@@ -107,23 +106,17 @@
             db = null;
         }
         mDb = db;
-        // The work-stealing thread pool executor will spawn threads as needed up to
-        // the max only when there is no free thread available. This generally behaves
-        // exactly like one would expect it intuitively :
-        // - When work arrives, it will spawn a new thread iff there are no available threads
-        // - When there is no work to do it will shutdown threads after a while (the while
-        //   being equal to 2 seconds (not configurable) when max threads are spun up and
-        //   twice as much for every one less thread)
-        // - When all threads are busy the work is enqueued and waits for any worker
-        //   to become available.
-        // Because the stealing pool is made for very heavily parallel execution of
-        // small tasks that spawn others, it creates a queue per thread that in this
-        // case is overhead. However, the three behaviors above make it a superior
-        // choice to cached or fixedThreadPoolExecutor, neither of which can actually
-        // enqueue a task waiting for a thread to be free. This can probably be solved
-        // with judicious subclassing of ThreadPoolExecutor, but that's a lot of dangerous
-        // complexity for little benefit in this case.
-        mExecutor = Executors.newWorkStealingPool(MAX_CONCURRENT_THREADS);
+        // The single thread executor guarantees that all work is executed sequentially on the
+        // same thread, and no two tasks can be active at the same time. This is required to
+        // ensure operations from multiple clients don't interfere with each other (in particular,
+        // operations involving a transaction must not run concurrently with other operations
+        // as the other operations might be taken as part of the transaction). By default, the
+        // single thread executor runs off an unbounded queue.
+        // TODO : investigate replacing this scheme with a scheme where each thread has its own
+        // instance of the database, as it may be faster. It is likely however that IpMemoryStore
+        // operations are mostly IO-bound anyway, and additional contention is unlikely to bring
+        // benefits. Alternatively, a read-write lock might increase throughput.
+        mExecutor = Executors.newSingleThreadExecutor();
         RegularMaintenanceJobService.schedule(mContext, this);
     }
 
diff --git a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index c1d6a05..64fe3a6 100644
--- a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -732,4 +732,25 @@
                             latch.countDown();
                         })));
     }
+
+    public void testTasksAreSerial() {
+        final long sleepTimeMs = 1000;
+        final long startTime = System.currentTimeMillis();
+        mService.retrieveNetworkAttributes("somekey", onNetworkAttributesRetrieved(
+                (status, key, attr) -> {
+                    assertTrue("Unexpected status : " + status.resultCode, status.isSuccess());
+                    try {
+                        Thread.sleep(sleepTimeMs);
+                    } catch (InterruptedException e) {
+                        fail("InterruptedException");
+                    }
+                }));
+        doLatched("Serial tasks timing out", latch ->
+                mService.retrieveNetworkAttributes("somekey", onNetworkAttributesRetrieved(
+                        (status, key, attr) -> {
+                            assertTrue("Unexpected status : " + status.resultCode,
+                                    status.isSuccess());
+                            assertTrue(System.currentTimeMillis() >= startTime + sleepTimeMs);
+                        })), DEFAULT_TIMEOUT_MS);
+    }
 }
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4c72f48..0e91839 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -578,6 +578,8 @@
     <string name="wifi_display_certification">Wireless display certification</string>
     <!-- Setting Checkbox title whether to enable WiFi Verbose Logging. [CHAR LIMIT=40] -->
     <string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string>
+    <!-- Setting Checkbox title whether to disable WiFi Scan Throttling. [CHAR LIMIT=40] -->
+    <string name="wifi_scan_throttling">Wi\u2011Fi scan throttling</string>
     <!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] -->
     <string name="mobile_data_always_on">Mobile data always active</string>
     <!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] -->
@@ -633,6 +635,8 @@
     <string name="wifi_display_certification_summary">Show options for wireless display certification</string>
     <!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] -->
     <string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string>
+    <!-- Setting Checkbox summary whether to disable Wifi scan throttling [CHAR LIMIT=NONE] -->
+    <string name="wifi_scan_throttling_summary">Reduces battery drain &amp; improves network performance</string>
     <!-- Label indicating network has been manually marked as metered -->
     <string name="wifi_metered_label">Metered</string>
     <!-- Label indicating network has been manually marked as unmetered -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/SignalStrengthUtil.java b/packages/SettingsLib/src/com/android/settingslib/net/SignalStrengthUtil.java
new file mode 100644
index 0000000..246f2ce
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/SignalStrengthUtil.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.net;
+
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+
+/**
+ * Utilities for dealing with signal strength.
+ */
+public class SignalStrengthUtil {
+    /**
+     * @return whether we should artificially inflate the signal strength and number of levels by 1
+     * bar for the subscription with the given id
+     */
+    public static boolean shouldInflateSignalStrength(Context context, int subscriptionId) {
+        return SubscriptionManager.getResourcesForSubId(context, subscriptionId)
+                .getBoolean(com.android.internal.R.bool.config_inflateSignalStrength);
+    }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index e00f204..a245086 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -22,7 +22,8 @@
         android:sharedUserId="android.uid.shell"
         >
 
-    <!-- Standard permissions granted to the shell. -->
+        <!-- Standard permissions granted to the shell. -->
+    <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.SEND_SMS" />
     <uses-permission android:name="android.permission.READ_SMS" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index bcdcb3b..9a04222 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -103,9 +103,12 @@
         <item name="android:colorControlActivated">?attr/wallpaperTextColor</item>
     </style>
 
-    <style name="keyguard_presentation_theme" parent="@android:style/Theme.Material.NoActionBar.Fullscreen">
-        <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
-        <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
+    <style name="Theme.SystemUI.KeyguardPresentation">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:colorBackground">@*android:color/background_material_dark</item>
     </style>
 
     <style name="TextAppearance.Keyguard" parent="Theme.SystemUI">
diff --git a/packages/SystemUI/res/values-sw320dp-land/dimens.xml b/packages/SystemUI/res/color/notification_guts_priority_button_bg_fill.xml
similarity index 62%
copy from packages/SystemUI/res/values-sw320dp-land/dimens.xml
copy to packages/SystemUI/res/color/notification_guts_priority_button_bg_fill.xml
index 2ec5abd..8ef1bf9 100644
--- a/packages/SystemUI/res/values-sw320dp-land/dimens.xml
+++ b/packages/SystemUI/res/color/notification_guts_priority_button_bg_fill.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2019 The Android Open Source Project
   ~
@@ -14,14 +13,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<resources>
 
-    <!-- Global actions grid -->
-    <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
-    <dimen name="global_actions_grid_horizontal_padding">0dp</dimen>
-
-    <dimen name="global_actions_grid_item_side_margin">4dp</dimen>
-    <dimen name="global_actions_grid_item_vertical_margin">5dp</dimen>
-
-</resources>
-
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true"
+          android:color="@color/notification_guts_priority_button_bg_fill_color_selected" />
+    <item android:color="@color/notification_guts_priority_button_bg_fill_color" />
+</selector>
diff --git a/packages/SystemUI/res/values-sw320dp-land/dimens.xml b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
similarity index 62%
copy from packages/SystemUI/res/values-sw320dp-land/dimens.xml
copy to packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
index 2ec5abd..7964609 100644
--- a/packages/SystemUI/res/values-sw320dp-land/dimens.xml
+++ b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2019 The Android Open Source Project
   ~
@@ -14,14 +13,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<resources>
 
-    <!-- Global actions grid -->
-    <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
-    <dimen name="global_actions_grid_horizontal_padding">0dp</dimen>
-
-    <dimen name="global_actions_grid_item_side_margin">4dp</dimen>
-    <dimen name="global_actions_grid_item_vertical_margin">5dp</dimen>
-
-</resources>
-
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true"
+          android:color="@color/notification_guts_priority_button_bg_stroke_color_selected" />
+    <item android:color="@color/notification_guts_priority_button_bg_stroke_color" />
+</selector>
diff --git a/packages/SystemUI/res/color/notification_guts_priority_contents.xml b/packages/SystemUI/res/color/notification_guts_priority_contents.xml
new file mode 100644
index 0000000..56c43f0
--- /dev/null
+++ b/packages/SystemUI/res/color/notification_guts_priority_contents.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true"
+          android:color="@color/notification_guts_priority_button_content_color_selected" />
+    <item android:color="@color/notification_guts_priority_button_content_color" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/button_border_selected.xml b/packages/SystemUI/res/drawable/button_border_selected.xml
deleted file mode 100644
index 01e7099..0000000
--- a/packages/SystemUI/res/drawable/button_border_selected.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <solid
-        android:color="@color/notification_guts_selection_bg" />
-    <stroke
-        android:width="1dp"
-        android:color="@color/GM2_grey_300"/>
-    <corners android:radius="@dimen/rect_button_radius" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/button_border_unselected.xml b/packages/SystemUI/res/drawable/button_border_unselected.xml
deleted file mode 100644
index b9c4ced..0000000
--- a/packages/SystemUI/res/drawable/button_border_unselected.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle"
-       android:color="@color/notification_guts_selection_bg">
-    <stroke
-        android:width="1dp"
-        android:color="@color/GM2_grey_300"/>
-
-    <corners android:radius="@dimen/rect_button_radius" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/button_ripple_radius.xml b/packages/SystemUI/res/drawable/button_ripple_radius.xml
deleted file mode 100644
index 5c2857a..0000000
--- a/packages/SystemUI/res/drawable/button_ripple_radius.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
-  -->
-<ripple
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="?android:attr/colorControlHighlight">
-    <item android:id="@android:id/mask">
-        <shape android:shape="rectangle">
-            <solid android:color="@color/notification_guts_selection_bg" />
-            <corners android:radius="@dimen/rect_button_radius" />
-        </shape>
-    </item>
-</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_volume_media.xml b/packages/SystemUI/res/drawable/ic_volume_media.xml
index c8fa3fb..99be7b6 100644
--- a/packages/SystemUI/res/drawable/ic_volume_media.xml
+++ b/packages/SystemUI/res/drawable/ic_volume_media.xml
@@ -22,6 +22,6 @@
 
     <path
         android:fillColor="#FFFFFFFF"
-        android:pathData="M12,3l0.01,10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55C7.79,13 6,14.79 6,17c0,2.21 1.79,4 4.01,4S14,19.21 14,17V7h4V3H12zM10.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C12.01,18.1 11.11,19 10.01,19z"/>
+        android:pathData="M12 3l0.01 10.55c-0.59-0.34-1.27-0.55-2-0.55C7.79 13 6 14.79 6 17s1.79 4 4.01 4S14 19.21 14 17V7h4V3h-6z" />
 
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_mute.xml b/packages/SystemUI/res/drawable/ic_volume_media_mute.xml
index 45b5b87..3a495963 100644
--- a/packages/SystemUI/res/drawable/ic_volume_media_mute.xml
+++ b/packages/SystemUI/res/drawable/ic_volume_media_mute.xml
@@ -22,9 +22,6 @@
 
     <path
         android:fillColor="#FFFFFFFF"
-        android:pathData="M21.19,21.19L14,14l-2,-2l-9.2,-9.2L1.39,4.22l8.79,8.79c-0.06,0 -0.12,-0.01 -0.18,-0.01C7.79,13 6,14.79 6,17c0,2.21 1.79,4 4.01,4S14,19.21 14,17v-0.17l5.78,5.78L21.19,21.19zM10.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C12.01,18.1 11.11,19 10.01,19z"/>
-    <path
-        android:fillColor="#FFffffff"
-        android:pathData="M14,11.17l0,-4.17l4,0l0,-4l-6,0l0,6.17z"/>
+        android:pathData="M21.19 21.19L14 14l-2-2-9.2-9.2-1.41 1.42 8.79 8.79c-0.06 0-0.12-0.01-0.18-0.01-2.21 0-4 1.79-4 4s1.79 4 4.01 4S14 19.21 14 17v-0.17l5.78 5.78 1.41-1.42zM14 11.17V7h4V3h-6v6.17z" />
 
 </vector>
diff --git a/packages/SystemUI/res/values-sw320dp-land/dimens.xml b/packages/SystemUI/res/drawable/notification_guts_priority_button_bg.xml
similarity index 63%
rename from packages/SystemUI/res/values-sw320dp-land/dimens.xml
rename to packages/SystemUI/res/drawable/notification_guts_priority_button_bg.xml
index 2ec5abd..a0e025f 100644
--- a/packages/SystemUI/res/values-sw320dp-land/dimens.xml
+++ b/packages/SystemUI/res/drawable/notification_guts_priority_button_bg.xml
@@ -14,14 +14,14 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<resources>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle" >
+    <solid
+        android:color="@color/notification_guts_priority_button_bg_fill" />
 
-    <!-- Global actions grid -->
-    <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
-    <dimen name="global_actions_grid_horizontal_padding">0dp</dimen>
+    <stroke
+        android:width="1dp"
+        android:color="@color/notification_guts_priority_button_bg_stroke" />
 
-    <dimen name="global_actions_grid_item_side_margin">4dp</dimen>
-    <dimen name="global_actions_grid_item_vertical_margin">5dp</dimen>
-
-</resources>
-
+    <corners android:radius="@dimen/rect_button_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid.xml b/packages/SystemUI/res/layout-land/global_actions_grid.xml
index 4619430..6a10812 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid.xml
@@ -28,10 +28,10 @@
             android:orientation="vertical"
             android:layout_marginTop="@dimen/global_actions_grid_side_margin"
             android:translationZ="@dimen/global_actions_translate"
-            android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingTop="@dimen/global_actions_grid_vertical_padding"
-            android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+            android:paddingLeft="@dimen/global_actions_grid_vertical_padding"
+            android:paddingRight="@dimen/global_actions_grid_vertical_padding"
+            android:paddingTop="@dimen/global_actions_grid_horizontal_padding"
+            android:paddingBottom="@dimen/global_actions_grid_horizontal_padding"
             android:background="?android:attr/colorBackgroundFloating"
         >
             <LinearLayout
@@ -60,10 +60,10 @@
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/global_actions_grid_side_margin"
             android:layout_marginBottom="@dimen/global_actions_grid_side_margin"
-            android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingTop="@dimen/global_actions_grid_vertical_padding"
-            android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+            android:paddingLeft="@dimen/global_actions_grid_vertical_padding"
+            android:paddingRight="@dimen/global_actions_grid_vertical_padding"
+            android:paddingTop="@dimen/global_actions_grid_horizontal_padding"
+            android:paddingBottom="@dimen/global_actions_grid_horizontal_padding"
             android:orientation="horizontal"
             android:layoutDirection="ltr"
             android:background="?android:attr/colorBackgroundFloating"
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
new file mode 100644
index 0000000..bc12338
--- /dev/null
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+     work around this for now with LinearLayouts. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:layout_marginTop="@dimen/global_actions_grid_item_side_margin"
+    android:layout_marginBottom="@dimen/global_actions_grid_item_side_margin"
+    android:layout_marginLeft="@dimen/global_actions_grid_item_vertical_margin"
+    android:layout_marginRight="@dimen/global_actions_grid_item_vertical_margin"
+>
+    <LinearLayout
+        android:layout_width="@dimen/global_actions_grid_item_height"
+        android:layout_height="@dimen/global_actions_grid_item_width"
+        android:gravity="top|center_horizontal"
+        android:orientation="vertical"
+    >
+        <ImageView
+            android:id="@*android:id/icon"
+            android:layout_width="@dimen/global_actions_grid_item_icon_width"
+            android:layout_height="@dimen/global_actions_grid_item_icon_height"
+            android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin"
+            android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin"
+            android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
+            android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
+            android:scaleType="centerInside"
+            android:tint="@color/global_actions_text"
+        />
+
+        <TextView
+            android:id="@*android:id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="marquee"
+            android:marqueeRepeatLimit="marquee_forever"
+            android:singleLine="true"
+            android:gravity="center"
+            android:textSize="12dp"
+            android:textColor="@color/global_actions_text"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+        />
+
+        <TextView
+            android:visibility="gone"
+            android:id="@*android:id/status"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textColor="@color/global_actions_text"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+        />
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
index 4ece03b..79c51d9 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
@@ -29,10 +29,10 @@
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/global_actions_grid_side_margin"
             android:layout_marginBottom="@dimen/global_actions_grid_side_margin"
-            android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingTop="@dimen/global_actions_grid_vertical_padding"
-            android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+            android:paddingLeft="@dimen/global_actions_grid_vertical_padding"
+            android:paddingRight="@dimen/global_actions_grid_vertical_padding"
+            android:paddingTop="@dimen/global_actions_grid_horizontal_padding"
+            android:paddingBottom="@dimen/global_actions_grid_horizontal_padding"
             android:orientation="horizontal"
             android:layoutDirection="rtl"
             android:background="?android:attr/colorBackgroundFloating"
@@ -49,10 +49,10 @@
             android:orientation="vertical"
             android:layout_marginBottom="@dimen/global_actions_grid_side_margin"
             android:translationZ="@dimen/global_actions_translate"
-            android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
-            android:paddingTop="@dimen/global_actions_grid_vertical_padding"
-            android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+            android:paddingLeft="@dimen/global_actions_grid_vertical_padding"
+            android:paddingRight="@dimen/global_actions_grid_vertical_padding"
+            android:paddingTop="@dimen/global_actions_grid_horizontal_padding"
+            android:paddingBottom="@dimen/global_actions_grid_horizontal_padding"
             android:background="?android:attr/colorBackgroundFloating"
         >
             <LinearLayout
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
index 3cefce0..4404c874 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -18,8 +18,8 @@
      work around this for now with LinearLayouts. -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/global_actions_grid_item_width"
-    android:layout_height="@dimen/global_actions_grid_item_height"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:gravity="center"
     android:layout_marginTop="@dimen/global_actions_grid_item_vertical_margin"
     android:layout_marginBottom="@dimen/global_actions_grid_item_vertical_margin"
@@ -41,7 +41,7 @@
             android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
             android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
             android:scaleType="centerInside"
-            android:alpha="?android:attr/primaryContentAlpha"
+            android:tint="@color/global_actions_text"
         />
 
         <TextView
@@ -53,6 +53,7 @@
             android:singleLine="true"
             android:gravity="center"
             android:textSize="12dp"
+            android:textColor="@color/global_actions_text"
             android:textAppearance="?android:attr/textAppearanceSmall"
         />
 
@@ -62,7 +63,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
-            android:textColor="?android:attr/textColorTertiary"
+            android:textColor="@color/global_actions_text"
             android:textAppearance="?android:attr/textAppearanceSmall"
         />
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 4b672ee..8ffa2d8 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -219,106 +219,127 @@
             android:gravity="center"
             android:orientation="vertical">
 
-            <RelativeLayout
+            <LinearLayout
                 android:id="@+id/alert"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:padding="@dimen/notification_importance_button_padding"
                 android:clickable="true"
-                android:focusable="true">
-                <ImageView
-                    android:id="@+id/alert_icon"
-                    android:src="@drawable/ic_notifications_alert"
-                    android:background="@android:color/transparent"
-                    android:layout_gravity="center"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:clickable="false"
-                    android:focusable="false"/>
-                <TextView
-                    android:id="@+id/alert_label"
+                android:focusable="true"
+                android:background="@drawable/notification_guts_priority_button_bg"
+                android:orientation="vertical">
+                <LinearLayout
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:ellipsize="end"
-                    android:maxLines="1"
-                    android:clickable="false"
-                    android:focusable="false"
-                    android:layout_toEndOf="@id/alert_icon"
-                    android:layout_marginStart="@dimen/notification_importance_drawable_padding"
-                    android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
-                    android:text="@string/notification_alert_title"/>
+                    android:orientation="horizontal"
+                    android:gravity="center"
+                    >
+                    <ImageView
+                        android:id="@+id/alert_icon"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:src="@drawable/ic_notifications_alert"
+                        android:background="@android:color/transparent"
+                        android:tint="@color/notification_guts_priority_contents"
+                        android:clickable="false"
+                        android:focusable="false"/>
+                    <TextView
+                        android:id="@+id/alert_label"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+                        android:layout_weight="1"
+                        android:ellipsize="end"
+                        android:maxLines="1"
+                        android:clickable="false"
+                        android:focusable="false"
+                        android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+                        android:text="@string/notification_alert_title"/>
+                </LinearLayout>
                 <TextView
                     android:id="@+id/alert_summary"
-                    android:visibility="gone"
-                    android:paddingTop="@dimen/notification_importance_button_padding"
-                    android:text="@string/notification_channel_summary_default"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
+                    android:visibility="gone"
+                    android:text="@string/notification_channel_summary_default"
                     android:clickable="false"
                     android:focusable="false"
                     android:ellipsize="end"
                     android:maxLines="2"
-                    android:layout_below="@id/alert_icon"
                     android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
-            </RelativeLayout>
+            </LinearLayout>
 
-            <RelativeLayout
+            <LinearLayout
                 android:id="@+id/silence"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:padding="@dimen/notification_importance_button_padding"
                 android:layout_marginTop="@dimen/notification_importance_button_separation"
+                android:padding="@dimen/notification_importance_button_padding"
                 android:clickable="true"
-                android:focusable="true">
-                <ImageView
-                    android:id="@+id/silence_icon"
-                    android:src="@drawable/ic_notifications_silence"
-                    android:background="@android:color/transparent"
-                    android:layout_gravity="center"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:clickable="false"
-                    android:focusable="false"/>
-                <TextView
-                    android:id="@+id/silence_label"
+                android:focusable="true"
+                android:background="@drawable/notification_guts_priority_button_bg"
+                android:orientation="vertical">
+                <LinearLayout
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:ellipsize="end"
-                    android:maxLines="1"
-                    android:clickable="false"
-                    android:focusable="false"
-                    android:layout_toEndOf="@id/silence_icon"
-                    android:layout_marginStart="@dimen/notification_importance_drawable_padding"
-                    android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
-                    android:text="@string/notification_silence_title"/>
+                    android:orientation="horizontal"
+                    android:gravity="center"
+                    >
+                    <ImageView
+                        android:id="@+id/silence_icon"
+                        android:src="@drawable/ic_notifications_silence"
+                        android:background="@android:color/transparent"
+                        android:tint="@color/notification_guts_priority_contents"
+                        android:layout_gravity="center"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:clickable="false"
+                        android:focusable="false"/>
+                    <TextView
+                        android:id="@+id/silence_label"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:ellipsize="end"
+                        android:maxLines="1"
+                        android:clickable="false"
+                        android:focusable="false"
+                        android:layout_toEndOf="@id/silence_icon"
+                        android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+                        android:textAppearance="@style/TextAppearance.NotificationImportanceButton"
+                        android:text="@string/notification_silence_title"/>
+                </LinearLayout>
                 <TextView
                     android:id="@+id/silence_summary"
-                    android:visibility="gone"
-                    android:paddingTop="@dimen/notification_importance_button_padding"
-                    android:text="@string/notification_channel_summary_default"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
+                    android:visibility="gone"
+                    android:text="@string/notification_channel_summary_low"
                     android:clickable="false"
                     android:focusable="false"
                     android:ellipsize="end"
                     android:maxLines="2"
-                    android:layout_below="@id/silence_icon"
                     android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
-            </RelativeLayout>
+            </LinearLayout>
 
         </LinearLayout>
 
         <RelativeLayout
             android:id="@+id/bottom_buttons"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/notification_guts_button_spacing" >
+            android:layout_height="60dp"
+            android:gravity="center_vertical"
+            android:paddingStart="4dp"
+            android:paddingEnd="4dp"
+            >
             <TextView
                 android:id="@+id/turn_off_notifications"
                 android:text="@string/inline_turn_off_notifications"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_alignParentStart="true"
+                android:gravity="start|center_vertical"
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
                 android:maxWidth="200dp"
@@ -329,10 +350,10 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_alignParentEnd="true"
-                android:gravity="center_vertical|end"
-                android:maxWidth="125dp"
+                android:gravity="end|center_vertical"
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
+                android:maxWidth="125dp"
                 style="@style/TextAppearance.NotificationInfo.Button"/>
         </RelativeLayout>
 
diff --git a/packages/SystemUI/res/layout/qs_carrier.xml b/packages/SystemUI/res/layout/qs_carrier.xml
index 8dd06f0..28b2d21 100644
--- a/packages/SystemUI/res/layout/qs_carrier.xml
+++ b/packages/SystemUI/res/layout/qs_carrier.xml
@@ -35,7 +35,7 @@
         android:layout_marginEnd="@dimen/qs_carrier_margin_width"
         android:visibility="gone" />
 
-    <view class="com.android.systemui.qs.QSCarrier$QSCarrierText"
+    <com.android.systemui.util.AutoMarqueeTextView
         android:id="@+id/qs_carrier_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml
index 1e16e5d1..c812489 100644
--- a/packages/SystemUI/res/layout/quick_settings_header_info.xml
+++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml
@@ -45,7 +45,7 @@
                     android:contentDescription="@string/accessibility_quick_settings_alarm_set"
                     android:visibility="gone"/>
 
-                <TextView
+                <com.android.systemui.util.AutoMarqueeTextView
                     android:id="@+id/next_alarm_text"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
@@ -77,7 +77,7 @@
                     android:layout_height="@dimen/qs_header_alarm_icon_size"
                     android:visibility="gone"/>
 
-                <TextView
+                <com.android.systemui.util.AutoMarqueeTextView
                     android:id="@+id/ringer_mode_text"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/start_contextual.xml b/packages/SystemUI/res/layout/start_contextual.xml
deleted file mode 100644
index e022c73..0000000
--- a/packages/SystemUI/res/layout/start_contextual.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             xmlns:systemui="http://schemas.android.com/apk/res-auto"
-             android:id="@+id/start_menu_container"
-             android:layout_width="@dimen/navigation_key_width"
-             android:layout_height="match_parent"
-             android:importantForAccessibility="no"
-             android:focusable="false"
-             android:clipChildren="false"
-             android:clipToPadding="false"
-             >
-    <include layout="@layout/rotate_suggestion"
-             android:layout_width="match_parent"
-             android:layout_height="match_parent"
-             android:visibility="invisible"
-    />
-    <include layout="@layout/back"
-             android:layout_width="match_parent"
-             android:layout_height="match_parent"
-             android:visibility="invisible"
-    />
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-mcc310-mnc410/config.xml b/packages/SystemUI/res/values-mcc310-mnc410/config.xml
deleted file mode 100644
index 26b9192..0000000
--- a/packages/SystemUI/res/values-mcc310-mnc410/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2019, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds. -->
-<resources>
-    <!-- Enable 5 bar signal strength icon -->
-    <bool name="config_inflateSignalStrength">true</bool>
-</resources>
-
diff --git a/packages/SystemUI/res/values-mcc311-mnc480/config.xml b/packages/SystemUI/res/values-mcc311-mnc480/config.xml
deleted file mode 100644
index 7dadae7..0000000
--- a/packages/SystemUI/res/values-mcc311-mnc480/config.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2017, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds. -->
-<resources>
-    <!-- Enable 5 bar signal strength icon -->
-    <bool name="config_inflateSignalStrength">true</bool>
-</resources>
-
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index addc10a..dd3073f 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -42,12 +42,16 @@
     <!-- The color of the text inside a notification -->
     <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
 
-    <color name="notification_guts_selection_bg">@color/GM2_grey_800</color>
-    <color name="notification_guts_selection_border">@color/GM2_grey_700</color>
     <color name="notification_guts_link_icon_tint">@color/GM2_grey_500</color>
-    <color name="notification_guts_sub_text_color">@color/GM2_grey_200</color>
+    <color name="notification_guts_sub_text_color">@color/GM2_grey_500</color>
     <color name="notification_guts_header_text_color">@color/GM2_grey_200</color>
-    <color name="notification_guts_button_color">@color/GM2_blue_200</color>
+    <color name="notification_guts_info_button_color">@color/GM2_blue_300</color>
+    <color name="notification_guts_priority_button_content_color">@color/GM2_grey_500</color>
+    <color name="notification_guts_priority_button_content_color_selected">@color/GM2_blue_300</color>
+    <color name="notification_guts_priority_button_bg_fill_color">@color/transparent</color>
+    <color name="notification_guts_priority_button_bg_fill_color_selected">@color/GM2_grey_800</color>
+    <color name="notification_guts_priority_button_bg_stroke_color">@color/GM2_grey_700</color>
+    <color name="notification_guts_priority_button_bg_stroke_color_selected">@color/GM2_grey_700</color>
 
     <color name="notification_section_header_label_color">@color/GM2_grey_200</color>
     <color name="notification_section_clear_all_btn_color">@color/GM2_grey_500</color>
diff --git a/packages/SystemUI/res/values-sw320dp/dimens.xml b/packages/SystemUI/res/values-sw320dp/dimens.xml
index 4390d35..2774554 100644
--- a/packages/SystemUI/res/values-sw320dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw320dp/dimens.xml
@@ -16,7 +16,7 @@
   -->
 <resources>
     <!-- Global actions grid -->
-    <dimen name="global_actions_grid_vertical_padding">0dp</dimen>
+    <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
     <dimen name="global_actions_grid_horizontal_padding">3dp</dimen>
 
     <dimen name="global_actions_grid_item_side_margin">5dp</dimen>
@@ -24,9 +24,9 @@
     <dimen name="global_actions_grid_item_width">64dp</dimen>
     <dimen name="global_actions_grid_item_height">64dp</dimen>
 
-    <dimen name="global_actions_grid_item_icon_width">18dp</dimen>
-    <dimen name="global_actions_grid_item_icon_height">18dp</dimen>
-    <dimen name="global_actions_grid_item_icon_top_margin">10dp</dimen>
+    <dimen name="global_actions_grid_item_icon_width">20dp</dimen>
+    <dimen name="global_actions_grid_item_icon_height">20dp</dimen>
+    <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen>
     <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen>
     <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen>
 
diff --git a/packages/SystemUI/res/values-sw392dp-land/dimens.xml b/packages/SystemUI/res/values-sw392dp-land/dimens.xml
deleted file mode 100644
index c718614..0000000
--- a/packages/SystemUI/res/values-sw392dp-land/dimens.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-<resources>
-
-    <!-- Global actions grid -->
-    <dimen name="global_actions_grid_vertical_padding">3dp</dimen>
-    <dimen name="global_actions_grid_horizontal_padding">0dp</dimen>
-
-    <dimen name="global_actions_grid_item_side_margin">6dp</dimen>
-    <dimen name="global_actions_grid_item_vertical_margin">10dp</dimen>
-
-</resources>
-
diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml
index 2557ff4..be1d952 100644
--- a/packages/SystemUI/res/values-sw392dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw392dp/dimens.xml
@@ -16,7 +16,7 @@
   -->
 <resources>
     <!-- Global actions grid -->
-    <dimen name="global_actions_grid_vertical_padding">0dp</dimen>
+    <dimen name="global_actions_grid_vertical_padding">6dp</dimen>
     <dimen name="global_actions_grid_horizontal_padding">3dp</dimen>
 
     <dimen name="global_actions_grid_item_side_margin">10dp</dimen>
@@ -26,7 +26,7 @@
 
     <dimen name="global_actions_grid_item_icon_width">22dp</dimen>
     <dimen name="global_actions_grid_item_icon_height">22dp</dimen>
-    <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen>
+    <dimen name="global_actions_grid_item_icon_top_margin">14dp</dimen>
     <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen>
     <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen>
 
diff --git a/packages/SystemUI/res/values-sw410dp-land/dimens.xml b/packages/SystemUI/res/values-sw410dp-land/dimens.xml
deleted file mode 100644
index 61ba2d0..0000000
--- a/packages/SystemUI/res/values-sw410dp-land/dimens.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-<resources>
-
-    <!-- Global actions grid -->
-    <dimen name="global_actions_grid_vertical_padding">4dp</dimen>
-    <dimen name="global_actions_grid_horizontal_padding">8dp</dimen>
-
-    <dimen name="global_actions_grid_item_side_margin">8dp</dimen>
-    <dimen name="global_actions_grid_item_vertical_margin">12dp</dimen>
-
-</resources>
-
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 811f1a3..3f84b32 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -39,16 +39,16 @@
     <color name="qs_customize_decoration">@color/GM2_grey_100</color>
 
     <!-- The color of the background in the separated list of the Global Actions menu -->
-    <color name="global_actions_separated_background">@color/GM2_grey_300</color>
+    <color name="global_actions_separated_background">#F5F5F5</color>
 
     <!-- The color of the background in the grid of the Global Actions menu -->
-    <color name="global_actions_grid_background">@color/GM2_grey_200</color>
+    <color name="global_actions_grid_background">#F1F3F4</color>
 
     <!-- The color of the text in the Global Actions menu -->
-    <color name="global_actions_text">@color/GM2_grey_900</color>
+    <color name="global_actions_text">@color/GM2_grey_700</color>
 
     <!-- The color of the text in the Global Actions menu -->
-    <color name="global_actions_alert_text">@color/GM2_red_500</color>
+    <color name="global_actions_alert_text">@color/GM2_red_700</color>
 
     <!-- Tint color for the content on the notification overflow card. -->
     <color name="keyguard_overflow_content_color">#ff686868</color>
@@ -91,14 +91,18 @@
     <!-- The "inside" of a notification, reached via longpress -->
     <color name="notification_guts_bg_color">@color/GM2_grey_50</color>
 
-    <color name="notification_guts_selection_bg">#FFFFFF</color>
-    <color name="notification_guts_selection_border">#4285F4</color>
     <color name="notification_guts_link_icon_tint">@color/GM2_grey_700</color>
     <color name="notification_guts_sub_text_color">@color/GM2_grey_700</color>
     <color name="notification_guts_header_text_color">@color/GM2_grey_900</color>
     <color name="notification_silence_color">#FF32c1de</color>
     <color name="notification_alert_color">#FFF87B2B</color>
-    <color name="notification_guts_button_color">@color/GM2_blue_700</color>
+    <color name="notification_guts_info_button_color">@color/GM2_blue_700</color>
+    <color name="notification_guts_priority_button_content_color">@color/GM2_grey_700</color>
+    <color name="notification_guts_priority_button_content_color_selected">@color/GM2_blue_700</color>
+    <color name="notification_guts_priority_button_bg_fill_color">@color/transparent</color>
+    <color name="notification_guts_priority_button_bg_fill_color_selected">#FFFFFF</color>
+    <color name="notification_guts_priority_button_bg_stroke_color">@color/GM2_grey_300</color>
+    <color name="notification_guts_priority_button_bg_stroke_color_selected">@color/GM2_blue_600</color>
 
     <color name="notification_section_header_label_color">@color/GM2_grey_900</color>
     <color name="notification_section_clear_all_btn_color">@color/GM2_grey_700</color>
@@ -185,8 +189,11 @@
 
     <color name="GM2_red_300">#F28B82</color>
     <color name="GM2_red_500">#B71C1C</color>
+    <color name="GM2_red_700">#C5221F</color>
 
     <color name="GM2_blue_200">#AECBFA</color>
+    <color name="GM2_blue_300">#8AB4F8</color>
+    <color name="GM2_blue_600">#1A73E8</color>
     <color name="GM2_blue_700">#1967D2</color>
 
     <color name="GM2_yellow_500">#FFFBBC04</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d774c55..19e7b73 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -331,7 +331,7 @@
     <!-- Nav bar button default ordering/layout -->
     <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
     <string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string>
-    <string name="config_navBarLayoutHandle" translatable="false">start_contextual[40AC];home_handle;ime_switcher[40AC]</string>
+    <string name="config_navBarLayoutHandle" translatable="false">back[40AC];home_handle;ime_switcher[40AC]</string>
 
     <bool name="quick_settings_show_full_alarm">false</bool>
 
@@ -417,10 +417,6 @@
          it has been expanded to reveal its children. -->
     <bool name="config_showGroupNotificationBgWhenExpanded">false</bool>
 
-    <!-- Whether to artificially interpret all signal strengths as
-         one bar higher than they actually are -->
-    <bool name="config_inflateSignalStrength">false</bool>
-
     <!-- Should we vibrate on an icon animation of the shelf. This should only be active if the
      vibrator is capable of subtle vibrations -->
     <bool name="config_vibrateOnIconAnimation">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ec538ff..c04d28a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -38,7 +38,6 @@
     <dimen name="navigation_handle_sample_horizontal_margin">10dp</dimen>
     <dimen name="navigation_home_handle_width">72dp</dimen>
 
-
     <!-- Size of the nav bar edge panels, should be greater to the
          edge sensitivity + the drag threshold -->
     <dimen name="navigation_edge_panel_width">70dp</dimen>
@@ -57,6 +56,9 @@
     <!-- Luminance change threshold that allows applying new value if difference was exceeded -->
     <item name="navigation_luminance_change_threshold" type="dimen" format="float">0.05</item>
 
+    <dimen name="floating_rotation_button_diameter">40dp</dimen>
+    <dimen name="floating_rotation_button_margin">4dp</dimen>
+
     <!-- Height of notification icons in the status bar -->
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
 
@@ -218,7 +220,7 @@
     <dimen name="notification_guts_option_horizontal_padding">15dp</dimen>
 
     <!-- The vertical space between items in the alert selections in the inline settings -->
-    <dimen name="notification_guts_option_vertical_padding">20dp</dimen>
+    <dimen name="notification_guts_option_vertical_padding">16dp</dimen>
 
     <!-- The vertical space between the alert selections in the inline settings -->
     <dimen name="notification_guts_option_vertical_margin">6dp</dimen>
@@ -227,7 +229,7 @@
     <dimen name="notification_importance_toggle_marginTop">28dp</dimen>
     <dimen name="notification_importance_toggle_marginBottom">28dp</dimen>
     <dimen name="notification_importance_text_marginTop">20dp</dimen>
-    <dimen name="notification_importance_button_separation">16dp</dimen>
+    <dimen name="notification_importance_button_separation">8dp</dimen>
     <dimen name="notification_importance_button_width">178dp</dimen>
     <dimen name="notification_importance_button_horiz_padding">28dp</dimen>
     <dimen name="notification_importance_drawable_padding">8dp</dimen>
@@ -236,8 +238,9 @@
     <dimen name="notification_importance_description_text">14sp</dimen>
     <dimen name="notification_importance_channel_text">16sp</dimen>
     <dimen name="notification_importance_channel_group_text">14sp</dimen>
-    <dimen name="notification_importance_button_text">16sp</dimen>
-    <dimen name="notification_importance_button_padding">14dp</dimen>
+    <dimen name="notification_importance_button_text">14sp</dimen>
+    <dimen name="notification_importance_button_padding">16dp</dimen>
+    <dimen name="notification_importance_button_description_top_margin">12dp</dimen>
     <dimen name="rect_button_radius">8dp</dimen>
 
     <!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
@@ -950,7 +953,7 @@
 
     <!-- Global actions grid layout -->
     <dimen name="global_actions_grid_side_margin">4dp</dimen>
-    <dimen name="global_actions_grid_container_bottom_margin">4dp</dimen>
+    <dimen name="global_actions_grid_container_bottom_margin">8dp</dimen>
 
     <!-- Used to workaround a bug where shadows are clipped during animations by expanding
          the bounds of the parent view. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9831250..8bc84c6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1677,25 +1677,16 @@
     <string name="inline_keep_showing_app">Keep showing notifications from this app?</string>
 
     <!-- [CHAR LIMIT=100] Notification Importance title -->
-    <string name="notification_silence_title">Gentle</string>
+    <string name="notification_silence_title">Silent</string>
 
     <!-- [CHAR LIMIT=100] Notification Importance title -->
-    <string name="notification_alert_title">Prioritized</string>
+    <string name="notification_alert_title">Alerting</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
-    <string name="notification_channel_summary_low">Helps you focus with notifications only in the pull-down shade. Always silent.</string>
-
-    <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
-    <string name="notification_channel_summary_low_status">Displays below priority notifications. Always silent.</string>
-
-    <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
-    <string name="notification_channel_summary_low_lock">Displays below priority notifications. Always silent.</string>
-
-    <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
-    <string name="notification_channel_summary_low_status_lock">Displays below priority notifications. Always silent.</string>
+    <string name="notification_channel_summary_low">Helps you focus without sound or vibration.</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
-    <string name="notification_channel_summary_default">Gets your attention with sound &amp; a status bar icon. Shows on lock screen.</string>
+    <string name="notification_channel_summary_default">Gets your attention with sound or vibration.</string>
 
     <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
     <string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9861750..b387793 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -314,11 +314,12 @@
         <item name="*android:errorColor">?android:attr/colorError</item>
     </style>
 
-    <style name="qs_theme" parent="@style/Theme.SystemUI">
+    <style name="qs_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
         <item name="lightIconTheme">@style/QSIconTheme</item>
         <item name="darkIconTheme">@style/QSIconTheme</item>
         <item name="android:colorError">@*android:color/error_color_material_dark</item>
         <item name="android:windowIsFloating">true</item>
+        <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
     </style>
 
     <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light">
@@ -459,9 +460,9 @@
     </style>
 
     <style name="TextAppearance.NotificationInfo.Button">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
-        <item name="android:textSize">16sp</item>
-        <item name="android:textColor">@color/notification_guts_button_color</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/notification_guts_info_button_color</item>
         <item name="android:background">@drawable/btn_borderless_rect</item>
         <item name="android:gravity">center_vertical</item>
         <item name="android:focusable">true</item>
@@ -469,21 +470,21 @@
 
     <style name="TextAppearance.NotificationImportanceChannel">
         <item name="android:textSize">@dimen/notification_importance_channel_text</item>
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
         <item name="android:textColor">@color/notification_guts_header_text_color</item>
         <item name="android:textSize">@dimen/notification_importance_channel_text</item>
     </style>
 
     <style name="TextAppearance.NotificationImportanceChannelGroup">
         <item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:textColor">@color/notification_guts_sub_text_color</item>
         <item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
     </style>
 
     <style name="TextAppearance.NotificationImportanceHeader">
         <item name="android:textSize">@dimen/notification_importance_header_text</item>
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:textColor">@color/notification_guts_header_text_color</item>
     </style>
 
@@ -496,18 +497,11 @@
 
     <style name="TextAppearance.NotificationImportanceButton">
         <item name="android:textSize">@dimen/notification_importance_button_text</item>
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textColor">@color/notification_guts_priority_contents</item>
         <item name="android:gravity">center</item>
     </style>
 
-    <style name="TextAppearance.NotificationImportanceButton.Selected" parent="TextAppearance.NotificationImportanceButton">
-        <item name="android:textColor">?android:attr/colorAccent</item>
-    </style>
-
-    <style name="TextAppearance.NotificationImportanceButton.Unselected" parent="TextAppearance.NotificationImportanceButton">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
-    </style>
-
     <style name="TextAppearance.HeadsUpStatusBarText"
            parent="@*android:style/TextAppearance.DeviceDefault.Notification.Info">
     </style>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 050655c..0ec60e5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -33,6 +33,7 @@
 import android.view.View;
 import android.view.WindowManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.phone.NavigationBarView;
@@ -234,7 +235,8 @@
 
     }
 
-    private final static class KeyguardPresentation extends Presentation {
+    @VisibleForTesting
+    static final class KeyguardPresentation extends Presentation {
         private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
         private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
         private final InjectionInflationController mInjectableInflater;
@@ -256,7 +258,7 @@
 
         KeyguardPresentation(Context context, Display display,
                 InjectionInflationController injectionInflater) {
-            super(context, display, R.style.keyguard_presentation_theme);
+            super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
             mInjectableInflater = injectionInflater;
             getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
             setCancelable(false);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 4ec79a6..392183b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bubbles;
 
+import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
@@ -40,8 +41,6 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -53,6 +52,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
+import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
 import android.util.Log;
@@ -138,7 +138,6 @@
 
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
-    private final IActivityTaskManager mActivityTaskManager;
     private final BubbleTaskStackListener mTaskStackListener;
     private BubbleStateChangeListener mStateChangeListener;
     private BubbleExpandListener mExpandListener;
@@ -250,7 +249,6 @@
         mStatusBarStateListener = new StatusBarStateListener();
         Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
 
-        mActivityTaskManager = ActivityTaskManager.getService();
         mTaskStackListener = new BubbleTaskStackListener();
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
 
@@ -509,6 +507,12 @@
                 updateBubble(entry);
             }
         }
+
+        @Override
+        public void onNotificationRankingUpdated(RankingMap rankingMap) {
+            // Forward to BubbleData to block any bubbles which should no longer be shown
+            mBubbleData.notificationRankingUpdated(rankingMap);
+        }
     };
 
     @SuppressWarnings("FieldCanBeLocal")
@@ -547,8 +551,11 @@
                     mNotificationEntryManager.performRemoveNotification(bubble.entry.notification,
                             UNDEFINED_DISMISS_REASON);
                 } else {
-                    // The notification is still in the shade but we've removed the bubble so
-                    // lets make sure NoMan knows it's not a bubble anymore
+                    // Update the flag for SysUI
+                    bubble.entry.notification.getNotification().flags &= ~FLAG_BUBBLE;
+
+                    // Make sure NoMan knows it's not a bubble anymore so anyone querying it will
+                    // get right result back
                     try {
                         mBarService.onNotificationBubbleChanged(bubble.getKey(),
                                 false /* isBubble */);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 6ab973e..5575b35 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -22,6 +22,8 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.RankingMap;
 import android.util.Log;
 import android.util.Pair;
 
@@ -114,6 +116,8 @@
     // State tracked during an operation -- keeps track of what listener events to dispatch.
     private Update mStateChange;
 
+    private NotificationListenerService.Ranking mTmpRanking;
+
     private TimeSource mTimeSource = System::currentTimeMillis;
 
     @Nullable
@@ -193,6 +197,31 @@
         dispatchPendingChanges();
     }
 
+    /**
+     * Called when NotificationListener has received adjusted notification rank and reapplied
+     * filtering and sorting. This is used to dismiss any bubbles which should no longer be shown
+     * due to changes in permissions on the notification channel or the global setting.
+     *
+     * @param rankingMap the updated ranking map from NotificationListenerService
+     */
+    public void notificationRankingUpdated(RankingMap rankingMap) {
+        if (mTmpRanking == null) {
+            mTmpRanking = new NotificationListenerService.Ranking();
+        }
+
+        String[] orderedKeys = rankingMap.getOrderedKeys();
+        for (int i = 0; i < orderedKeys.length; i++) {
+            String key = orderedKeys[i];
+            if (hasBubbleWithKey(key)) {
+                rankingMap.getRanking(key, mTmpRanking);
+                if (!mTmpRanking.canBubble()) {
+                    doRemove(key, BubbleController.DISMISS_BLOCKED);
+                }
+            }
+        }
+        dispatchPendingChanges();
+    }
+
     private void doAdd(Bubble bubble) {
         if (DEBUG) {
             Log.d(TAG, "doAdd: " + bubble);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 0abd25d..c63389a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -237,16 +237,16 @@
     private ViewClippingUtil.ClippingParameters mClippingParameters =
             new ViewClippingUtil.ClippingParameters() {
 
-        @Override
-        public boolean shouldFinish(View view) {
-            return false;
-        }
+                @Override
+                public boolean shouldFinish(View view) {
+                    return false;
+                }
 
-        @Override
-        public boolean isClippingEnablingAllowed(View view) {
-            return !mIsExpanded;
-        }
-    };
+                @Override
+                public boolean isClippingEnablingAllowed(View view) {
+                    return !mIsExpanded;
+                }
+            };
 
     /** Float property that 'drags' the flyout. */
     private final FloatPropertyCompat mFlyoutCollapseProperty =
@@ -291,7 +291,7 @@
     private boolean mSuppressFlyout = false;
 
     public BubbleStackView(Context context, BubbleData data,
-                           @Nullable SurfaceSynchronizer synchronizer) {
+            @Nullable SurfaceSynchronizer synchronizer) {
         super(context);
 
         mBubbleData = data;
@@ -699,6 +699,7 @@
         animateInFlyoutForBubble(bubble);
         requestUpdate();
         logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
+        updatePointerPosition();
     }
 
     // via BubbleData.Listener
@@ -714,6 +715,7 @@
         } else {
             Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
         }
+        updatePointerPosition();
     }
 
     // via BubbleData.Listener
@@ -730,7 +732,6 @@
         }
     }
 
-
     /**
      * Changes the currently selected bubble. If the stack is already expanded, the newly selected
      * bubble will be shown immediately. This does not change the expanded state or change the
@@ -1073,8 +1074,8 @@
                     (overscrollingPastDot ? collapsePercent - 1f : collapsePercent * -1)
                             * (overscrollingLeft ? -1 : 1)
                             * (mFlyout.getWidth() / (FLYOUT_OVERSCROLL_ATTENUATION_FACTOR
-                                // Attenuate the smaller dot less than the larger flyout.
-                                / (overscrollingPastDot ? 2 : 1)));
+                            // Attenuate the smaller dot less than the larger flyout.
+                            / (overscrollingPastDot ? 2 : 1)));
         }
 
         mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
@@ -1530,18 +1531,22 @@
         if (DEBUG) {
             Log.d(TAG, "updatePointerPosition()");
         }
-        Bubble expandedBubble = getExpandedBubble();
-        if (expandedBubble != null) {
-            BubbleView iconView = expandedBubble.iconView;
-            float bubbleLeft = iconView.getTranslationX();
-            float halfBubbleWidth = (iconView.getWidth() / 2f);
 
-            // Bubbles live in expanded view container (x includes expanded view padding).
-            // Pointer lives in expanded view, which has padding (x does not include padding).
-            // Remove padding when deriving pointer location from bubbles.
-            float pointerX = bubbleLeft - mExpandedViewPadding + halfBubbleWidth;
-            expandedBubble.expandedView.setPointerPosition((int) pointerX);
+        Bubble expandedBubble = getExpandedBubble();
+        if (expandedBubble == null) {
+            return;
         }
+
+        int index = getBubbleIndex(expandedBubble);
+        float bubbleLeftFromScreenLeft = mExpandedAnimationController.getBubbleLeft(index);
+        float halfBubble = mBubbleSize / 2f;
+
+        // Bubbles live in expanded view container (x includes expanded view padding).
+        // Pointer lives in expanded view, which has padding (x does not include padding).
+        // Remove padding when deriving pointer location from bubbles.
+        float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble - mExpandedViewPadding;
+
+        expandedBubble.expandedView.setPointerPosition(bubbleCenter);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index ae8043f..24337a3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -63,6 +63,8 @@
     private Point mDisplaySize;
     /** Size of dismiss target at bottom of screen. */
     private float mPipDismissHeight;
+    /** Max number of bubbles shown in row above expanded view.*/
+    private int mBubblesMaxRendered;
 
     /** Whether the dragged-out bubble is in the dismiss target. */
     private boolean mIndividualBubbleWithinDismissTarget = false;
@@ -112,32 +114,25 @@
         mStatusBarHeight =
                 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
         mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
+        mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
     }
 
     /**
      * Animates expanding the bubbles into a row along the top of the screen.
-     *
-     * @return The y-value to which the bubbles were expanded, in case that's useful.
      */
-    public float expandFromStack(PointF collapseTo, Runnable after) {
+    public void expandFromStack(PointF collapseTo, Runnable after) {
         animationsForChildrenFromIndex(
                 0, /* startIndex */
                 new ChildAnimationConfigurator() {
-                    // How much to translate the next bubble, so that it is not overlapping the
-                    // previous one.
-                    float mTranslateNextBubbleXBy = mExpandedViewPadding + mBubblePaddingPx;
-
                     @Override
                     public void configureAnimationForChildAtIndex(
                             int index, PhysicsAnimationLayout.PhysicsPropertyAnimator animation) {
-                        animation.position(mTranslateNextBubbleXBy, getExpandedY());
-                        mTranslateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx;
+                        animation.position(getBubbleLeft(index), getExpandedY());
                     }
             })
             .startAll(after);
 
         mCollapseToPoint = collapseTo;
-        return getExpandedY();
     }
 
     /** Animate collapsing the bubbles back to their stacked position. */
@@ -189,9 +184,7 @@
         final boolean draggedOutEnough =
                 y > getExpandedY() + mBubbleSizePx || y < getExpandedY() - mBubbleSizePx;
         if (draggedOutEnough != mBubbleDraggedOutEnough) {
-            animateStackByBubbleWidthsStartingFrom(
-                    /* numBubbleWidths */ draggedOutEnough ? -1 : 0,
-                    /* startIndex */ mLayout.indexOfChild(bubbleView) + 1);
+            updateBubblePositions();
             mBubbleDraggedOutEnough = draggedOutEnough;
         }
     }
@@ -200,17 +193,14 @@
     public void dismissDraggedOutBubble(Runnable after) {
         mIndividualBubbleWithinDismissTarget = false;
 
-        // Fill the space from the soon to be dismissed bubble.
-        animateStackByBubbleWidthsStartingFrom(
-                /* numBubbleWidths */ -1,
-                /* startIndex */ mLayout.indexOfChild(mBubbleDraggingOut) + 1);
-
         animationForChild(mBubbleDraggingOut)
                 .withStiffness(SpringForce.STIFFNESS_HIGH)
                 .scaleX(1.1f)
                 .scaleY(1.1f)
                 .alpha(0f, after)
                 .start();
+
+        updateBubblePositions();
     }
 
     /** Magnets the given bubble to the dismiss target. */
@@ -251,15 +241,13 @@
         final int index = mLayout.indexOfChild(bubbleView);
 
         animationForChildAtIndex(index)
-                .position(getXForChildAtIndex(index), getExpandedY())
-                .withPositionStartVelocities(velX, velY)
-                .start(() -> bubbleView.setTranslationZ(0f) /* after */);
-
-        animateStackByBubbleWidthsStartingFrom(
-                /* numBubbleWidths */ 0, /* startIndex */ index + 1);
+            .position(getBubbleLeft(index), getExpandedY())
+            .withPositionStartVelocities(velX, velY)
+            .start(() -> bubbleView.setTranslationZ(0f) /* after */);
 
         mBubbleDraggingOut = null;
         mBubbleDraggedOutEnough = false;
+        updateBubblePositions();
     }
 
     /**
@@ -337,8 +325,6 @@
 
     @Override
     void onChildAdded(View child, int index) {
-        // Pop in from the top.
-        // TODO: Reverse this when bubbles are at the bottom.
         child.setTranslationX(getXForChildAtIndex(index));
 
         animationForChild(child)
@@ -346,7 +332,7 @@
                         getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
                         getExpandedY() /* to */)
                 .start();
-        animateBubblesAfterIndexToCorrectX(index);
+        updateBubblePositions();
     }
 
     @Override
@@ -367,7 +353,7 @@
         }
 
         // Animate all the other bubbles to their new positions sans this bubble.
-        animateBubblesAfterIndexToCorrectX(index);
+        updateBubblePositions();
     }
 
     @Override
@@ -383,21 +369,18 @@
                 .start(() -> super.setChildVisibility(child, index, visibility) /* after */);
     }
 
-    /**
-     * Animates the bubbles after the given index to the X position they should be in according to
-     * {@link #getXForChildAtIndex}.
-     */
-    private void animateBubblesAfterIndexToCorrectX(int start) {
-        for (int i = start; i < mLayout.getChildCount(); i++) {
+    private void updateBubblePositions() {
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
             final View bubble = mLayout.getChildAt(i);
 
             // Don't animate the dragging out bubble, or it'll jump around while being dragged. It
             // will be snapped to the correct X value after the drag (if it's not dismissed).
-            if (!bubble.equals(mBubbleDraggingOut)) {
-                animationForChild(bubble)
-                        .translationX(getXForChildAtIndex(i))
-                        .start();
+            if (bubble.equals(mBubbleDraggingOut)) {
+                return;
             }
+            animationForChild(bubble)
+                    .translationX(getBubbleLeft(i))
+                    .start();
         }
     }
 
@@ -405,4 +388,34 @@
     private float getXForChildAtIndex(int index) {
         return mBubblePaddingPx + (mBubbleSizePx + mBubblePaddingPx) * index;
     }
+
+    /**
+     * @param index Bubble index in row.
+     * @return Bubble left x from left edge of screen.
+     */
+    public float getBubbleLeft(int index) {
+        float bubbleLeftFromRowLeft = index * (mBubbleSizePx + mBubblePaddingPx);
+        return getRowLeft() + bubbleLeftFromRowLeft;
+    }
+
+    private float getRowLeft() {
+        if (mLayout == null) {
+            return 0;
+        }
+        int bubbleCount = mLayout.getChildCount();
+        if (bubbleCount > mBubblesMaxRendered) {
+            // Only shown bubbles are relevant for calculating position.
+            bubbleCount = mBubblesMaxRendered;
+        }
+        // Width calculations.
+        double bubble = bubbleCount * mBubbleSizePx;
+        float gap = (bubbleCount - 1) * mBubblePaddingPx;
+        float row = gap + (float) bubble;
+
+        float halfRow = row / 2f;
+        float centerScreen = mDisplaySize.x / 2;
+        float rowLeftFromScreenLeft = centerScreen - halfRow;
+
+        return rowLeftFromScreenLeft;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
index c5bf402..4d18312 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
@@ -33,7 +33,7 @@
 public class QSCarrier extends LinearLayout {
 
     private View mMobileGroup;
-    private QSCarrierText mCarrierText;
+    private TextView mCarrierText;
     private ImageView mMobileSignal;
     private ImageView mMobileRoaming;
     private DualToneHandler mDualToneHandler;
@@ -111,43 +111,4 @@
     public void setCarrierText(CharSequence text) {
         mCarrierText.setText(text);
     }
-
-    /**
-     * TextView that changes its ellipsize value with its visibility.
-     */
-    public static class QSCarrierText extends TextView {
-
-        public QSCarrierText(Context context) {
-            super(context);
-        }
-
-        public QSCarrierText(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr) {
-            super(context, attrs, defStyleAttr);
-        }
-
-        public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr,
-                int defStyleRes) {
-            super(context, attrs, defStyleAttr, defStyleRes);
-        }
-
-        @Override
-        protected void onFinishInflate() {
-            setSelected(true);
-        }
-
-        @Override
-        protected void onVisibilityChanged(View changedView, int visibility) {
-            super.onVisibilityChanged(changedView, visibility);
-            // Only show marquee when visible
-            if (visibility == VISIBLE) {
-                setEllipsize(TextUtils.TruncateAt.MARQUEE);
-            } else {
-                setEllipsize(TextUtils.TruncateAt.END);
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 9d5871e..c375574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -169,6 +169,5 @@
     public interface NotificationSettingsListener {
 
         default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
-
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index fe8c6b7..1440803 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -194,7 +194,7 @@
             }
             final Notification.Action action =
                     statusBarNotification.getNotification().actions[actionIndex];
-            if (Objects.equals(action.actionIntent, actionIntent)) {
+            if (!Objects.equals(action.actionIntent, actionIntent)) {
                 Log.w(TAG, "actionIntent does not match");
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index a5a6d87..1aa6bc9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -16,6 +16,8 @@
 package com.android.systemui.statusbar.notification;
 
 import android.annotation.Nullable;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 
 import com.android.internal.statusbar.NotificationVisibility;
@@ -98,4 +100,14 @@
             @Nullable NotificationVisibility visibility,
             boolean removedByUser) {
     }
+
+    /**
+     * Called whenever notification ranking changes, in response to
+     * {@link NotificationListenerService#onNotificationRankingUpdate}. This is called after
+     * NotificationData has processed the update and notifications have been re-sorted and filtered.
+     *
+     * @param rankingMap provides access to ranking information on currently active notifications
+     */
+    default void onNotificationRankingUpdated(RankingMap rankingMap) {
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 3ac5768..e8388ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -483,6 +483,10 @@
         }
 
         updateNotifications();
+
+        for (NotificationEntryListener listener : mNotificationEntryListeners) {
+            listener.onNotificationRankingUpdated(rankingMap);
+        }
     }
 
     private void updateRankingOfPendingNotifications(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index c65e90e..35cc960 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -65,7 +65,7 @@
     private val mDozeParameters: DozeParameters;
     var willWakeUp = false
         set(value) {
-            if (value && mDozeAmount != 0.0f) {
+            if (!value || mDozeAmount != 0.0f) {
                 field = value
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index e4e8c80..1dc96b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -20,6 +20,10 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
+import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -40,11 +44,12 @@
 import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
-import android.transition.AutoTransition;
+import android.transition.ChangeBounds;
+import android.transition.Fade;
 import android.transition.TransitionManager;
+import android.transition.TransitionSet;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -62,6 +67,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 
+import java.lang.annotation.Retention;
 import java.util.List;
 import java.util.Set;
 
@@ -92,9 +98,8 @@
     // standard controls
     private static final int ACTION_ALERT = 5;
 
-    private static final int BUTTON_ANIM_TIME_MS = 200;
-
-    private static final boolean SHOW_BUTTON_SUMMARY = false;
+    private TextView mPriorityDescriptionView;
+    private TextView mSilentDescriptionView;
 
     private INotificationManager mINotificationManager;
     private PackageManager mPm;
@@ -110,8 +115,6 @@
     private NotificationChannel mSingleNotificationChannel;
     private int mStartingChannelImportance;
     private boolean mWasShownHighPriority;
-    private boolean mShowOnLockscreen;
-    private boolean mShowInStatusBar;
     private boolean mPressedApply;
 
     /**
@@ -129,8 +132,6 @@
     private OnSettingsClickListener mOnSettingsClickListener;
     private OnAppSettingsClickListener mAppSettingsClickListener;
     private NotificationGuts mGutsContainer;
-    private Drawable mSelectedBackground;
-    private Drawable mUnselectedBackground;
     private Drawable mPkgIcon;
 
     /** Whether this view is being shown as part of the blocking helper. */
@@ -145,16 +146,14 @@
     private OnClickListener mOnAlert = v -> {
         mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
         mChosenImportance = IMPORTANCE_DEFAULT;
-        setImportanceSummary(ACTION_ALERT, true);
-        updateButtons(ACTION_ALERT);
+        applyAlertingBehavior(BEHAVIOR_ALERTING, true /* userTriggered */);
     };
 
     // used by standard ui
     private OnClickListener mOnSilent = v -> {
         mExitReason = NotificationCounters.BLOCKING_HELPER_DELIVER_SILENTLY;
         mChosenImportance = IMPORTANCE_LOW;
-        setImportanceSummary(ACTION_TOGGLE_SILENT, true);
-        updateButtons(ACTION_TOGGLE_SILENT);
+        applyAlertingBehavior(BEHAVIOR_SILENT, true /* userTriggered */);
     };
 
     // used by standard ui
@@ -218,6 +217,14 @@
         super(context, attrs);
     }
 
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mPriorityDescriptionView = findViewById(R.id.alert_summary);
+        mSilentDescriptionView = findViewById(R.id.silence_summary);
+    }
+
     // Specify a CheckSaveListener to override when/if the user's changes are committed.
     public interface CheckSaveListener {
         // Invoked when importance has changed and the NotificationInfo wants to try to save it.
@@ -293,9 +300,6 @@
         mDelegatePkg = mSbn.getOpPkg();
         mIsDeviceProvisioned = isDeviceProvisioned;
 
-        mSelectedBackground = mContext.getDrawable(R.drawable.button_border_selected);
-        mUnselectedBackground = mContext.getDrawable(R.drawable.button_border_unselected);
-
         int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
                 pkg, mAppUid, false /* includeDeleted */);
         if (mNumUniqueChannelsInRow == 0) {
@@ -308,11 +312,6 @@
                     && numTotalChannels == 1;
         }
 
-        mShowInStatusBar = !mINotificationManager.shouldHideSilentStatusIcons(
-                mContext.getPackageName());
-        mShowOnLockscreen = Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0) == 1;
-
         bindHeader();
         bindChannelDetails();
 
@@ -376,13 +375,9 @@
         silent.setOnClickListener(mOnSilent);
         alert.setOnClickListener(mOnAlert);
 
-        if (mWasShownHighPriority) {
-            updateButtons(ACTION_ALERT);
-            setImportanceSummary(ACTION_ALERT, false);
-        } else {
-            updateButtons(ACTION_TOGGLE_SILENT);
-            setImportanceSummary(ACTION_TOGGLE_SILENT, false);
-        }
+        applyAlertingBehavior(
+                mWasShownHighPriority ? BEHAVIOR_ALERTING : BEHAVIOR_SILENT,
+                false /* userTriggered */);
     }
 
     private void bindHeader() {
@@ -545,66 +540,45 @@
         }
     }
 
-    private void updateButtons(int blockState) {
-        View silence = findViewById(R.id.silence);
-        View alert = findViewById(R.id.alert);
-        TextView done = findViewById(R.id.done);
-        switch (blockState) {
-            case ACTION_TOGGLE_SILENT:
-                updateButtons(silence, alert);
-                if (mWasShownHighPriority) {
-                    done.setText(R.string.inline_ok_button);
-                } else {
-                    done.setText(R.string.inline_done_button);
-                }
-                break;
-            case ACTION_ALERT:
-                updateButtons(alert, silence);
-                if (mWasShownHighPriority) {
-                    done.setText(R.string.inline_done_button);
-                } else {
-                    done.setText(R.string.inline_ok_button);
-                }
-                break;
-        }
-    }
-
-    private void updateButtons(View selected, View unselected) {
-        selected.setBackground(mSelectedBackground);
-        selected.setSelected(true);
-        unselected.setBackground(mUnselectedBackground);
-        unselected.setSelected(false);
-    }
-
-    void setImportanceSummary(int blockState, boolean userTriggered) {
+    private void applyAlertingBehavior(@AlertingBehavior int behavior, boolean userTriggered) {
         if (userTriggered) {
-            AutoTransition transition = new AutoTransition();
-            transition.setDuration(BUTTON_ANIM_TIME_MS);
+            TransitionSet transition = new TransitionSet();
+            transition.setOrdering(TransitionSet.ORDERING_TOGETHER);
+            transition.addTransition(new Fade(Fade.OUT))
+                    .addTransition(new ChangeBounds())
+                    .addTransition(
+                            new Fade(Fade.IN)
+                                    .setStartDelay(150)
+                                    .setDuration(200)
+                                    .setInterpolator(FAST_OUT_SLOW_IN));
+            transition.setDuration(350);
+            transition.setInterpolator(FAST_OUT_SLOW_IN);
             TransitionManager.beginDelayedTransition(this, transition);
         }
-        if (SHOW_BUTTON_SUMMARY) {
-            if (blockState == ACTION_ALERT) {
-                TextView view = findViewById(R.id.alert_summary);
-                view.setVisibility(VISIBLE);
-                findViewById(R.id.silence_summary).setVisibility(GONE);
-                view.setText(R.string.notification_channel_summary_default);
-            } else {
-                TextView view = findViewById(R.id.silence_summary);
-                view.setVisibility(VISIBLE);
-                findViewById(R.id.alert_summary).setVisibility(GONE);
-                if (mShowInStatusBar) {
-                    if (mShowOnLockscreen) {
-                        view.setText(R.string.notification_channel_summary_low_status_lock);
-                    } else {
-                        view.setText(R.string.notification_channel_summary_low_status);
-                    }
-                } else if (mShowOnLockscreen) {
-                    view.setText(R.string.notification_channel_summary_low_lock);
-                } else {
-                    view.setText(R.string.notification_channel_summary_low);
-                }
-            }
+
+        View alert = findViewById(R.id.alert);
+        View silence = findViewById(R.id.silence);
+
+        switch (behavior) {
+            case BEHAVIOR_ALERTING:
+                alert.setSelected(true);
+                silence.setSelected(false);
+                mPriorityDescriptionView.setVisibility(VISIBLE);
+                mSilentDescriptionView.setVisibility(GONE);
+                break;
+            case BEHAVIOR_SILENT:
+                alert.setSelected(false);
+                silence.setSelected(true);
+                mSilentDescriptionView.setVisibility(VISIBLE);
+                mPriorityDescriptionView.setVisibility(GONE);
+                break;
+            default:
+                throw new IllegalArgumentException("Unrecognized alerting behavior: " + behavior);
         }
+
+        boolean isAChange = mWasShownHighPriority != (behavior == BEHAVIOR_ALERTING);
+        TextView done = findViewById(R.id.done);
+        done.setText(isAChange ? R.string.inline_ok_button : R.string.inline_done_button);
     }
 
     private void saveImportanceAndExitReason(@NotificationInfoAction int action) {
@@ -883,4 +857,10 @@
                 .setSubtype(mIsForBlockingHelper ? MetricsEvent.BLOCKING_HELPER_DISPLAY
                         : MetricsEvent.BLOCKING_HELPER_UNKNOWN);
     }
+
+    @Retention(SOURCE)
+    @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT})
+    private @interface AlertingBehavior {}
+    private static final int BEHAVIOR_ALERTING = 0;
+    private static final int BEHAVIOR_SILENT = 1;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
new file mode 100644
index 0000000..6286d4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
@@ -0,0 +1,167 @@
+/*
+ * 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.statusbar.phone;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
+/** Containing logic for the rotation button on the physical left bottom corner of the screen. */
+public class FloatingRotationButton implements RotationButton {
+
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final KeyButtonView mKeyButtonView;
+    private final int mDiameter;
+    private final int mMargin;
+    private KeyButtonDrawable mKeyButtonDrawable;
+    private boolean mIsShowing;
+    private boolean mCanShow = true;
+
+    private RotationButtonController mRotationButtonController;
+
+    FloatingRotationButton(Context context) {
+        mContext = context;
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        mKeyButtonView = (KeyButtonView) LayoutInflater.from(mContext).inflate(
+                R.layout.rotate_suggestion, null);
+        mKeyButtonView.setVisibility(View.VISIBLE);
+
+        Resources resources = mContext.getResources();
+        mDiameter = resources.getDimensionPixelSize(R.dimen.floating_rotation_button_diameter);
+        mMargin = resources.getDimensionPixelSize(R.dimen.floating_rotation_button_margin);
+    }
+
+    @Override
+    public void setRotationButtonController(RotationButtonController rotationButtonController) {
+        mRotationButtonController = rotationButtonController;
+    }
+
+    @Override
+    public View getCurrentView() {
+        return mKeyButtonView;
+    }
+
+    @Override
+    public boolean show() {
+        if (!mCanShow || mIsShowing) {
+            return false;
+        }
+        mIsShowing = true;
+        int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(mDiameter, mDiameter,
+                mMargin, mMargin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.setTitle("FloatingRotationButton");
+        switch (mWindowManager.getDefaultDisplay().getRotation()) {
+            case Surface.ROTATION_0:
+                lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
+                break;
+            case Surface.ROTATION_90:
+                lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+                break;
+            case Surface.ROTATION_180:
+                lp.gravity = Gravity.TOP | Gravity.RIGHT;
+                break;
+            case Surface.ROTATION_270:
+                lp.gravity = Gravity.TOP | Gravity.LEFT;
+                break;
+            default:
+                break;
+        }
+        updateIcon();
+        mWindowManager.addView(mKeyButtonView, lp);
+        if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) {
+            mKeyButtonDrawable.resetAnimation();
+            mKeyButtonDrawable.startAnimation();
+        }
+        return true;
+    }
+
+    @Override
+    public boolean hide() {
+        if (!mIsShowing) {
+            return false;
+        }
+        mWindowManager.removeViewImmediate(mKeyButtonView);
+        mRotationButtonController.cleanUp();
+        mIsShowing = false;
+        return true;
+    }
+
+    @Override
+    public boolean isVisible() {
+        return mIsShowing;
+    }
+
+    @Override
+    public void updateIcon() {
+        if (!mIsShowing) {
+            return;
+        }
+        mKeyButtonDrawable = getImageDrawable();
+        mKeyButtonView.setImageDrawable(mKeyButtonDrawable);
+        mKeyButtonDrawable.setCallback(mKeyButtonView);
+        if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) {
+            mKeyButtonDrawable.resetAnimation();
+            mKeyButtonDrawable.startAnimation();
+        }
+    }
+
+    @Override
+    public void setOnClickListener(View.OnClickListener onClickListener) {
+        mKeyButtonView.setOnClickListener(onClickListener);
+    }
+
+    @Override
+    public void setOnHoverListener(View.OnHoverListener onHoverListener) {
+        mKeyButtonView.setOnHoverListener(onHoverListener);
+    }
+
+    @Override
+    public KeyButtonDrawable getImageDrawable() {
+        Context context = new ContextThemeWrapper(mContext.getApplicationContext(),
+                mRotationButtonController.getStyleRes());
+        return KeyButtonDrawable.create(context, R.drawable.ic_sysbar_rotate_button,
+                false /* shadow */, true /* hasOvalBg */);
+    }
+
+    @Override
+    public void setDarkIntensity(float darkIntensity) {
+        mKeyButtonView.setDarkIntensity(darkIntensity);
+    }
+
+    @Override
+    public void setCanShowRotationButton(boolean canShow) {
+        mCanShow = canShow;
+        if (!mCanShow) {
+            hide();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index c9ba76c..337c6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -188,7 +188,7 @@
         @Override
         public void onQuickStepStarted() {
             // Use navbar dragging as a signal to hide the rotate button
-            mNavigationBarView.getRotateSuggestionButton().setRotateSuggestionButtonState(false);
+            mNavigationBarView.getRotationButtonController().setRotateSuggestionButtonState(false);
 
             // Hide the notifications panel when quick step starts
             mStatusBar.collapsePanel(true /* animate */);
@@ -333,16 +333,16 @@
 
         // Currently there is no accelerometer sensor on non-default display.
         if (mIsOnDefaultDisplay) {
-            final RotationContextButton rotationButton =
-                    mNavigationBarView.getRotateSuggestionButton();
-            rotationButton.setListener(mRotationButtonListener);
-            rotationButton.addRotationCallback(mRotationWatcher);
+            mNavigationBarView.getRotateSuggestionButton().setListener(mRotationButtonListener);
+
+            final RotationButtonController rotationButtonController =
+                    mNavigationBarView.getRotationButtonController();
+            rotationButtonController.addRotationCallback(mRotationWatcher);
 
             // Reset user rotation pref to match that of the WindowManager if starting in locked
             // mode. This will automatically happen when switching from auto-rotate to locked mode.
-            if (display != null && rotationButton.isRotationLocked()) {
-                final int winRotation = display.getRotation();
-                rotationButton.setRotationLockedAtAngle(winRotation);
+            if (display != null && rotationButtonController.isRotationLocked()) {
+                rotationButtonController.setRotationLockedAtAngle(display.getRotation());
             }
         } else {
             mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
@@ -458,34 +458,34 @@
             if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
 
             updateSystemUiStateFlags(-1);
-            mNavigationBarView.getRotateSuggestionButton()
-                    .onNavigationBarWindowVisibilityChange(isNavBarWindowVisible());
+            mNavigationBarView.getRotationButtonController().onNavigationBarWindowVisibilityChange(
+                    isNavBarWindowVisible());
         }
     }
 
     @Override
     public void onRotationProposal(final int rotation, boolean isValid) {
         final int winRotation = mNavigationBarView.getDisplay().getRotation();
-        final boolean rotateSuggestionsDisabled = RotationContextButton
+        final boolean rotateSuggestionsDisabled = RotationButtonController
                 .hasDisable2RotateSuggestionFlag(mDisabledFlags2);
+        final RotationButtonController rotationButtonController =
+                mNavigationBarView.getRotationButtonController();
+        final RotationButton rotationButton = rotationButtonController.getRotationButton();
+
         if (RotationContextButton.DEBUG_ROTATION) {
             Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation)
                     + ", winRotation=" + Surface.rotationToString(winRotation)
                     + ", isValid=" + isValid + ", mNavBarWindowState="
                     + StatusBarManager.windowStateToString(mNavigationBarWindowState)
                     + ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled
-                    + ", isRotateButtonVisible=" + (mNavigationBarView == null ? "null" :
-                        mNavigationBarView.getRotateSuggestionButton().isVisible()));
+                    + ", isRotateButtonVisible=" + (mNavigationBarView == null ? "null"
+                    : rotationButton.isVisible()));
         }
 
         // Respect the disabled flag, no need for action as flag change callback will handle hiding
         if (rotateSuggestionsDisabled) return;
 
-        View rotationButton = mNavigationBarView.getRotateSuggestionButton().getCurrentView();
-        if (rotationButton != null && rotationButton.isAttachedToWindow()) {
-            mNavigationBarView.getRotateSuggestionButton()
-                    .onRotationProposal(rotation, winRotation, isValid);
-        }
+        rotationButtonController.onRotationProposal(rotation, winRotation, isValid);
     }
 
     /** Restores the System UI flags saved state to {@link NavigationBarFragment}. */
@@ -593,7 +593,7 @@
     private void setDisabled2Flags(int state2) {
         // Method only called on change of disable2 flags
         if (mNavigationBarView != null) {
-            mNavigationBarView.getRotateSuggestionButton().onDisable2FlagChanged(state2);
+            mNavigationBarView.getRotationButtonController().onDisable2FlagChanged(state2);
         }
     }
 
@@ -862,8 +862,8 @@
         boolean[] feedbackEnabled = new boolean[1];
         int a11yFlags = getA11yButtonState(feedbackEnabled);
 
-        mNavigationBarView.getRotateSuggestionButton()
-                .setAccessibilityFeedbackEnabled(feedbackEnabled[0]);
+        mNavigationBarView.getRotationButtonController().setAccessibilityFeedbackEnabled(
+                feedbackEnabled[0]);
 
         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index a12ae96..662c744 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -63,7 +63,6 @@
     public static final String RIGHT = "right";
     public static final String CONTEXTUAL = "contextual";
     public static final String IME_SWITCHER = "ime_switcher";
-    public static final String START_CONTEXTUAL = "start_contextual";
 
     public static final String GRAVITY_SEPARATOR = ";";
     public static final String BUTTON_SEPARATOR = ",";
@@ -395,8 +394,6 @@
             v = inflater.inflate(R.layout.home_handle, parent, false);
         } else if (IME_SWITCHER.equals(button)) {
             v = inflater.inflate(R.layout.ime_switcher, parent, false);
-        } else if (START_CONTEXTUAL.equals(button)) {
-            v = inflater.inflate(R.layout.start_contextual, parent, false);
         } else if (button.startsWith(KEY)) {
             String uri = extractImage(button);
             int code = extractKeycode(button);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 3b3336b..8a28c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -173,6 +173,7 @@
         for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
             buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
         }
+        mView.getRotationButtonController().setDarkIntensity(darkIntensity);
         if (mAutoDim) {
             applyLightsOut(false, true);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 5f61975..7682e8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -22,6 +22,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
 
 import android.animation.LayoutTransition;
@@ -130,7 +131,6 @@
     private boolean mImeVisible;
 
     private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
-    private final ContextualButtonGroup mStartContextualButtonGroup;
     private final ContextualButtonGroup mContextualButtonGroup;
     private Configuration mConfiguration;
     private Configuration mTmpLastConfiguration;
@@ -138,6 +138,8 @@
     private NavigationBarInflaterView mNavigationInflaterView;
     private RecentsOnboarding mRecentsOnboarding;
     private NotificationPanelView mPanelView;
+    private FloatingRotationButton mFloatingRotationButton;
+    private RotationButtonController mRotationButtonController;
 
     private NavBarTintController mTintController;
 
@@ -232,24 +234,12 @@
     private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
         // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
         // gestural mode, the entire nav bar should be touchable.
-        if (!QuickStepContract.isGesturalMode(mNavBarMode) || mImeVisible) {
+        if (!isGesturalMode(mNavBarMode) || mImeVisible) {
             info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
             return;
         }
         info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        RotationContextButton rotationContextButton = getRotateSuggestionButton();
-        // If the rotate suggestion button is not visible in fully gestural mode, the entire nav bar
-        // is not touchable so that the app underneath can be clicked through.
-        if (rotationContextButton.getVisibility() != VISIBLE) {
-            info.touchableRegion.setEmpty();
-        } else {
-            // Set the rotate suggestion button area to be touchable.
-            rotationContextButton.getCurrentView().getLocationInWindow(mTmpPosition);
-            Rect rect = new Rect(mTmpPosition[0], mTmpPosition[1],
-                    mTmpPosition[0] + mRotationButtonBounds.width(),
-                    mTmpPosition[1] + mRotationButtonBounds.height());
-            info.touchableRegion.union(rect);
-        }
+        info.touchableRegion.setEmpty();
     };
 
     public NavigationBarView(Context context, AttributeSet attrs) {
@@ -258,15 +248,14 @@
         mIsVertical = false;
         mLongClickableAccessibilityButton = false;
         mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
-        boolean isGesturalMode = QuickStepContract.isGesturalMode(mNavBarMode);
+        boolean isGesturalMode = isGesturalMode(mNavBarMode);
 
         // Set up the context group of buttons
         mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
         final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
                 R.drawable.ic_ime_switcher_default);
         final RotationContextButton rotateSuggestionButton = new RotationContextButton(
-                R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button, getContext(),
-                R.style.RotateButtonCCWStart90);
+                R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
         final ContextualButton accessibilityButton =
                 new ContextualButton(R.id.accessibility_button,
                         R.drawable.ic_sysbar_accessibility_button);
@@ -278,13 +267,12 @@
 
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
         mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
+        mFloatingRotationButton = new FloatingRotationButton(context);
+        mRotationButtonController = new RotationButtonController(context,
+                R.style.RotateButtonCCWStart90,
+                isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
 
         final ContextualButton backButton = new ContextualButton(R.id.back, 0);
-        mStartContextualButtonGroup = new ContextualButtonGroup(R.id.start_menu_container);
-        if (isGesturalMode) {
-            mStartContextualButtonGroup.addButton(rotateSuggestionButton);
-        }
-        mStartContextualButtonGroup.addButton(backButton);
 
         mConfiguration = new Configuration();
         mTmpLastConfiguration = new Configuration();
@@ -301,7 +289,6 @@
         mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
         mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
         mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
-        mButtonDispatchers.put(R.id.start_menu_container, mStartContextualButtonGroup);
         mDeadZone = new DeadZone(this);
 
         mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService);
@@ -390,6 +377,14 @@
         return mCurrentView;
     }
 
+    public RotationButtonController getRotationButtonController() {
+        return mRotationButtonController;
+    }
+
+    public FloatingRotationButton getFloatingRotationButton() {
+        return mFloatingRotationButton;
+    }
+
     public ButtonDispatcher getRecentsButton() {
         return mButtonDispatchers.get(R.id.recent_apps);
     }
@@ -414,10 +409,6 @@
         return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion);
     }
 
-    public ContextualButtonGroup getStartContextualButtonGroup() {
-        return mStartContextualButtonGroup;
-    }
-
     public ButtonDispatcher getHomeHandle() {
         return mButtonDispatchers.get(R.id.home_handle);
     }
@@ -454,7 +445,6 @@
         if (densityChange || dirChange) {
             mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
             mContextualButtonGroup.updateIcons();
-            mStartContextualButtonGroup.updateIcons();
         }
         if (orientationChange || densityChange || dirChange) {
             mBackIcon = getBackDrawable();
@@ -490,7 +480,7 @@
             return;
         }
 
-        if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+        if (isGesturalMode(mNavBarMode)) {
             drawable.setRotation(degrees);
             return;
         }
@@ -561,6 +551,7 @@
             mTransitionListener.onBackAltCleared();
         }
         mImeVisible = visible;
+        mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
     }
 
     public void setDisabledFlags(int disabledFlags) {
@@ -603,7 +594,7 @@
 
         mBarTransitions.reapplyDarkIntensity();
 
-        boolean disableHome = QuickStepContract.isGesturalMode(mNavBarMode)
+        boolean disableHome = isGesturalMode(mNavBarMode)
                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
 
         // TODO(b/113914868): investigation log for disappearing home button
@@ -613,7 +604,7 @@
         // Always disable recents when alternate car mode UI is active and for secondary displays.
         boolean disableRecent = isRecentsButtonDisabled();
 
-        boolean disableBack = !useAltBack && (QuickStepContract.isGesturalMode(mNavBarMode)
+        boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode)
                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
 
         // When screen pinning, don't hide back and home when connected service or back and
@@ -641,7 +632,6 @@
         }
 
         getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
-        mStartContextualButtonGroup.setButtonVisibility(R.id.back, !disableBack);
         getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
         getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
     }
@@ -782,7 +772,7 @@
 
         // Color adaption is tied with showing home handle, only avaliable if visible
         mTintController.onNavigationModeChanged(mNavBarMode);
-        if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+        if (isGesturalMode(mNavBarMode)) {
             mTintController.start();
         } else {
             mTintController.stop();
@@ -939,7 +929,7 @@
                 "onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
 
         final boolean newVertical = w > 0 && h > w
-                && !QuickStepContract.isGesturalMode(mNavBarMode);
+                && !isGesturalMode(mNavBarMode);
         if (newVertical != mIsVertical) {
             mIsVertical = newVertical;
             if (DEBUG) {
@@ -950,7 +940,7 @@
             notifyVerticalChangedListener(newVertical);
         }
 
-        if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+        if (isGesturalMode(mNavBarMode)) {
             // Update the nav bar background to match the height of the visible nav bar
             int height = mIsVertical
                     ? getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 9da75b6..7623dee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -1440,10 +1440,8 @@
         } else if (statusBarState == StatusBarState.KEYGUARD
                 || statusBarState == StatusBarState.SHADE_LOCKED) {
             mKeyguardBottomArea.setVisibility(View.VISIBLE);
-            mKeyguardBottomArea.setAlpha(1f);
         } else {
             mKeyguardBottomArea.setVisibility(View.GONE);
-            mKeyguardBottomArea.setAlpha(1f);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
new file mode 100644
index 0000000..2580c0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
@@ -0,0 +1,39 @@
+/*
+ * 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.statusbar.phone;
+
+import android.view.View;
+
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+
+/** Interface of a rotation button that interacts {@link RotationButtonController}. */
+interface RotationButton {
+    void setRotationButtonController(RotationButtonController rotationButtonController);
+    View getCurrentView();
+    boolean show();
+    boolean hide();
+    boolean isVisible();
+    void updateIcon();
+    void setOnClickListener(View.OnClickListener onClickListener);
+    void setOnHoverListener(View.OnHoverListener onHoverListener);
+    KeyButtonDrawable getImageDrawable();
+    void setDarkIntensity(float darkIntensity);
+    default void setCanShowRotationButton(boolean canShow) {}
+    default boolean acceptRotationProposal() {
+        return getCurrentView() != null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
new file mode 100644
index 0000000..1e5406f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
@@ -0,0 +1,478 @@
+/*
+ * 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.statusbar.phone;
+
+import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.StyleRes;
+import android.app.StatusBarManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.view.IRotationWatcher.Stub;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.policy.RotationLockController;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/** Contains logic that deals with showing a rotate suggestion button with animation. */
+public class RotationButtonController {
+
+    private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
+    private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
+
+    private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
+
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    private final ViewRippler mViewRippler = new ViewRippler();
+
+    private @StyleRes int mStyleRes;
+    private int mLastRotationSuggestion;
+    private boolean mPendingRotationSuggestion;
+    private boolean mHoveringRotationSuggestion;
+    private RotationLockController mRotationLockController;
+    private TaskStackListenerImpl mTaskStackListener;
+    private Consumer<Integer> mRotWatcherListener;
+    private boolean mIsNavigationBarShowing;
+
+    private final Runnable mRemoveRotationProposal =
+            () -> setRotateSuggestionButtonState(false /* visible */);
+    private final Runnable mCancelPendingRotationProposal =
+            () -> mPendingRotationSuggestion = false;
+    private Animator mRotateHideAnimator;
+    private boolean mAccessibilityFeedbackEnabled;
+
+    private final Context mContext;
+    private final RotationButton mRotationButton;
+
+    private final Stub mRotationWatcher = new Stub() {
+        @Override
+        public void onRotationChanged(final int rotation) throws RemoteException {
+            if (mRotationButton.getCurrentView() == null) {
+                return;
+            }
+
+            // We need this to be scheduled as early as possible to beat the redrawing of
+            // window in response to the orientation change.
+            Handler h = mRotationButton.getCurrentView().getHandler();
+            Message msg = Message.obtain(h, () -> {
+                // If the screen rotation changes while locked, potentially update lock to flow with
+                // new screen rotation and hide any showing suggestions.
+                if (mRotationLockController.isRotationLocked()) {
+                    if (shouldOverrideUserLockPrefs(rotation)) {
+                        setRotationLockedAtAngle(rotation);
+                    }
+                    setRotateSuggestionButtonState(false /* visible */, true /* forced */);
+                }
+
+                if (mRotWatcherListener != null) {
+                    mRotWatcherListener.accept(rotation);
+                }
+            });
+            msg.setAsynchronous(true);
+            h.sendMessageAtFrontOfQueue(msg);
+        }
+    };
+
+    /**
+     * Determines if rotation suggestions disabled2 flag exists in flag
+     * @param disable2Flags see if rotation suggestion flag exists in this flag
+     * @return whether flag exists
+     */
+    static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
+        return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
+    }
+
+    RotationButtonController(Context context, @StyleRes int style, RotationButton rotationButton) {
+        mContext = context;
+        mRotationButton = rotationButton;
+        mRotationButton.setRotationButtonController(this);
+
+        mStyleRes = style;
+        mIsNavigationBarShowing = true;
+        mRotationLockController = Dependency.get(RotationLockController.class);
+
+        // Register the task stack listener
+        mTaskStackListener = new TaskStackListenerImpl();
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+        mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
+        mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
+
+        try {
+            WindowManagerGlobal.getWindowManagerService()
+                    .watchRotation(mRotationWatcher, mContext.getDisplay().getDisplayId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    void cleanUp() {
+        // Unregister the task stack listener
+        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+
+        try {
+            WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    void addRotationCallback(Consumer<Integer> watcher) {
+        mRotWatcherListener = watcher;
+    }
+
+    void setAccessibilityFeedbackEnabled(boolean flag) {
+        mAccessibilityFeedbackEnabled = flag;
+    }
+
+    void setRotationLockedAtAngle(int rotationSuggestion) {
+        mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion);
+    }
+
+    public boolean isRotationLocked() {
+        return mRotationLockController.isRotationLocked();
+    }
+
+    void setRotateSuggestionButtonState(boolean visible) {
+        setRotateSuggestionButtonState(visible, false /* force */);
+    }
+
+    void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
+        // At any point the the button can become invisible because an a11y service became active.
+        // Similarly, a call to make the button visible may be rejected because an a11y service is
+        // active. Must account for this.
+        // Rerun a show animation to indicate change but don't rerun a hide animation
+        if (!visible && !mRotationButton.isVisible()) return;
+
+        final View view = mRotationButton.getCurrentView();
+        if (view == null) return;
+
+        final KeyButtonDrawable currentDrawable = mRotationButton.getImageDrawable();
+        if (currentDrawable == null) return;
+
+        // Clear any pending suggestion flag as it has either been nullified or is being shown
+        mPendingRotationSuggestion = false;
+        view.removeCallbacks(mCancelPendingRotationProposal);
+
+        // Handle the visibility change and animation
+        if (visible) { // Appear and change (cannot force)
+            // Stop and clear any currently running hide animations
+            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+                mRotateHideAnimator.cancel();
+            }
+            mRotateHideAnimator = null;
+
+            // Reset the alpha if any has changed due to hide animation
+            view.setAlpha(1f);
+
+            // Run the rotate icon's animation if it has one
+            if (currentDrawable.canAnimate()) {
+                currentDrawable.resetAnimation();
+                currentDrawable.startAnimation();
+            }
+
+            if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
+
+            // Set visibility unless a11y service is active.
+            mRotationButton.show();
+        } else { // Hide
+            mViewRippler.stop(); // Prevent any pending ripples, force hide or not
+
+            if (force) {
+                // If a hide animator is running stop it and make invisible
+                if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+                    mRotateHideAnimator.pause();
+                }
+                mRotationButton.hide();
+                return;
+            }
+
+            // Don't start any new hide animations if one is running
+            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+
+            ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
+            fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
+            fadeOut.setInterpolator(Interpolators.LINEAR);
+            fadeOut.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mRotationButton.hide();
+                }
+            });
+
+            mRotateHideAnimator = fadeOut;
+            fadeOut.start();
+        }
+    }
+
+    void setDarkIntensity(float darkIntensity) {
+        mRotationButton.setDarkIntensity(darkIntensity);
+    }
+
+    void onRotationProposal(int rotation, int windowRotation, boolean isValid) {
+        if (!mRotationButton.acceptRotationProposal()) {
+            return;
+        }
+
+        // This method will be called on rotation suggestion changes even if the proposed rotation
+        // is not valid for the top app. Use invalid rotation choices as a signal to remove the
+        // rotate button if shown.
+        if (!isValid) {
+            setRotateSuggestionButtonState(false /* visible */);
+            return;
+        }
+
+        final View currentView = mRotationButton.getCurrentView();
+
+        // If window rotation matches suggested rotation, remove any current suggestions
+        if (rotation == windowRotation) {
+            if (currentView != null) {
+                currentView.removeCallbacks(mRemoveRotationProposal);
+            }
+            setRotateSuggestionButtonState(false /* visible */);
+            return;
+        }
+
+        // Prepare to show the navbar icon by updating the icon style to change anim params
+        mLastRotationSuggestion = rotation; // Remember rotation for click
+        final boolean rotationCCW = isRotationAnimationCCW(windowRotation, rotation);
+        int style;
+        if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
+            style = rotationCCW ? R.style.RotateButtonCCWStart90 : R.style.RotateButtonCWStart90;
+        } else { // 90 or 270
+            style = rotationCCW ? R.style.RotateButtonCCWStart0 : R.style.RotateButtonCWStart0;
+        }
+        mStyleRes = style;
+        mRotationButton.updateIcon();
+
+        if (mIsNavigationBarShowing) {
+            // The navbar is visible so show the icon right away
+            showAndLogRotationSuggestion();
+        } else {
+            // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
+            // visible given some time limit.
+            mPendingRotationSuggestion = true;
+            if (currentView != null) {
+                currentView.removeCallbacks(mCancelPendingRotationProposal);
+                currentView.postDelayed(mCancelPendingRotationProposal,
+                        NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
+            }
+        }
+    }
+
+    void onDisable2FlagChanged(int state2) {
+        final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
+        if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
+    }
+
+    void onNavigationBarWindowVisibilityChange(boolean showing) {
+        if (mIsNavigationBarShowing != showing) {
+            mIsNavigationBarShowing = showing;
+
+            // If the navbar is visible, show the rotate button if there's a pending suggestion
+            if (showing && mPendingRotationSuggestion) {
+                showAndLogRotationSuggestion();
+            }
+        }
+    }
+
+    @StyleRes int getStyleRes() {
+        return mStyleRes;
+    }
+
+    RotationButton getRotationButton() {
+        return mRotationButton;
+    }
+
+    private void onRotateSuggestionClick(View v) {
+        mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
+        incrementNumAcceptedRotationSuggestionsIfNeeded();
+        setRotationLockedAtAngle(mLastRotationSuggestion);
+    }
+
+    private boolean onRotateSuggestionHover(View v, MotionEvent event) {
+        final int action = event.getActionMasked();
+        mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
+                || (action == MotionEvent.ACTION_HOVER_MOVE);
+        rescheduleRotationTimeout(true /* reasonHover */);
+        return false; // Must return false so a11y hover events are dispatched correctly.
+    }
+
+    private void onRotationSuggestionsDisabled() {
+        // Immediately hide the rotate button and clear any planned removal
+        setRotateSuggestionButtonState(false /* visible */, true /* force */);
+        if (mRotationButton.getCurrentView() != null) {
+            mRotationButton.getCurrentView().removeCallbacks(mRemoveRotationProposal);
+        }
+    }
+
+    private void showAndLogRotationSuggestion() {
+        setRotateSuggestionButtonState(true /* visible */);
+        rescheduleRotationTimeout(false /* reasonHover */);
+        mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
+    }
+
+    private boolean shouldOverrideUserLockPrefs(final int rotation) {
+        // Only override user prefs when returning to the natural rotation (normally portrait).
+        // Don't let apps that force landscape or 180 alter user lock.
+        return rotation == NATURAL_ROTATION;
+    }
+
+    private boolean isRotationAnimationCCW(int from, int to) {
+        // All 180deg WM rotation animations are CCW, match that
+        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
+        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
+        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
+        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
+        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
+        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
+        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
+        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
+        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
+        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
+        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
+        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
+        return false; // Default
+    }
+
+    private void rescheduleRotationTimeout(final boolean reasonHover) {
+        if (mRotationButton.getCurrentView() == null) {
+            return;
+        }
+
+        // May be called due to a new rotation proposal or a change in hover state
+        if (reasonHover) {
+            // Don't reschedule if a hide animator is running
+            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+            // Don't reschedule if not visible
+            if (!mRotationButton.isVisible()) return;
+        }
+
+        // Stop any pending removal
+        mRotationButton.getCurrentView().removeCallbacks(mRemoveRotationProposal);
+        // Schedule timeout
+        mRotationButton.getCurrentView().postDelayed(mRemoveRotationProposal,
+                computeRotationProposalTimeout());
+    }
+
+    private int computeRotationProposalTimeout() {
+        if (mAccessibilityFeedbackEnabled) return 10000;
+        if (mHoveringRotationSuggestion) return 8000;
+        return 5000;
+    }
+
+    private boolean isRotateSuggestionIntroduced() {
+        ContentResolver cr = mContext.getContentResolver();
+        return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
+                >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
+    }
+
+    private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
+        // Get the number of accepted suggestions
+        ContentResolver cr = mContext.getContentResolver();
+        final int numSuggestions = Settings.Secure.getInt(cr,
+                Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
+
+        // Increment the number of accepted suggestions only if it would change intro mode
+        if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
+            Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
+                    numSuggestions + 1);
+        }
+    }
+
+    private class TaskStackListenerImpl extends TaskStackChangeListener {
+        // Invalidate any rotation suggestion on task change or activity orientation change
+        // Note: all callbacks happen on main thread
+
+        @Override
+        public void onTaskStackChanged() {
+            setRotateSuggestionButtonState(false /* visible */);
+        }
+
+        @Override
+        public void onTaskRemoved(int taskId) {
+            setRotateSuggestionButtonState(false /* visible */);
+        }
+
+        @Override
+        public void onTaskMovedToFront(int taskId) {
+            setRotateSuggestionButtonState(false /* visible */);
+        }
+
+        @Override
+        public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
+            // Only hide the icon if the top task changes its requestedOrientation
+            // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
+            Optional.ofNullable(ActivityManagerWrapper.getInstance())
+                    .map(ActivityManagerWrapper::getRunningTask)
+                    .ifPresent(a -> {
+                        if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
+                    });
+        }
+    }
+
+    private class ViewRippler {
+        private static final int RIPPLE_OFFSET_MS = 50;
+        private static final int RIPPLE_INTERVAL_MS = 2000;
+        private View mRoot;
+
+        public void start(View root) {
+            stop(); // Stop any pending ripple animations
+
+            mRoot = root;
+
+            // Schedule pending ripples, offset the 1st to avoid problems with visibility change
+            mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
+            mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
+            mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
+            mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
+            mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
+        }
+
+        public void stop() {
+            if (mRoot != null) mRoot.removeCallbacks(mRipple);
+        }
+
+        private final Runnable mRipple = new Runnable() {
+            @Override
+            public void run() { // Cause the ripple to fire via false presses
+                if (!mRoot.isAttachedToWindow()) return;
+                mRoot.setPressed(true /* pressed */);
+                mRoot.setPressed(false /* pressed */);
+            }
+        };
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
index 7203e57..f1460a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
@@ -18,281 +18,30 @@
 
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
-import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
-import android.annotation.NonNull;
-import android.annotation.StyleRes;
-import android.app.StatusBarManager;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.provider.Settings;
 import android.view.ContextThemeWrapper;
-import android.view.IRotationWatcher.Stub;
-import android.view.MotionEvent;
-import android.view.Surface;
 import android.view.View;
-import android.view.WindowManagerGlobal;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
-import com.android.systemui.statusbar.policy.RotationLockController;
-
-import java.util.Optional;
-import java.util.function.Consumer;
 
 /** Containing logic for the rotation button in nav bar. */
 public class RotationContextButton extends ContextualButton implements
-        NavigationModeController.ModeChangedListener {
+        NavigationModeController.ModeChangedListener, RotationButton {
     public static final boolean DEBUG_ROTATION = false;
 
-    private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
-    private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
-
-    private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
-
-    private @StyleRes int mStyleRes;
-
-    private int mLastRotationSuggestion;
-    private boolean mPendingRotationSuggestion;
-    private boolean mHoveringRotationSuggestion;
-    private RotationLockController mRotationLockController;
-    private TaskStackListenerImpl mTaskStackListener;
-    private Consumer<Integer> mRotWatcherListener;
-    private boolean mIsNavigationBarShowing;
-
-    private final Runnable mRemoveRotationProposal =
-            () -> setRotateSuggestionButtonState(false /* visible */);
-    private final Runnable mCancelPendingRotationProposal =
-            () -> mPendingRotationSuggestion = false;
-    private Animator mRotateHideAnimator;
-    private boolean mAccessibilityFeedbackEnabled;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
+    private RotationButtonController mRotationButtonController;
 
-    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
-    private final ViewRippler mViewRippler = new ViewRippler();
-
-    private final Stub mRotationWatcher = new Stub() {
-        @Override
-        public void onRotationChanged(final int rotation) throws RemoteException {
-            if (getCurrentView() == null) {
-                return;
-            }
-
-            // We need this to be scheduled as early as possible to beat the redrawing of
-            // window in response to the orientation change.
-            Handler h = getCurrentView().getHandler();
-            Message msg = Message.obtain(h, () -> {
-                // If the screen rotation changes while locked, potentially update lock to flow with
-                // new screen rotation and hide any showing suggestions.
-                if (mRotationLockController.isRotationLocked()) {
-                    if (shouldOverrideUserLockPrefs(rotation)) {
-                        setRotationLockedAtAngle(rotation);
-                    }
-                    setRotateSuggestionButtonState(false /* visible */, true /* forced */);
-                }
-
-                if (mRotWatcherListener != null) {
-                    mRotWatcherListener.accept(rotation);
-                }
-            });
-            msg.setAsynchronous(true);
-            h.sendMessageAtFrontOfQueue(msg);
-        }
-    };
-
-    /**
-     * Determines if rotation suggestions disabled2 flag exists in flag
-     * @param disable2Flags see if rotation suggestion flag exists in this flag
-     * @return whether flag exists
-     */
-    static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
-        return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
-    }
-
-    public RotationContextButton(@IdRes int buttonResId, @DrawableRes int iconResId,
-            @NonNull Context context, @StyleRes int style) {
+    public RotationContextButton(@IdRes int buttonResId, @DrawableRes int iconResId) {
         super(buttonResId, iconResId);
-
-        mStyleRes = style;
-        mIsNavigationBarShowing = true;
-        mRotationLockController = Dependency.get(RotationLockController.class);
-
-        // Register the task stack listener
-        mTaskStackListener = new TaskStackListenerImpl();
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
-        setOnClickListener(this::onRotateSuggestionClick);
-        setOnHoverListener(this::onRotateSuggestionHover);
-
-        try {
-            WindowManagerGlobal.getWindowManagerService()
-                    .watchRotation(mRotationWatcher, context.getDisplay().getDisplayId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
     }
 
-    public void addRotationCallback(Consumer<Integer> watcher) {
-        mRotWatcherListener = watcher;
-    }
-
-    public void setRotateSuggestionButtonState(boolean visible) {
-        setRotateSuggestionButtonState(visible, false /* force */);
-    }
-
-    public void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
-        // At any point the the button can become invisible because an a11y service became active.
-        // Similarly, a call to make the button visible may be rejected because an a11y service is
-        // active. Must account for this.
-        // Rerun a show animation to indicate change but don't rerun a hide animation
-        if (!visible && !isVisible()) return;
-
-        final View view = getCurrentView();
-        if (view == null) return;
-
-        final KeyButtonDrawable currentDrawable = getImageDrawable();
-        if (currentDrawable == null) return;
-
-        // Clear any pending suggestion flag as it has either been nullified or is being shown
-        mPendingRotationSuggestion = false;
-        view.removeCallbacks(mCancelPendingRotationProposal);
-
-        // Handle the visibility change and animation
-        if (visible) { // Appear and change (cannot force)
-            // Stop and clear any currently running hide animations
-            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
-                mRotateHideAnimator.cancel();
-            }
-            mRotateHideAnimator = null;
-
-            // Reset the alpha if any has changed due to hide animation
-            view.setAlpha(1f);
-
-            // Run the rotate icon's animation if it has one
-            if (currentDrawable.canAnimate()) {
-                currentDrawable.resetAnimation();
-                currentDrawable.startAnimation();
-            }
-
-            if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
-
-            // Set visibility unless a11y service is active.
-            show();
-        } else { // Hide
-            mViewRippler.stop(); // Prevent any pending ripples, force hide or not
-
-            if (force) {
-                // If a hide animator is running stop it and make invisible
-                if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
-                    mRotateHideAnimator.pause();
-                }
-                hide();
-                return;
-            }
-
-            // Don't start any new hide animations if one is running
-            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
-
-            ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
-            fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
-            fadeOut.setInterpolator(Interpolators.LINEAR);
-            fadeOut.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    hide();
-                }
-            });
-
-            mRotateHideAnimator = fadeOut;
-            fadeOut.start();
-        }
-    }
-
-    public void setAccessibilityFeedbackEnabled(boolean flag) {
-        mAccessibilityFeedbackEnabled = flag;
-    }
-
-    public void setRotationLockedAtAngle(int rotationSuggestion) {
-        mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion);
-    }
-
-    public boolean isRotationLocked() {
-        return mRotationLockController.isRotationLocked();
-    }
-
-    public void onRotationProposal(int rotation, int windowRotation, boolean isValid) {
-        // This method will be called on rotation suggestion changes even if the proposed rotation
-        // is not valid for the top app. Use invalid rotation choices as a signal to remove the
-        // rotate button if shown.
-        if (!isValid) {
-            setRotateSuggestionButtonState(false /* visible */);
-            return;
-        }
-
-        // If window rotation matches suggested rotation, remove any current suggestions
-        if (rotation == windowRotation) {
-            if (getCurrentView() != null) {
-                getCurrentView().removeCallbacks(mRemoveRotationProposal);
-            }
-            setRotateSuggestionButtonState(false /* visible */);
-            return;
-        }
-
-        // Prepare to show the navbar icon by updating the icon style to change anim params
-        mLastRotationSuggestion = rotation; // Remember rotation for click
-        final boolean rotationCCW = isRotationAnimationCCW(windowRotation, rotation);
-        int style;
-        if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
-            style = rotationCCW ? R.style.RotateButtonCCWStart90 : R.style.RotateButtonCWStart90;
-        } else { // 90 or 270
-            style = rotationCCW ? R.style.RotateButtonCCWStart0 : R.style.RotateButtonCWStart0;
-        }
-        mStyleRes = style;
-        updateIcon();
-
-        if (mIsNavigationBarShowing) {
-            // The navbar is visible so show the icon right away
-            showAndLogRotationSuggestion();
-        } else {
-            // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
-            // visible given some time limit.
-            mPendingRotationSuggestion = true;
-            if (getCurrentView() != null) {
-                getCurrentView().removeCallbacks(mCancelPendingRotationProposal);
-                getCurrentView().postDelayed(mCancelPendingRotationProposal,
-                        NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
-            }
-        }
-    }
-
-    public void onDisable2FlagChanged(int state2) {
-        final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
-        if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
-    }
-
-    public void onNavigationBarWindowVisibilityChange(boolean showing) {
-        if (mIsNavigationBarShowing != showing) {
-            mIsNavigationBarShowing = showing;
-
-            // If the navbar is visible, show the rotate button if there's a pending suggestion
-            if (showing && mPendingRotationSuggestion) {
-                showAndLogRotationSuggestion();
-            }
-        }
+    @Override
+    public void setRotationButtonController(RotationButtonController rotationButtonController) {
+        mRotationButtonController = rotationButtonController;
     }
 
     @Override
@@ -309,116 +58,15 @@
 
     @Override
     protected KeyButtonDrawable getNewDrawable() {
-        Context context = new ContextThemeWrapper(getContext().getApplicationContext(), mStyleRes);
+        Context context = new ContextThemeWrapper(getContext().getApplicationContext(),
+                mRotationButtonController.getStyleRes());
         return KeyButtonDrawable.create(context, mIconResId, false /* shadow */,
                 QuickStepContract.isGesturalMode(mNavBarMode));
     }
 
     @Override
     public void onDestroy() {
-        // Unregister the task stack listener
-        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
-
-        try {
-            WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private void onRotateSuggestionClick(View v) {
-        mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
-        incrementNumAcceptedRotationSuggestionsIfNeeded();
-        setRotationLockedAtAngle(mLastRotationSuggestion);
-    }
-
-    private boolean onRotateSuggestionHover(View v, MotionEvent event) {
-        final int action = event.getActionMasked();
-        mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
-                || (action == MotionEvent.ACTION_HOVER_MOVE);
-        rescheduleRotationTimeout(true /* reasonHover */);
-        return false; // Must return false so a11y hover events are dispatched correctly.
-    }
-
-    private void onRotationSuggestionsDisabled() {
-        // Immediately hide the rotate button and clear any planned removal
-        setRotateSuggestionButtonState(false /* visible */, true /* force */);
-        if (getCurrentView() != null) {
-            getCurrentView().removeCallbacks(mRemoveRotationProposal);
-        }
-    }
-
-    private void showAndLogRotationSuggestion() {
-        setRotateSuggestionButtonState(true /* visible */);
-        rescheduleRotationTimeout(false /* reasonHover */);
-        mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
-    }
-
-    private boolean shouldOverrideUserLockPrefs(final int rotation) {
-        // Only override user prefs when returning to the natural rotation (normally portrait).
-        // Don't let apps that force landscape or 180 alter user lock.
-        return rotation == NATURAL_ROTATION;
-    }
-
-    private boolean isRotationAnimationCCW(int from, int to) {
-        // All 180deg WM rotation animations are CCW, match that
-        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
-        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
-        if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
-        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
-        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
-        if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
-        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
-        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
-        if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
-        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
-        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
-        if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
-        return false; // Default
-    }
-
-    private void rescheduleRotationTimeout(final boolean reasonHover) {
-        if (getCurrentView() == null) {
-            return;
-        }
-
-        // May be called due to a new rotation proposal or a change in hover state
-        if (reasonHover) {
-            // Don't reschedule if a hide animator is running
-            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
-            // Don't reschedule if not visible
-            if (!isVisible()) return;
-        }
-
-        // Stop any pending removal
-        getCurrentView().removeCallbacks(mRemoveRotationProposal);
-        // Schedule timeout
-        getCurrentView().postDelayed(mRemoveRotationProposal, computeRotationProposalTimeout());
-    }
-
-    private int computeRotationProposalTimeout() {
-        if (mAccessibilityFeedbackEnabled) return 10000;
-        if (mHoveringRotationSuggestion) return 8000;
-        return 5000;
-    }
-
-    private boolean isRotateSuggestionIntroduced() {
-        ContentResolver cr = getContext().getContentResolver();
-        return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
-                >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
-    }
-
-    private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
-        // Get the number of accepted suggestions
-        ContentResolver cr = getContext().getContentResolver();
-        final int numSuggestions = Settings.Secure.getInt(cr,
-                Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
-
-        // Increment the number of accepted suggestions only if it would change intro mode
-        if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
-            Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
-                    numSuggestions + 1);
-        }
+        mRotationButtonController.cleanUp();
     }
 
     @Override
@@ -426,66 +74,9 @@
         mNavBarMode = mode;
     }
 
-    private class TaskStackListenerImpl extends TaskStackChangeListener {
-        // Invalidate any rotation suggestion on task change or activity orientation change
-        // Note: all callbacks happen on main thread
-
-        @Override
-        public void onTaskStackChanged() {
-            setRotateSuggestionButtonState(false /* visible */);
-        }
-
-        @Override
-        public void onTaskRemoved(int taskId) {
-            setRotateSuggestionButtonState(false /* visible */);
-        }
-
-        @Override
-        public void onTaskMovedToFront(int taskId) {
-            setRotateSuggestionButtonState(false /* visible */);
-        }
-
-        @Override
-        public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
-            // Only hide the icon if the top task changes its requestedOrientation
-            // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
-            Optional.ofNullable(ActivityManagerWrapper.getInstance())
-                    .map(ActivityManagerWrapper::getRunningTask)
-                    .ifPresent(a -> {
-                        if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
-                    });
-        }
-    }
-
-    private class ViewRippler {
-        private static final int RIPPLE_OFFSET_MS = 50;
-        private static final int RIPPLE_INTERVAL_MS = 2000;
-        private View mRoot;
-
-        public void start(View root) {
-            stop(); // Stop any pending ripple animations
-
-            mRoot = root;
-
-            // Schedule pending ripples, offset the 1st to avoid problems with visibility change
-            mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
-            mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
-            mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
-            mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
-            mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
-        }
-
-        public void stop() {
-            if (mRoot != null) mRoot.removeCallbacks(mRipple);
-        }
-
-        private final Runnable mRipple = new Runnable() {
-            @Override
-            public void run() { // Cause the ripple to fire via false presses
-                if (!mRoot.isAttachedToWindow()) return;
-                mRoot.setPressed(true /* pressed */);
-                mRoot.setPressed(false /* pressed */);
-            }
-        };
+    @Override
+    public boolean acceptRotationProposal() {
+        View currentView = getCurrentView();
+        return currentView != null && currentView.isAttachedToWindow();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index a5bb92d..da1ef2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -71,6 +71,7 @@
     public CastControllerImpl(Context context) {
         mContext = context;
         mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mMediaRouter.setRouterGroupId(MediaRouter.MIRRORING_GROUP_ID);
         mProjectionManager = (MediaProjectionManager)
                 context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
         mProjection = mProjectionManager.getActiveProjectionInfo();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index c08390f..2afe485 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -38,6 +38,7 @@
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.settingslib.Utils;
 import com.android.settingslib.graph.SignalDrawable;
+import com.android.settingslib.net.SignalStrengthUtil;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -248,9 +249,8 @@
     }
 
     private void updateInflateSignalStrength() {
-        mInflateSignalStrengths = SubscriptionManager.getResourcesForSubId(mContext,
-               mSubscriptionInfo.getSubscriptionId())
-               .getBoolean(R.bool.config_inflateSignalStrength);
+        mInflateSignalStrengths = SignalStrengthUtil.shouldInflateSignalStrength(mContext,
+                mSubscriptionInfo.getSubscriptionId());
     }
 
     private int getNumLevels() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index b8a14ef..b2972fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -1140,7 +1140,8 @@
                     res.getBoolean(com.android.internal.R.bool.config_alwaysUseCdmaRssi);
             config.hspaDataDistinguishable =
                     res.getBoolean(R.bool.config_hspa_data_distinguishable);
-            config.inflateSignalStrengths = res.getBoolean(R.bool.config_inflateSignalStrength);
+            config.inflateSignalStrengths = res.getBoolean(
+                    com.android.internal.R.bool.config_inflateSignalStrength);
 
             CarrierConfigManager configMgr = (CarrierConfigManager)
                     context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java b/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java
new file mode 100644
index 0000000..d9d410d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/AutoMarqueeTextView.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/**
+ * TextView that changes its ellipsize value with its visibility.
+ *
+ * The View responds to changes in user-visibility to change its ellipsize from MARQUEE to END
+ * and back. Useful for TextView that need to marquee forever.
+ */
+public class AutoMarqueeTextView extends TextView {
+
+    private boolean mAggregatedVisible = false;
+
+    public AutoMarqueeTextView(Context context) {
+        super(context);
+    }
+
+    public AutoMarqueeTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AutoMarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AutoMarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        setSelected(true);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        setSelected(false);
+    }
+
+    @Override
+    public void onVisibilityAggregated(boolean isVisible) {
+        super.onVisibilityAggregated(isVisible);
+        if (isVisible == mAggregatedVisible) return;
+
+        mAggregatedVisible = isVisible;
+        if (mAggregatedVisible) {
+            setEllipsize(TextUtils.TruncateAt.MARQUEE);
+        } else {
+            setEllipsize(TextUtils.TruncateAt.END);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
similarity index 73%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardPresentationTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index dfe2913..8138420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,18 +11,18 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone;
+package com.android.keyguard;
 
+import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.LayoutInflater;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
+import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.InjectionInflationController;
@@ -39,8 +39,9 @@
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance().getRootComponent());
-        LayoutInflater inflater = inflationController
-                .injectable(LayoutInflater.from(getContext()));
-        inflater.inflate(R.layout.keyguard_presentation, null);
+        Context context = getContext();
+        KeyguardPresentation keyguardPresentation =
+                new KeyguardPresentation(context, context.getDisplay(), inflationController);
+        keyguardPresentation.onCreate(null /*savedInstanceState */);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index 74b15fb..756cf3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -43,10 +43,14 @@
 @RunWith(AndroidTestingRunner.class)
 public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
 
+    private int mDisplayWidth = 500;
+    private int mDisplayHeight = 1000;
+
     @Spy
     private ExpandedAnimationController mExpandedController =
             new ExpandedAnimationController(
-                new Point(500, 1000) /* displaySize */, 0 /* expandedViewPadding */);
+                    new Point(mDisplayWidth, mDisplayHeight) /* displaySize */,
+                    0 /* expandedViewPadding */);
     private int mStackOffset;
     private float mBubblePadding;
     private float mBubbleSize;
@@ -58,11 +62,11 @@
         super.setUp();
         addOneMoreThanRenderLimitBubbles();
         mLayout.setController(mExpandedController);
+
         Resources res = mLayout.getResources();
         mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
         mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-
         mExpansionPoint = new PointF(100, 100);
     }
 
@@ -243,7 +247,7 @@
     private void testBubblesInCorrectExpandedPositions() {
         // Check all the visible bubbles to see if they're in the right place.
         for (int i = 0; i < Math.min(mLayout.getChildCount(), mMaxRenderedBubbles); i++) {
-            assertEquals(mBubblePadding + (i * (mBubbleSize + mBubblePadding)),
+            assertEquals(getBubbleLeft(i),
                     mLayout.getChildAt(i).getTranslationX(),
                     2f);
             assertEquals(mExpandedController.getExpandedY(),
@@ -254,4 +258,33 @@
             }
         }
     }
+
+    /**
+     * @param index Bubble index in row.
+     * @return Bubble left x from left edge of screen.
+     */
+    public float getBubbleLeft(int index) {
+        float bubbleLeftFromRowLeft = index * (mBubbleSize + mBubblePadding);
+        return getRowLeft() + bubbleLeftFromRowLeft;
+    }
+
+    private float getRowLeft() {
+        if (mLayout == null) {
+            return 0;
+        }
+        int bubbleCount = mLayout.getChildCount();
+        if (bubbleCount > mMaxRenderedBubbles) {
+            bubbleCount = mMaxRenderedBubbles;
+        }
+        // Width calculations.
+        double bubble = bubbleCount * mBubbleSize;
+        float gap = (bubbleCount - 1) * mBubblePadding;
+        float row = gap + (float) bubble;
+
+        float halfRow = row / 2f;
+        float centerScreen = mDisplayWidth / 2;
+        float rowLeftFromScreenLeft = centerScreen - halfRow;
+
+        return rowLeftFromScreenLeft;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 4d593c1..72f3a62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -402,6 +402,7 @@
         verify(mRow).setEntry(eq(mEntry));
         assertEquals(1, mEntry.systemGeneratedSmartActions.size());
         assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
+        verify(mEntryListener).onNotificationRankingUpdated(mRankingMap);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index ad9c729..4181d38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -271,6 +271,8 @@
             when(view.getBarTransitions()).thenReturn(mock(BarTransitions.class));
             when(view.getLightTransitionsController()).thenReturn(
                     mock(LightBarTransitionsController.class));
+            when(view.getRotationButtonController()).thenReturn(
+                    mock(RotationButtonController.class));
             return view;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
index 28e91ea..cf08428 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
@@ -51,7 +51,8 @@
     public final SysuiTestableContext mContext = new SysuiTestableContext(
             InstrumentationRegistry.getContext(), getLeakCheck());
     private final TestableDependency mDependency = new TestableDependency(mContext);
-    private RotationContextButton mButton;
+    private RotationButtonController mRotationButtonController;
+    private RotationButton mRotationButton;
 
     @Before
     public void setup() {
@@ -59,50 +60,66 @@
         mDependency.injectMockDependency(RotationLockController.class);
 
         final View view = new View(mContext);
-        mButton = spy(new RotationContextButton(RES_UNDEF, RES_UNDEF, mContext, RES_UNDEF));
+        mRotationButton = mock(RotationButton.class);
+        mRotationButtonController = spy(
+                new RotationButtonController(mContext, RES_UNDEF, mRotationButton));
         final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
-        doReturn(view).when(mButton).getCurrentView();
-        doReturn(kbd).when(mButton).getNewDrawable();
+        doReturn(view).when(mRotationButton).getCurrentView();
+        doReturn(true).when(mRotationButton).acceptRotationProposal();
     }
 
     @Test
     public void testOnInvalidRotationProposal() {
-        mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, false /* isValid */);
-        verify(mButton, times(1)).setRotateSuggestionButtonState(false /* visible */);
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
+                false /* isValid */);
+        verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
+                false /* visible */);
     }
 
     @Test
     public void testOnSameRotationProposal() {
-        mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE, true /* isValid */);
-        verify(mButton, times(1)).setRotateSuggestionButtonState(false /* visible */);
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE,
+                true /* isValid */);
+        verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
+                false /* visible */);
     }
 
     @Test
     public void testOnRotationProposalShowButtonShowNav() {
         // No navigation bar should not call to set visibility state
-        mButton.onNavigationBarWindowVisibilityChange(false /* showing */);
-        verify(mButton, times(0)).setRotateSuggestionButtonState(false /* visible */);
-        verify(mButton, times(0)).setRotateSuggestionButtonState(true /* visible */);
+        mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                false /* visible */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                true /* visible */);
 
         // No navigation bar with rotation change should not call to set visibility state
-        mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, true /* isValid */);
-        verify(mButton, times(0)).setRotateSuggestionButtonState(false /* visible */);
-        verify(mButton, times(0)).setRotateSuggestionButtonState(true /* visible */);
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
+                true /* isValid */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                false /* visible */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                true /* visible */);
 
         // Since rotation has changed rotation should be pending, show mButton when showing nav bar
-        mButton.onNavigationBarWindowVisibilityChange(true /* showing */);
-        verify(mButton, times(1)).setRotateSuggestionButtonState(true /* visible */);
+        mRotationButtonController.onNavigationBarWindowVisibilityChange(true /* showing */);
+        verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
+                true /* visible */);
     }
 
     @Test
     public void testOnRotationProposalShowButton() {
         // Navigation bar being visible should not call to set visibility state
-        mButton.onNavigationBarWindowVisibilityChange(true /* showing */);
-        verify(mButton, times(0)).setRotateSuggestionButtonState(false /* visible */);
-        verify(mButton, times(0)).setRotateSuggestionButtonState(true /* visible */);
+        mRotationButtonController.onNavigationBarWindowVisibilityChange(true /* showing */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                false /* visible */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                true /* visible */);
 
         // Navigation bar is visible and rotation requested
-        mButton.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1, true /* isValid */);
-        verify(mButton, times(1)).setRotateSuggestionButtonState(true /* visible */);
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
+                true /* isValid */);
+        verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
+                true /* visible */);
     }
 }
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index 2675e90..3f16c12 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -49,7 +49,10 @@
 	IconShapeTeardropOverlay \
 	NavigationBarMode3ButtonOverlay \
 	NavigationBarMode2ButtonOverlay \
-	NavigationBarModeGesturalOverlay
+	NavigationBarModeGesturalOverlay \
+	NavigationBarModeGesturalOverlayNarrowBack \
+	NavigationBarModeGesturalOverlayWideBack \
+	NavigationBarModeGesturalOverlayExtraWideBack
 
 include $(BUILD_PHONY_PACKAGE)
 include $(CLEAR_VARS)
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk
new file mode 100644
index 0000000..9a38efa
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk
@@ -0,0 +1,30 @@
+#
+#  Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := NavigationBarModeGesturalExtraWideBack
+
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlayExtraWideBack
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_RRO_PACKAGE)
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
new file mode 100644
index 0000000..ba7beba
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<!--
+/**
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.internal.systemui.navbar.gestural_extra_wide_back"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+        android:category="com.android.internal.navigation_bar_mode"
+        android:priority="1"/>
+
+    <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
new file mode 100644
index 0000000..c8f994c
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+    <!-- Controls the navigation bar interaction mode:
+         0: 3 button mode (back, home, overview buttons)
+         1: 2 button mode (back, home buttons + swipe up for overview)
+         2: gestures only for back, home and overview -->
+    <integer name="config_navBarInteractionMode">2</integer>
+
+    <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
+         Only applies if the device display is not square. -->
+    <bool name="config_navBarCanMove">false</bool>
+
+    <!-- Controls whether the navigation bar lets through taps. -->
+    <bool name="config_navBarTapThrough">true</bool>
+
+    <!-- Controls the size of the back gesture inset. -->
+    <dimen name="config_backGestureInset">40dp</dimen>
+
+    <!-- Controls whether the navbar needs a scrim with
+     {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
+    <bool name="config_navBarNeedsScrim">false</bool>
+
+    <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+         (which normally prevents seamless rotation). -->
+    <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
+
+    <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
+         show. -->
+    <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
+
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
new file mode 100644
index 0000000..987d203
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+    <!-- Height of the bottom navigation / system bar. -->
+    <dimen name="navigation_bar_height">16dp</dimen>
+    <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
+    <dimen name="navigation_bar_height_landscape">16dp</dimen>
+    <!-- Width of the navigation bar when it is placed vertically on the screen -->
+    <dimen name="navigation_bar_width">16dp</dimen>
+    <!-- Height of the bottom navigation / system bar. -->
+    <dimen name="navigation_bar_frame_height">48dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
new file mode 100644
index 0000000..bbab5e047
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Name of overlay [CHAR LIMIT=64] -->
+    <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk
new file mode 100644
index 0000000..1d004c8
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk
@@ -0,0 +1,30 @@
+#
+#  Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := NavigationBarModeGesturalNarrowBack
+
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlayNarrowBack
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_RRO_PACKAGE)
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
new file mode 100644
index 0000000..8de91c0
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<!--
+/**
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.internal.systemui.navbar.gestural_narrow_back"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+        android:category="com.android.internal.navigation_bar_mode"
+        android:priority="1"/>
+
+    <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
new file mode 100644
index 0000000..693110a
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+    <!-- Controls the navigation bar interaction mode:
+         0: 3 button mode (back, home, overview buttons)
+         1: 2 button mode (back, home buttons + swipe up for overview)
+         2: gestures only for back, home and overview -->
+    <integer name="config_navBarInteractionMode">2</integer>
+
+    <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
+         Only applies if the device display is not square. -->
+    <bool name="config_navBarCanMove">false</bool>
+
+    <!-- Controls whether the navigation bar lets through taps. -->
+    <bool name="config_navBarTapThrough">true</bool>
+
+    <!-- Controls the size of the back gesture inset. -->
+    <dimen name="config_backGestureInset">18dp</dimen>
+
+    <!-- Controls whether the navbar needs a scrim with
+     {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
+    <bool name="config_navBarNeedsScrim">false</bool>
+
+    <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+         (which normally prevents seamless rotation). -->
+    <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
+
+    <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
+         show. -->
+    <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
+
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
new file mode 100644
index 0000000..987d203
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+    <!-- Height of the bottom navigation / system bar. -->
+    <dimen name="navigation_bar_height">16dp</dimen>
+    <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
+    <dimen name="navigation_bar_height_landscape">16dp</dimen>
+    <!-- Width of the navigation bar when it is placed vertically on the screen -->
+    <dimen name="navigation_bar_width">16dp</dimen>
+    <!-- Height of the bottom navigation / system bar. -->
+    <dimen name="navigation_bar_frame_height">48dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
new file mode 100644
index 0000000..bbab5e047
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Name of overlay [CHAR LIMIT=64] -->
+    <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk
new file mode 100644
index 0000000..0ab463f
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk
@@ -0,0 +1,30 @@
+#
+#  Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := NavigationBarModeGesturalWideBack
+
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlayWideBack
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_RRO_PACKAGE)
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
new file mode 100644
index 0000000..daf4613
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<!--
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.internal.systemui.navbar.gestural_wide_back"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+        android:category="com.android.internal.navigation_bar_mode"
+        android:priority="1"/>
+
+    <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
new file mode 100644
index 0000000..5cd6ce3
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+    <!-- Controls the navigation bar interaction mode:
+         0: 3 button mode (back, home, overview buttons)
+         1: 2 button mode (back, home buttons + swipe up for overview)
+         2: gestures only for back, home and overview -->
+    <integer name="config_navBarInteractionMode">2</integer>
+
+    <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
+         Only applies if the device display is not square. -->
+    <bool name="config_navBarCanMove">false</bool>
+
+    <!-- Controls whether the navigation bar lets through taps. -->
+    <bool name="config_navBarTapThrough">true</bool>
+
+    <!-- Controls the size of the back gesture inset. -->
+    <dimen name="config_backGestureInset">32dp</dimen>
+
+    <!-- Controls whether the navbar needs a scrim with
+     {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
+    <bool name="config_navBarNeedsScrim">false</bool>
+
+    <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+         (which normally prevents seamless rotation). -->
+    <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
+
+    <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
+         show. -->
+    <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
+
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
new file mode 100644
index 0000000..987d203
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+    <!-- Height of the bottom navigation / system bar. -->
+    <dimen name="navigation_bar_height">16dp</dimen>
+    <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
+    <dimen name="navigation_bar_height_landscape">16dp</dimen>
+    <!-- Width of the navigation bar when it is placed vertically on the screen -->
+    <dimen name="navigation_bar_width">16dp</dimen>
+    <!-- Height of the bottom navigation / system bar. -->
+    <dimen name="navigation_bar_frame_height">48dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
new file mode 100644
index 0000000..bbab5e047
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Name of overlay [CHAR LIMIT=64] -->
+    <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
+</resources>
\ No newline at end of file
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 71d03a8..3061981 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -7376,6 +7376,9 @@
     // OS: Q
     FIELD_BIOMETRIC_AUTH_ERROR = 1741;
 
+    // Custom tag for NotificationItem. Hash of the NAS that made adjustments.
+    FIELD_NOTIFICATION_ASSISTANT_SERVICE_HASH = 1742;
+
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 64bcaa0..7a947f1 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -54,6 +54,7 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.WorkSource;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.DebugUtils;
@@ -78,6 +79,7 @@
     private static final boolean DEBUG = false;
     private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
     private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
+    private static final String RAMPING_RINGER_ENABLED = "ramping_ringer_enabled";
 
     private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
 
@@ -874,6 +876,11 @@
         if (Settings.System.getInt(
                 mContext.getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING, 0) != 0) {
             return ringerMode != AudioManager.RINGER_MODE_SILENT;
+        } else if (Settings.Global.getInt(
+                    mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) != 0
+                && DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_TELEPHONY, RAMPING_RINGER_ENABLED, false)) {
+            return ringerMode != AudioManager.RINGER_MODE_SILENT;
         } else {
             return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
         }
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index bdbff3d..4b48ef9 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -661,12 +661,6 @@
         return mTestUserKeyFile == null ? getAdbFile(ADB_KEYS_FILE) : mTestUserKeyFile;
     }
 
-    private void createKeyFile(File keyFile) throws IOException {
-        keyFile.createNewFile();
-        FileUtils.setPermissions(keyFile.toString(),
-                FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
-    }
-
     private void writeKey(String key) {
         try {
             File keyFile = getUserKeyFile();
@@ -675,14 +669,13 @@
                 return;
             }
 
-            if (!keyFile.exists()) {
-                createKeyFile(keyFile);
-            }
-
             FileOutputStream fo = new FileOutputStream(keyFile, true);
             fo.write(key.getBytes());
             fo.write('\n');
             fo.close();
+
+            FileUtils.setPermissions(keyFile.toString(),
+                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
         } catch (IOException ex) {
             Slog.e(TAG, "Error writing key:" + ex);
         }
@@ -698,10 +691,6 @@
                 return;
             }
 
-            if (!keyFile.exists()) {
-                createKeyFile(keyFile);
-            }
-
             atomicKeyFile = new AtomicFile(keyFile);
             fo = atomicKeyFile.startWrite();
             for (String key : keys) {
@@ -709,6 +698,9 @@
                 fo.write('\n');
             }
             atomicKeyFile.finishWrite(fo);
+
+            FileUtils.setPermissions(keyFile.toString(),
+                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
         } catch (IOException ex) {
             Slog.e(TAG, "Error writing keys: " + ex);
             if (atomicKeyFile != null) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1160e33..c4eb661 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1036,19 +1036,12 @@
                     final StatusBarNotification n = r.sbn;
                     final int callingUid = n.getUid();
                     final String pkg = n.getPackageName();
-                    final boolean wasBubble = r.getNotification().isBubbleNotification();
                     if (isBubble && isNotificationAppropriateToBubble(r, pkg, callingUid,
                             null /* oldEntry */)) {
                         r.getNotification().flags |= FLAG_BUBBLE;
                     } else {
                         r.getNotification().flags &= ~FLAG_BUBBLE;
                     }
-                    if (wasBubble != r.getNotification().isBubbleNotification()) {
-                        // Add the "alert only once" flag so that the notification won't HUN
-                        // unnecessarily just because the bubble flag was changed.
-                        r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
-                        mListeners.notifyPostedLocked(r, r);
-                    }
                 }
             }
         }
@@ -2643,18 +2636,25 @@
                 ParceledListSlice channelsList) {
             List<NotificationChannel> channels = channelsList.getList();
             final int channelsSize = channels.size();
+            boolean needsPolicyFileChange = false;
             for (int i = 0; i < channelsSize; i++) {
                 final NotificationChannel channel = channels.get(i);
                 Preconditions.checkNotNull(channel, "channel in list is null");
-                mPreferencesHelper.createNotificationChannel(pkg, uid, channel,
-                        true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed(
+                needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid,
+                        channel, true /* fromTargetApp */,
+                        mConditionProviders.isPackageOrComponentAllowed(
                                 pkg, UserHandle.getUserId(uid)));
-                mListeners.notifyNotificationChannelChanged(pkg,
-                        UserHandle.getUserHandleForUid(uid),
-                        mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
-                        NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+                if (needsPolicyFileChange) {
+                    mListeners.notifyNotificationChannelChanged(pkg,
+                            UserHandle.getUserHandleForUid(uid),
+                            mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(),
+                                    false),
+                            NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+                }
             }
-            handleSavePolicyFile();
+            if (needsPolicyFileChange) {
+                handleSavePolicyFile();
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 4ed24ec..c2e559a 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -174,6 +174,7 @@
     private ArrayList<CharSequence> mSmartReplies;
 
     private final List<Adjustment> mAdjustments;
+    private String mAdjustmentIssuer;
     private final NotificationStats mStats;
     private int mUserSentiment;
     private boolean mIsInterruptive;
@@ -684,6 +685,9 @@
                     importance = Math.min(IMPORTANCE_HIGH, importance);
                     setAssistantImportance(importance);
                 }
+                if (!signals.isEmpty() && adjustment.getIssuer() != null) {
+                    mAdjustmentIssuer = adjustment.getIssuer();
+                }
             }
             // We have now gotten all the information out of the adjustments and can forget them.
             mAdjustments.clear();
@@ -1297,6 +1301,13 @@
             lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST,
                         mAssistantImportance);
         }
+        // Log the issuer of any adjustments that may have affected this notification. We only log
+        // the hash here as NotificationItem events are frequent, and the number of NAS
+        // implementations (and hence the chance of collisions) is low.
+        if (mAdjustmentIssuer != null) {
+            lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_ASSISTANT_SERVICE_HASH,
+                    mAdjustmentIssuer.hashCode());
+        }
         return lm;
     }
 
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 9886d0a..1c0ac16 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -481,11 +481,16 @@
      * @param allowed whether bubbles are allowed.
      */
     public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
+        boolean changed = false;
         synchronized (mPackagePreferences) {
             PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid);
+            changed = p.allowBubble != allowed;
             p.allowBubble = allowed;
             p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
         }
+        if (changed) {
+            updateConfig();
+        }
     }
 
     /**
@@ -610,12 +615,13 @@
     }
 
     @Override
-    public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
+    public boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp, boolean hasDndAccess) {
         Preconditions.checkNotNull(pkg);
         Preconditions.checkNotNull(channel);
         Preconditions.checkNotNull(channel.getId());
         Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
+        boolean needsPolicyFileChange = false;
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
             if (r == null) {
@@ -632,17 +638,28 @@
             if (existing != null && fromTargetApp) {
                 if (existing.isDeleted()) {
                     existing.setDeleted(false);
+                    needsPolicyFileChange = true;
 
                     // log a resurrected channel as if it's new again
                     MetricsLogger.action(getChannelLog(channel, pkg).setType(
                             com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
                 }
 
-                existing.setName(channel.getName().toString());
-                existing.setDescription(channel.getDescription());
-                existing.setBlockableSystem(channel.isBlockableSystem());
-                if (existing.getGroup() == null) {
+                if (!Objects.equals(channel.getName().toString(), existing.getName().toString())) {
+                    existing.setName(channel.getName().toString());
+                    needsPolicyFileChange = true;
+                }
+                if (!Objects.equals(channel.getDescription(), existing.getDescription())) {
+                    existing.setDescription(channel.getDescription());
+                    needsPolicyFileChange = true;
+                }
+                if (channel.isBlockableSystem() != existing.isBlockableSystem()) {
+                    existing.setBlockableSystem(channel.isBlockableSystem());
+                    needsPolicyFileChange = true;
+                }
+                if (channel.getGroup() != null && existing.getGroup() == null) {
                     existing.setGroup(channel.getGroup());
+                    needsPolicyFileChange = true;
                 }
 
                 // Apps are allowed to downgrade channel importance if the user has not changed any
@@ -651,23 +668,30 @@
                 if (existing.getUserLockedFields() == 0 &&
                         channel.getImportance() < existing.getImportance()) {
                     existing.setImportance(channel.getImportance());
+                    needsPolicyFileChange = true;
                 }
 
                 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
                 // fields on the channel yet
                 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
                     boolean bypassDnd = channel.canBypassDnd();
-                    existing.setBypassDnd(bypassDnd);
+                    if (bypassDnd != existing.canBypassDnd()) {
+                        existing.setBypassDnd(bypassDnd);
+                        needsPolicyFileChange = true;
 
-                    if (bypassDnd != mAreChannelsBypassingDnd
-                            || previousExistingImportance != existing.getImportance()) {
-                        updateChannelsBypassingDnd(mContext.getUserId());
+                        if (bypassDnd != mAreChannelsBypassingDnd
+                                || previousExistingImportance != existing.getImportance()) {
+                            updateChannelsBypassingDnd(mContext.getUserId());
+                        }
                     }
                 }
 
                 updateConfig();
-                return;
+                return needsPolicyFileChange;
             }
+
+            needsPolicyFileChange = true;
+
             if (channel.getImportance() < IMPORTANCE_NONE
                     || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
                 throw new IllegalArgumentException("Invalid importance level");
@@ -703,6 +727,8 @@
             MetricsLogger.action(getChannelLog(channel, pkg).setType(
                     com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
         }
+
+        return needsPolicyFileChange;
     }
 
     void clearLockedFieldsLocked(NotificationChannel channel) {
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 72502acd..5de00e4 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,7 @@
             boolean fromTargetApp);
     ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);
-    void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
+    boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp, boolean hasDndAccess);
     void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
     NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index eec4b70..d6e87aa 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -674,9 +674,10 @@
             return new ArrayList<>();
         }
 
-        // Get the list of all dynamic shortcuts in this package
+        // Get the list of all dynamic shortcuts in this package.
         final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
-        findAll(shortcuts, ShortcutInfo::isDynamicVisible, ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+        findAll(shortcuts, ShortcutInfo::isDynamicVisible,
+                ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);
 
         final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
         for (int i = 0; i < shortcuts.size(); i++) {
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 610c82f..3f9eb2d 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -29,16 +29,10 @@
             "name": "CtsPermission2TestCases",
             "options": [
                 {
-                    "include-filter": "android.permission.cts.SharedUidPermissionsTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.RestrictedPermissionsTest"
+                    "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
                 },
                 {
                     "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
-                },
-                {
-                    "include-filter": "android.permission.cts.PrivappPermissionsTest"
                 }
             ]
         },
diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
index e65eae0..54369ca 100644
--- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java
+++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
@@ -16,21 +16,17 @@
 
 package com.android.server.telecom;
 
+import android.app.role.RoleManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.telecom.DefaultDialerManager;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -221,19 +217,10 @@
     private void registerDefaultAppNotifier() {
         final DefaultPermissionGrantPolicy permissionPolicy = getDefaultPermissionGrantPolicy();
         // Notify the package manager on default app changes
-        final Uri defaultDialerAppUri = Settings.Secure.getUriFor(
-                Settings.Secure.DIALER_DEFAULT_APPLICATION);
-        ContentObserver contentObserver = new ContentObserver(
-                new Handler(Looper.getMainLooper())) {
-            @Override
-            public void onChange(boolean selfChange, Uri uri, int userId) {
-                if (defaultDialerAppUri.equals(uri)) {
-                    updateSimCallManagerPermissions(permissionPolicy, userId);
-                }
-            }
-        };
-        mContext.getContentResolver().registerContentObserver(defaultDialerAppUri,
-                false, contentObserver, UserHandle.USER_ALL);
+        final RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+        roleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
+                (roleName, user) -> updateSimCallManagerPermissions(permissionPolicy,
+                        user.getIdentifier()), UserHandle.ALL);
     }
 
 
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 3a39053..a2eb40b 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -518,8 +518,11 @@
                     agentInfo = mActiveAgents.valueAt(index);
                 }
 
-                boolean directUnlock = resolveInfo.serviceInfo.directBootAware
-                    && agentInfo.settings.canUnlockProfile;
+                boolean directUnlock = false;
+                if (agentInfo.settings != null) {
+                    directUnlock = resolveInfo.serviceInfo.directBootAware
+                        && agentInfo.settings.canUnlockProfile;
+                }
 
                 if (directUnlock) {
                     if (DEBUG) Slog.d(TAG, "refreshAgentList: trustagent " + name
diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
index bb4d67e..e4cb19e 100644
--- a/services/core/java/com/android/server/twilight/TwilightService.java
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -162,9 +162,6 @@
             if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
                 mLocationManager.requestSingleUpdate(
                         LocationManager.NETWORK_PROVIDER, this, Looper.getMainLooper());
-            } else if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
-                mLocationManager.requestSingleUpdate(
-                        LocationManager.GPS_PROVIDER, this, Looper.getMainLooper());
             }
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index fa1bcac..fd3678d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -423,9 +423,7 @@
         assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
         assertEquals(null, si.getIntent());
         assertEquals(123, si.getRank());
-        assertEquals("person", si.getPersons()[0].getName());
-        assertEquals("personKey", si.getPersons()[0].getKey());
-        assertEquals("personUri", si.getPersons()[0].getUri());
+        assertEquals(null, si.getPersons());
         assertEquals(1, si.getExtras().getInt("k"));
 
         assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
@@ -455,6 +453,30 @@
 
         assertEquals(456, si.getIconResourceId());
         assertEquals(null, si.getIconResName());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);
+
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("dismes", si.getDisabledMessage());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getRank());
+        assertEquals("person", si.getPersons()[0].getName());
+        assertEquals("personKey", si.getPersons()[0].getKey());
+        assertEquals("personUri", si.getPersons()[0].getUri());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+
+        assertEquals(456, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
     }
 
     public void testShortcutInfoClone_resId() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 87f221a..e75a30b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -22,7 +22,6 @@
 import static android.app.Notification.CATEGORY_CALL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
-import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
 import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -1660,11 +1659,14 @@
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(channel2.getId()), anyBoolean()))
                 .thenReturn(channel2);
+        when(mPreferencesHelper.createNotificationChannel(eq(PKG), anyInt(),
+                eq(channel2), anyBoolean(), anyBoolean()))
+                .thenReturn(true);
 
         reset(mListeners);
         mBinderService.createNotificationChannels(PKG,
                 new ParceledListSlice(Arrays.asList(mTestNotificationChannel, channel2)));
-        verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(PKG),
+        verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
                 eq(Process.myUserHandle()), eq(mTestNotificationChannel),
                 eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED));
         verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(PKG),
@@ -4963,13 +4965,10 @@
         mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false);
         waitForIdle();
 
-        // Make sure we are not a bubble / reported as such to listeners
-        ArgumentCaptor<NotificationRecord> captor =
-                ArgumentCaptor.forClass(NotificationRecord.class);
-        verify(mListeners, times(1)).notifyPostedLocked(captor.capture(), any());
-
-        assertEquals((captor.getValue().getNotification().flags & FLAG_BUBBLE), 0);
-        assertTrue((captor.getValue().getNotification().flags & FLAG_ONLY_ALERT_ONCE) != 0);
+        // Make sure we are not a bubble
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+        assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
     }
 
     @Test
@@ -5000,13 +4999,10 @@
         mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true);
         waitForIdle();
 
-        // Make sure we are a bubble / reported as such to listeners
-        ArgumentCaptor<NotificationRecord> captor =
-                ArgumentCaptor.forClass(NotificationRecord.class);
-        verify(mListeners, times(1)).notifyPostedLocked(captor.capture(), any());
-
-        assertTrue((captor.getValue().getNotification().flags & FLAG_BUBBLE) != 0);
-        assertTrue((captor.getValue().getNotification().flags & FLAG_ONLY_ALERT_ONCE) != 0);
+        // Make sure we are a bubble
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+        assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
     }
 
     @Test
@@ -5037,7 +5033,6 @@
         StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifsAfter.length);
         assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
-        verify(mListeners, times(0)).notifyPostedLocked(any(), any());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index e22f827..8f8b746 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -262,13 +262,13 @@
         int uid0 = 1001;
         setUpPackageWithUid(package0, uid0);
         NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(package0, uid0, channel0, true, false);
+        assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
 
         String package10 = "test.package.user10";
         int uid10 = 1001001;
         setUpPackageWithUid(package10, uid10);
         NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(package10, uid10, channel10, true, false);
+        assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false));
 
         ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10);
 
@@ -293,7 +293,7 @@
         int uid0 = 1001;
         setUpPackageWithUid(package0, uid0);
         NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(package0, uid0, channel0, true, false);
+        assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
 
         ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0);
 
@@ -334,8 +334,8 @@
 
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false));
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false));
 
         mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
         mHelper.setAppImportanceLocked(PKG_N_MR1, UID_N_MR1);
@@ -716,8 +716,8 @@
     public void testCreateChannel_blocked() throws Exception {
         mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_NONE);
 
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
-                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false));
     }
 
     @Test
@@ -746,10 +746,10 @@
         } catch (IllegalArgumentException e) {
             // yay
         }
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
-                new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false);
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
-                new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false));
+        assertFalse(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false));
     }
 
 
@@ -763,7 +763,7 @@
         channel.setBypassDnd(true);
         channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
 
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false));
 
         // same id, try to update all fields
         final NotificationChannel channel2 =
@@ -776,7 +776,8 @@
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true);
 
         // all fields should be changed
-        assertEquals(channel2, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false));
+        assertEquals(channel2,
+                mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false));
 
         verify(mHandler, times(1)).requestSort();
     }
@@ -894,7 +895,7 @@
         }
         channel.lockFields(lockMask);
 
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false));
 
         NotificationChannel savedChannel =
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false);
@@ -1469,13 +1470,18 @@
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel.setVibrationPattern(vibration);
 
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false));
 
         NotificationChannel newChannel = new NotificationChannel(
                 channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
         newChannel.setVibrationPattern(new long[]{100});
+        newChannel.setAllowBubbles(!channel.canBubble());
+        newChannel.setLightColor(Color.BLUE);
+        newChannel.setSound(Uri.EMPTY, null);
+        newChannel.setShowBadge(!channel.canShowBadge());
 
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, false);
+        assertFalse(mHelper.createNotificationChannel(
+                PKG_N_MR1, UID_N_MR1, newChannel, true, false));
 
         // Old settings not overridden
         compareChannels(channel,
@@ -1588,16 +1594,17 @@
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
 
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
-                UID_N_MR1});
+        assertTrue(mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1},
+                new int[]{UID_N_MR1}));
 
-        assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList().size());
+        assertEquals(0, mHelper.getNotificationChannels(
+                PKG_N_MR1, UID_N_MR1, true).getList().size());
 
         // Not deleted
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
 
-        mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
-                UID_N_MR1});
+        assertFalse(mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM,
+                new String[]{PKG_N_MR1}, new int[]{UID_N_MR1}));
         assertEquals(2, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
     }
 
@@ -1825,13 +1832,13 @@
     @Test
     public void testCreateChannel_updateName() {
         NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
         NotificationChannel actual =
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
         assertEquals("hello", actual.getName());
 
         nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
 
         actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
         assertEquals("goodbye", actual.getName());
@@ -1845,14 +1852,14 @@
         NotificationChannelGroup group = new NotificationChannelGroup("group", "");
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
         NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
         NotificationChannel actual =
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
         assertNull(actual.getGroup());
 
         nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
         nc.setGroup(group.getId());
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
 
         actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false);
         assertNotNull(actual.getGroup());
@@ -2109,7 +2116,7 @@
 
         NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
         update.setBypassDnd(true);
-        mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false);
+        assertFalse(mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false));
 
         assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
                 .canBypassDnd());
@@ -2122,7 +2129,7 @@
 
         NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
         update.setBypassDnd(true);
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true, true);
+        assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true, true));
 
         assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "A", false).canBypassDnd());
     }
@@ -2676,4 +2683,11 @@
         assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, channel1.getId(), false)
                 .isImportanceLockedByCriticalDeviceFunction());
     }
+
+    @Test
+    public void testSetBubblesAllowed_false() {
+        mHelper.setBubblesAllowed(PKG_O, UID_O, false);
+        assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
+        verify(mHandler, times(1)).requestSort();
+    }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java
index 32fc796..8ca77f0 100644
--- a/services/usb/java/com/android/server/usb/UsbSerialReader.java
+++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java
@@ -85,22 +85,22 @@
                     throw new RemoteException("package " + packageName + " cannot be found");
                 }
                 packageTargetSdkVersion = pkg.applicationInfo.targetSdkVersion;
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
 
-            if (packageTargetSdkVersion >= Build.VERSION_CODES.Q) {
-                if (mContext.checkPermission(android.Manifest.permission.MANAGE_USB, pid, uid)
-                        == PackageManager.PERMISSION_DENIED) {
-                    UsbUserSettingsManager settings = mSettingsManager.getSettingsForUser(
-                            UserHandle.getUserId(uid));
+                if (packageTargetSdkVersion >= Build.VERSION_CODES.Q) {
+                    if (mContext.checkPermission(android.Manifest.permission.MANAGE_USB, pid, uid)
+                            == PackageManager.PERMISSION_DENIED) {
+                        UsbUserSettingsManager settings = mSettingsManager.getSettingsForUser(
+                                UserHandle.getUserId(uid));
 
-                    if (mDevice instanceof UsbDevice) {
-                        settings.checkPermission((UsbDevice) mDevice, packageName, uid);
-                    } else {
-                        settings.checkPermission((UsbAccessory) mDevice, uid);
+                        if (mDevice instanceof UsbDevice) {
+                            settings.checkPermission((UsbDevice) mDevice, packageName, uid);
+                        } else {
+                            settings.checkPermission((UsbAccessory) mDevice, uid);
+                        }
                     }
                 }
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
         }
 
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index 7b51457..511adf6 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -299,7 +299,7 @@
      * Get the dialing number of the emergency number.
      *
      * The character in the number string is only the dial pad
-     * character('0'-'9', '*', or '#'). For example: 911.
+     * character('0'-'9', '*', '+', or '#'). For example: 911.
      *
      * @return the dialing number.
      */
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index df1f57f..cd2bd26 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -65,6 +65,7 @@
 import android.os.UserManager;
 import android.util.SparseIntArray;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -96,6 +97,7 @@
     private static final int SYSTEM_UID1 = 1000;
     private static final int SYSTEM_UID2 = 1008;
     private static final int VPN_UID = 10002;
+    private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
     private static final String MOCK_PACKAGE1 = "appName1";
     private static final String MOCK_PACKAGE2 = "appName2";
     private static final String SYSTEM_PACKAGE1 = "sysName1";
@@ -188,8 +190,10 @@
     private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
         final PackageInfo pkgInfo;
         if (hasSystemPermission) {
-            pkgInfo = packageInfoWithPermissions(new String[] {CHANGE_NETWORK_STATE, NETWORK_STACK},
-                    PARTITION_SYSTEM);
+            final String[] systemPermissions = new String[]{
+                    CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS
+            };
+            pkgInfo = packageInfoWithPermissions(systemPermissions, PARTITION_SYSTEM);
         } else {
             pkgInfo = packageInfoWithPermissions(new String[] {}, "");
         }
@@ -646,4 +650,16 @@
         mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
         mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
     }
+
+    @Test
+    public void testRealSystemPermission() throws Exception {
+        // Use the real context as this test must ensure the *real* system package holds the
+        // necessary permission.
+        final Context realContext = InstrumentationRegistry.getContext();
+        final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService);
+        final PackageManager manager = realContext.getPackageManager();
+        final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
+                GET_PERMISSIONS | MATCH_ANY_USER);
+        assertTrue(monitor.hasPermission(systemInfo, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+    }
 }