Merge "Remove LOCAL_SRC_FILES from RRO modules" into stage-aosp-master
am: 651b6ee8c5

Change-Id: I02487eee98057e0358c3136e27c2ac181b0264e7
diff --git a/api/test-current.txt b/api/test-current.txt
index d2dbacb..fa018a3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2313,6 +2313,7 @@
   }
 
   public static interface DeviceConfig.WindowManager {
+    field public static final String KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE = "system_gestures_excluded_by_pre_q_sticky_immersive";
     field public static final String KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP = "system_gesture_exclusion_limit_dp";
   }
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 60f1424..2f0782b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2507,7 +2507,7 @@
         }
 
         /**
-         * @return The duration of the operation in milliseconds.
+         * @return The duration of the operation in milliseconds. The duration is in wall time.
          */
         public long getDuration() {
             return getLastDuration(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
@@ -2515,7 +2515,7 @@
 
         /**
          * Return the duration in milliseconds the app accessed this op while
-         * in the foreground.
+         * in the foreground. The duration is in wall time.
          *
          * @param flags The flags which are any combination of
          * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
@@ -2534,7 +2534,7 @@
 
         /**
          * Return the duration in milliseconds the app accessed this op while
-         * in the background.
+         * in the background. The duration is in wall time.
          *
          * @param flags The flags which are any combination of
          * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
@@ -2553,7 +2553,7 @@
 
         /**
          * Return the duration in milliseconds the app accessed this op for
-         * a given range of UID states.
+         * a given range of UID states. The duration is in wall time.
          *
          * @param fromUidState The UID state for which to query. Could be one of
          * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
@@ -3968,6 +3968,7 @@
 
         /**
          * Gets the total duration the app op was accessed (performed) in the foreground.
+         * The duration is in wall time.
          *
          * @param flags The flags which are any combination of
          * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
@@ -3986,6 +3987,7 @@
 
         /**
          * Gets the total duration the app op was accessed (performed) in the background.
+         * The duration is in wall time.
          *
          * @param flags The flags which are any combination of
          * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
@@ -4004,7 +4006,7 @@
 
         /**
          * Gets the total duration the app op was accessed (performed) for a given
-         * range of UID states.
+         * range of UID states. The duration is in wall time.
          *
          * @param fromUidState The UID state from which to query. Could be one of
          * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index 2d18838..bb775fc 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -17,6 +17,7 @@
 package android.app.servertransaction;
 
 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
+import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
 
 import android.annotation.UnsupportedAppUsage;
 import android.app.ClientTransactionHandler;
@@ -38,10 +39,11 @@
 
     @UnsupportedAppUsage
     private List<ReferrerIntent> mIntents;
+    private boolean mResume;
 
     @Override
     public int getPostExecutionState() {
-        return ON_RESUME;
+        return mResume ? ON_RESUME : UNDEFINED;
     }
 
     @Override
@@ -58,12 +60,13 @@
     private NewIntentItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static NewIntentItem obtain(List<ReferrerIntent> intents) {
+    public static NewIntentItem obtain(List<ReferrerIntent> intents, boolean resume) {
         NewIntentItem instance = ObjectPool.obtain(NewIntentItem.class);
         if (instance == null) {
             instance = new NewIntentItem();
         }
         instance.mIntents = intents;
+        instance.mResume = resume;
 
         return instance;
     }
@@ -71,6 +74,7 @@
     @Override
     public void recycle() {
         mIntents = null;
+        mResume = false;
         ObjectPool.recycle(this);
     }
 
@@ -80,11 +84,13 @@
     /** Write to Parcel. */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(mResume);
         dest.writeTypedList(mIntents, flags);
     }
 
     /** Read from Parcel. */
     private NewIntentItem(Parcel in) {
+        mResume = in.readBoolean();
         mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
     }
 
@@ -108,18 +114,19 @@
             return false;
         }
         final NewIntentItem other = (NewIntentItem) o;
-        return Objects.equals(mIntents, other.mIntents);
+        return mResume == other.mResume && Objects.equals(mIntents, other.mIntents);
     }
 
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + (mResume ? 1 : 0);
         result = 31 * result + mIntents.hashCode();
         return result;
     }
 
     @Override
     public String toString() {
-        return "NewIntentItem{intents=" + mIntents + "}";
+        return "NewIntentItem{intents=" + mIntents + ",resume=" + mResume + "}";
     }
 }
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index b0b1874..23fbefb 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -17,7 +17,6 @@
 package android.content.pm;
 
 import android.Manifest;
-import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -177,8 +176,7 @@
         mContext.registerReceiver(mUserRemovedReceiver, userFilter);
     }
 
