Merge "Revert "[DO NOT MERGE] Handle config override via settings correctly"" into qt-dev
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
index 83c034b..21c6bd2 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -68,9 +68,10 @@
 };
 
 bool VendorIsQOrLater() {
-  // STOPSHIP(b/119390857): Check api version once Q sdk version is finalized
-  std::string version = android::base::GetProperty("ro.vndk.version", "Q");
-  return version == "Q" || version == "q";
+  constexpr int kQSdkVersion = 29;
+  int version = std::atoi(android::base::GetProperty("ro.vndk.version", "29").data());
+  // If the string cannot be parsed, it is a development sdk codename.
+  return version >= kQSdkVersion || version == 0;
 }
 
 Result<std::unique_ptr<std::vector<std::string>>> FindApkFiles(const std::vector<std::string>& dirs,
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 0a9161d..9b48a02 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -442,7 +442,7 @@
 
         if (erase_data) {
             remove(fullPathName.c_str());
-        } else if (output.mIsHistory && !isAdb) {
+        } else if (!output.mIsHistory && !isAdb) {
             // This means a real data owner has called to get this data. But the config says it
             // wants to keep a local history. So now this file must be renamed as a history file.
             // So that next time, when owner calls getData() again, this data won't be uploaded
diff --git a/cmds/statsd/tests/storage/StorageManager_test.cpp b/cmds/statsd/tests/storage/StorageManager_test.cpp
index cae2f30..9e15e99 100644
--- a/cmds/statsd/tests/storage/StorageManager_test.cpp
+++ b/cmds/statsd/tests/storage/StorageManager_test.cpp
@@ -125,6 +125,105 @@
     EXPECT_EQ("300_2000_123454_history", list[3].mFileName);
 }
 
+const string testDir = "/data/misc/stats-data/";
+const string file1 = testDir + "2557169347_1066_1";
+const string file2 = testDir + "2557169349_1066_1";
+const string file1_history = file1 + "_history";
+const string file2_history = file2 + "_history";
+
+bool prepareLocalHistoryTestFiles() {
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(
+            open(file1.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR)));
+    if (fd != -1) {
+        dprintf(fd, "content");
+    } else {
+        return false;
+    }
+
+    android::base::unique_fd fd2(TEMP_FAILURE_RETRY(
+            open(file2.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR)));
+    if (fd2 != -1) {
+        dprintf(fd2, "content");
+    } else {
+        return false;
+    }
+    return true;
+}
+
+void clearLocalHistoryTestFiles() {
+    TEMP_FAILURE_RETRY(remove(file1.c_str()));
+    TEMP_FAILURE_RETRY(remove(file2.c_str()));
+    TEMP_FAILURE_RETRY(remove(file1_history.c_str()));
+    TEMP_FAILURE_RETRY(remove(file2_history.c_str()));
+}
+
+bool fileExist(string name) {
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(name.c_str(), O_RDONLY | O_CLOEXEC)));
+    return fd != -1;
+}
+
+/* The following AppendConfigReportTests test the 4 combinations of [whether erase data] [whether
+ * the caller is adb] */
+TEST(StorageManagerTest, AppendConfigReportTest1) {
+    EXPECT_TRUE(prepareLocalHistoryTestFiles());
+
+    ProtoOutputStream out;
+    StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, false /*erase?*/,
+                                              false /*isAdb?*/);
+
+    EXPECT_FALSE(fileExist(file1));
+    EXPECT_FALSE(fileExist(file2));
+
+    EXPECT_TRUE(fileExist(file1_history));
+    EXPECT_TRUE(fileExist(file2_history));
+    clearLocalHistoryTestFiles();
+}
+
+TEST(StorageManagerTest, AppendConfigReportTest2) {
+    EXPECT_TRUE(prepareLocalHistoryTestFiles());
+
+    ProtoOutputStream out;
+    StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, true /*erase?*/,
+                                              false /*isAdb?*/);
+
+    EXPECT_FALSE(fileExist(file1));
+    EXPECT_FALSE(fileExist(file2));
+    EXPECT_FALSE(fileExist(file1_history));
+    EXPECT_FALSE(fileExist(file2_history));
+
+    clearLocalHistoryTestFiles();
+}
+
+TEST(StorageManagerTest, AppendConfigReportTest3) {
+    EXPECT_TRUE(prepareLocalHistoryTestFiles());
+
+    ProtoOutputStream out;
+    StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, false /*erase?*/,
+                                              true /*isAdb?*/);
+
+    EXPECT_TRUE(fileExist(file1));
+    EXPECT_TRUE(fileExist(file2));
+    EXPECT_FALSE(fileExist(file1_history));
+    EXPECT_FALSE(fileExist(file2_history));
+
+    clearLocalHistoryTestFiles();
+}
+
+TEST(StorageManagerTest, AppendConfigReportTest4) {
+    EXPECT_TRUE(prepareLocalHistoryTestFiles());
+
+    ProtoOutputStream out;
+    StorageManager::appendConfigMetricsReport(ConfigKey(1066, 1), &out, true /*erase?*/,
+                                              true /*isAdb?*/);
+
+    EXPECT_FALSE(fileExist(file1));
+    EXPECT_FALSE(fileExist(file2));
+    EXPECT_FALSE(fileExist(file1_history));
+    EXPECT_FALSE(fileExist(file2_history));
+
+    clearLocalHistoryTestFiles();
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1e982bc..3a74f7d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5564,15 +5564,9 @@
 
     private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
 
-        int configDiff = 0;
+        int configDiff;
+        boolean equivalent;
 
-        // This flag tracks whether the new configuration is fundamentally equivalent to the
-        // existing configuration. This is necessary to determine whether non-activity
-        // callbacks should receive notice when the only changes are related to non-public fields.
-        // We do not gate calling {@link #performActivityConfigurationChanged} based on this flag
-        // as that method uses the same check on the activity config override as well.
-        final boolean equivalent = config != null && mConfiguration != null
-                && (0 == mConfiguration.diffPublicOnly(config));
         final Theme systemTheme = getSystemContext().getTheme();
         final Theme systemUiTheme = getSystemUiContext().getTheme();
 
@@ -5590,6 +5584,13 @@
                 return;
             }
 
+            // This flag tracks whether the new configuration is fundamentally equivalent to the
+            // existing configuration. This is necessary to determine whether non-activity callbacks
+            // should receive notice when the only changes are related to non-public fields.
+            // We do not gate calling {@link #performActivityConfigurationChanged} based on this
+            // flag as that method uses the same check on the activity config override as well.
+            equivalent = mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config));
+
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
                     + config);
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3adddc7..7a3609a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2002,9 +2002,18 @@
                 mDisplay.getRealSize(size);
                 desiredWindowWidth = size.x;
                 desiredWindowHeight = size.y;
+            } else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
+                    || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                // For wrap content, we have to remeasure later on anyways. Use size consistent with
+                // below so we get best use of the measure cache.
+                desiredWindowWidth = dipToPx(config.screenWidthDp);
+                desiredWindowHeight = dipToPx(config.screenHeightDp);
             } else {
-                desiredWindowWidth = mWinFrame.width();
-                desiredWindowHeight = mWinFrame.height();
+                // After addToDisplay, the frame contains the frameHint from window manager, which
+                // for most windows is going to be the same size as the result of relayoutWindow.
+                // Using this here allows us to avoid remeasuring after relayoutWindow
+                desiredWindowWidth = frame.width();
+                desiredWindowHeight = frame.height();
             }
 
             // We used to use the following condition to choose 32 bits drawing caches:
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index b9ed963..7af45fc 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.app;
 
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -64,8 +66,21 @@
 
         String component = Settings.Secure.getString(getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+
+        if (isGestureNavigateEnabled()) {
+            TextView promptPrologue = findViewById(R.id.accessibility_button_prompt_prologue);
+            promptPrologue.setText(isTouchExploreOn()
+                    ? R.string.accessibility_gesture_3finger_prompt_text
+                    : R.string.accessibility_gesture_prompt_text);
+        }
+
         if (TextUtils.isEmpty(component)) {
             TextView prompt = findViewById(R.id.accessibility_button_prompt);
+            if (isGestureNavigateEnabled()) {
+                prompt.setText(isTouchExploreOn()
+                        ? R.string.accessibility_gesture_3finger_instructional_text
+                        : R.string.accessibility_gesture_instructional_text);
+            }
             prompt.setVisibility(View.VISIBLE);
         }
 
@@ -91,6 +106,16 @@
         });
     }
 
+    private boolean isGestureNavigateEnabled() {
+        return NAV_BAR_MODE_GESTURAL == getResources().getInteger(
+                com.android.internal.R.integer.config_navBarInteractionMode);
+    }
+
+    private boolean isTouchExploreOn() {
+        return ((AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE))
+                .isTouchExplorationEnabled();
+    }
+
     private static List<AccessibilityButtonTarget> getServiceAccessibilityButtonTargets(
             @NonNull Context context) {
         AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
@@ -177,4 +202,4 @@
             return mDrawable;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/res/res/layout/accessibility_button_chooser.xml b/core/res/res/layout/accessibility_button_chooser.xml
index 480defb..383780a 100644
--- a/core/res/res/layout/accessibility_button_chooser.xml
+++ b/core/res/res/layout/accessibility_button_chooser.xml
@@ -40,6 +40,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:minHeight="56dp"
+            android:id="@+id/accessibility_button_prompt_prologue"
             android:textAppearance="?attr/textAppearanceMedium"
             android:text="@string/accessibility_button_prompt_text"
             android:gravity="start|center_vertical"
diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml
index 8ba2c60..93975a9 100644
--- a/core/res/res/values-night/themes_device_defaults.xml
+++ b/core/res/res/values-night/themes_device_defaults.xml
@@ -87,5 +87,7 @@
 
     <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.ResolverCommon">
         <item name="windowLightNavigationBar">false</item>
+        <item name="colorBackgroundFloating">@android:color/GM2_grey_800</item>
+        <item name="textColorSecondary">@android:color/resolver_text_color_secondary_dark</item>
     </style>
 </resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 048f341..3b28265 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -218,4 +218,9 @@
     <color name="chooser_row_divider">@color/list_divider_color_light</color>
     <color name="chooser_gradient_background">@color/loading_gradient_background_color_light</color>
     <color name="chooser_gradient_highlight">@color/loading_gradient_highlight_color_light</color>
+
+    <color name="GM2_grey_800">#ff3C4043</color>
+
+    <!-- Resolver/Chooser -->
+    <color name="resolver_text_color_secondary_dark">#ffC4C6C6</color>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e3f84c0..382e154 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4498,10 +4498,18 @@
         <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g></string>
 
     <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. -->
-    <string name="accessibility_button_prompt_text">Choose a feature to use when you tap the Accessibility button:</string>
+    <string name="accessibility_button_prompt_text">Choose a service to use when you tap the accessibility button:</string>
+    <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button when gesture navigation is enabled [CHAR LIMIT=none] -->
+    <string name="accessibility_gesture_prompt_text">Choose a service to use with the accessibility gesture (swipe up from the bottom of the screen with two fingers):</string>
+    <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button when gesture navigation and TalkBack is enabled [CHAR LIMIT=none] -->
+    <string name="accessibility_gesture_3finger_prompt_text">Choose a service to use with the accessibility gesture (swipe up from the bottom of the screen with three fingers):</string>
 
     <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. -->
-    <string name="accessibility_button_instructional_text">To change features, touch &amp; hold the Accessibility button.</string>
+    <string name="accessibility_button_instructional_text">To switch between services, touch &amp; hold the accessibility button.</string>
+    <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button when gesture navigation is enabled. [CHAR LIMIT=none] -->
+    <string name="accessibility_gesture_instructional_text">To switch between services, swipe up with two fingers and hold.</string>
+    <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button when gesture navigation and TalkBack is enabled. [CHAR LIMIT=none] -->
+    <string name="accessibility_gesture_3finger_instructional_text">To switch between services, swipe up with three fingers and hold.</string>
 
     <!-- Text used to describe system navigation features, shown within a UI allowing a user to assign system magnification features to the Accessibility button in the navigation bar. -->
     <string name="accessibility_magnification_chooser_text">Magnification</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3b3a062..27a4a46 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3286,9 +3286,16 @@
   <java-symbol type="layout" name="accessibility_button_chooser_item" />
   <java-symbol type="id" name="accessibility_button_chooser_grid" />
   <java-symbol type="id" name="accessibility_button_prompt" />
+  <java-symbol type="id" name="accessibility_button_prompt_prologue" />
   <java-symbol type="id" name="accessibility_button_target_icon" />
   <java-symbol type="id" name="accessibility_button_target_label" />
   <java-symbol type="string" name="accessibility_magnification_chooser_text" />
+
+  <java-symbol type="string" name="accessibility_gesture_prompt_text" />
+  <java-symbol type="string" name="accessibility_gesture_3finger_prompt_text" />
+  <java-symbol type="string" name="accessibility_gesture_instructional_text" />
+  <java-symbol type="string" name="accessibility_gesture_3finger_instructional_text" />
+
   <java-symbol type="drawable" name="ic_accessibility_magnification" />
 
   <!-- com.android.internal.widget.RecyclerView -->
diff --git a/packages/CarSystemUI/res/layout/car_volume_item.xml b/packages/CarSystemUI/res/layout/car_volume_item.xml
index 2275ca6..0b42904 100644
--- a/packages/CarSystemUI/res/layout/car_volume_item.xml
+++ b/packages/CarSystemUI/res/layout/car_volume_item.xml
@@ -19,16 +19,18 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:background="@color/car_volume_dialog_background_color"
+    android:paddingStart="@dimen/car_volume_item_padding_start"
+    android:paddingEnd="@dimen/car_volume_item_padding_end"
     android:minHeight="@dimen/car_volume_item_height">
 
     <!-- Primary Action. -->
     <ImageView
         android:id="@+id/primary_icon"
-        android:layout_width="@dimen/car_primary_icon_size"
+        android:layout_width="@dimen/car_volume_item_icon_size"
         android:layout_centerVertical="true"
-        android:layout_marginStart="@dimen/car_volume_item_margin_horizontal"
         android:layout_alignParentStart="true"
-        android:layout_height="@dimen/car_primary_icon_size"/>
+        android:layout_height="@dimen/car_volume_item_icon_size"/>
 
     <!-- Note: the horizontal padding and offset are set to 0 so that the track and thumb
              aligns with the proper keylines. -->
@@ -61,11 +63,10 @@
         android:background="@color/car_volume_item_divider_color"/>
     <ImageView
         android:id="@+id/supplemental_icon"
-        android:layout_width="@dimen/car_primary_icon_size"
-        android:layout_height="@dimen/car_primary_icon_size"
+        android:layout_width="@dimen/car_volume_item_icon_size"
+        android:layout_height="@dimen/car_volume_item_icon_size"
         android:background="?android:attr/selectableItemBackground"
         android:layout_centerVertical="true"
         android:layout_alignParentEnd="true"
-        android:layout_marginEnd="@dimen/car_volume_item_margin_horizontal"
         android:scaleType="fitCenter"/>
 </RelativeLayout>
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index dac5f2a..e13c940 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -37,6 +37,9 @@
     <!-- The background color of the notification shade -->
     <color name="notification_shade_background_color">#DD000000</color>
 
+    <!-- The background color of the car volume dialog -->
+    <color name="car_volume_dialog_background_color">@color/system_bar_background_opaque</color>
+
     <!-- The color of the dividing line between grouped notifications. -->
     <color name="notification_divider_color">@*android:color/notification_action_list</color>
 
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index 0358357b..7017484 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -78,8 +78,10 @@
     <dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen>
 
     <!-- Car volume dimens. -->
+    <dimen name="car_volume_item_icon_size">@dimen/car_primary_icon_size</dimen>
     <dimen name="car_volume_item_height">@*android:dimen/car_single_line_list_item_height</dimen>
-    <dimen name="car_volume_item_margin_horizontal">@*android:dimen/car_keyline_1</dimen>
+    <dimen name="car_volume_item_padding_start">@*android:dimen/car_keyline_1</dimen>
+    <dimen name="car_volume_item_padding_end">@*android:dimen/car_keyline_1</dimen>
     <dimen name="car_volume_item_seekbar_margin_vertical">@*android:dimen/car_padding_1</dimen>
     <dimen name="car_volume_item_seekbar_margin_start">@*android:dimen/car_keyline_3</dimen>
     <dimen name="car_volume_item_seekbar_margin_end">@*android:dimen/car_padding_4</dimen>
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 94962f7..d0a63f0 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -32,7 +32,9 @@
 import android.content.ServiceConnection;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.graphics.Color;
 import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.os.Debug;
@@ -245,6 +247,7 @@
         mExpanded = false;
         mWindow = mDialog.getWindow();
         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+        mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
                 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
         mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
index f30de13..ea3c1d9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
@@ -110,13 +110,7 @@
     }
 
     public DataUsageInfo getDataUsageInfo() {
-        final String subscriberId = getActiveSubscriberId();
-        if (subscriberId == null) {
-            return warn("no subscriber id");
-        }
-        NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId);
-        template = NetworkTemplate.normalize(template, getTelephonyManager()
-                .getMergedSubscriberIds());
+        NetworkTemplate template = DataUsageUtils.getMobileTemplate(mContext, mSubscriptionId);
 
         return getDataUsageInfo(template);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
new file mode 100644
index 0000000..de38e8a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -0,0 +1,71 @@
+/*
+ * 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.net.NetworkTemplate;
+import android.os.ParcelUuid;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utils class for data usage
+ */
+public class DataUsageUtils {
+    private static final String TAG = "DataUsageUtils";
+
+    /**
+     * Return mobile NetworkTemplate based on {@code subId}
+     */
+    public static NetworkTemplate getMobileTemplate(Context context, int subId) {
+        final TelephonyManager telephonyManager = context.getSystemService(
+                TelephonyManager.class);
+        final SubscriptionManager subscriptionManager = context.getSystemService(
+                SubscriptionManager.class);
+        final SubscriptionInfo info = subscriptionManager.getActiveSubscriptionInfo(subId);
+        final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
+                telephonyManager.getSubscriberId(subId));
+
+        if (info == null) {
+            Log.i(TAG, "Subscription is not active: " + subId);
+            return mobileAll;
+        }
+        final ParcelUuid groupUuid = info.getGroupUuid();
+        if (groupUuid == null) {
+            Log.i(TAG, "Subscription doesn't have valid group uuid: " + subId);
+            return mobileAll;
+        }
+
+        // Otherwise merge other subscriberId to create new NetworkTemplate
+        final List<SubscriptionInfo> groupInfos = subscriptionManager.getSubscriptionsInGroup(
+                groupUuid);
+        final List<String> mergedSubscriberIds = new ArrayList<>();
+        for (SubscriptionInfo subInfo : groupInfos) {
+            final String subscriberId = telephonyManager.getSubscriberId(
+                    subInfo.getSubscriptionId());
+            if (subscriberId != null) {
+                mergedSubscriberIds.add(subscriberId);
+            }
+        }
+        return NetworkTemplate.normalize(mobileAll, mergedSubscriberIds.toArray(new String[0]));
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
new file mode 100644
index 0000000..dc33cfe
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.NetworkTemplate;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class DataUsageUtilsTest {
+
+    private static final int SUB_ID = 1;
+    private static final int SUB_ID_2 = 2;
+    private static final String SUBSCRIBER_ID = "Test Subscriber";
+    private static final String SUBSCRIBER_ID_2 = "Test Subscriber 2";
+
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
+    @Mock
+    private SubscriptionInfo mInfo1;
+    @Mock
+    private SubscriptionInfo mInfo2;
+    @Mock
+    private ParcelUuid mParcelUuid;
+    private Context mContext;
+    private List<SubscriptionInfo> mInfos;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(RuntimeEnvironment.application);
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
+        when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID);
+        when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2);
+        when(mInfo1.getSubscriptionId()).thenReturn(SUB_ID);
+        when(mInfo2.getSubscriptionId()).thenReturn(SUB_ID_2);
+
+        mInfos = new ArrayList<>();
+        mInfos.add(mInfo1);
+        mInfos.add(mInfo2);
+        when(mSubscriptionManager.getSubscriptionsInGroup(mParcelUuid)).thenReturn(mInfos);
+    }
+
+    @Test
+    public void getMobileTemplate_infoNull_returnMobileAll() {
+        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(null);
+
+        final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
+        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
+        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID_2)).isFalse();
+    }
+
+    @Test
+    public void getMobileTemplate_groupUuidNull_returnMobileAll() {
+        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
+        when(mInfo1.getGroupUuid()).thenReturn(null);
+
+        final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
+        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
+        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID_2)).isFalse();
+    }
+
+    @Test
+    public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
+        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
+        when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
+
+        final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
+        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
+        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID_2)).isTrue();
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index cc3a67c..240691b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -122,13 +122,21 @@
      * disabled.
      */
     public static boolean isAssistantGestureDisabled(int sysuiStateFlags) {
-        // Disable when in screen pinning, immersive, the bouncer is showing, or the notifications
-        // are interactive
+        // Disable when in screen pinning, immersive, the bouncer is showing
         int disableFlags = SYSUI_STATE_SCREEN_PINNING
                 | SYSUI_STATE_NAV_BAR_HIDDEN
-                | SYSUI_STATE_BOUNCER_SHOWING
-                | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
-        return (sysuiStateFlags & disableFlags) != 0;
+                | SYSUI_STATE_BOUNCER_SHOWING;
+        if ((sysuiStateFlags & disableFlags) != 0) {
+            return true;
+        }
+
+        // Disable when notifications are showing (only if unlocked)
+        if ((sysuiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0
+                && (sysuiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) == 0) {
+            return true;
+        }
+
+        return false;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index c26fdc2..2090748 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -324,25 +324,13 @@
         final CharSequence[] carrierNames = new CharSequence[numSubs];
         if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
 
-        boolean anySimEmergency = mKeyguardUpdateMonitor.isAnySimEmergencyAble();
         for (int i = 0; i < numSubs; i++) {
             int subId = subs.get(i).getSubscriptionId();
             carrierNames[i] = "";
             subsIds[i] = subId;
             subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
             IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
-            ServiceState s = mKeyguardUpdateMonitor.getServiceState(subId);
             CharSequence carrierName = subs.get(i).getCarrierName();
-            // If this sub is showing No service but at least one slot currently supports emergency
-            // calls, it should replace it by Emergency calls only
-            if (s != null && s.getState() != ServiceState.STATE_IN_SERVICE && !s.isEmergencyOnly()
-                    && anySimEmergency) {
-                carrierName = getContext().getText(
-                        com.android.internal.R.string.emergency_calls_only);
-                if (DEBUG) {
-                    Log.d(TAG, "Subscription " + subId + "switched to ECO");
-                }
-            }
             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
             if (DEBUG) {
                 Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
@@ -352,15 +340,16 @@
                 carrierNames[i] = carrierTextForSimState;
             }
             if (simState == IccCardConstants.State.READY) {
-                if (s != null && s.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+                ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
+                if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
                     // hack for WFC (IWLAN) not turning off immediately once
                     // Wi-Fi is disassociated or disabled
-                    if (s.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+                    if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
                             || (mWifiManager.isWifiEnabled()
                             && mWifiManager.getConnectionInfo() != null
                             && mWifiManager.getConnectionInfo().getBSSID() != null)) {
                         if (DEBUG) {
-                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + s);
+                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
                         }
                         anySimReadyAndInService = true;
                     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 446366b..6a4dbc8d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -195,12 +195,6 @@
     HashMap<Integer, SimData> mSimDatas = new HashMap<Integer, SimData>();
     HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>();
 
-    /**
-     * Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount}
-     */
-    private static final int SIM_SLOTS = 3;
-    private final ServiceState[] mServiceStatesBySlot = new ServiceState[SIM_SLOTS];
-
     private int mRingMode;
     private int mPhoneState;
     private boolean mKeyguardIsVisible;
@@ -332,7 +326,7 @@
                     handleAirplaneModeChanged();
                     break;
                 case MSG_SERVICE_STATE_CHANGE:
-                    handleServiceStateChange(msg.arg1, msg.arg2, (ServiceState) msg.obj);
+                    handleServiceStateChange(msg.arg1, (ServiceState) msg.obj);
                     break;
                 case MSG_SCREEN_TURNED_ON:
                     handleScreenTurnedOn();
@@ -1044,13 +1038,12 @@
                 ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
                 int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY, -1);
                 if (DEBUG) {
                     Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
                             + subId);
                 }
-                mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, slotId, serviceState)
-                        .sendToTarget();
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
             } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
                     action)) {
                 mHandler.sendEmptyMessage(MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED);
@@ -2049,14 +2042,6 @@
      */
     @VisibleForTesting
     void handleServiceStateChange(int subId, ServiceState serviceState) {
-        handleServiceStateChange(subId, -1, serviceState);
-    }
-
-    /**
-     * Handle {@link #MSG_SERVICE_STATE_CHANGE}
-     */
-    @VisibleForTesting
-    void handleServiceStateChange(int subId, int slotId, ServiceState serviceState) {
         if (DEBUG) {
             Log.d(TAG,
                     "handleServiceStateChange(subId=" + subId + ", serviceState=" + serviceState);
@@ -2070,7 +2055,6 @@
         }
 
         mServiceStates.put(subId, serviceState);
-        if (slotId >= 0 && slotId < SIM_SLOTS) mServiceStatesBySlot[slotId] = serviceState;
 
         for (int j = 0; j < mCallbacks.size(); j++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
@@ -2296,21 +2280,6 @@
         return mServiceStates.get(subId);
     }
 
-    /**
-     * @return true iff at least one slot currently supports emergency calls
-     */
-    public boolean isAnySimEmergencyAble() {
-        for (int i = 0; i < SIM_SLOTS; i++) {
-            ServiceState s = mServiceStatesBySlot[i];
-            if (s != null) {
-                if (s.getState() == ServiceState.STATE_IN_SERVICE || s.isEmergencyOnly()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     public void clearBiometricRecognized() {
         mUserFingerprintAuthenticated.clear();
         mUserFaceAuthenticated.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 4aaf85a..5e19219 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -199,6 +199,10 @@
             return;
         }
 
+        if (mOverlay == null || mBottomOverlay == null) {
+            return;
+        }
+
         if (mAssistHintVisible != visible) {
             mAssistHintVisible = visible;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 396a3a7..4a6c7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -16,18 +16,26 @@
 
 package com.android.systemui.statusbar.notification;
 
+import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
+
+import android.os.Handler;
+import android.os.SystemClock;
 import android.view.View;
 
 import androidx.collection.ArraySet;
 
+import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -35,14 +43,19 @@
  * and reorder at the right time when they are out of view.
  */
 @Singleton
-public class VisualStabilityManager implements OnHeadsUpChangedListener {
+public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpable {
+
+    private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
 
     private final ArrayList<Callback> mCallbacks =  new ArrayList<>();
+    private final Handler mHandler;
 
     private NotificationPresenter mPresenter;
     private boolean mPanelExpanded;
     private boolean mScreenOn;
     private boolean mReorderingAllowed;
+    private boolean mIsTemporaryReorderingAllowed;
+    private long mTemporaryReorderingStart;
     private VisibilityLocationProvider mVisibilityLocationProvider;
     private ArraySet<View> mAllowedReorderViews = new ArraySet<>();
     private ArraySet<NotificationEntry> mLowPriorityReorderingViews = new ArraySet<>();
@@ -50,7 +63,12 @@
     private boolean mPulsing;
 
     @Inject
-    public VisualStabilityManager(NotificationEntryManager notificationEntryManager) {
+    public VisualStabilityManager(
+            NotificationEntryManager notificationEntryManager,
+            @Named(MAIN_HANDLER_NAME) Handler handler) {
+
+        mHandler = handler;
+
         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
             public void onPreEntryUpdated(NotificationEntry entry) {
@@ -114,10 +132,11 @@
     }
 
     private void updateReorderingAllowed() {
-        boolean reorderingAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
-        boolean changed = reorderingAllowed && !mReorderingAllowed;
+        boolean reorderingAllowed =
+                (!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing;
+        boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
         mReorderingAllowed = reorderingAllowed;
-        if (changed) {
+        if (changedToTrue) {
             notifyCallbacks();
         }
     }
@@ -180,6 +199,25 @@
     }
 
     /**
+     * Temporarily allows reordering of the entire shade for a period of 1000ms. Subsequent calls
+     * to this method will extend the timer.
+     */
+    public void temporarilyAllowReordering() {
+        mHandler.removeCallbacks(mOnTemporaryReorderingExpired);
+        mHandler.postDelayed(mOnTemporaryReorderingExpired, TEMPORARY_REORDERING_ALLOWED_DURATION);
+        if (!mIsTemporaryReorderingAllowed) {
+            mTemporaryReorderingStart = SystemClock.elapsedRealtime();
+        }
+        mIsTemporaryReorderingAllowed = true;
+        updateReorderingAllowed();
+    }
+
+    private final Runnable mOnTemporaryReorderingExpired = () -> {
+        mIsTemporaryReorderingAllowed = false;
+        updateReorderingAllowed();
+    };
+
+    /**
      * Notify the visual stability manager that a new view was added and should be allowed to
      * reorder next time.
      */
@@ -187,6 +225,20 @@
         mAddedChildren.add(view);
     }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("VisualStabilityManager state:");
+        pw.print("  mIsTemporaryReorderingAllowed="); pw.println(mIsTemporaryReorderingAllowed);
+        pw.print("  mTemporaryReorderingStart="); pw.println(mTemporaryReorderingStart);
+
+        long now = SystemClock.elapsedRealtime();
+        pw.print("    Temporary reordering window has been open for ");
+        pw.print(now - (mIsTemporaryReorderingAllowed ? mTemporaryReorderingStart : now));
+        pw.println("ms");
+
+        pw.println();
+    }
+
     public interface Callback {
         /**
          * Called when reordering is allowed again.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index f15d6b7..fe890fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -73,6 +74,7 @@
 
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     private final Context mContext;
+    private final VisualStabilityManager mVisualStabilityManager;
     private final AccessibilityManager mAccessibilityManager;
 
     // Dependencies:
@@ -96,8 +98,11 @@
     protected String mKeyToRemoveOnGutsClosed;
 
     @Inject
-    public NotificationGutsManager(Context context) {
+    public NotificationGutsManager(
+            Context context,
+            VisualStabilityManager visualStabilityManager) {
         mContext = context;
+        mVisualStabilityManager = visualStabilityManager;
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
     }
@@ -304,6 +309,7 @@
         notificationInfoView.bindNotification(
                 pmUser,
                 iNotificationManager,
+                mVisualStabilityManager,
                 packageName,
                 row.getEntry().channel,
                 row.getUniqueChannels(),
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 626701c..0f6740d 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
@@ -65,6 +65,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 
 import java.lang.annotation.Retention;
@@ -104,6 +105,7 @@
     private INotificationManager mINotificationManager;
     private PackageManager mPm;
     private MetricsLogger mMetricsLogger;
+    private VisualStabilityManager mVisualStabilityManager;
     private ChannelEditorDialogController mChannelEditorDialogController;
 
     private String mPackageName;
@@ -244,6 +246,7 @@
     void bindNotification(
             final PackageManager pm,
             final INotificationManager iNotificationManager,
+            final VisualStabilityManager visualStabilityManager,
             final String pkg,
             final NotificationChannel notificationChannel,
             final Set<NotificationChannel> uniqueChannelsInRow,
@@ -256,7 +259,7 @@
             int importance,
             boolean wasShownHighPriority)
             throws RemoteException {
-        bindNotification(pm, iNotificationManager, pkg, notificationChannel,
+        bindNotification(pm, iNotificationManager, visualStabilityManager, pkg, notificationChannel,
                 uniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
                 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
                 false /* isBlockingHelper */,
@@ -266,6 +269,7 @@
     public void bindNotification(
             PackageManager pm,
             INotificationManager iNotificationManager,
+            VisualStabilityManager visualStabilityManager,
             String pkg,
             NotificationChannel notificationChannel,
             Set<NotificationChannel> uniqueChannelsInRow,
@@ -281,6 +285,7 @@
             throws RemoteException {
         mINotificationManager = iNotificationManager;
         mMetricsLogger = Dependency.get(MetricsLogger.class);
+        mVisualStabilityManager = visualStabilityManager;
         mChannelEditorDialogController = Dependency.get(ChannelEditorDialogController.class);
         mPackageName = pkg;
         mUniqueChannelsInRow = uniqueChannelsInRow;
@@ -539,6 +544,7 @@
                     new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
                             mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
                             mStartingChannelImportance, newImportance));
+            mVisualStabilityManager.temporarilyAllowReordering();
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index fa81e40..db45ad78 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -37,7 +36,6 @@
 import android.net.ConnectivityManager;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
-import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -45,7 +43,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
-import com.android.internal.R;
 import com.android.internal.telephony.IccCardConstants;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
@@ -59,6 +56,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 @SmallTest
@@ -70,7 +68,6 @@
     private static final String TEST_CARRIER = "TEST_CARRIER";
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
     private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
-    private static final String EMERGENCY = "Emergency";
     private static final int TEST_CARRIER_ID = 1;
     private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(0, "", 0,
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
@@ -109,8 +106,6 @@
         mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager);
         mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
         mContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.string.emergency_calls_only, EMERGENCY);
         mDependency.injectMockDependency(WakefulnessLifecycle.class);
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 new Handler(mTestableLooper.getLooper()));
@@ -195,6 +190,8 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -217,6 +214,8 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -260,6 +259,8 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -283,6 +284,8 @@
                 .thenReturn(IccCardConstants.State.NOT_READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -306,6 +309,8 @@
                 .thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
 
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
                         CarrierTextController.CarrierTextCallbackInfo.class);
@@ -330,6 +335,7 @@
                 .thenReturn(IccCardConstants.State.NOT_READY)
                 .thenReturn(IccCardConstants.State.READY);
         when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
         ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
@@ -352,6 +358,7 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt()))
             .thenReturn(IccCardConstants.State.READY);
 
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
         mCarrierTextController.updateDisplayOpportunisticSubscriptionCarrierText(true);
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(list);
 
@@ -366,127 +373,6 @@
         assertEquals(TEST_CARRIER_2, captor.getValue().carrierText);
     }
 
-    @Test
-    public void testCarrierText_replaceOutOfServiceWithEmergency() {
-        reset(mCarrierTextCallback);
-
-        List<SubscriptionInfo> list = new ArrayList<>();
-        list.add(TEST_SUBSCRIPTION);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
-
-        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
-                .thenReturn(IccCardConstants.State.READY);
-        ServiceState s = mock(ServiceState.class);
-        when(mKeyguardUpdateMonitor.getServiceState(anyInt())).thenReturn(s);
-        when(s.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-
-        when(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).thenReturn(true);
-
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
-                ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
-
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
-        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
-
-        assertEquals(1, captor.getValue().listOfCarriers.length);
-        assertEquals(EMERGENCY, captor.getValue().listOfCarriers[0]);
-    }
-
-    @Test
-    public void testCarrierText_replaceOutOfServiceWithEmergencyOnlyInNoService() {
-        reset(mCarrierTextCallback);
-
-        List<SubscriptionInfo> list = new ArrayList<>();
-        list.add(TEST_SUBSCRIPTION);
-        list.add(TEST_SUBSCRIPTION_2);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
-
-        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
-                .thenReturn(IccCardConstants.State.READY);
-        ServiceState sInService = mock(ServiceState.class);
-        ServiceState sOutOfService = mock(ServiceState.class);
-        when(mKeyguardUpdateMonitor.getServiceState(anyInt()))
-                .thenReturn(sInService)
-                .thenReturn(sOutOfService);
-        when(sInService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
-        when(sOutOfService.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-
-        when(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).thenReturn(true);
-
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
-                ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
-
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
-        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
-
-        assertEquals(2, captor.getValue().listOfCarriers.length);
-        assertEquals(TEST_CARRIER, captor.getValue().listOfCarriers[0]);
-        assertEquals(EMERGENCY, captor.getValue().listOfCarriers[1]);
-    }
-
-    @Test
-    public void testCarrierText_dontReplaceWithEmergencyIfNotAble() {
-        reset(mCarrierTextCallback);
-
-        List<SubscriptionInfo> list = new ArrayList<>();
-        list.add(TEST_SUBSCRIPTION);
-        list.add(TEST_SUBSCRIPTION_2);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
-
-        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
-                .thenReturn(IccCardConstants.State.READY);
-        ServiceState sOutOfService = mock(ServiceState.class);
-        when(mKeyguardUpdateMonitor.getServiceState(anyInt())).thenReturn(sOutOfService);
-        when(sOutOfService.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-
-        when(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).thenReturn(false);
-
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
-                ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
-
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
-        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
-
-        assertEquals(2, captor.getValue().listOfCarriers.length);
-        assertEquals(TEST_CARRIER, captor.getValue().listOfCarriers[0]);
-        assertEquals(TEST_CARRIER_2, captor.getValue().listOfCarriers[1]);
-    }
-
-    @Test
-    public void testCarrierText_dontReplaceWithEmergencyIfAlreadyEmergency() {
-        reset(mCarrierTextCallback);
-
-        List<SubscriptionInfo> list = new ArrayList<>();
-        list.add(TEST_SUBSCRIPTION);
-        when(mKeyguardUpdateMonitor.getSubscriptionInfo(anyBoolean())).thenReturn(list);
-
-        when(mKeyguardUpdateMonitor.getSimState(anyInt()))
-                .thenReturn(IccCardConstants.State.READY);
-        ServiceState sOutOfService = mock(ServiceState.class);
-        when(mKeyguardUpdateMonitor.getServiceState(anyInt())).thenReturn(sOutOfService);
-        when(sOutOfService.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(sOutOfService.isEmergencyOnly()).thenReturn(true);
-
-        when(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).thenReturn(false);
-
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
-                ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
-
-        mCarrierTextController.updateCarrierText();
-        mTestableLooper.processAllMessages();
-        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
-
-        assertEquals(1, captor.getValue().listOfCarriers.length);
-        assertEquals(TEST_CARRIER, captor.getValue().listOfCarriers[0]);
-    }
-
     public static class TestCarrierTextController extends CarrierTextController {
         private KeyguardUpdateMonitor mKUM;
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 3a3cbad..6208ab8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -22,7 +22,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
@@ -362,52 +361,6 @@
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
-    @Test
-    public void testAnySimEmergency_allSimsInService() {
-        ServiceState s0 = mock(ServiceState.class);
-        when(s0.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
-
-        mKeyguardUpdateMonitor.handleServiceStateChange(0, 0, s0);
-        assertThat(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).isTrue();
-    }
-
-    @Test
-    public void testAnySimEmergency_someSimsInServiceOthersNotECC() {
-        ServiceState s0 = mock(ServiceState.class);
-        when(s0.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        ServiceState s1 = mock(ServiceState.class);
-        when(s1.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
-
-        mKeyguardUpdateMonitor.handleServiceStateChange(0, 0, s0);
-        mKeyguardUpdateMonitor.handleServiceStateChange(0, 1, s1);
-        assertThat(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).isTrue();
-    }
-
-    @Test
-    public void testAnySimEmergency_someSimsEmergencyCapable() {
-        ServiceState s0 = mock(ServiceState.class);
-        when(s0.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
-        ServiceState s1 = mock(ServiceState.class);
-        when(s1.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(s1.isEmergencyOnly()).thenReturn(true);
-
-        mKeyguardUpdateMonitor.handleServiceStateChange(0, 0, s0);
-        mKeyguardUpdateMonitor.handleServiceStateChange(0, 1, s1);
-        assertThat(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).isTrue();
-    }
-
-    @Test
-    public void testAnySimEmergency_noEmergencyCapable() {
-        ServiceState s0 = mock(ServiceState.class);
-        when(s0.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
-        ServiceState s1 = mock(ServiceState.class);
-        when(s1.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-
-        mKeyguardUpdateMonitor.handleServiceStateChange(0, 0, s0);
-        mKeyguardUpdateMonitor.handleServiceStateChange(0, 1, s1);
-        assertThat(mKeyguardUpdateMonitor.isAnySimEmergencyAble()).isFalse();
-    }
-
     private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
         int subscription = simInited
                 ? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index b35dcb6..dd2630b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -16,17 +16,20 @@
 
 package com.android.systemui.statusbar.notification;
 
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.Matchers.anyObject;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.os.Handler;
 import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -38,11 +41,13 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
 public class VisualStabilityManagerTest extends SysuiTestCase {
 
-    private VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager(
-            mock(NotificationEntryManager.class));
+    private TestableLooper mTestableLooper;
+
+    private VisualStabilityManager mVisualStabilityManager;
     private VisualStabilityManager.Callback mCallback = mock(VisualStabilityManager.Callback.class);
     private VisibilityLocationProvider mLocationProvider = mock(VisibilityLocationProvider.class);
     private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
@@ -50,46 +55,53 @@
 
     @Before
     public void setUp() {
+        mTestableLooper = TestableLooper.get(this);
+        mVisualStabilityManager = new VisualStabilityManager(
+                mock(NotificationEntryManager.class),
+                new Handler(mTestableLooper.getLooper()));
+
         mVisualStabilityManager.setUpWithPresenter(mock(NotificationPresenter.class));
         mVisualStabilityManager.setVisibilityLocationProvider(mLocationProvider);
         mEntry = new NotificationEntry(mock(StatusBarNotification.class));
         mEntry.setRow(mRow);
+
+        when(mRow.getEntry()).thenReturn(mEntry);
     }
 
     @Test
     public void testPanelExpansion() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), false);
+        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
         mVisualStabilityManager.setPanelExpanded(false);
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), true);
+        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testScreenOn() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), false);
+        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
         mVisualStabilityManager.setScreenOn(false);
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), true);
+        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingAllowedChangesScreenOn() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        assertEquals(mVisualStabilityManager.isReorderingAllowed(), false);
+        assertFalse(mVisualStabilityManager.isReorderingAllowed());
         mVisualStabilityManager.setScreenOn(false);
-        assertEquals(mVisualStabilityManager.isReorderingAllowed(), true);
+        assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
     @Test
     public void testReorderingAllowedChangesPanel() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        assertEquals(mVisualStabilityManager.isReorderingAllowed(), false);
+        assertFalse(mVisualStabilityManager.isReorderingAllowed());
         mVisualStabilityManager.setPanelExpanded(false);
-        assertEquals(mVisualStabilityManager.isReorderingAllowed(), true);
+        assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
     @Test
@@ -126,51 +138,51 @@
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
         mVisualStabilityManager.notifyViewAddition(mRow);
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), true);
+        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingVisibleHeadsUpNotAllowed() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        when(mLocationProvider.isInVisibleLocation(anyObject())).thenReturn(true);
+        when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(true);
         mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), false);
+        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingVisibleHeadsUpAllowed() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        when(mLocationProvider.isInVisibleLocation(anyObject())).thenReturn(false);
+        when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
         mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), true);
+        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingVisibleHeadsUpAllowedOnce() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        when(mLocationProvider.isInVisibleLocation(anyObject())).thenReturn(false);
+        when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
         mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
         mVisualStabilityManager.onReorderingFinished();
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), false);
+        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testPulsing() {
         mVisualStabilityManager.setPulsing(true);
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), false);
+        assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
         mVisualStabilityManager.setPulsing(false);
-        assertEquals(mVisualStabilityManager.canReorderNotification(mRow), true);
+        assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
     }
 
     @Test
     public void testReorderingAllowedChanges_Pulsing() {
         mVisualStabilityManager.setPulsing(true);
-        assertEquals(mVisualStabilityManager.isReorderingAllowed(), false);
+        assertFalse(mVisualStabilityManager.isReorderingAllowed());
         mVisualStabilityManager.setPulsing(false);
-        assertEquals(mVisualStabilityManager.isReorderingAllowed(), true);
+        assertTrue(mVisualStabilityManager.isReorderingAllowed());
     }
 
     @Test
@@ -180,4 +192,49 @@
         mVisualStabilityManager.setPulsing(false);
         verify(mCallback).onReorderingAllowed();
     }
+
+    @Test
+    public void testTemporarilyAllowReorderingNotifiesCallbacks() {
+        // GIVEN having the panel open (which would block reordering)
+        mVisualStabilityManager.setScreenOn(true);
+        mVisualStabilityManager.setPanelExpanded(true);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+
+        // WHEN we temprarily allow reordering
+        mVisualStabilityManager.temporarilyAllowReordering();
+
+        // THEN callbacks are notified that reordering is allowed
+        verify(mCallback).onReorderingAllowed();
+        assertTrue(mVisualStabilityManager.isReorderingAllowed());
+    }
+
+    @Test
+    public void testTemporarilyAllowReorderingDoesntOverridePulsing() {
+        // GIVEN we are in a pulsing state
+        mVisualStabilityManager.setPulsing(true);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+
+        // WHEN we temprarily allow reordering
+        mVisualStabilityManager.temporarilyAllowReordering();
+
+        // THEN reordering is still not allowed
+        verify(mCallback, never()).onReorderingAllowed();
+        assertFalse(mVisualStabilityManager.isReorderingAllowed());
+    }
+
+    @Test
+    public void testTemporarilyAllowReorderingExpires() {
+        // GIVEN having the panel open (which would block reordering)
+        mVisualStabilityManager.setScreenOn(true);
+        mVisualStabilityManager.setPanelExpanded(true);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+
+        // WHEN we temprarily allow reordering and then wait until the window expires
+        mVisualStabilityManager.temporarilyAllowReordering();
+        assertTrue(mVisualStabilityManager.isReorderingAllowed());
+        mTestableLooper.processMessages(1);
+
+        // THEN reordering is no longer allowed
+        assertFalse(mVisualStabilityManager.isReorderingAllowed());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 6376bd3..ef13b61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -63,6 +63,7 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -97,6 +98,7 @@
 
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
     @Mock private MetricsLogger mMetricsLogger;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private NotificationPresenter mPresenter;
     @Mock private NotificationActivityStarter mNotificationActivityStarter;
     @Mock private NotificationStackScrollLayout mStackScroller;
@@ -111,11 +113,12 @@
         mDependency.injectTestDependency(DeviceProvisionedController.class,
                 mDeviceProvisionedController);
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
 
         mHelper = new NotificationTestHelper(mContext);
 
-        mGutsManager = new NotificationGutsManager(mContext);
+        mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager);
         mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -316,6 +319,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
+                eq(mVisualStabilityManager),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
                 anySet(),
@@ -344,6 +348,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
+                eq(mVisualStabilityManager),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
                 anySet(),
@@ -374,6 +379,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
+                eq(mVisualStabilityManager),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
                 anySet(),
@@ -403,6 +409,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
+                eq(mVisualStabilityManager),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
                 anySet(),
@@ -431,6 +438,7 @@
         verify(notificationInfoView).bindNotification(
                 any(PackageManager.class),
                 any(INotificationManager.class),
+                eq(mVisualStabilityManager),
                 eq(statusBarNotification.getPackageName()),
                 any(NotificationChannel.class),
                 anySet(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 78970d9..703adf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -72,6 +72,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -116,6 +117,8 @@
     private PackageManager mMockPackageManager;
     @Mock
     private NotificationBlockingHelperManager mBlockingHelperManager;
+    @Mock
+    private VisualStabilityManager mVisualStabilityManager;
 
     @Before
     public void setUp() throws Exception {
@@ -193,11 +196,21 @@
     @Test
     public void testBindNotification_SetsTextApplicationName() throws Exception {
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn,
-                null, null, null,
-                true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView textView = mNotificationInfo.findViewById(R.id.pkgname);
         assertTrue(textView.getText().toString().contains("App Name"));
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -208,20 +221,42 @@
         final Drawable iconDrawable = mock(Drawable.class);
         when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class)))
                 .thenReturn(iconDrawable);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn,
-                null, null, null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final ImageView iconView = mNotificationInfo.findViewById(R.id.pkgicon);
         assertEquals(iconDrawable, iconView.getDrawable());
     }
 
     @Test
     public void testBindNotification_noDelegate() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn,
-                null, null, null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(GONE, nameView.getVisibility());
         final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
@@ -238,10 +273,21 @@
                 applicationInfo);
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
 
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(VISIBLE, nameView.getVisibility());
         assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -251,10 +297,21 @@
 
     @Test
     public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(GONE, groupNameView.getVisibility());
     }