-    @VisibleForTesting
-    protected void handlePackageEvent(Intent intent, int userId) {
+    private void handlePackageEvent(Intent intent, int userId) {
         // Don't regenerate the services map when the package is removed or its
         // ASEC container unmounted as a step in replacement.  The subsequent
         // _ADDED / _AVAILABLE call will regenerate the map in the final state.
@@ -240,9 +238,6 @@
 
     public void invalidateCache(int userId) {
         synchronized (mServicesLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "invalidating cache for " + userId + " " + mInterfaceName);
-            }
             final UserServices<V> user = findOrCreateUserLocked(userId);
             user.services = null;
             onServicesChangedLocked(userId);
@@ -472,48 +467,34 @@
      *                    or null to assume that everything is affected.
      * @param userId the user for whom to update the services map.
      */
-    private void generateServicesMap(@Nullable int[] changedUids, int userId) {
+    private void generateServicesMap(int[] changedUids, int userId) {
         if (DEBUG) {
             Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = "
                     + Arrays.toString(changedUids));
         }
 
+        final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
+        final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            try {
+                ServiceInfo<V> info = parseServiceInfo(resolveInfo);
+                if (info == null) {
+                    Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
+                    continue;
+                }
+                serviceInfos.add(info);
+            } catch (XmlPullParserException | IOException e) {
+                Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
+            }
+        }
+
         synchronized (mServicesLock) {
             final UserServices<V> user = findOrCreateUserLocked(userId);
-            final boolean cacheInvalid = user.services == null;
-            if (cacheInvalid) {
+            final boolean firstScan = user.services == null;
+            if (firstScan) {
                 user.services = Maps.newHashMap();
             }
 
-            final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
-            final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
-
-            for (ResolveInfo resolveInfo : resolveInfos) {
-                try {
-                    // when changedUids == null, we want to do a rescan of everything, this means
-                    // it's the initial scan, and containsUid will trivially return true
-                    // when changedUids != null, we got here because a package changed, but
-                    // invalidateCache could have been called (thus user.services == null), and we
-                    // should query from PackageManager again
-                    if (!cacheInvalid
-                            && !containsUid(
-                                    changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "Skipping parseServiceInfo for " + resolveInfo);
-                        }
-                        continue;
-                    }
-                    ServiceInfo<V> info = parseServiceInfo(resolveInfo);
-                    if (info == null) {
-                        Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
-                        continue;
-                    }
-                    serviceInfos.add(info);
-                } catch (XmlPullParserException | IOException e) {
-                    Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
-                }
-            }
-
             StringBuilder changes = new StringBuilder();
             boolean changed = false;
             for (ServiceInfo<V> info : serviceInfos) {
@@ -534,7 +515,7 @@
                     changed = true;
                     user.services.put(info.type, info);
                     user.persistentServices.put(info.type, info.uid);
-                    if (!(user.mPersistentServicesFileDidNotExist && cacheInvalid)) {
+                    if (!(user.mPersistentServicesFileDidNotExist && firstScan)) {
                         notifyListener(info.type, userId, false /* removed */);
                     }
                 } else if (previousUid == info.uid) {
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 920eb4b..e30ba38 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -325,6 +325,17 @@
          */
         @TestApi
         String KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP = "system_gesture_exclusion_limit_dp";
+
+        /**
+         * Key for controlling whether system gestures are implicitly excluded by windows requesting
+         * sticky immersive mode from apps that are targeting an SDK prior to Q.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
+         * @hide
+         */
+        @TestApi
+        String KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE =
+                "system_gestures_excluded_by_pre_q_sticky_immersive";
     }
 
     private static final Object sLock = new Object();
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f9d27bb..00206fc 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1534,7 +1534,11 @@
                 if (driList.get(i).getResolvedComponentName().equals(
                             resultList.get(j).getTargetComponent())) {
                     ShortcutManager.ShareShortcutInfo shareShortcutInfo = resultList.get(j);
-                    ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo);
+                    // Incoming results are ordered but without a score. Create a score
+                    // based on the index in order to be sorted appropriately when joined
+                    // with legacy direct share api results.
+                    float score = Math.max(1.0f - (0.05f * j), 0.0f);
+                    ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo, score);
                     chooserTargets.add(chooserTarget);
                     if (mDirectShareAppTargetCache != null && appTargets != null) {
                         mDirectShareAppTargetCache.put(chooserTarget, appTargets.get(j));
@@ -1580,7 +1584,8 @@
         return false;
     }
 
-    private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) {
+    private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut,
+                                                 float score) {
         ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
         Bundle extras = new Bundle();
         extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
@@ -1591,7 +1596,7 @@
                 null,
                 // The ranking score for this target (0.0-1.0); the system will omit items with low
                 // scores when there are too many Direct Share items.
-                1.0f,
+                score,
                 // The name of the component to be launched if this target is chosen.
                 shareShortcut.getTargetComponent().clone(),
                 // The extra values here will be merged into the Intent when this target is chosen.
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f905ea2..58ce03b 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -889,7 +889,8 @@
                 : mAdapterView.getCheckedItemPosition();
         boolean hasIndexBeenFiltered = !mAdapter.hasFilteredItem();
         ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
-        if (!ri.handleAllWebDataURI && id == R.id.button_always) {
+        if (mUseLayoutForBrowsables
+                && !ri.handleAllWebDataURI && id == R.id.button_always) {
             showSettingsForSelected(ri);
         } else {
             startSelected(which, id == R.id.button_always, hasIndexBeenFiltered);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 6f4f337..fe66cf9 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1311,7 +1311,7 @@
             return semiTransparentBarColor;
         } else if ((flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
             return Color.BLACK;
-        } else if (scrimTransparent && barColor == Color.TRANSPARENT) {
+        } else if (scrimTransparent && Color.alpha(barColor) == 0) {
             boolean light = (sysuiVis & lightSysuiFlag) != 0;
             return light ? SCRIM_LIGHT : semiTransparentBarColor;
         } else {
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 711eaa7..c50cbe3 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -19,6 +19,8 @@
 import static android.content.Intent.ACTION_EDIT;
 import static android.content.Intent.ACTION_VIEW;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
@@ -31,6 +33,7 @@
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.StopActivityItem;
 import android.content.Intent;
@@ -45,9 +48,13 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.content.ReferrerIntent;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -307,6 +314,24 @@
         assertEquals(400, activity.mConfig.smallestScreenWidthDp);
     }
 
+    @Test
+    public void testResumeAfterNewIntent() {
+        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+        final ActivityThread activityThread = activity.getActivityThread();
+        final ArrayList<ReferrerIntent> rIntents = new ArrayList<>();
+        rIntents.add(new ReferrerIntent(new Intent(), "android.app.activity"));
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, false));
+        });
+        assertThat(activity.isResumed()).isFalse();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, true));
+        });
+        assertThat(activity.isResumed()).isTrue();
+    }
+
     /**
      * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)}
      * to try to push activity configuration to the activity for the given sequence number.
@@ -386,6 +411,16 @@
         return transaction;
     }
 
+    private static ClientTransaction newNewIntentTransaction(Activity activity,
+            List<ReferrerIntent> intents, boolean resume) {
+        final NewIntentItem item = NewIntentItem.obtain(intents, resume);
+
+        final ClientTransaction transaction = newTransaction(activity);
+        transaction.addCallback(item);
+
+        return transaction;
+    }
+
     private static ClientTransaction newTransaction(Activity activity) {
         final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
         return ClientTransaction.obtain(appThread, activity.getActivityToken());
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 1e49c0a..37d21f0 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -214,15 +214,15 @@
 
     @Test
     public void testRecycleNewIntentItem() {
-        NewIntentItem emptyItem = NewIntentItem.obtain(null);
-        NewIntentItem item = NewIntentItem.obtain(referrerIntentList());
+        NewIntentItem emptyItem = NewIntentItem.obtain(null, false);
+        NewIntentItem item = NewIntentItem.obtain(referrerIntentList(), false);
         assertNotSame(item, emptyItem);
         assertFalse(item.equals(emptyItem));
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        NewIntentItem item2 = NewIntentItem.obtain(referrerIntentList());
+        NewIntentItem item2 = NewIntentItem.obtain(referrerIntentList(), false);
         assertSame(item, item2);
         assertFalse(item2.equals(emptyItem));
     }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 36ed88f..d2b18cb 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -128,7 +128,7 @@
     @Test
     public void testNewIntent() {
         // Write to parcel
-        NewIntentItem item = NewIntentItem.obtain(referrerIntentList());
+        NewIntentItem item = NewIntentItem.obtain(referrerIntentList(), false);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index c8150b1..365e97d 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -16,7 +16,6 @@
 
 package android.content.pm;
 
-import android.content.Intent;
 import android.content.res.Resources;
 import android.os.FileUtils;
 import android.os.Parcel;
@@ -190,36 +189,6 @@
         assertEquals(0, cache.getPersistentServicesSize(u1));
     }
 
-    /**
-     * Check that an optimization to skip a call to PackageManager handles an invalidated cache.
-     *
-     * We added an optimization in generateServicesMap to only query PackageManager for packages
-     * that have been changed, because if a package is unchanged, we have already cached the
-     * services info for it, so we can save a query to PackageManager (and save some memory).
-     * However, if invalidateCache was called, we cannot optimize, and must do a full query.
-     * The initial optimization was buggy because it failed to check for an invalidated cache, and
-     * only scanned the changed packages, given in the ACTION_PACKAGE_CHANGED intent (b/122912184).
-     */
-    public void testParseServiceInfoOptimizationHandlesInvalidatedCache() {
-        TestServicesCache cache = new TestServicesCache();
-        cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
-        cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
-        assertEquals(2, cache.getAllServicesSize(U0));
-
-        // simulate the client of the cache invalidating it
-        cache.invalidateCache(U0);
-
-        // there should be 0 services (userServices.services == null ) at this point, but we don't
-        // call getAllServicesSize since that would force a full scan of packages,
-        // instead we trigger a package change in a package that is in the list of services
-        Intent intent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
-        intent.putExtra(Intent.EXTRA_UID, UID1);
-        cache.handlePackageEvent(intent, U0);
-
-        // check that the optimization does a full query and caches both services
-        assertEquals(2, cache.getAllServicesSize(U0));
-    }
-
     private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
             TestServiceType type, int uid) {
         final ComponentInfo info = new ComponentInfo();
@@ -297,11 +266,6 @@
                 map = new HashMap<>();
                 mServices.put(userId, map);
             }