@@ -267,10 +324,21 @@
         when(mMockINotificationManager.getNotificationChannelGroupForPackage(
                 eq("test_group_id"), eq(TEST_PACKAGE_NAME), eq(TEST_UID)))
                 .thenReturn(notificationChannelGroup);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(View.VISIBLE, groupNameView.getVisibility());
         assertEquals("Test Group Name", groupNameView.getText());
@@ -278,19 +346,42 @@
 
     @Test
     public void testBindNotification_SetsTextChannelName() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(TEST_CHANNEL_NAME, textView.getText());
     }
 
     @Test
     public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mDefaultNotificationChannel, mDefaultNotificationChannelSet,
-                mSbn, null, null, null, true, false, IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mDefaultNotificationChannel,
+                mDefaultNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(GONE, textView.getVisibility());
     }
@@ -301,30 +392,64 @@
         // Package has one channel by default.
         when(mMockINotificationManager.getNumNotificationChannelsForPackage(
                 eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mDefaultNotificationChannel, mDefaultNotificationChannelSet,
-                mSbn, null, null, null, true,
-                false, IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mDefaultNotificationChannel,
+                mDefaultNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(VISIBLE, textView.getVisibility());
     }
 
     @Test
     public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, true,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                true,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(VISIBLE, textView.getVisibility());
     }
 
     @Test
     public void testBindNotification_BlockLink_BlockingHelper() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, mock(
-                        NotificationInfo.OnSettingsClickListener.class), null, true, false,
-                true /* isBlockingHelper */, IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                mock(NotificationInfo.OnSettingsClickListener.class),
+                null,
+                true,
+                false,
+                true /* isBlockingHelper */,
+                IMPORTANCE_DEFAULT,
+                true);
         final View block =
                 mNotificationInfo.findViewById(R.id.blocking_helper_turn_off_notifications);
         final View interruptivenessSettings = mNotificationInfo.findViewById(
@@ -336,12 +461,24 @@
     @Test
     public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
                 (View v, NotificationChannel c, int appUid) -> {
                     assertEquals(mNotificationChannel, c);
                     latch.countDown();
-                }, null, true, false, IMPORTANCE_DEFAULT, true);
+                },
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
 
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         settingsButton.performClick();
@@ -351,10 +488,21 @@
 
     @Test
     public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -362,36 +510,80 @@
     @Test
     public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned()
             throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
                 (View v, NotificationChannel c, int appUid) -> {
                     assertEquals(mNotificationChannel, c);
-                }, null, false, false, IMPORTANCE_DEFAULT, true);
+                },
+                null,
+                false,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
 
     @Test
     public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null,
-                (View v, NotificationChannel c, int appUid) -> {
-                }, null, true, false, IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                (View v, NotificationChannel c, int appUid) -> { },
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertEquals(View.VISIBLE, settingsButton.getVisibility());
     }
 
     @Test
     public void testBindNotificationLogging_notBlockingHelper() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn,
-                null, null, null,
-                true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         verify(mMetricsLogger).write(argThat(logMaker ->
                 logMaker.getCategory() == MetricsEvent.ACTION_NOTE_CONTROLS
                         && logMaker.getType() == MetricsEvent.TYPE_OPEN
@@ -401,12 +593,22 @@
 
     @Test
     public void testBindNotificationLogging_BlockingHelper() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn,
-                null, null, null,
-                false, true,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                false,
                 true,
-                IMPORTANCE_DEFAULT, true);
+                true,
+                IMPORTANCE_DEFAULT,
+                true);
         verify(mMetricsLogger).write(argThat(logMaker ->
                 logMaker.getCategory() == MetricsEvent.ACTION_NOTE_CONTROLS
                         && logMaker.getType() == MetricsEvent.TYPE_OPEN
@@ -416,12 +618,22 @@
 
     @Test
     public void testLogBlockingHelperCounter_logsForBlockingHelper() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn,
-                null, null, null,
-                false, true,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                false,
                 true,
-                IMPORTANCE_DEFAULT, true);
+                true,
+                IMPORTANCE_DEFAULT,
+                true);
         mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
         verify(mMetricsLogger).count(eq("HowCanNotifsBeRealIfAppsArent"), eq(1));
     }
@@ -429,13 +641,23 @@
     @Test
     public void testOnClickListenerPassesNullChannelForBundle() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
                 TEST_PACKAGE_NAME, mNotificationChannel,
-                createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT), mSbn, null,
+                createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT),
+                mSbn,
+                null,
                 (View v, NotificationChannel c, int appUid) -> {
                     assertEquals(null, c);
                     latch.countDown();
-                }, null, true, true, IMPORTANCE_DEFAULT, true);
+                },
+                null,
+                true,
+                true,
+                IMPORTANCE_DEFAULT,
+                true);
 
         mNotificationInfo.findViewById(R.id.info).performClick();
         // Verify that listener was triggered.
@@ -446,10 +668,21 @@
     @UiThreadTest
     public void testBindNotification_ChannelNameInvisibleWhenBundleFromDifferentChannels()
             throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel,
-                createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT), mSbn, null, null,
-                null, true, false, IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT),
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView channelNameView =
                 mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(GONE, channelNameView.getVisibility());
@@ -458,10 +691,21 @@
     @Test
     @UiThreadTest
     public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel,
-                createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT), mSbn, null, null,
-                null, true, false, IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT),
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         assertEquals(GONE, mNotificationInfo.findViewById(
                 R.id.interruptiveness_settings).getVisibility());
         assertEquals(VISIBLE, mNotificationInfo.findViewById(
@@ -470,10 +714,21 @@
 
     @Test
     public void testBindNotification_whenAppUnblockable() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, true,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                true,
+                IMPORTANCE_DEFAULT,
+                true);
         final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text);
         assertEquals(View.VISIBLE, view.getVisibility());
         assertEquals(mContext.getString(R.string.notification_unblockable_desc),
@@ -484,10 +739,21 @@
 
     @Test
     public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
         mTestableLooper.processAllMessages();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), eq(TEST_UID), any());
@@ -496,10 +762,21 @@
     @Test
     public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_LOW, false);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_LOW,
+                false);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
         mTestableLooper.processAllMessages();
@@ -511,10 +788,21 @@
     public void testDoesNotUpdateNotificationChannelAfterImportanceChangedSilenced()
             throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
 
         mNotificationInfo.findViewById(R.id.silence).performClick();
         mTestableLooper.processAllMessages();
@@ -526,10 +814,21 @@
     public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnchanged()
             throws Exception {
         int originalImportance = mNotificationChannel.getImportance();
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
 
         mNotificationInfo.handleCloseControls(true, false);
         mTestableLooper.processAllMessages();
@@ -542,10 +841,21 @@
     public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnspecified()
             throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_UNSPECIFIED, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_UNSPECIFIED,
+                true);
 
         mNotificationInfo.handleCloseControls(true, false);
 
@@ -561,13 +871,22 @@
         NotificationInfo.CheckSaveListener listener =
                 mock(NotificationInfo.CheckSaveListener.class);
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
-                createMultipleChannelSet(10) /* numUniqueChannelsInRow */, mSbn,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel /* notificationChannel */,
+                createMultipleChannelSet(10) /* numUniqueChannelsInRow */,
+                mSbn,
                 listener /* checkSaveListener */,
-                null /* onSettingsClick */, null /* onAppSettingsClick */, true /* provisioned */,
-                false /* isNonblockable */, true /* isForBlockingHelper */,
-                IMPORTANCE_DEFAULT, true);
+                null /* onSettingsClick */,
+                null /* onAppSettingsClick */,
+                true /* provisioned */,
+                false /* isNonblockable */,
+                true /* isForBlockingHelper */,
+                IMPORTANCE_DEFAULT,
+                true);
 
         NotificationGuts guts = spy(new NotificationGuts(mContext, null));
         when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -591,13 +910,21 @@
         NotificationInfo.CheckSaveListener listener =
                 mock(NotificationInfo.CheckSaveListener.class);
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
-                createMultipleChannelSet(10) /* numUniqueChannelsInRow */, mSbn,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel /* notificationChannel */,
+                createMultipleChannelSet(10) /* numUniqueChannelsInRow */,
+                mSbn,
                 listener /* checkSaveListener */,
-                null /* onSettingsClick */, null /* onAppSettingsClick */,
-                false /* isNonblockable */, true /* isForBlockingHelper */,
-                true, IMPORTANCE_DEFAULT, true);
+                null /* onSettingsClick */,
+                null /* onAppSettingsClick */,
+                false /* isNonblockable */,
+                true /* isForBlockingHelper */,
+                true, IMPORTANCE_DEFAULT,
+                true);
 
         mNotificationInfo.handleCloseControls(true /* save */, false /* force */);
 
@@ -611,14 +938,22 @@
         NotificationInfo.CheckSaveListener listener =
                 mock(NotificationInfo.CheckSaveListener.class);
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
-                createMultipleChannelSet(10) /* numUniqueChannelsInRow */, mSbn,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel /* notificationChannel */,
+                createMultipleChannelSet(10) /* numUniqueChannelsInRow */,
+                mSbn,
                 listener /* checkSaveListener */,
-                null /* onSettingsClick */, null /* onAppSettingsClick */,
+                null /* onSettingsClick */,
+                null /* onAppSettingsClick */,
                 true /* provisioned */,
-                false /* isNonblockable */, true /* isForBlockingHelper */,
-                IMPORTANCE_DEFAULT, true);
+                false /* isNonblockable */,
+                true /* isForBlockingHelper */,
+                IMPORTANCE_DEFAULT,
+                true);
 
         mNotificationInfo.findViewById(R.id.deliver_silently).performClick();
         mTestableLooper.processAllMessages();
@@ -631,6 +966,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
+                mVisualStabilityManager,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mNotificationChannelSet /* numChannels */,
@@ -641,7 +977,8 @@
                 false /* isNonblockable */,
                 true /* isForBlockingHelper */,
                 true,
-                IMPORTANCE_DEFAULT, true);
+                IMPORTANCE_DEFAULT,
+                true);
         NotificationGuts guts = mock(NotificationGuts.class);
         doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean());
         mNotificationInfo.setGutsParent(guts);
@@ -658,6 +995,7 @@
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
+                mVisualStabilityManager,
                 TEST_PACKAGE_NAME,
                 mNotificationChannel,
                 mNotificationChannelSet /* numChannels */,
@@ -688,10 +1026,21 @@
     @Test
     public void testKeepUpdatesNotificationChannel_blockingHelper() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, true,
-                IMPORTANCE_LOW, false);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                true,
+                IMPORTANCE_LOW,
+                false);
 
         mNotificationInfo.findViewById(R.id.keep_showing).performClick();
         mNotificationInfo.handleCloseControls(true, false);