-            // in actual cases, resolveInfo should always have a serviceInfo, since we specifically
-            // query for intent services
-            resolveInfo.serviceInfo = new android.content.pm.ServiceInfo();
-            resolveInfo.serviceInfo.applicationInfo =
-                new ApplicationInfo(serviceInfo.componentInfo.applicationInfo);
             map.put(resolveInfo, serviceInfo);
         }
 
@@ -340,11 +304,6 @@
         public void onUserRemoved(int userId) {
             super.onUserRemoved(userId);
         }
-
-        @Override
-        public void handlePackageEvent(Intent intent, int userId) {
-            super.handlePackageEvent(intent, userId);
-        }
     }
 
     static class TestSerializer implements XmlSerializerAndParser<TestServiceType> {
diff --git a/libs/androidfw/PosixUtils.cpp b/libs/androidfw/PosixUtils.cpp
index df0dd7c..f1ab149 100644
--- a/libs/androidfw/PosixUtils.cpp
+++ b/libs/androidfw/PosixUtils.cpp
@@ -64,6 +64,9 @@
     return nullptr;
   }
 
+  auto gid = getgid();
+  auto uid = getuid();
+
   char const** argv0 = (char const**)malloc(sizeof(char*) * (argv.size() + 1));
   for (size_t i = 0; i < argv.size(); i++) {
     argv0[i] = argv[i].c_str();
@@ -75,6 +78,16 @@
       PLOG(ERROR) << "fork";
       return nullptr;
     case 0: // child
+      if (setgid(gid) != 0) {
+        PLOG(ERROR) << "setgid";
+        exit(1);
+      }
+
+      if (setuid(uid) != 0) {
+        PLOG(ERROR) << "setuid";
+        exit(1);
+      }
+
       close(stdout[0]);
       if (dup2(stdout[1], STDOUT_FILENO) == -1) {
         abort();
diff --git a/media/apex/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java
index 63a4510..c3dd3fe 100644
--- a/media/apex/java/android/media/MediaController2.java
+++ b/media/apex/java/android/media/MediaController2.java
@@ -46,14 +46,14 @@
 import java.util.concurrent.Executor;
 
 /**
+ * This API is not generally intended for third party application developers.
+ * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+ * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+ * Library</a> for consistent behavior across all devices.
+ *
  * Allows an app to interact with an active {@link MediaSession2} or a
  * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other
  * commands can be sent to the session.
- * <p>
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
- * for consistent behavior across all devices.
  */
 public class MediaController2 implements AutoCloseable {
     static final String TAG = "MediaController2";
@@ -405,6 +405,11 @@
     }
 
     /**
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
      * Builder for {@link MediaController2}.
      * <p>
      * Any incoming event from the {@link MediaSession2} will be handled on the callback
@@ -502,9 +507,12 @@
     }
 
     /**
-     * Interface for listening to change in activeness of the {@link MediaSession2}.
-     * <p>
      * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
+     * Interface for listening to change in activeness of the {@link MediaSession2}.
      */
     public abstract static class ControllerCallback {
         /**
diff --git a/media/apex/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java
index b3edf3f..081e76a 100644
--- a/media/apex/java/android/media/MediaSession2.java
+++ b/media/apex/java/android/media/MediaSession2.java
@@ -52,13 +52,13 @@
 import java.util.concurrent.Executor;
 
 /**
- * Allows a media app to expose its transport controls and playback information in a process to
- * other processes including the Android framework and other apps.
- * <p>
  * This API is not generally intended for third party application developers.
  * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
- * for consistent behavior across all devices.
+ * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+ * Library</a> for consistent behavior across all devices.
+ * <p>
+ * Allows a media app to expose its transport controls and playback information in a process to
+ * other processes including the Android framework and other apps.
  */
 public class MediaSession2 implements AutoCloseable {
     static final String TAG = "MediaSession2";
@@ -481,6 +481,11 @@
     }
 
     /**
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
      * Builder for {@link MediaSession2}.
      * <p>
      * Any incoming event from the {@link MediaController2} will be handled on the callback
@@ -616,9 +621,12 @@
     }
 
     /**
-     * Information of a controller.
-     * <p>
      * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
+     * Information of a controller.
      */
     public static final class ControllerInfo {
         private final RemoteUserInfo mRemoteUserInfo;
@@ -807,9 +815,12 @@
     }
 
     /**
-     * Callback to be called for all incoming commands from {@link MediaController2}s.
-     * <p>
      * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
+     * Callback to be called for all incoming commands from {@link MediaController2}s.
      */
     public abstract static class SessionCallback {
         /**
diff --git a/media/apex/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
index ee584e5..f6fd509 100644
--- a/media/apex/java/android/media/MediaSession2Service.java
+++ b/media/apex/java/android/media/MediaSession2Service.java
@@ -44,12 +44,12 @@
 import java.util.Map;
 
 /**
- * Service containing {@link MediaSession2}.
- * <p>
  * This API is not generally intended for third party application developers.
  * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
- * for consistent behavior across all devices.
+ * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+ * Library</a> for consistent behavior across all devices.
+ * <p>
+ * Service containing {@link MediaSession2}.
  */
 public abstract class MediaSession2Service extends Service {
     /**
@@ -287,6 +287,11 @@
     }
 
     /**
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
      * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service
      * foreground service to keep playback running in the background. It's highly recommended to
      * show media style notification here.
diff --git a/media/apex/java/android/media/Session2Command.java b/media/apex/java/android/media/Session2Command.java
index 7c752e1..26f4568 100644
--- a/media/apex/java/android/media/Session2Command.java
+++ b/media/apex/java/android/media/Session2Command.java
@@ -26,6 +26,11 @@
 import java.util.Objects;
 
 /**
+ * This API is not generally intended for third party application developers.
+ * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+ * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+ * Library</a> for consistent behavior across all devices.
+ * <p>
  * Define a command that a {@link MediaController2} can send to a {@link MediaSession2}.
  * <p>
  * If {@link #getCommandCode()} isn't {@link #COMMAND_CODE_CUSTOM}), it's predefined command.
@@ -35,11 +40,6 @@
  * Refer to the
  * <a href="{@docRoot}reference/androidx/media2/SessionCommand2.html">AndroidX SessionCommand</a>
  * class for the list of valid commands.
- * <p>
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
- * for consistent behavior across all devices.
  */
 public final class Session2Command implements Parcelable {
     /**
@@ -162,6 +162,11 @@
     }
 
     /**
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
      * Contains the result of {@link Session2Command}.
      */
     public static final class Result {
diff --git a/media/apex/java/android/media/Session2CommandGroup.java b/media/apex/java/android/media/Session2CommandGroup.java
index 06ae873..0ee5f62 100644
--- a/media/apex/java/android/media/Session2CommandGroup.java
+++ b/media/apex/java/android/media/Session2CommandGroup.java
@@ -28,13 +28,12 @@
 import java.util.Set;
 
 /**
- * A set of {@link Session2Command} which represents a command group.
- * <p>
  * This API is not generally intended for third party application developers.
  * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
- * for consistent behavior across all devices.
- * </p>
+ * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+ * Library</a> for consistent behavior across all devices.
+ * <p>
+ * A set of {@link Session2Command} which represents a command group.
  */
 public final class Session2CommandGroup implements Parcelable {
     private static final String TAG = "Session2CommandGroup";
@@ -131,6 +130,11 @@
     }
 
     /**
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
      * Builds a {@link Session2CommandGroup} object.
      */
     public static final class Builder {
diff --git a/media/apex/java/android/media/Session2Token.java b/media/apex/java/android/media/Session2Token.java
index 6d499fa..6eb76b1 100644
--- a/media/apex/java/android/media/Session2Token.java
+++ b/media/apex/java/android/media/Session2Token.java
@@ -36,13 +36,13 @@
 import java.util.Objects;
 
 /**
- * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}.
- * If it's representing a session service, it may not be ongoing.
- * <p>
  * This API is not generally intended for third party application developers.
  * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
- * for consistent behavior across all devices.
+ * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+ * Library</a> for consistent behavior across all devices.
+ * <p>
+ * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}.
+ * If it's representing a session service, it may not be ongoing.
  * <p>
  * This may be passed to apps by the session owner to allow them to create a
  * {@link MediaController2} to communicate with the session.
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 569d11e..dec0140 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -119,6 +119,11 @@
     }
 
     /**
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
      * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is
      * created.
      * <p>
@@ -192,6 +197,11 @@
     }
 
     /**
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
      * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
      * current user.
      * <p>
@@ -335,12 +345,12 @@
     }
 
     /**
-     * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
-     * <p>
      * This API is not generally intended for third party application developers.
      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
-     * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
-     * for consistent behavior across all devices.
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
+     * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
      *
      * @param listener The listener to add
      */
@@ -350,12 +360,12 @@
     }
 
     /**
-     * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
-     * <p>
      * This API is not generally intended for third party application developers.
      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
-     * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
-     * for consistent behavior across all devices.
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
+     * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
      *
      * @param listener The listener to add
      * @param handler The handler to call listener on.
@@ -366,12 +376,12 @@
     }
 
     /**
-     * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
-     * <p>
      * This API is not generally intended for third party application developers.
      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
-     * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
-     * for consistent behavior across all devices.
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
+     * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
      *
      * @param userId The userId to listen for changes on
      * @param listener The listener to add
@@ -402,6 +412,11 @@
     }
 
     /**
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
      * Removes the {@link OnSession2TokensChangedListener} to stop receiving session token updates.
      *
      * @param listener The listener to remove.
@@ -765,13 +780,13 @@
     }
 
     /**
-     * Listens for changes to the {@link #getSession2Tokens()}. This can be added
-     * using {@link #addOnSession2TokensChangedListener(OnSession2TokensChangedListener, Handler)}.
-     * <p>
      * This API is not generally intended for third party application developers.
      * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
-     * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
-     * for consistent behavior across all devices.
+     * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
+     * Library</a> for consistent behavior across all devices.
+     * <p>
+     * Listens for changes to the {@link #getSession2Tokens()}. This can be added
+     * using {@link #addOnSession2TokensChangedListener(OnSession2TokensChangedListener, Handler)}.
      */
     public interface OnSession2TokensChangedListener {
         /**
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 19e7b73..6e6c009 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -112,6 +112,9 @@
         wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
     </string>
 
+    <!-- The minimum number of tiles to display in QuickSettings -->
+    <integer name="quick_settings_min_num_tiles">6</integer>
+
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
         wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8199ea3..591af82 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2009,7 +2009,7 @@
     <string name="drag_to_remove_tiles">Drag here to remove</string>
 
     <!-- Label to indicate to users that additional tiles cannot be removed. [CHAR LIMIT=60] -->
-    <string name="drag_to_remove_disabled">You need at least 6 tiles</string>
+    <string name="drag_to_remove_disabled">You need at least <xliff:g id="min_num_tiles" example="6">%1$d</xliff:g> tiles</string>
 
     <!-- Button to edit the tile ordering of quick settings [CHAR LIMIT=60] -->
     <string name="qs_edit">Edit</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 8ed5424..2542abd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
@@ -54,7 +55,6 @@
 import java.util.List;
 
 public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileStateListener {
-    private static final int MIN_NUM_TILES = 6;
     private static final long DRAG_LENGTH = 100;
     private static final float DRAG_SCALE = 1.2f;
     public static final long MOVE_DURATION = 150;
@@ -79,6 +79,7 @@
     private final ItemTouchHelper mItemTouchHelper;
     private final ItemDecoration mDecoration;
     private final AccessibilityManager mAccessibilityManager;
+    private final int mMinNumTiles;
     private int mEditIndex;
     private int mTileDividerIndex;
     private boolean mNeedsFocus;
@@ -97,6 +98,7 @@
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mItemTouchHelper = new ItemTouchHelper(mCallbacks);
         mDecoration = new TileItemDecoration(context);
+        mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles);
     }
 
     public void setHost(QSTileHost host) {
@@ -247,15 +249,17 @@
             return;
         }
         if (holder.getItemViewType() == TYPE_EDIT) {
-            final int titleResId;
+            final String titleText;
+            Resources res = mContext.getResources();
             if (mCurrentDrag == null) {
-                titleResId = R.string.drag_to_add_tiles;
+                titleText = res.getString(R.string.drag_to_add_tiles);
             } else if (!canRemoveTiles() && mCurrentDrag.getAdapterPosition() < mEditIndex) {
-                titleResId = R.string.drag_to_remove_disabled;
+                titleText = res.getString(R.string.drag_to_remove_disabled, mMinNumTiles);
             } else {
-                titleResId = R.string.drag_to_remove_tiles;
+                titleText = res.getString(R.string.drag_to_remove_tiles);
             }
-            ((TextView) holder.itemView.findViewById(android.R.id.title)).setText(titleResId);
+
+            ((TextView) holder.itemView.findViewById(android.R.id.title)).setText(titleText);
             return;
         }
         if (holder.getItemViewType() == TYPE_ACCESSIBLE_DROP) {
@@ -337,7 +341,7 @@
     }
 
     private boolean canRemoveTiles() {
-        return mCurrentSpecs.size() > MIN_NUM_TILES;
+        return mCurrentSpecs.size() > mMinNumTiles;
     }
 
     private void selectPosition(int position, View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index b70b45b..4049201 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
+
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.Handler;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.util.Log;
@@ -43,6 +46,7 @@
 import java.util.Stack;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 import javax.inject.Singleton;
 
 import dagger.Lazy;
@@ -58,6 +62,8 @@
 public class NotificationViewHierarchyManager implements DynamicPrivacyController.Listener {
     private static final String TAG = "NotificationViewHierarchyManager";
 
+    private final Handler mHandler;
+
     //TODO: change this top <Entry, List<Entry>>?
     private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
             mTmpChildOrderMap = new HashMap<>();
@@ -86,9 +92,13 @@
 
     // Used to help track down re-entrant calls to our update methods, which will cause bugs.
     private boolean mPerformingUpdate;
+    // Hack to get around re-entrant call in onDynamicPrivacyChanged() until we can track down
+    // the problem.
+    private boolean mIsHandleDynamicPrivacyChangeScheduled;
 
     @Inject
     public NotificationViewHierarchyManager(Context context,
+            @Named(MAIN_HANDLER_NAME) Handler mainHandler,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotificationGroupManager groupManager,
             VisualStabilityManager visualStabilityManager,
@@ -97,6 +107,7 @@
             Lazy<ShadeController> shadeController,
             BubbleData bubbleData,
             DynamicPrivacyController privacyController) {
+        mHandler = mainHandler;
         mLockscreenUserManager = notificationLockscreenUserManager;
         mGroupManager = groupManager;
         mVisualStabilityManager = visualStabilityManager;
@@ -435,19 +446,33 @@
 
     @Override
     public void onDynamicPrivacyChanged() {
+        if (mPerformingUpdate) {
+            Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call");
+        }
+        // This listener can be called from updateNotificationViews() via a convoluted listener
+        // chain, so we post here to prevent a re-entrant call. See b/136186188
+        // TODO: Refactor away the need for this
+        if (!mIsHandleDynamicPrivacyChangeScheduled) {
+            mIsHandleDynamicPrivacyChangeScheduled = true;
+            mHandler.post(this::onHandleDynamicPrivacyChanged);
+        }
+    }
+
+    private void onHandleDynamicPrivacyChanged() {
+        mIsHandleDynamicPrivacyChangeScheduled = false;
         updateNotificationViews();
     }
 
     private void beginUpdate() {
         if (mPerformingUpdate) {
-            throw new IllegalStateException("Re-entrant code during update.");
+            Log.wtf(TAG, "Re-entrant code during update", new Exception());
         }
         mPerformingUpdate = true;
     }
 
     private void endUpdate() {
         if (!mPerformingUpdate) {
-            throw new IllegalStateException("Manager state has become desynced.");
+            Log.wtf(TAG, "Manager state has become desynced", new Exception());
         }
         mPerformingUpdate = false;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index c476d80..5103e8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -20,12 +20,16 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -78,13 +82,19 @@
     @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private ShadeController mShadeController;
 
+    private TestableLooper mTestableLooper;
+    private Handler mHandler;
     private NotificationViewHierarchyManager mViewHierarchyManager;
     private NotificationTestHelper mHelper;
+    private boolean mMadeReentrantCall = false;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        mTestableLooper = TestableLooper.get(this);
+        Assert.sMainLooper = mTestableLooper.getLooper();
+        mHandler = Handler.createAsync(mTestableLooper.getLooper());
+
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
                 mLockscreenUserManager);
@@ -97,7 +107,7 @@
         when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
 
         mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
-                mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
+                mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
                 mock(StatusBarStateControllerImpl.class), mEntryManager,
                 () -> mShadeController, new BubbleData(mContext), mock(DynamicPrivacyController.class));
         Dependency.get(InitController.class).executePostInitTasks();
@@ -212,9 +222,60 @@
         verify(entry0.getRow(), times(1)).showAppOpsIcons(any());
     }
 
+    @Test
+    public void testReentrantCallsToOnDynamicPrivacyChangedPostForLater() {
+        // GIVEN a ListContainer that will make a re-entrant call to updateNotificationViews()
+        mMadeReentrantCall = false;
+        doAnswer((invocation) -> {
+            if (!mMadeReentrantCall) {
+                mMadeReentrantCall = true;
+                mViewHierarchyManager.onDynamicPrivacyChanged();
+            }
+            return null;
+        }).when(mListContainer).setMaxDisplayedNotifications(anyInt());
+
+        // WHEN we call updateNotificationViews()
+        mViewHierarchyManager.updateNotificationViews();
+
+        // THEN onNotificationViewUpdateFinished() is only called once
+        verify(mListContainer).onNotificationViewUpdateFinished();
+
+        // WHEN we drain the looper
+        mTestableLooper.processAllMessages();
+
+        // THEN updateNotificationViews() is called a second time (for the reentrant call)
+        verify(mListContainer, times(2)).onNotificationViewUpdateFinished();
+    }
+
+    @Test
+    public void testMultipleReentrantCallsToOnDynamicPrivacyChangedOnlyPostOnce() {
+        // GIVEN a ListContainer that will make many re-entrant calls to updateNotificationViews()
+        mMadeReentrantCall = false;
+        doAnswer((invocation) -> {
+            if (!mMadeReentrantCall) {
+                mMadeReentrantCall = true;
+                mViewHierarchyManager.onDynamicPrivacyChanged();
+                mViewHierarchyManager.onDynamicPrivacyChanged();
+                mViewHierarchyManager.onDynamicPrivacyChanged();
+                mViewHierarchyManager.onDynamicPrivacyChanged();
+            }
+            return null;
+        }).when(mListContainer).setMaxDisplayedNotifications(anyInt());
+
+        // WHEN we call updateNotificationViews() and drain the looper
+        mViewHierarchyManager.updateNotificationViews();
+        verify(mListContainer).onNotificationViewUpdateFinished();
+        clearInvocations(mListContainer);
+        mTestableLooper.processAllMessages();
+
+        // THEN updateNotificationViews() is called only one more time
+        verify(mListContainer).onNotificationViewUpdateFinished();
+    }
+
     private class FakeListContainer implements NotificationListContainer {
         final LinearLayout mLayout = new LinearLayout(mContext);
         final List<View> mRows = Lists.newArrayList();
+        private boolean mMakeReentrantCallDuringSetMaxDisplayedNotifications;
 
         @Override
         public void setChildTransferInProgress(boolean childTransferInProgress) {}
@@ -263,7 +324,11 @@
         }
 
         @Override
-        public void setMaxDisplayedNotifications(int maxNotifications) {}
+        public void setMaxDisplayedNotifications(int maxNotifications) {
+            if (mMakeReentrantCallDuringSetMaxDisplayedNotifications) {
+                mViewHierarchyManager.onDynamicPrivacyChanged();
+            }
+        }
 
         @Override
         public ViewGroup getViewParentForNotification(NotificationEntry entry) {
@@ -298,5 +363,7 @@
             return false;
         }
 
+        @Override
+        public void onNotificationViewUpdateFinished() { }
     }
 }
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 4e416a2..1046e82 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -103,6 +103,7 @@
     public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
             "android.hardware.audio@2.0::IDevicesFactory",
             "android.hardware.audio@4.0::IDevicesFactory",
+            "android.hardware.biometrics.face@1.0::IBiometricsFace",
             "android.hardware.bluetooth@1.0::IBluetoothHci",
             "android.hardware.camera.provider@2.4::ICameraProvider",
             "android.hardware.graphics.allocator@2.0::IAllocator",
@@ -113,7 +114,7 @@
             "android.hardware.media.omx@1.0::IOmxStore",
             "android.hardware.sensors@1.0::ISensors",
             "android.hardware.vr@1.0::IVr",
-            "android.hardware.biometrics.face@1.0::IBiometricsFace"
+            "android.system.suspend@1.0::ISystemSuspend"
     );
 
     static Watchdog sWatchdog;
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 a7da3ec..d3e5df5 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2269,6 +2269,11 @@
             return;
         }
 
+        // Permission is already revoked, no need to do anything.
+        if (!permissionsState.hasRuntimePermission(permName, userId)) {
+            return;
+        }
+
         if (permissionsState.revokeRuntimePermission(bp, userId) ==
                 PERMISSION_OPERATION_FAILURE) {
             return;
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 9cd6b0d..c6a1867 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -49,6 +49,7 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
+import android.provider.Telephony;
 import android.service.sms.FinancialSmsService;
 import android.telephony.IFinancialSmsCallback;
 import android.text.TextUtils;
@@ -60,6 +61,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.telephony.SmsApplication;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.CollectionUtils;
@@ -377,13 +379,16 @@
     }
 
     @Override
-    public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
+    public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId,
+            @Nullable String removedHolder, @Nullable String addedHolder) {
         mListenerHandler.sendMessage(PooledLambda.obtainMessage(
-                RoleManagerService::notifyRoleHoldersChanged, this, roleName, userId));
+                RoleManagerService::notifyRoleHoldersChanged, this, roleName, userId,
+                removedHolder, addedHolder));
     }
 
     @WorkerThread
-    private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
+    private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId,
+            @Nullable String removedHolder, @Nullable String addedHolder) {
         RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
         if (listeners != null) {
             notifyRoleHoldersChangedForListeners(listeners, roleName, userId);
@@ -394,6 +399,12 @@
         if (allUsersListeners != null) {
             notifyRoleHoldersChangedForListeners(allUsersListeners, roleName, userId);
         }
+
+        // Legacy: sms app changed broadcasts
+        if (RoleManager.ROLE_SMS.equals(roleName)) {
+            SmsApplication.broadcastSmsAppChange(getContext(), UserHandle.of(userId),
+                    removedHolder, addedHolder);
+        }
     }
 
     @WorkerThread
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
index c7e3fa4..6375b48 100644
--- a/services/core/java/com/android/server/role/RoleUserState.java
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -294,7 +294,7 @@
         }
 
         if (changed) {
-            mCallback.onRoleHoldersChanged(roleName, mUserId);
+            mCallback.onRoleHoldersChanged(roleName, mUserId, null, packageName);
         }
         return true;
     }
@@ -328,7 +328,7 @@
         }
 
         if (changed) {
-            mCallback.onRoleHoldersChanged(roleName, mUserId);
+            mCallback.onRoleHoldersChanged(roleName, mUserId, packageName, null);
         }
         return true;
     }
@@ -632,6 +632,7 @@
          * @param roleName the name of the role whose holders are changed
          * @param userId the user id for this role holder change
          */
-        void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId);
+        void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId,
+                @Nullable String removedHolder, @Nullable String addedHolder);
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0faea61..371a943 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1616,8 +1616,11 @@
             try {
                 ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
                 ar.add(rintent);
+                // Making sure the client state is RESUMED after transaction completed and doing
+                // so only if activity is currently RESUMED. Otherwise, client may have extra
+                // life-cycle calls to RESUMED (and PAUSED later).
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                        NewIntentItem.obtain(ar));
+                        NewIntentItem.obtain(ar, mState == RESUMED));
                 unsent = false;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 30e866b..6bed462 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -2964,7 +2964,8 @@
                 }
 
                 if (next.newIntents != null) {
-                    transaction.addCallback(NewIntentItem.obtain(next.newIntents));
+                    transaction.addCallback(
+                            NewIntentItem.obtain(next.newIntents, true /* resume */));
                 }
 
                 // Well the app will no longer be stopped.
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index c992a69..19916bc 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -415,7 +415,7 @@
 
         void sendErrorResult(String message) {
             try {
-                if (callerApp.hasThread()) {
+                if (callerApp != null && callerApp.hasThread()) {
                     callerApp.getThread().scheduleCrash(message);
                 }
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1659131..b837d9e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -140,7 +140,7 @@
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
 import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
-import static com.android.server.wm.utils.RegionUtils.forEachRect;
+import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
 import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
 
 import android.animation.AnimationHandler;
@@ -5142,7 +5142,7 @@
         final int[] remainingLeftRight =
                 {mSystemGestureExclusionLimit, mSystemGestureExclusionLimit};
 
-        // Traverse all windows bottom up to assemble the gesture exclusion rects.
+        // Traverse all windows top down to assemble the gesture exclusion rects.
         // For each window, we only take the rects that fall within its touchable region.
         forAllWindows(w -> {
             if (w.cantReceiveTouchInput() || !w.isVisible()
@@ -5150,23 +5150,25 @@
                     || unhandled.isEmpty()) {
                 return;
             }
-            final boolean modal =
-                    (w.mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
 
             // Get the touchable region of the window, and intersect with where the screen is still
             // touchable, i.e. touchable regions on top are not covering it yet.
-            w.getTouchableRegion(touchableRegion);
+            w.getEffectiveTouchableRegion(touchableRegion);
             touchableRegion.op(unhandled, Op.INTERSECT);
 
-            rectListToRegion(w.getSystemGestureExclusion(), local);
+            if (w.isImplicitlyExcludingAllSystemGestures()) {
+                local.set(touchableRegion);
+            } else {
+                rectListToRegion(w.getSystemGestureExclusion(), local);
 
-            // Transform to display coordinates
-            local.scale(w.mGlobalScale);
-            final Rect frame = w.getWindowFrames().mFrame;
-            local.translate(frame.left, frame.top);
+                // Transform to display coordinates
+                local.scale(w.mGlobalScale);
+                final Rect frame = w.getWindowFrames().mFrame;
+                local.translate(frame.left, frame.top);
 
-            // A window can only exclude system gestures where it is actually touchable
-            local.op(touchableRegion, Op.INTERSECT);
+                // A window can only exclude system gestures where it is actually touchable
+                local.op(touchableRegion, Op.INTERSECT);
+            }
 
             // Apply restriction if necessary.
             if (needsGestureExclusionRestrictions(w, mLastDispatchedSystemUiVisibility)) {
@@ -5225,13 +5227,13 @@
         r.op(edge, Op.INTERSECT);
 
         final int[] remaining = {limit};
-        forEachRect(r, rect -> {
+        forEachRectReverse(r, rect -> {
             if (remaining[0] <= 0) {
                 return;
             }
             final int height = rect.height();
             if (height > remaining[0]) {
-                rect.bottom = rect.top + remaining[0];
+                rect.top = rect.bottom - remaining[0];
             }
             remaining[0] -= height;
             global.op(rect, Op.UNION);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 6127303..553b0ff 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -176,6 +176,8 @@
         mTransaction.transferTouchFocus(mTransferTouchFromToken, h.token);
         mTransferTouchFromToken = null;
 
+        // syncInputWindows here to ensure the input channel isn't removed before the transfer.
+        mTransaction.syncInputWindows();
         mTransaction.apply();
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2db0131..fb57d73 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -32,6 +32,7 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.myPid;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE;
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -844,6 +845,7 @@
     boolean mWindowsChanged = false;
 
     int mSystemGestureExclusionLimitDp;
+    boolean mSystemGestureExcludedByPreQStickyImmersive;
 
     public interface WindowChangeListener {
         public void windowsChanged();
@@ -1142,13 +1144,21 @@
         mSystemGestureExclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
                 DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                         KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
+        mSystemGestureExcludedByPreQStickyImmersive =
+                DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                        KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 new HandlerExecutor(mH), properties -> {
                     synchronized (mGlobalLock) {
                         final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
                                 properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
-                        if (mSystemGestureExclusionLimitDp != exclusionLimitDp) {
+                        final boolean excludedByPreQSticky = DeviceConfig.getBoolean(
+                                DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                                KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
+                        if (mSystemGestureExcludedByPreQStickyImmersive != excludedByPreQSticky
+                                || mSystemGestureExclusionLimitDp != exclusionLimitDp) {
                             mSystemGestureExclusionLimitDp = exclusionLimitDp;
+                            mSystemGestureExcludedByPreQStickyImmersive = excludedByPreQSticky;
                             mRoot.forAllDisplays(DisplayContent::updateSystemGestureExclusionLimit);
                         }
                     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c6c9e1b..43ad091 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -24,6 +24,8 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.SurfaceControl.Transaction;
+import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -155,6 +157,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.PowerManager;
@@ -657,6 +660,15 @@
         return true;
     }
 
+    boolean isImplicitlyExcludingAllSystemGestures() {
+        final int immersiveStickyFlags =
+                SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+        final boolean immersiveSticky =
+                (mSystemUiVisibility & immersiveStickyFlags) == immersiveStickyFlags;
+        return immersiveSticky && mWmService.mSystemGestureExcludedByPreQStickyImmersive
+                && mAppToken != null && mAppToken.mTargetSdk < Build.VERSION_CODES.Q;
+    }
+
     interface PowerManagerWrapper {
         void wakeUp(long time, @WakeReason int reason, String details);
 
@@ -2999,6 +3011,25 @@
         subtractTouchExcludeRegionIfNeeded(outRegion);
     }
 
+    /**
+     * Get the effective touchable region in global coordinates.
+     *
+     * In contrast to {@link #getTouchableRegion}, this takes into account
+     * {@link WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL touch modality.}
+     */
+    void getEffectiveTouchableRegion(Region outRegion) {
+        final boolean modal = (mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
+        final DisplayContent dc = getDisplayContent();
+
+        if (modal && dc != null) {
+            outRegion.set(dc.getBounds());
+            cropRegionToStackBoundsIfNeeded(outRegion);
+            subtractTouchExcludeRegionIfNeeded(outRegion);
+        } else {
+            getTouchableRegion(outRegion);
+        }
+    }
+
     private void setTouchableRegionCropIfNeeded(InputWindowHandle handle) {
         final Task task = getTask();
         if (task == null || !task.cropWindowsToStackBounds()) {
diff --git a/services/core/java/com/android/server/wm/utils/RegionUtils.java b/services/core/java/com/android/server/wm/utils/RegionUtils.java
index 8cd6f88..b1b3070 100644
--- a/services/core/java/com/android/server/wm/utils/RegionUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RegionUtils.java
@@ -20,6 +20,8 @@
 import android.graphics.Region;
 import android.graphics.RegionIterator;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -48,14 +50,21 @@
     /**
      * Applies actions on each rect contained within a {@code Region}.
      *
+     * Order is bottom to top, then right to left.
+     *
      * @param region the given region.
      * @param rectConsumer the action holder.
      */
-    public static void forEachRect(Region region, Consumer<Rect> rectConsumer) {
+    public static void forEachRectReverse(Region region, Consumer<Rect> rectConsumer) {
         final RegionIterator it = new RegionIterator(region);
+        final ArrayList<Rect> rects = new ArrayList<>();
         final Rect rect = new Rect();
         while (it.next(rect)) {
-            rectConsumer.accept(rect);
+            rects.add(new Rect(rect));
         }
+        // TODO: instead of creating an array and reversing it, expose the reverse iterator through
+        //       JNI.
+        Collections.reverse(rects);
+        rects.forEach(rectConsumer);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index f49a575..7cd097e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -26,8 +26,13 @@
 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
 import static android.view.DisplayCutout.fromBoundingRect;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -789,6 +794,58 @@
     }
 
     @Test
+    public void testCalculateSystemGestureExclusion_modal() throws Exception {
+        final DisplayContent dc = createNewDisplay();
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "base");
+        win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+        win.setSystemGestureExclusion(Collections.singletonList(new Rect(0, 0, 1000, 1000)));
+
+        final WindowState win2 = createWindow(null, TYPE_APPLICATION, dc, "modal");
+        win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+        win2.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
+        win2.getAttrs().width = 10;
+        win2.getAttrs().height = 10;
+        win2.setSystemGestureExclusion(Collections.emptyList());
+
+        dc.setLayoutNeeded();
+        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+
+        win.setHasSurface(true);
+        win2.setHasSurface(true);
+
+        final Region expected = Region.obtain();
+        assertEquals(expected, dc.calculateSystemGestureExclusion());
+    }
+
+    @Test
+    public void testCalculateSystemGestureExclusion_immersiveStickyLegacyWindow() throws Exception {
+        synchronized (mWm.mGlobalLock) {
+            mWm.mSystemGestureExcludedByPreQStickyImmersive = true;
+
+            final DisplayContent dc = createNewDisplay();
+            final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
+            win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+            win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+            win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
+            win.getAttrs().subtreeSystemUiVisibility = win.mSystemUiVisibility =
+                    SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                            | SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+            win.mAppToken.mTargetSdk = P;
+
+            dc.setLayoutNeeded();
+            dc.performLayout(true /* initial */, false /* updateImeWindows */);
+
+            win.setHasSurface(true);
+
+            final Region expected = Region.obtain();
+            expected.set(dc.getBounds());
+            assertEquals(expected, dc.calculateSystemGestureExclusion());
+
+            win.setHasSurface(false);
+        }
+    }
+
+    @Test
     public void testOrientationChangeLogging() {
         MetricsLogger mockLogger = mock(MetricsLogger.class);
         Configuration oldConfig = new Configuration();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index c3561f4..d034f27 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -49,6 +49,7 @@
 
     int mRotationToReport = 0;
     boolean mKeyguardShowingAndNotOccluded = false;
+    boolean mOkToAnimate = true;
 
     private Runnable mRunnableWhenAddingSplashScreen;
 
@@ -222,7 +223,7 @@
 
     @Override
     public boolean okToAnimate() {
-        return true;
+        return mOkToAnimate;
     }
 
     @Override
diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/java/com/android/internal/telephony/SmsApplication.java
index 44dc24b..98f52cb 100644
--- a/telephony/java/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/java/com/android/internal/telephony/SmsApplication.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.Manifest.permission;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.role.RoleManager;
 import android.content.ComponentName;
@@ -662,49 +663,69 @@
             }
 
             defaultSmsAppChanged(context);
+        }
+    }
 
-            if (DEBUG_MULTIUSER) {
-                Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData);
-            }
-            if (oldAppData != null && oldAppData.mSmsAppChangedReceiverClass != null) {
-                // Notify the old sms app that it's no longer the default
-                final Intent oldAppIntent =
-                        new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
-                final ComponentName component = new ComponentName(oldAppData.mPackageName,
-                        oldAppData.mSmsAppChangedReceiverClass);
-                oldAppIntent.setComponent(component);
-                oldAppIntent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, false);
-                if (DEBUG_MULTIUSER) {
-                    Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldAppData.mPackageName);
-                }
-                context.sendBroadcastAsUser(oldAppIntent, userHandle);
-            }
-            // Notify the new sms app that it's now the default (if the new sms app has a receiver
-            // to handle the changed default sms intent).
-            if (DEBUG_MULTIUSER) {
-                Log.i(LOG_TAG, "setDefaultApplicationInternal new applicationData=" +
-                        applicationData);
-            }
-            if (applicationData.mSmsAppChangedReceiverClass != null) {
-                final Intent intent =
-                        new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
-                final ComponentName component = new ComponentName(applicationData.mPackageName,
-                        applicationData.mSmsAppChangedReceiverClass);
-                intent.setComponent(component);
-                intent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, true);
-                if (DEBUG_MULTIUSER) {
-                    Log.i(LOG_TAG, "setDefaultApplicationInternal new=" + packageName);
-                }
-                context.sendBroadcastAsUser(intent, userHandle);
-            }
+    /**
+     * Sends broadcasts on sms app change:
+     * {@link Intent#ACTION_DEFAULT_SMS_PACKAGE_CHANGED}
+     * {@link Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL}
+     */
+    public static void broadcastSmsAppChange(Context context,
+            UserHandle userHandle, @Nullable String oldPackage, @Nullable String newPackage) {
+        Collection<SmsApplicationData> apps = getApplicationCollection(context);
 
-            // Send an implicit broadcast for the system server.
-            // (or anyone with MONITOR_DEFAULT_SMS_PACKAGE, really.)
+        broadcastSmsAppChange(context, userHandle,
+                getApplicationForPackage(apps, oldPackage),
+                getApplicationForPackage(apps, newPackage));
+    }
+
+    private static void broadcastSmsAppChange(Context context, UserHandle userHandle,
+            @Nullable SmsApplicationData oldAppData,
+            @Nullable SmsApplicationData applicationData) {
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData);
+        }
+        if (oldAppData != null && oldAppData.mSmsAppChangedReceiverClass != null) {
+            // Notify the old sms app that it's no longer the default
+            final Intent oldAppIntent =
+                    new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+            final ComponentName component = new ComponentName(oldAppData.mPackageName,
+                    oldAppData.mSmsAppChangedReceiverClass);
+            oldAppIntent.setComponent(component);
+            oldAppIntent.putExtra(Intents.EXTRA_IS_DEFAULT_SMS_APP, false);
+            if (DEBUG_MULTIUSER) {
+                Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldAppData.mPackageName);
+            }
+            context.sendBroadcastAsUser(oldAppIntent, userHandle);
+        }
+        // Notify the new sms app that it's now the default (if the new sms app has a receiver
+        // to handle the changed default sms intent).
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "setDefaultApplicationInternal new applicationData=" +
+                    applicationData);
+        }
+        if (applicationData != null && applicationData.mSmsAppChangedReceiverClass != null) {
             final Intent intent =
-                    new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL);
-            context.sendBroadcastAsUser(intent, userHandle,
-                    permission.MONITOR_DEFAULT_SMS_PACKAGE);
+                    new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+            final ComponentName component = new ComponentName(applicationData.mPackageName,
+                    applicationData.mSmsAppChangedReceiverClass);
+            intent.setComponent(component);
+            intent.putExtra(Intents.EXTRA_IS_DEFAULT_SMS_APP, true);
+            if (DEBUG_MULTIUSER) {
+                Log.i(LOG_TAG, "setDefaultApplicationInternal new=" + applicationData.mPackageName);
+            }
+            context.sendBroadcastAsUser(intent, userHandle);
+        }
 
+        // Send an implicit broadcast for the system server.
+        // (or anyone with MONITOR_DEFAULT_SMS_PACKAGE, really.)
+        final Intent intent =
+                new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL);
+        context.sendBroadcastAsUser(intent, userHandle,
+                permission.MONITOR_DEFAULT_SMS_PACKAGE);
+
+        if (applicationData != null) {
             MetricsLogger.action(context, MetricsEvent.ACTION_DEFAULT_SMS_APP_CHANGED,
                     applicationData.mPackageName);
         }