@@ -708,10 +1057,21 @@
     @Test
     public void testNoActionsUpdatesNotificationChannel_blockingHelper() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, true,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                true,
+                IMPORTANCE_DEFAULT,
+                true);
 
         mNotificationInfo.handleCloseControls(true, false);
 
@@ -727,10 +1087,21 @@
     @Test
     public void testSilenceCallsUpdateNotificationChannel() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_DEFAULT, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
 
         mNotificationInfo.findViewById(R.id.silence).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -749,10 +1120,21 @@
     @Test
     public void testUnSilenceCallsUpdateNotificationChannel() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_LOW, false);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_LOW,
+                false);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -772,10 +1154,21 @@
     public void testSilenceCallsUpdateNotificationChannel_channelImportanceUnspecified()
             throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_UNSPECIFIED, true);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_UNSPECIFIED,
+                true);
 
         mNotificationInfo.findViewById(R.id.silence).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -795,10 +1188,21 @@
     public void testSilenceCallsUpdateNotificationChannel_channelImportanceMin()
             throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_MIN);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_MIN, false);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_MIN,
+                false);
 
         assertEquals(mContext.getString(R.string.inline_done_button),
                 ((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -821,10 +1225,21 @@
     public void testAlertCallsUpdateNotificationChannel_channelImportanceMin()
             throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_MIN);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_MIN, false);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_MIN,
+                false);
 
         assertEquals(mContext.getString(R.string.inline_done_button),
                 ((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -844,13 +1259,50 @@
     }
 
     @Test
+    public void testAdjustImportanceTemporarilyAllowsReordering() throws Exception {
+        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_DEFAULT,
+                true);
+
+        mNotificationInfo.findViewById(R.id.silence).performClick();
+        mNotificationInfo.findViewById(R.id.done).performClick();
+        mNotificationInfo.handleCloseControls(true, false);
+
+        verify(mVisualStabilityManager).temporarilyAllowReordering();
+    }
+
+    @Test
     public void testDoneText()
             throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_LOW, false);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_LOW,
+                false);
 
         assertEquals(mContext.getString(R.string.inline_done_button),
                 ((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -866,10 +1318,21 @@
     public void testUnSilenceCallsUpdateNotificationChannel_channelImportanceUnspecified()
             throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_LOW, false);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_LOW,
+                false);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -888,10 +1351,21 @@
     @Test
     public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn, null, null,
-                null, true, false,
-                IMPORTANCE_LOW, false);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
+                null,
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_LOW,
+                false);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -905,11 +1379,23 @@
     @Test
     public void testCloseControlsUpdatesWhenCheckSaveListenerUsesCallback() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
                 (Runnable saveImportance, StatusBarNotification sbn) -> {
                     saveImportance.run();
-                }, null, null, true, false, IMPORTANCE_LOW, false
+                },
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_LOW,
+                false
         );
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
@@ -928,11 +1414,23 @@
     @Test
     public void testCloseControls_withoutHittingApply() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
                 (Runnable saveImportance, StatusBarNotification sbn) -> {
                     saveImportance.run();
-                }, null, null, true, false, IMPORTANCE_LOW, false
+                },
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_LOW,
+                false
         );
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
@@ -944,11 +1442,23 @@
     public void testWillBeRemovedReturnsFalse() throws Exception {
         assertFalse(mNotificationInfo.willBeRemoved());
 
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, mNotificationChannelSet, mSbn,
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mSbn,
                 (Runnable saveImportance, StatusBarNotification sbn) -> {
                     saveImportance.run();
-                }, null, null, true, false, IMPORTANCE_LOW, false
+                },
+                null,
+                null,
+                true,
+                false,
+                IMPORTANCE_LOW,
+                false
         );
 
         assertFalse(mNotificationInfo.willBeRemoved());
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5da1a19..39ce4a0 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3649,6 +3649,13 @@
                 || nai.networkMisc.acceptPartialConnectivity) {
             return;
         }
+
+        // Stop automatically reconnecting to this network in the future. Automatically connecting
+        // to a network that provides no or limited connectivity is not useful, because the user
+        // cannot use that network except through the notification shown by this method, and the
+        // notification is only shown if the network is explicitly selected by the user.
+        nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
+
         // TODO: Evaluate if it's needed to wait 8 seconds for triggering notification when
         // NetworkMonitor detects the network is partial connectivity. Need to change the design to
         // popup the notification immediately when the network is partial connectivity.
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index e097d85..4e416a2 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -63,7 +63,7 @@
     static final String TAG = "Watchdog";
 
     /** Debug flag. */
-    public static final boolean DEBUG = true; // STOPSHIP disable it (b/113252928)
+    public static final boolean DEBUG = false;
 
     // Set this to true to use debug default values.
     static final boolean DB = false;
@@ -570,7 +570,7 @@
                         continue;
                     } else if (waitState == WAITED_HALF) {
                         if (!waitedHalf) {
-                            if (DEBUG) Slog.d(TAG, "WAITED_HALF");
+                            Slog.i(TAG, "WAITED_HALF");
                             // We've waited half the deadlock-detection interval.  Pull a stack
                             // trace and wait another half.
                             ArrayList<Integer> pids = new ArrayList<Integer>();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 376e9b5..e43c548 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -162,6 +162,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * The implementation of the volume manager service.
@@ -265,6 +266,7 @@
     private static final int MSG_SET_DEVICE_STREAM_VOLUME = 26;
     private static final int MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS = 27;
     private static final int MSG_HDMI_VOLUME_CHECK = 28;
+    private static final int MSG_PLAYBACK_CONFIG_CHANGE = 29;
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -1867,9 +1869,15 @@
 
             // Check if volume update should be send to Hearing Aid
             if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
-                Log.i(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex
-                        + " stream=" + streamType);
-                mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
+                // only modify the hearing aid attenuation when the stream to modify matches
+                // the one expected by the hearing aid
+                if (streamType == getHearingAidStreamType()) {
+                    if (DEBUG_VOL) {
+                        Log.d(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index="
+                                + newIndex + " stream=" + streamType);
+                    }
+                    mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
+                }
             }
 
             // Check if volume update should be sent to Hdmi system audio.
@@ -2161,11 +2169,53 @@
                 return AudioSystem.STREAM_VOICE_CALL;
             case AudioSystem.MODE_NORMAL:
             default:
+                // other conditions will influence the stream type choice, read on...
                 break;
         }
+        if (mVoiceActive.get()) {
+            return AudioSystem.STREAM_VOICE_CALL;
+        }
         return AudioSystem.STREAM_MUSIC;
     }
 
+    private AtomicBoolean mVoiceActive = new AtomicBoolean(false);
+
+    private final IPlaybackConfigDispatcher mVoiceActivityMonitor =
+            new IPlaybackConfigDispatcher.Stub() {
+        @Override
+        public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
+                                                 boolean flush) {
+            sendMsg(mAudioHandler, MSG_PLAYBACK_CONFIG_CHANGE, SENDMSG_REPLACE,
+                    0 /*arg1 ignored*/, 0 /*arg2 ignored*/,
+                    configs /*obj*/, 0 /*delay*/);
+        }
+    };
+
+    private void onPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
+        boolean voiceActive = false;
+        for (AudioPlaybackConfiguration config : configs) {
+            final int usage = config.getAudioAttributes().getUsage();
+            if ((usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+                    || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
+                    && config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                voiceActive = true;
+                break;
+            }
+        }
+        if (mVoiceActive.getAndSet(voiceActive) != voiceActive) {
+            updateHearingAidVolumeOnVoiceActivityUpdate();
+        }
+    }
+
+    private void updateHearingAidVolumeOnVoiceActivityUpdate() {
+        final int streamType = getHearingAidStreamType();
+        final int index = getStreamVolume(streamType);
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_VOICE_ACTIVITY_HEARING_AID,
+                mVoiceActive.get(), streamType, index));
+        mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
+
+    }
+
     /**
      * Manage an audio mode change for audio devices that use an "absolute volume" model,
      * i.e. the framework sends the full scale signal, and the actual volume for the use case
@@ -2200,10 +2250,8 @@
         // handling of specific interfaces goes here:
         if ((device & mAbsVolumeMultiModeCaseDevices) == AudioSystem.DEVICE_OUT_HEARING_AID) {
             final int index = getStreamVolume(streamType);
-            mModeLogger.log(new AudioEventLogger.StringEvent("setMode to "
-                    + AudioSystem.modeToString(newMode)
-                    + " causes setting HEARING_AID volume to idx:" + index
-                    + " stream:" + AudioSystem.streamToString(streamType)));
+            sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_MODE_CHANGE_HEARING_AID,
+                    newMode, streamType, index));
             mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
         }
     }
@@ -2269,7 +2317,8 @@
                 mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
             }
 
-            if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
+            if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0
+                    && streamType == getHearingAidStreamType()) {
                 Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
                         + " stream=" + streamType);
                 mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
@@ -4345,6 +4394,11 @@
             throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
                     + " (dis)connection, got " + state);
         }
+        if (state == BluetoothProfile.STATE_CONNECTED) {
+            mPlaybackMonitor.registerPlaybackCallback(mVoiceActivityMonitor, true);
+        } else {
+            mPlaybackMonitor.unregisterPlaybackCallback(mVoiceActivityMonitor);
+        }
         mDeviceBroker.postBluetoothHearingAidDeviceConnectionState(
                 device, state, suppressNoisyIntent, musicDevice, "AudioService");
     }
@@ -4832,6 +4886,7 @@
             pw.println((mIndexMin + 5) / 10);
             pw.print("   Max: ");
             pw.println((mIndexMax + 5) / 10);
+            pw.print("   streamVolume:"); pw.println(getStreamVolume(mStreamType));
             pw.print("   Current: ");
             for (int i = 0; i < mIndexMap.size(); i++) {
                 if (i > 0) {
@@ -5408,6 +5463,11 @@
 
                 case MSG_HDMI_VOLUME_CHECK:
                     onCheckVolumeCecOnHdmiConnection(msg.arg1, (String) msg.obj);
+                    break;
+
+                case MSG_PLAYBACK_CONFIG_CHANGE:
+                    onPlaybackConfigChange((List<AudioPlaybackConfiguration>) msg.obj);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index d999217..fcd8701 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -95,6 +95,8 @@
         static final int VOL_SET_HEARING_AID_VOL = 3;
         static final int VOL_SET_AVRCP_VOL = 4;
         static final int VOL_ADJUST_VOL_UID = 5;
+        static final int VOL_VOICE_ACTIVITY_HEARING_AID = 6;
+        static final int VOL_MODE_CHANGE_HEARING_AID = 7;
 
         final int mOp;
         final int mStream;
@@ -102,6 +104,10 @@
         final int mVal2;
         final String mCaller;
 
+        /** used for VOL_ADJUST_VOL_UID,
+         *           VOL_ADJUST_SUGG_VOL,
+         *           VOL_ADJUST_STREAM_VOL,
+         *           VOL_SET_STREAM_VOL */
         VolumeEvent(int op, int stream, int val1, int val2, String caller) {
             mOp = op;
             mStream = stream;
@@ -110,24 +116,46 @@
             mCaller = caller;
         }
 
+        /** used for VOL_SET_HEARING_AID_VOL*/
         VolumeEvent(int op, int index, int gainDb) {
             mOp = op;
             mVal1 = index;
             mVal2 = gainDb;
-            //unused
+            // unused
             mStream = -1;
             mCaller = null;
         }
 
+        /** used for VOL_SET_AVRCP_VOL */
         VolumeEvent(int op, int index) {
             mOp = op;
             mVal1 = index;
-            //unused
+            // unused
             mVal2 = 0;
             mStream = -1;
             mCaller = null;
         }
 
+        /** used for VOL_VOICE_ACTIVITY_HEARING_AID */
+        VolumeEvent(int op, boolean voiceActive, int stream, int index) {
+            mOp = op;
+            mStream = stream;
+            mVal1 = index;
+            mVal2 = voiceActive ? 1 : 0;
+            // unused
+            mCaller = null;
+        }
+
+        /** used for VOL_MODE_CHANGE_HEARING_AID */
+        VolumeEvent(int op, int mode, int stream, int index) {
+            mOp = op;
+            mStream = stream;
+            mVal1 = index;
+            mVal2 = mode;
+            // unused
+            mCaller = null;
+        }
+
         @Override
         public String eventToString() {
             switch (mOp) {
@@ -168,7 +196,19 @@
                             .append(" flags:0x").append(Integer.toHexString(mVal2))
                             .append(") from ").append(mCaller)
                             .toString();
-               default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
+                case VOL_VOICE_ACTIVITY_HEARING_AID:
+                    return new StringBuilder("Voice activity change (")
+                            .append(mVal2 == 1 ? "active" : "inactive")
+                            .append(") causes setting HEARING_AID volume to idx:").append(mVal1)
+                            .append(" stream:").append(AudioSystem.streamToString(mStream))
+                            .toString();
+                case VOL_MODE_CHANGE_HEARING_AID:
+                    return new StringBuilder("setMode(")
+                            .append(AudioSystem.modeToString(mVal2))
+                            .append(") causes setting HEARING_AID volume to idx:").append(mVal1)
+                            .append(" stream:").append(AudioSystem.streamToString(mStream))
+                            .toString();
+                default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1c775bd..5958715 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2578,9 +2578,7 @@
             mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N;
 
             mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
-            mIsPreQUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.Q
-                    // STOPSHIP: Remove next line when API level for Q is defined.
-                    && Build.VERSION.SDK_INT > Build.VERSION_CODES.P;
+            mIsPreQUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.Q;
 
             int preUpgradeSdkVersion = ver.sdkVersion;
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index beb7268..4edd9ef 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -97,6 +97,7 @@
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal.PermissionCallback;
 import com.android.server.pm.permission.PermissionsState.PermissionState;
+import com.android.server.policy.SoftRestrictedPermissionPolicy;
 
 import libcore.util.EmptyArray;
 
@@ -2121,11 +2122,18 @@
 
         if (bp.isHardRestricted()
                 && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
-            Log.e(TAG, "Cannot grant restricted non-exempt permission "
+            Log.e(TAG, "Cannot grant hard restricted non-exempt permission "
                     + permName + " for package " + packageName);
             return;
         }
 
+        if (bp.isSoftRestricted() && !SoftRestrictedPermissionPolicy.forPermission(mContext,
+                pkg.applicationInfo, permName).canBeGranted()) {
+            Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package "
+                    + packageName);
+            return;
+        }
+
         if (bp.isDevelopment()) {
             // Development permissions must be handled specially, since they are not
             // normal runtime permissions.  For now they apply to all users.
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index a799cd9..1d01a84 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -16,10 +16,14 @@
 
 package com.android.server.policy;
 
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_NONE;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 
-import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -426,20 +430,34 @@
                     mOpsToAllowIfDefault.add(new OpToUnrestrict(uid, pkg.packageName, opCode));
                 }
             } else if (permissionInfo.isSoftRestricted()) {
-                // Storage uses a special app op to decide the mount state and
-                // supports soft restriction where the restricted state allows
-                // the permission but only for accessing the medial collections.
-                if (Manifest.permission.READ_EXTERNAL_STORAGE.equals(permission)
-                        || Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) {
-                    if (applyRestriction) {
-                        mOpsToDefault.add(new OpToRestrict(uid,
-                                AppOpsManager.OP_LEGACY_STORAGE));
-                    } else if (pkg.applicationInfo.hasRequestedLegacyExternalStorage()) {
-                        mOpsToAllow.add(new OpToUnrestrict(uid, pkg.packageName,
-                                AppOpsManager.OP_LEGACY_STORAGE));
-                    } else {
-                        mOpsToIgnoreIfDefault.add(new OpToUnrestrict(uid, pkg.packageName,
-                                AppOpsManager.OP_LEGACY_STORAGE));
+                final SoftRestrictedPermissionPolicy policy =
+                        SoftRestrictedPermissionPolicy.forPermission(mContext, pkg.applicationInfo,
+                                permission);
+
+                final int op = policy.getAppOp();
+                if (op != OP_NONE) {
+                    switch (policy.getAppOpMode()) {
+                        case MODE_DEFAULT:
+                            mOpsToDefault.add(new OpToRestrict(uid, op));
+                            break;
+                        case MODE_ALLOWED:
+                            if (policy.shouldSetAppOpIfNotDefault()) {
+                                mOpsToAllow.add(new OpToUnrestrict(uid, pkg.packageName, op));
+                            } else {
+                                mOpsToAllowIfDefault.add(new OpToUnrestrict(uid, pkg.packageName,
+                                        op));
+                            }
+                            break;
+                        case MODE_IGNORED:
+                            if (policy.shouldSetAppOpIfNotDefault()) {
+                                Slog.wtf(LOG_TAG, "Always ignoring appops is not implemented");
+                            } else {
+                                mOpsToIgnoreIfDefault.add(new OpToUnrestrict(uid, pkg.packageName,
+                                        op));
+                            }
+                            break;
+                        case MODE_ERRORED:
+                            Slog.wtf(LOG_TAG, "Setting appop to errored is not implemented");
                     }
                 }
             }
@@ -483,7 +501,7 @@
 
             for (String permission : pkg.requestedPermissions) {
                 final int opCode = AppOpsManager.permissionToOpCode(permission);
-                if (opCode == AppOpsManager.OP_NONE) {
+                if (opCode == OP_NONE) {
                     continue;
                 }
 
@@ -515,13 +533,13 @@
                 @NonNull String packageName) {
             final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager
                     .opToPublicName(opCode), uid, packageName);
-            if (currentMode == AppOpsManager.MODE_DEFAULT) {
+            if (currentMode == MODE_DEFAULT) {
                 mAppOpsManager.setUidMode(opCode, uid, mode);
             }
         }
 
         private void setUidModeDefault(int opCode, int uid) {
-            mAppOpsManager.setUidMode(opCode, uid, AppOpsManager.MODE_DEFAULT);
+            mAppOpsManager.setUidMode(opCode, uid, MODE_DEFAULT);
         }
 
         private class OpToRestrict {
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
new file mode 100644
index 0000000..e19b708
--- /dev/null
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * The behavior of soft restricted permissions is different for each permission. This class collects
+ * the policies in one place.
+ *
+ * This is the twin of
+ * {@link com.android.packageinstaller.permission.utils.SoftRestrictedPermissionPolicy}
+ */
+public abstract class SoftRestrictedPermissionPolicy {
+    private static final String LOG_TAG = SoftRestrictedPermissionPolicy.class.getSimpleName();
+
+    private static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
+            FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
+                    | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
+                    | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+
+    private static final SoftRestrictedPermissionPolicy DUMMY_POLICY =
+            new SoftRestrictedPermissionPolicy() {
+                @Override
+                public int getAppOp() {
+                    return OP_NONE;
+                }
+
+                @Override
+                public int getAppOpMode() {
+                    return MODE_DEFAULT;
+                }
+
+                @Override
+                public boolean shouldSetAppOpIfNotDefault() {
+                    return false;
+                }
+
+                @Override
+                public boolean canBeGranted() {
+                    return true;
+                }
+            };
+
+    /**
+     * Get the policy for a soft restricted permission.
+     *
+     * @param context A context to use
+     * @param appInfo The application the permission belongs to
+     * @param permission The name of the permission
+     *
+     * @return The policy for this permission
+     */
+    public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context,
+            @NonNull ApplicationInfo appInfo, @NonNull String permission) {
+        switch (permission) {
+            // Storage uses a special app op to decide the mount state and supports soft restriction
+            // where the restricted state allows the permission but only for accessing the medial
+            // collections.
+            case READ_EXTERNAL_STORAGE:
+            case WRITE_EXTERNAL_STORAGE: {
+                int flags = context.getPackageManager().getPermissionFlags(
+                        permission, appInfo.packageName, context.getUser());
+                boolean applyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+                boolean isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
+                boolean hasRequestedLegacyExternalStorage =
+                        appInfo.hasRequestedLegacyExternalStorage();
+                int targetSDK = appInfo.targetSdkVersion;
+
+                return new SoftRestrictedPermissionPolicy() {
+                    @Override
+                    public int getAppOp() {
+                        return OP_LEGACY_STORAGE;
+                    }
+
+                    @Override
+                    public int getAppOpMode() {
+                        if (applyRestriction) {
+                            return MODE_DEFAULT;
+                        } else if (hasRequestedLegacyExternalStorage) {
+                            return MODE_ALLOWED;
+                        } else {
+                            return MODE_IGNORED;
+                        }
+                    }
+
+                    @Override
+                    public boolean shouldSetAppOpIfNotDefault() {
+                        // Do not switch from allowed -> ignored as this would mean to retroactively
+                        // turn on isolated storage. This will make the app loose all its files.
+                        return getAppOpMode() != MODE_IGNORED;
+                    }
+
+                    @Override
+                    public boolean canBeGranted() {
+                        if (isWhiteListed || targetSDK >= Build.VERSION_CODES.Q) {
+                            return true;
+                        } else {
+                            Log.w(LOG_TAG, permission + " for " + appInfo.packageName
+                                    + " is not whitelisted and targetSDK " + targetSDK + "<"
+                                    + Build.VERSION_CODES.Q);
+
+                            return false;
+                        }
+                    }
+                };
+            }
+            default:
+                return DUMMY_POLICY;
+        }
+    }
+
+    /**
+     * @return An app op to be changed based on the state of the permission or
+     * {@link AppOpsManager#OP_NONE} if not app-op should be set.
+     */
+    public abstract int getAppOp();
+
+    /**
+     * @return The mode the {@link #getAppOp() app op} should be in.
+     */
+    public abstract @AppOpsManager.Mode int getAppOpMode();
+
+    /**
+     * @return If the {@link #getAppOp() app op} should be set even if the app-op is currently not
+     * {@link AppOpsManager#MODE_DEFAULT}.
+     */
+    public abstract boolean shouldSetAppOpIfNotDefault();
+
+    /**
+     * @return If the permission can be granted
+     */
+    public abstract boolean canBeGranted();
+}
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index 437ef73..02b0e21 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -27,6 +27,14 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsPermission2TestCases",
+      "options": [
+        {
+          "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
+        }
+      ]
     }
   ],
   "postsubmit": [
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index 2f50fcb..94a1baa 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -43,14 +43,14 @@
             int callingUid, String targetPkg, Uri uri, int modeFlags, int userId);
     NeededUriGrants checkGrantUriPermissionFromIntent(int callingUid,
             String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId);
+    NeededUriGrants checkGrantUriPermissionFromIntent(int callingUid,
+            Intent intent, String targetPkg, int targetUserId);
     /**
      * Grant Uri permissions from one app to another. This method only extends
      * permission grants if {@code callingUid} has permission to them.
      */
     void grantUriPermissionFromIntent(int callingUid,
             String targetPkg, Intent intent, int targetUserId);
-    void grantUriPermissionFromIntent(int callingUid,
-            String targetPkg, Intent intent, UriPermissionOwner owner, int targetUserId);
     void grantUriPermissionUncheckedFromIntent(
             NeededUriGrants needed, UriPermissionOwner owner);
     IBinder newUriPermissionOwner(String name);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 8b332d2..04f7c7e 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -1362,20 +1362,21 @@
         }
 
         @Override
-        public void grantUriPermissionFromIntent(int callingUid, String targetPkg, Intent intent,
-                int targetUserId) {
+        public NeededUriGrants checkGrantUriPermissionFromIntent(int callingUid, Intent intent,
+                String targetPkg, int targetUserId) {
             synchronized (mLock) {
-                UriGrantsManagerService.this.grantUriPermissionFromIntent(
-                        callingUid, targetPkg, intent, null, targetUserId);
+                final int mode = (intent != null) ? intent.getFlags() : 0;
+                return UriGrantsManagerService.this.checkGrantUriPermissionFromIntent(
+                        callingUid, targetPkg, intent, mode, null, targetUserId);
             }
         }
 
         @Override
         public void grantUriPermissionFromIntent(int callingUid, String targetPkg, Intent intent,
-                UriPermissionOwner owner, int targetUserId) {
+                int targetUserId) {
             synchronized (mLock) {
                 UriGrantsManagerService.this.grantUriPermissionFromIntent(
-                        callingUid, targetPkg, intent, owner, targetUserId);
+                        callingUid, targetPkg, intent, null, targetUserId);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1344727..7c4b571 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -215,6 +215,7 @@
 import com.android.server.AttributeCache.Entry;
 import com.android.server.am.AppTimeTracker;
 import com.android.server.am.PendingIntentRecord;
+import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityMetricsLogger.WindowingModeTransitionInfoSnapshot;
 import com.android.server.wm.ActivityStack.ActivityState;
@@ -1599,10 +1600,11 @@
      * Deliver a new Intent to an existing activity, so that its onNewIntent()
      * method will be called at the proper time.
      */
-    final void deliverNewIntentLocked(int callingUid, Intent intent, String referrer) {
+    final void deliverNewIntentLocked(int callingUid, Intent intent, NeededUriGrants intentGrants,
+            String referrer) {
         // The activity now gets access to the data associated with this Intent.
-        mAtmService.mUgmInternal.grantUriPermissionFromIntent(callingUid, packageName,
-                intent, getUriPermissionsLocked(), mUserId);
+        mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(intentGrants,
+                getUriPermissionsLocked());
         final ReferrerIntent rintent = new ReferrerIntent(intent, referrer);
         boolean unsent = true;
         final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 3d59e66..1ad5e7c 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -162,6 +162,7 @@
 import com.android.server.am.AppTimeTracker;
 import com.android.server.am.EventLogTags;
 import com.android.server.am.PendingIntentRecord;
+import com.android.server.uri.NeededUriGrants;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -2780,7 +2781,7 @@
             if (DEBUG_STATES) Slog.d(TAG_STATES,
                     "no-history finish of " + mLastNoHistoryActivity + " on new resume");
             requestFinishActivityLocked(mLastNoHistoryActivity.appToken, Activity.RESULT_CANCELED,
-                    null, "resume-no-history", false);
+                    null, null, "resume-no-history", false);
             mLastNoHistoryActivity = null;
         }
 
@@ -3014,7 +3015,7 @@
                 // If any exception gets thrown, toss away this
                 // activity and try the next one.
                 Slog.w(TAG, "Exception thrown during resume of " + next, e);
-                requestFinishActivityLocked(next.appToken, Activity.RESULT_CANCELED, null,
+                requestFinishActivityLocked(next.appToken, Activity.RESULT_CANCELED, null, null,
                         "resume-exception", true);
                 return true;
             }
@@ -3422,7 +3423,7 @@
                     if (DEBUG_TASKS) Slog.w(TAG_TASKS,
                             "resetTaskIntendedTask: calling finishActivity on " + p);
                     if (finishActivityLocked(
-                            p, Activity.RESULT_CANCELED, null, "reset-task", false)) {
+                            p, Activity.RESULT_CANCELED, null, null, "reset-task", false)) {
                         end--;
                         srcPos--;
                     }
@@ -3501,7 +3502,7 @@
                             continue;
                         }
                         finishActivityLocked(
-                                p, Activity.RESULT_CANCELED, null, "move-affinity", false);
+                                p, Activity.RESULT_CANCELED, null, null, "move-affinity", false);
                     }
                 } else {
                     if (taskInsertionPoint < 0) {
@@ -3535,8 +3536,8 @@
                         if (targetNdx > 0) {
                             ActivityRecord p = taskActivities.get(targetNdx - 1);
                             if (p.intent.getComponent().equals(target.intent.getComponent())) {
-                                finishActivityLocked(p, Activity.RESULT_CANCELED, null, "replace",
-                                        false);
+                                finishActivityLocked(p, Activity.RESULT_CANCELED, null, null,
+                                        "replace", false);
                             }
                         }
                     }
@@ -3596,22 +3597,21 @@
         return taskTop;
     }
 
-    void sendActivityResultLocked(int callingUid, ActivityRecord r,
-            String resultWho, int requestCode, int resultCode, Intent data) {
-
+    void sendActivityResultLocked(int callingUid, ActivityRecord r, String resultWho,
+            int requestCode, int resultCode, Intent resultData, NeededUriGrants resultGrants) {
         if (callingUid > 0) {
-            mService.mUgmInternal.grantUriPermissionFromIntent(callingUid, r.packageName,
-                    data, r.getUriPermissionsLocked(), r.mUserId);
+            mService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
+                    r.getUriPermissionsLocked());
         }
 
         if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r
                 + " : who=" + resultWho + " req=" + requestCode
-                + " res=" + resultCode + " data=" + data);
+                + " res=" + resultCode + " data=" + resultData);
         if (mResumedActivity == r && r.attachedToProcess()) {
             try {
                 ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
                 list.add(new ResultInfo(resultWho, requestCode,
-                        resultCode, data));
+                        resultCode, resultData));
                 mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken,
                         ActivityResultItem.obtain(list));
                 return;
@@ -3620,7 +3620,7 @@
             }
         }
 
-        r.addResultLocked(null, resultWho, requestCode, resultCode, data);
+        r.addResultLocked(null, resultWho, requestCode, resultCode, resultData);
     }
 
     /** Returns true if the task is one of the task finishing on-top of the top running task. */
@@ -3727,8 +3727,8 @@
             if (!r.finishing) {
                 if (!shouldSleepActivities()) {
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + r);
-                    if (requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
-                            "stop-no-history", false)) {
+                    if (requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED,
+                            null, null, "stop-no-history", false)) {
                         // If {@link requestFinishActivityLocked} returns {@code true},
                         // {@link adjustFocusedActivityStack} would have been already called.
                         r.resumeKeyDispatchingLocked();
@@ -3784,7 +3784,7 @@
      * some reason it is being left as-is.
      */
     final boolean requestFinishActivityLocked(IBinder token, int resultCode,
-            Intent resultData, String reason, boolean oomAdj) {
+            Intent resultData, NeededUriGrants resultGrants, String reason, boolean oomAdj) {
         ActivityRecord r = isInStackLocked(token);
         if (DEBUG_RESULTS || DEBUG_STATES) Slog.v(TAG_STATES,
                 "Finishing activity token=" + token + " r="
@@ -3794,7 +3794,7 @@
             return false;
         }
 
-        finishActivityLocked(r, resultCode, resultData, reason, oomAdj);
+        finishActivityLocked(r, resultCode, resultData, resultGrants, reason, oomAdj);
         return true;
     }
 
@@ -3806,8 +3806,8 @@
                 if (r.resultTo == self && r.requestCode == requestCode) {
                     if ((r.resultWho == null && resultWho == null) ||
                         (r.resultWho != null && r.resultWho.equals(resultWho))) {
-                        finishActivityLocked(r, Activity.RESULT_CANCELED, null, "request-sub",
-                                false);
+                        finishActivityLocked(r, Activity.RESULT_CANCELED, null, null,
+                                "request-sub", false);
                     }
                 }
             }
@@ -3837,7 +3837,7 @@
         int activityNdx = task.mActivities.indexOf(r);
         getDisplay().mDisplayContent.prepareAppTransition(
                 TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
-        finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false);
+        finishActivityLocked(r, Activity.RESULT_CANCELED, null, null, reason, false);
         finishedTask = task;
         // Also terminate any activities below it that aren't yet
         // stopped, to avoid a situation where one will get
@@ -3858,7 +3858,7 @@
                 if (!r.isActivityTypeHome() || mService.mHomeProcess != r.app) {
                     Slog.w(TAG, "  Force finishing activity "
                             + r.intent.getComponent().flattenToShortString());
-                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false);
+                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, null, reason, false);
                 }
             }
         }
@@ -3874,8 +3874,8 @@
                 for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
                     ActivityRecord r = tr.mActivities.get(activityNdx);
                     if (!r.finishing) {
-                        finishActivityLocked(r, Activity.RESULT_CANCELED, null, "finish-voice",
-                                false);
+                        finishActivityLocked(r, Activity.RESULT_CANCELED, null, null,
+                                "finish-voice", false);
                         didOne = true;
                     }
                 }
@@ -3911,12 +3911,14 @@
             if (!Objects.equals(cur.taskAffinity, r.taskAffinity)) {
                 break;
             }
-            finishActivityLocked(cur, Activity.RESULT_CANCELED, null, "request-affinity", true);
+            finishActivityLocked(cur, Activity.RESULT_CANCELED, null, null,
+                    "request-affinity", true);
         }
         return true;
     }
 
-    private void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData) {
+    private void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData,
+            NeededUriGrants resultGrants) {
         // send the result
         ActivityRecord resultTo = r.resultTo;
         if (resultTo != null) {
@@ -3929,9 +3931,8 @@
                 }
             }
             if (r.info.applicationInfo.uid > 0) {
-                mService.mUgmInternal.grantUriPermissionFromIntent(r.info.applicationInfo.uid,
-                        resultTo.packageName, resultData,
-                        resultTo.getUriPermissionsLocked(), resultTo.mUserId);
+                mService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
+                        resultTo.getUriPermissionsLocked());
             }
             resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode, resultData);
             r.resultTo = null;
@@ -3947,12 +3948,10 @@
         r.icicle = null;
     }
 
-    /**
-     * See {@link #finishActivityLocked(ActivityRecord, int, Intent, String, boolean, boolean)}
-     */
     final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,
-            String reason, boolean oomAdj) {
-        return finishActivityLocked(r, resultCode, resultData, reason, oomAdj, !PAUSE_IMMEDIATELY);
+            NeededUriGrants resultGrants, String reason, boolean oomAdj) {
+        return finishActivityLocked(r, resultCode, resultData, resultGrants, reason, oomAdj,
+                !PAUSE_IMMEDIATELY);
     }
 
     /**
@@ -3960,7 +3959,7 @@
      * list, or false if it is still in the list and will be removed later.
      */
     final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,
-            String reason, boolean oomAdj, boolean pauseImmediately) {
+            NeededUriGrants resultGrants, String reason, boolean oomAdj, boolean pauseImmediately) {
         if (r.finishing) {
             Slog.w(TAG, "Duplicate finish request for " + r);
             return false;
@@ -3990,7 +3989,7 @@
 
             adjustFocusedActivityStack(r, "finishActivity");
 
-            finishActivityResultsLocked(r, resultCode, resultData);
+            finishActivityResultsLocked(r, resultCode, resultData, resultGrants);
 
             final boolean endTask = index <= 0 && !task.isClearingToReuseTask();
             final int transit = endTask ? TRANSIT_TASK_CLOSE : TRANSIT_ACTIVITY_CLOSE;
@@ -4223,8 +4222,9 @@
         return false;
     }
 
-    final boolean navigateUpToLocked(ActivityRecord srec, Intent destIntent, int resultCode,
-            Intent resultData) {
+    final boolean navigateUpToLocked(ActivityRecord srec, Intent destIntent,
+            NeededUriGrants destGrants, int resultCode, Intent resultData,
+            NeededUriGrants resultGrants) {
         final TaskRecord task = srec.getTaskRecord();
         final ArrayList<ActivityRecord> activities = task.mActivities;
         final int start = activities.indexOf(srec);
@@ -4271,7 +4271,8 @@
         final long origId = Binder.clearCallingIdentity();
         for (int i = start; i > finishTo; i--) {
             ActivityRecord r = activities.get(i);
-            requestFinishActivityLocked(r.appToken, resultCode, resultData, "navigate-up", true);
+            requestFinishActivityLocked(r.appToken, resultCode, resultData, resultGrants,
+                    "navigate-up", true);
             // Only return the supplied result for the first activity finished
             resultCode = Activity.RESULT_CANCELED;
             resultData = null;
@@ -4285,7 +4286,7 @@
                     parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP ||
                     (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
                 parent.deliverNewIntentLocked(srec.info.applicationInfo.uid, destIntent,
-                        srec.packageName);
+                        destGrants, srec.packageName);
             } else {
                 try {
                     ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
@@ -4309,7 +4310,7 @@
                     foundParentInTask = false;
                 }
                 requestFinishActivityLocked(parent.appToken, resultCode,
-                        resultData, "navigate-top", true);
+                        resultData, resultGrants, "navigate-top", true);
             }
         }
         Binder.restoreCallingIdentity(origId);
@@ -4394,7 +4395,7 @@
     }
 
     private void removeActivityFromHistoryLocked(ActivityRecord r, String reason) {
-        finishActivityResultsLocked(r, Activity.RESULT_CANCELED, null);
+        finishActivityResultsLocked(r, Activity.RESULT_CANCELED, null, null);
         r.makeFinishingLocked();
         if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE,
                 "Removing activity " + r + " from stack callers=" + Debug.getCallers(5));
@@ -5126,7 +5127,8 @@
             for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                 final ActivityRecord r = activities.get(activityNdx);
                 if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) {
-                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, "close-sys", true);
+                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, null,
+                            "close-sys", true);
                 }
             }
         }
@@ -5170,8 +5172,8 @@
                     didSomething = true;
                     Slog.i(TAG, "  Force finishing activity " + r);
                     lastTask = r.getTaskRecord();
-                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, "force-stop",
-                            true);
+                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, null,
+                            "force-stop", true);
                 }
             }
         }
@@ -5225,8 +5227,8 @@
             final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities;
             int activityTop = activities.size() - 1;
             if (activityTop >= 0) {
-                finishActivityLocked(activities.get(activityTop), Activity.RESULT_CANCELED, null,
-                        "unhandled-back", true);
+                finishActivityLocked(activities.get(activityTop), Activity.RESULT_CANCELED,
+                        null, null, "unhandled-back", true);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index c992a69..8d2a92a 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -140,6 +140,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.EventLogTags;
 import com.android.server.am.UserState;
+import com.android.server.uri.NeededUriGrants;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -403,14 +404,17 @@
         final int startFlags;
         final ActivityStack stack;
         final WindowProcessController callerApp;
+        final NeededUriGrants neededGrants;
 
-        PendingActivityLaunch(ActivityRecord _r, ActivityRecord _sourceRecord,
-                int _startFlags, ActivityStack _stack, WindowProcessController app) {
-            r = _r;
-            sourceRecord = _sourceRecord;
-            startFlags = _startFlags;
-            stack = _stack;
-            callerApp = app;
+        PendingActivityLaunch(ActivityRecord r, ActivityRecord sourceRecord,
+                int startFlags, ActivityStack stack, WindowProcessController callerApp,
+                NeededUriGrants neededGrants) {
+            this.r = r;
+            this.sourceRecord = sourceRecord;
+            this.startFlags = startFlags;
+            this.stack = stack;
+            this.callerApp = callerApp;
+            this.neededGrants = neededGrants;
         }
 
         void sendErrorResult(String message) {
@@ -874,8 +878,8 @@
                     Slog.e(TAG, "Second failure launching "
                             + r.intent.getComponent().flattenToShortString() + ", giving up", e);
                     proc.appDied();
-                    stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
-                            "2nd-crash", false);
+                    stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED,
+                            null, null, "2nd-crash", false);
                     return false;
                 }
 
@@ -1020,7 +1024,7 @@
             if (resultRecord != null) {
                 resultStack.sendActivityResultLocked(-1,
                         resultRecord, resultWho, requestCode,
-                        Activity.RESULT_CANCELED, null);
+                        Activity.RESULT_CANCELED, null, null);
             }
             final String msg;
             if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 919141c..738d143 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -462,7 +462,7 @@
                     "pendingActivityLaunch");
             try {
                 starter.startResolvedActivity(pal.r, pal.sourceRecord, null, null, pal.startFlags,
-                        resume, pal.r.pendingOptions, null);
+                        resume, pal.r.pendingOptions, null, pal.neededGrants);
             } catch (Exception e) {
                 Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
                 pal.sendErrorResult(e.getMessage());
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1718ac6..fb18569 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -127,6 +127,7 @@
 import com.android.server.am.EventLogTags;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.pm.InstantAppResolver;
+import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityStackSupervisor.PendingActivityLaunch;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
 
@@ -549,7 +550,8 @@
      */
     int startResolvedActivity(final ActivityRecord r, ActivityRecord sourceRecord,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
-            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
+            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
+            NeededUriGrants neededGrants) {
         try {
             mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(r.intent);
             mLastStartReason = "startResolvedActivity";
@@ -557,7 +559,7 @@
             mLastStartActivityRecord[0] = r;
             mLastStartActivityResult = startActivity(r, sourceRecord, voiceSession, voiceInteractor,
                     startFlags, doResume, options, inTask, mLastStartActivityRecord,
-                    false /* restrictedBgActivity */);
+                    false /* restrictedBgActivity */, neededGrants);
             mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(mLastStartActivityResult,
                     mLastStartActivityRecord[0]);
             return mLastStartActivityResult;
@@ -576,6 +578,33 @@
             boolean allowPendingRemoteAnimationRegistryLookup,
             PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
 
+        // Carefully collect grants without holding lock
+        NeededUriGrants neededGrants = null;
+        if (aInfo != null) {
+            neededGrants = mService.mUgmInternal.checkGrantUriPermissionFromIntent(
+                resolveCallingUid(mRequest.caller), intent, aInfo.applicationInfo.packageName,
+                UserHandle.getUserId(aInfo.applicationInfo.uid));
+        }
+
+        return startActivity(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo,
+                voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid,
+                callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options,
+                ignoreTargetSecurity, componentSpecified, outActivity, inTask, reason,
+                allowPendingRemoteAnimationRegistryLookup, originatingPendingIntent,
+                allowBackgroundActivityStart, neededGrants);
+    }
+
+    private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
+            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
+            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
+            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
+            SafeActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
+            ActivityRecord[] outActivity, TaskRecord inTask, String reason,
+            boolean allowPendingRemoteAnimationRegistryLookup,
+            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart,
+            NeededUriGrants neededGrants) {
+
         if (TextUtils.isEmpty(reason)) {
             throw new IllegalArgumentException("Need to specify a reason.");
         }
@@ -588,7 +617,7 @@
                 callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                 options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord,
                 inTask, allowPendingRemoteAnimationRegistryLookup, originatingPendingIntent,
-                allowBackgroundActivityStart);
+                allowBackgroundActivityStart, neededGrants);
 
         if (outActivity != null) {
             // mLastStartActivityRecord[0] is set in the call to startActivity above.
@@ -619,7 +648,8 @@
             SafeActivityOptions options,
             boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
             TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
-            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
+            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart,
+            NeededUriGrants neededGrants) {
         mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
         int err = ActivityManager.START_SUCCESS;
         // Pull the optional Ephemeral Installer-only bundle out of the options early.
@@ -754,7 +784,7 @@
         if (err != START_SUCCESS) {
             if (resultRecord != null) {
                 resultStack.sendActivityResultLocked(
-                        -1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null);
+                        -1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null, null);
             }
             SafeActivityOptions.abort(options);
             return err;
@@ -814,12 +844,16 @@
             callingPid = mInterceptor.mCallingPid;
             callingUid = mInterceptor.mCallingUid;
             checkedOptions = mInterceptor.mActivityOptions;
+
+            // The interception target shouldn't get any permission grants
+            // intended for the original destination
+            neededGrants = null;
         }
 
         if (abort) {
             if (resultRecord != null) {
                 resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
-                        RESULT_CANCELED, null);
+                        RESULT_CANCELED, null, null);
             }
             // We pretend to the caller that it was really started, but
             // they will just get a cancel result.
@@ -875,6 +909,10 @@
                 aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags,
                         null /*profilerInfo*/);
 
+                // The permissions review target shouldn't get any permission
+                // grants intended for the original destination
+                neededGrants = null;
+
                 if (DEBUG_PERMISSIONS_REVIEW) {
                     final ActivityStack focusedStack =
                             mRootActivityContainer.getTopDisplayFocusedStack();
@@ -897,6 +935,10 @@
             callingPid = realCallingPid;
 
             aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
+
+            // The ephemeral installer shouldn't get any permission grants
+            // intended for the original destination
+            neededGrants = null;
         }
 
         ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
@@ -923,7 +965,7 @@
                     realCallingPid, realCallingUid, "Activity start")) {
                 if (!(restrictedBgActivity && handleBackgroundActivityAbort(r))) {
                     mController.addPendingActivityLaunch(new PendingActivityLaunch(r,
-                            sourceRecord, startFlags, stack, callerApp));
+                            sourceRecord, startFlags, stack, callerApp, neededGrants));
                 }
                 ActivityOptions.abort(checkedOptions);
                 return ActivityManager.START_SWITCHES_CANCELED;
@@ -934,7 +976,8 @@
         mController.doPendingActivityLaunches(false);
 
         final int res = startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
-                true /* doResume */, checkedOptions, inTask, outActivity, restrictedBgActivity);
+                true /* doResume */, checkedOptions, inTask, outActivity, restrictedBgActivity,
+                neededGrants);
         mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outActivity[0]);
         return res;
     }
@@ -1232,9 +1275,15 @@
                 }
             }
         }
+
         // Collect information about the target of the Intent.
         ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
 
+        // Carefully collect grants without holding lock
+        NeededUriGrants neededGrants = mService.mUgmInternal.checkGrantUriPermissionFromIntent(
+                resolveCallingUid(mRequest.caller), intent, aInfo.applicationInfo.packageName,
+                UserHandle.getUserId(aInfo.applicationInfo.uid));
+
         synchronized (mService.mGlobalLock) {
             final ActivityStack stack = mRootActivityContainer.getTopDisplayFocusedStack();
             stack.mConfigWillChange = globalConfig != null
@@ -1311,7 +1360,7 @@
                     callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options,
                     ignoreTargetSecurity, componentSpecified, outRecord, inTask, reason,
                     allowPendingRemoteAnimationRegistryLookup, originatingPendingIntent,
-                    allowBackgroundActivityStart);
+                    allowBackgroundActivityStart, neededGrants);
 
             Binder.restoreCallingIdentity(origId);
 
@@ -1406,14 +1455,16 @@
 
     private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
                 IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
-                int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
-                ActivityRecord[] outActivity, boolean restrictedBgActivity) {
+            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
+            ActivityRecord[] outActivity, boolean restrictedBgActivity,
+            NeededUriGrants neededGrants) {
         int result = START_CANCELED;
         final ActivityStack startedActivityStack;
         try {
             mService.mWindowManager.deferSurfaceLayout();
             result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
-                    startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
+                    startFlags, doResume, options, inTask, outActivity, restrictedBgActivity,
+                    neededGrants);
         } finally {
             final ActivityStack currentStack = r.getActivityStack();
             startedActivityStack = currentStack != null ? currentStack : mTargetStack;
@@ -1438,7 +1489,8 @@
                 final ActivityStack stack = mStartActivity.getActivityStack();
                 if (stack != null) {
                     stack.finishActivityLocked(mStartActivity, RESULT_CANCELED,
-                            null /* intentResultData */, "startActivity", true /* oomAdj */);
+                            null /* resultData */, null /* resultGrants */,
+                            "startActivity", true /* oomAdj */);
                 }
             }
             mService.mWindowManager.continueSurfaceLayout();
@@ -1467,7 +1519,7 @@
         if (resultRecord != null) {
             ActivityStack resultStack = resultRecord.getActivityStack();
             resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
-                    RESULT_CANCELED, null);
+                    RESULT_CANCELED, null, null);
         }
         // We pretend to the caller that it was really started to make it backward compatible, but
         // they will just get a cancel result.
@@ -1479,7 +1531,8 @@
     private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
-            ActivityRecord[] outActivity, boolean restrictedBgActivity) {
+            ActivityRecord[] outActivity, boolean restrictedBgActivity,
+            NeededUriGrants neededGrants) {
         setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                 voiceInteractor, restrictedBgActivity);
 
@@ -1629,7 +1682,7 @@
             if (sourceStack != null) {
                 sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo,
                         mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED,
-                        null /* data */);
+                        null /* resultData */, null /* resultGrants */);
             }
             ActivityOptions.abort(mOptions);
             return START_CLASS_NOT_FOUND;
@@ -1696,8 +1749,8 @@
             return result;
         }
 
-        mService.mUgmInternal.grantUriPermissionFromIntent(mCallingUid, mStartActivity.packageName,
-                mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.mUserId);
+        mService.mUgmInternal.grantUriPermissionUncheckedFromIntent(neededGrants,
+                mStartActivity.getUriPermissionsLocked());
         mService.getPackageManagerInternalLocked().grantEphemeralAccess(
                 mStartActivity.mUserId, mIntent, UserHandle.getAppId(mStartActivity.appInfo.uid),
                 UserHandle.getAppId(mCallingUid));
@@ -1933,7 +1986,7 @@
             Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
             sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo,
                     mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED,
-                    null /* data */);
+                    null /* resultData */, null /* resultGrants */);
             mStartActivity.resultTo = null;
         }
     }
@@ -2362,8 +2415,13 @@
         }
 
         ActivityStack.logStartActivity(AM_NEW_INTENT, activity, activity.getTaskRecord());
-        activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
+
+        Intent intent = mStartActivity.intent;
+        NeededUriGrants intentGrants = mService.mUgmInternal.checkGrantUriPermissionFromIntent(
+                mCallingUid, intent, activity.packageName, activity.mUserId);
+        activity.deliverNewIntentLocked(mCallingUid, intent, intentGrants,
                 mStartActivity.launchedFromPackage);
+
         mIntentDelivered = true;
     }
 
@@ -2742,6 +2800,18 @@
         }
     }
 
+    private int resolveCallingUid(IApplicationThread caller) {
+        if (caller != null) {
+            synchronized (mService.mGlobalLock) {
+                final WindowProcessController callerApp = mService.getProcessController(caller);
+                if (callerApp != null) {
+                    return callerApp.mInfo.uid;
+                }
+            }
+        }
+        return -1;
+    }
+
     private boolean isLaunchModeOneOf(int mode1, int mode2) {
         return mode1 == mLaunchMode || mode2 == mLaunchMode;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index b97ecec..53cf9e4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -265,6 +265,7 @@
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.pm.UserManagerService;
 import com.android.server.policy.PermissionPolicyInternal;
+import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 
@@ -1540,11 +1541,19 @@
             throw new IllegalArgumentException("File descriptors passed in Intent");
         }
 
+        final ActivityRecord r;
         synchronized (mGlobalLock) {
-            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            r = ActivityRecord.isInStackLocked(token);
             if (r == null) {
                 return true;
             }
+        }
+
+        // Carefully collect grants without holding lock
+        final NeededUriGrants resultGrants = mUgmInternal.checkGrantUriPermissionFromIntent(
+                Binder.getCallingUid(), resultData, r.packageName, r.mUserId);
+
+        synchronized (mGlobalLock) {
             // Keep track of the root activity of the task before we finish it
             final TaskRecord tr = r.getTaskRecord();
             ActivityRecord rootR = tr.getRootActivity();
@@ -1606,7 +1615,7 @@
                     r.mRelaunchReason = RELAUNCH_REASON_NONE;
                 } else {
                     res = tr.getStack().requestFinishActivityLocked(token, resultCode,
-                            resultData, "app-request", true);
+                            resultData, resultGrants, "app-request", true);
                     if (!res) {
                         Slog.i(TAG, "Failed to finish by app-request");
                     }
@@ -2132,14 +2141,23 @@
     @Override
     public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
             Intent resultData) {
+        final ActivityRecord r;
+        synchronized (mGlobalLock) {
+            r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return false;
+            }
+        }
+
+        // Carefully collect grants without holding lock
+        final NeededUriGrants destGrants = mUgmInternal.checkGrantUriPermissionFromIntent(
+                Binder.getCallingUid(), destIntent, r.packageName, r.mUserId);
+        final NeededUriGrants resultGrants = mUgmInternal.checkGrantUriPermissionFromIntent(
+                Binder.getCallingUid(), resultData, r.packageName, r.mUserId);
 
         synchronized (mGlobalLock) {
-            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-            if (r != null) {
-                return r.getActivityStack().navigateUpToLocked(
-                        r, destIntent, resultCode, resultData);
-            }
-            return false;
+            return r.getActivityStack().navigateUpToLocked(
+                    r, destIntent, destGrants, resultCode, resultData, resultGrants);
         }
     }
 
@@ -6591,14 +6609,23 @@
 
         @Override
         public void sendActivityResult(int callingUid, IBinder activityToken, String resultWho,
-                int requestCode, int resultCode, Intent data) {
+                int requestCode, int resultCode, Intent resultData) {
+            final ActivityRecord r;
             synchronized (mGlobalLock) {
-                final ActivityRecord r = ActivityRecord.isInStackLocked(activityToken);
-                if (r != null && r.getActivityStack() != null) {
-                    r.getActivityStack().sendActivityResultLocked(callingUid, r, resultWho,
-                            requestCode, resultCode, data);
+                r = ActivityRecord.isInStackLocked(activityToken);
+                if (r == null || r.getActivityStack() == null) {
+                    return;
                 }
             }
+
+            // Carefully collect grants without holding lock
+            final NeededUriGrants resultGrants = mUgmInternal.checkGrantUriPermissionFromIntent(
+                    Binder.getCallingUid(), resultData, r.packageName, r.mUserId);
+
+            synchronized (mGlobalLock) {
+                r.getActivityStack().sendActivityResultLocked(callingUid, r, resultWho,
+                        requestCode, resultCode, resultData, resultGrants);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 298b302..4a0bf02a 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -1442,7 +1442,7 @@
                 mActivities.remove(activityNdx);
                 --activityNdx;
                 --numActivities;
-            } else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null,
+            } else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, null,
                     reason, false, pauseImmediately)) {
                 --activityNdx;
                 --numActivities;
@@ -1497,8 +1497,8 @@
                     if (opts != null) {
                         ret.updateOptionsLocked(opts);
                     }
-                    if (mStack != null && mStack.finishActivityLocked(
-                            r, Activity.RESULT_CANCELED, null, "clear-task-stack", false)) {
+                    if (mStack != null && mStack.finishActivityLocked(r, Activity.RESULT_CANCELED,
+                            null, null, "clear-task-stack", false)) {
                         --activityNdx;
                         --numActivities;
                     }
@@ -1512,8 +1512,8 @@
                         && !ActivityStarter.isDocumentLaunchesIntoExisting(launchFlags)) {
                     if (!ret.finishing) {
                         if (mStack != null) {
-                            mStack.finishActivityLocked(
-                                    ret, Activity.RESULT_CANCELED, null, "clear-task-top", false);
+                            mStack.finishActivityLocked(ret, Activity.RESULT_CANCELED,
+                                    null, null, "clear-task-top", false);
                         }
                         return null;
                     }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 2ad25cf..fbd4ec7 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -623,7 +623,7 @@
             final ActivityRecord r = activities.get(i);
             if (!r.finishing && r.isInStackLocked()) {
                 r.getActivityStack().finishActivityLocked(r, Activity.RESULT_CANCELED,
-                        null, "finish-heavy", true);
+                        null, null, "finish-heavy", true);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5ef184a..de9d769 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1447,10 +1447,12 @@
 
     void clearPolicyVisibilityFlag(int policyVisibilityFlag) {
         mPolicyVisibility &= ~policyVisibilityFlag;
+        mWmService.scheduleAnimationLocked();
     }
 
     void setPolicyVisibilityFlag(int policyVisibilityFlag) {
         mPolicyVisibility |= policyVisibilityFlag;
+        mWmService.scheduleAnimationLocked();
     }
 
     private boolean isLegacyPolicyVisibility() {
@@ -3889,7 +3891,7 @@
     boolean performShowLocked() {
         if (isHiddenFromUserLocked()) {
             if (DEBUG_VISIBILITY) Slog.w(TAG, "hiding " + this + ", belonging to " + mOwnerUid);
-            hideLw(false);
+            clearPolicyVisibilityFlag(VISIBLE_FOR_USER);
             return false;
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 757267e5..4c0b582 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -940,7 +940,8 @@
         homeStask.removeTask(homeTask, "testAdjustFocusedStack", REMOVE_TASK_MODE_DESTROYING);
 
         // Finish the only activity.
-        mStack.finishActivityLocked(topActivity, 0 /* resultCode */, null /* resultData */,
+        mStack.finishActivityLocked(topActivity, 0 /* resultCode */,
+                null /* resultData */, null /* resultGrants */,
                 "testAdjustFocusedStack", false /* oomAdj */);
         // Although home stack is empty, it should still be the focused stack.
         assertEquals(homeStask, mDefaultDisplay.getFocusedStack());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
index a7bbe6e..2d12006 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
@@ -82,12 +82,12 @@
         wpc.setThread(mock(IApplicationThread.class));
 
         mController.addPendingActivityLaunch(
-                new PendingActivityLaunch(activity, source, startFlags, stack, wpc));
+                new PendingActivityLaunch(activity, source, startFlags, stack, wpc, null));
         final boolean resume = random.nextBoolean();
         mController.doPendingActivityLaunches(resume);
 
         verify(mStarter, times(1)).startResolvedActivity(eq(activity), eq(source), eq(null),
-                eq(null), eq(startFlags), eq(resume), eq(null), eq(null));
+                eq(null), eq(startFlags), eq(resume), eq(null), eq(null), eq(null));
     }
 
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index d77ea6e..e1ffb0f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -77,7 +77,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
 import com.android.server.soundtrigger.SoundTriggerInternal;
@@ -1284,9 +1283,7 @@
                 mRm.addOnRoleHoldersChangedListenerAsUser(executor, this, UserHandle.ALL);
                 UserHandle currentUser = UserHandle.of(LocalServices.getService(
                         ActivityManagerInternal.class).getCurrentUserId());
-                SystemServerInitThreadPool.get().submit(() -> onRoleHoldersChanged(
-                        RoleManager.ROLE_ASSISTANT, currentUser),
-                        "VoiceInteractionManagerService RoleObserver initialization");
+                onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT, currentUser);
             }
 
             private @NonNull String getDefaultRecognizer(@NonNull UserHandle user) {
diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/java/com/android/internal/telephony/SmsApplication.java
index ef7c605..44dc24b 100644
--- a/telephony/java/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/java/com/android/internal/telephony/SmsApplication.java
@@ -465,7 +465,11 @@
             int userId) {
         TelephonyManager tm = (TelephonyManager)
                 context.getSystemService(Context.TELEPHONY_SERVICE);
-        if (!tm.isSmsCapable()) {
+        RoleManager roleManager = (RoleManager) context.getSystemService(Context.ROLE_SERVICE);
+        // (b/134400042) RoleManager might be null in unit tests running older mockito versions
+        // that do not support mocking final classes.
+        if (!tm.isSmsCapable() && (roleManager == null || !roleManager.isRoleAvailable(
+                RoleManager.ROLE_SMS))) {
             // No phone, no SMS
             return null;
         }
@@ -584,7 +588,11 @@
     public static void setDefaultApplicationAsUser(String packageName, Context context,
             int userId) {
         TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
-        if (!tm.isSmsCapable()) {
+        RoleManager roleManager = (RoleManager) context.getSystemService(Context.ROLE_SERVICE);
+        // (b/134400042) RoleManager might be null in unit tests running older mockito versions
+        // that do not support mocking final classes.
+        if (!tm.isSmsCapable() && (roleManager == null || !roleManager.isRoleAvailable(
+                RoleManager.ROLE_SMS))) {
             // No phone, no SMS
             return;
         }