Merge "Add to TODO for unique pending intent Test: Change in comment only"
diff --git a/core/api/current.txt b/core/api/current.txt
index dc9d7d5..fd78bbd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7273,6 +7273,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.os.ParcelFileDescriptor getWallpaperFile(int);
     method public int getWallpaperId(int);
     method public android.app.WallpaperInfo getWallpaperInfo();
+    method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int);
     method public boolean hasResourceWallpaper(@RawRes int);
     method public boolean isSetWallpaperAllowed();
     method public boolean isWallpaperSupported();
@@ -40011,6 +40012,7 @@
     method public int getDesiredMinimumWidth();
     method @Nullable public android.content.Context getDisplayContext();
     method public android.view.SurfaceHolder getSurfaceHolder();
+    method public int getWallpaperFlags();
     method public boolean isPreview();
     method public boolean isVisible();
     method public void notifyColorsChanged();
@@ -40027,6 +40029,7 @@
     method @MainThread public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
     method @MainThread public void onTouchEvent(android.view.MotionEvent);
     method @MainThread public void onVisibilityChanged(boolean);
+    method @MainThread public void onWallpaperFlagsChanged(int);
     method @MainThread public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
     method public void setOffsetNotificationsEnabled(boolean);
     method public void setTouchEventsEnabled(boolean);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 78b2342..fbb3516 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -783,13 +783,19 @@
   }
 
   public class BroadcastOptions {
+    method public void clearDeliveryGroupMatchingFilter();
+    method public void clearDeliveryGroupMatchingKey();
     method public void clearDeliveryGroupPolicy();
     method public void clearRequireCompatChange();
+    method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
+    method @Nullable public String getDeliveryGroupMatchingKey();
     method public int getDeliveryGroupPolicy();
     method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public static android.app.BroadcastOptions makeBasic();
     method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
     method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
+    method public void setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
+    method public void setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
     method public void setDeliveryGroupPolicy(int);
     method public void setDontSendToRestrictedApps(boolean);
     method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
@@ -811,17 +817,50 @@
   public final class GameManager {
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public android.app.GameModeInfo getGameModeInfo(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(@NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public void updateCustomGameModeConfiguration(@NonNull String, @NonNull android.app.GameModeConfiguration);
+  }
+
+  public final class GameModeConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getFpsOverride();
+    method public float getScalingFactor();
+    method @NonNull public android.app.GameModeConfiguration.Builder toBuilder();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeConfiguration> CREATOR;
+    field public static final int FPS_OVERRIDE_NONE = 0; // 0x0
+  }
+
+  public static final class GameModeConfiguration.Builder {
+    ctor public GameModeConfiguration.Builder();
+    method @NonNull public android.app.GameModeConfiguration build();
+    method @NonNull public android.app.GameModeConfiguration.Builder setFpsOverride(int);
+    method @NonNull public android.app.GameModeConfiguration.Builder setScalingFactor(float);
   }
 
   public final class GameModeInfo implements android.os.Parcelable {
-    ctor public GameModeInfo(int, @NonNull int[]);
+    ctor @Deprecated public GameModeInfo(int, @NonNull int[]);
     method public int describeContents();
     method public int getActiveGameMode();
     method @NonNull public int[] getAvailableGameModes();
+    method @Nullable public android.app.GameModeConfiguration getGameModeConfiguration(int);
+    method @NonNull public int[] getOptedInGameModes();
+    method public boolean isDownscalingAllowed();
+    method public boolean isFpsOverrideAllowed();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeInfo> CREATOR;
   }
 
+  public static final class GameModeInfo.Builder {
+    ctor public GameModeInfo.Builder();
+    method @NonNull public android.app.GameModeInfo build();
+    method @NonNull public android.app.GameModeInfo.Builder setActiveGameMode(@NonNull int);
+    method @NonNull public android.app.GameModeInfo.Builder setAvailableGameModes(@NonNull int[]);
+    method @NonNull public android.app.GameModeInfo.Builder setDownscalingAllowed(boolean);
+    method @NonNull public android.app.GameModeInfo.Builder setFpsOverrideAllowed(boolean);
+    method @NonNull public android.app.GameModeInfo.Builder setGameModeConfiguration(int, @NonNull android.app.GameModeConfiguration);
+    method @NonNull public android.app.GameModeInfo.Builder setOptedInGameModes(@NonNull int[]);
+  }
+
   public abstract class InstantAppResolverService extends android.app.Service {
     ctor public InstantAppResolverService();
     method public final void attachBaseContext(android.content.Context);
@@ -1082,6 +1121,7 @@
     method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
     method public void setDisplayOffset(android.os.IBinder, int, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float);
   }
 
@@ -3185,7 +3225,9 @@
   }
 
   public class IntentFilter implements android.os.Parcelable {
+    method @NonNull public final android.os.PersistableBundle getExtras();
     method public final int getOrder();
+    method public final void setExtras(@NonNull android.os.PersistableBundle);
     method public final void setOrder(int);
   }
 
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 62b0f5c..dbdee07 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -623,6 +623,9 @@
      *
      * <p>No permission is required to call this method.
      *
+     * <p>Caller targeting API level 34 and above, the results are filtered
+     * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
+     *
      * @return An array of {@link AuthenticatorDescription} for every
      *     authenticator known to the AccountManager service.  Empty (never
      *     null) if no authenticators are known.
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 1777f37..f25e639 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -66,8 +66,9 @@
     private long mIdForResponseEvent;
     private @Nullable IntentFilter mRemoveMatchingFilter;
     private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
-    private @Nullable String mDeliveryGroupKey;
+    private @Nullable String mDeliveryGroupMatchingKey;
     private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
+    private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
 
     /**
      * Change ID which is invalid.
@@ -206,10 +207,10 @@
             "android:broadcast.deliveryGroupPolicy";
 
     /**
-     * Corresponds to {@link #setDeliveryGroupKey(String, String)}.
+     * Corresponds to {@link #setDeliveryGroupMatchingKey(String, String)}.
      */
     private static final String KEY_DELIVERY_GROUP_KEY =
-            "android:broadcast.deliveryGroupKey";
+            "android:broadcast.deliveryGroupMatchingKey";
 
     /**
      * Corresponds to {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
@@ -218,6 +219,12 @@
             "android:broadcast.deliveryGroupExtrasMerger";
 
     /**
+     * Corresponds to {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+     */
+    private static final String KEY_DELIVERY_GROUP_MATCHING_FILTER =
+            "android:broadcast.deliveryGroupMatchingFilter";
+
+    /**
      * The list of delivery group policies which specify how multiple broadcasts belonging to
      * the same delivery group has to be handled.
      * @hide
@@ -304,9 +311,11 @@
                 IntentFilter.class);
         mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
                 DELIVERY_GROUP_POLICY_ALL);
-        mDeliveryGroupKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+        mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
         mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
                 BundleMerger.class);
+        mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
+                IntentFilter.class);
     }
 
     /**
@@ -733,7 +742,7 @@
 
     /**
      * Clears any previously set delivery group policies using
-     * {@link #setDeliveryGroupKey(String, String)} and resets the delivery group policy to
+     * {@link #setDeliveryGroupMatchingKey(String, String)} and resets the delivery group policy to
      * the default value ({@link #DELIVERY_GROUP_POLICY_ALL}).
      *
      * @hide
@@ -745,22 +754,92 @@
 
     /**
      * Set namespace and key to identify the delivery group that this broadcast belongs to.
-     * If no namespace and key is set, then by default {@link Intent#filterEquals(Intent)} will be
-     * used to identify the delivery group.
+     *
+     * <p> If {@code namespace} and {@code key} are specified, then another broadcast will be
+     * considered to be in the same delivery group as this iff it has the same {@code namespace}
+     * and {@code key}.
+     *
+     * <p> If neither matching key using this API nor matching filter using
+     * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
+     * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
      *
      * @hide
      */
-    public void setDeliveryGroupKey(@NonNull String namespace, @NonNull String key) {
+    @SystemApi
+    public void setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) {
         Preconditions.checkArgument(!namespace.contains("/"),
                 "namespace should not contain '/'");
         Preconditions.checkArgument(!key.contains("/"),
                 "key should not contain '/'");
-        mDeliveryGroupKey = namespace + "/" + key;
+        mDeliveryGroupMatchingKey = namespace + "/" + key;
     }
 
-    /** @hide */
-    public String getDeliveryGroupKey() {
-        return mDeliveryGroupKey;
+    /**
+     * Return the namespace and key that is used to identify the delivery group that this
+     * broadcast belongs to.
+     *
+     * @return the delivery group namespace and key that was previously set using
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code /}.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public String getDeliveryGroupMatchingKey() {
+        return mDeliveryGroupMatchingKey;
+    }
+
+    /**
+     * Clears the namespace and key that was previously set using
+     * {@link #setDeliveryGroupMatchingKey(String, String)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void clearDeliveryGroupMatchingKey() {
+        mDeliveryGroupMatchingKey = null;
+    }
+
+    /**
+     * Set the {@link IntentFilter} object to identify the delivery group that this broadcast
+     * belongs to.
+     *
+     * <p> If a {@code matchingFilter} is specified, then another broadcast will be considered
+     * to be in the same delivery group as this iff the {@code matchingFilter} matches it's intent.
+     *
+     * <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor
+     * matching filter using this API is specified, then by default
+     * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) {
+        mDeliveryGroupMatchingFilter = Objects.requireNonNull(matchingFilter);
+    }
+
+    /**
+     * Return the {@link IntentFilter} object that is used to identify the delivery group
+     * that this broadcast belongs to.
+     *
+     * @return the {@link IntentFilter} object that was previously set using
+     *         {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public IntentFilter getDeliveryGroupMatchingFilter() {
+        return mDeliveryGroupMatchingFilter;
+    }
+
+    /**
+     * Clears the {@link IntentFilter} object that was previously set using
+     * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void clearDeliveryGroupMatchingFilter() {
+        mDeliveryGroupMatchingFilter = null;
     }
 
     /**
@@ -773,16 +852,33 @@
      * @hide
      */
     public void setDeliveryGroupExtrasMerger(@NonNull BundleMerger extrasMerger) {
-        Preconditions.checkNotNull(extrasMerger);
-        mDeliveryGroupExtrasMerger = extrasMerger;
+        mDeliveryGroupExtrasMerger = Objects.requireNonNull(extrasMerger);
     }
 
-    /** @hide */
-    public @Nullable BundleMerger getDeliveryGroupExtrasMerger() {
+    /**
+     * Return the {@link BundleMerger} that specifies how to merge the extras data from
+     * broadcasts in a delivery group.
+     *
+     * @return the {@link BundleMerger} object that was previously set using
+     *         {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
+     * @hide
+     */
+    @Nullable
+    public BundleMerger getDeliveryGroupExtrasMerger() {
         return mDeliveryGroupExtrasMerger;
     }
 
     /**
+     * Clear the {@link BundleMerger} object that was previously set using
+     * {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
+     *
+     * @hide
+     */
+    public void clearDeliveryGroupExtrasMerger() {
+        mDeliveryGroupExtrasMerger = null;
+    }
+
+    /**
      * Returns the created options as a Bundle, which can be passed to
      * {@link android.content.Context#sendBroadcast(android.content.Intent)
      * Context.sendBroadcast(Intent)} and related methods.
@@ -837,8 +933,8 @@
         if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
             b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
         }
-        if (mDeliveryGroupKey != null) {
-            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupKey);
+        if (mDeliveryGroupMatchingKey != null) {
+            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKey);
         }
         if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
             if (mDeliveryGroupExtrasMerger != null) {
@@ -849,6 +945,9 @@
                         + "when delivery group policy is 'MERGED'");
             }
         }
+        if (mDeliveryGroupMatchingFilter != null) {
+            b.putParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER, mDeliveryGroupMatchingFilter);
+        }
         return b.isEmpty() ? null : b;
     }
 }
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index aac163c..f92194d 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -187,7 +187,9 @@
      * Sets the game mode for the given package.
      * <p>
      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
-     *
+     * <p>
+     * Setting the game mode on a non-game application or setting a game to
+     * {@link #GAME_MODE_UNSUPPORTED} will have no effect.
      * @hide
      */
     @SystemApi
@@ -279,4 +281,25 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Updates the config for the game's {@link #GAME_MODE_CUSTOM} mode.
+     * <p>
+     * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+     *
+     * @param gameModeConfig The configuration to use for game mode interventions
+     * @hide
+     */
+    @SystemApi
+    @UserHandleAware
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public void updateCustomGameModeConfiguration(@NonNull String packageName,
+            @NonNull GameModeConfiguration gameModeConfig) {
+        try {
+            mService.updateCustomGameModeConfiguration(packageName, gameModeConfig,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/GameModeConfiguration.aidl b/core/java/android/app/GameModeConfiguration.aidl
new file mode 100644
index 0000000..14ac9cd
--- /dev/null
+++ b/core/java/android/app/GameModeConfiguration.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/** @hide*/
+parcelable GameModeConfiguration;
\ No newline at end of file
diff --git a/core/java/android/app/GameModeConfiguration.java b/core/java/android/app/GameModeConfiguration.java
new file mode 100644
index 0000000..b081e82
--- /dev/null
+++ b/core/java/android/app/GameModeConfiguration.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Display;
+
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.Preconditions;
+
+/**
+ * GameModeConfiguration is the game's platform configuration for a game mode.
+ * <p>
+ * Only the game modes that are enabled by OEMs will have an active configuration, whereas game
+ * modes opted in by the game will not.
+ *
+ * @hide
+ */
+@Immutable
+@SystemApi
+public final class GameModeConfiguration implements Parcelable {
+    // Default value indicating that no FPS override will be applied as game intervention, or
+    // default to the current display mode's frame rate.
+    public static final int FPS_OVERRIDE_NONE = 0;
+
+    public static final @NonNull Creator<GameModeConfiguration> CREATOR = new Creator<>() {
+        @Override
+        public GameModeConfiguration createFromParcel(Parcel in) {
+            return new GameModeConfiguration(in);
+        }
+
+        @Override
+        public GameModeConfiguration[] newArray(int size) {
+            return new GameModeConfiguration[size];
+        }
+    };
+
+    /**
+     * Builder for {@link GameModeConfiguration}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        /** Constructs a new Builder for a game mode’s configuration */
+        public Builder() {
+        }
+
+        /**
+         * Sets the scaling factor used for game resolution downscaling.
+         * <br>
+         *
+         * @param scalingFactor the desired scaling factor ranged from 0.1 to 1.0 inclusively
+         * @throws IllegalArgumentException if the scaling factor is not in range of [0.1, 1.0]
+         */
+        @NonNull
+        public GameModeConfiguration.Builder setScalingFactor(
+                @FloatRange(from = 0.1, to = 1.0) float scalingFactor) {
+            Preconditions.checkArgument(scalingFactor >= 0.1 && scalingFactor <= 1.0,
+                    "Scaling factor should fall between 0.1 and 1.0 (inclusive)");
+            mScalingFactor = scalingFactor;
+            return this;
+        }
+
+        /**
+         * Sets the FPS override used for game frame rate throttling.
+         * <br>
+         * The list of valid throttled frame rates can be queried by
+         * <ol>
+         * <li>Obtain display modes by calling {@link Display#getSupportedModes}
+         * <li>For each mode, get valid FPS by getting the divisor of the
+         * {@link Display.Mode#getRefreshRate()} that is >= 30,
+         * e.g. when Display.Mode#getRefreshRate() is 120 Hz, the valid FPS
+         * of this mode is 120, 60, 40, 30
+         * <li>Aggregate the valid FPS of each mode to get the full list
+         * </ol>
+         * <br>
+         *
+         * @param fpsOverride the desired non-negative FPS override value, default to
+         *                    {@link #FPS_OVERRIDE_NONE}.
+         * @throws IllegalArgumentException if the provided value is negative
+         */
+        @NonNull
+        public GameModeConfiguration.Builder setFpsOverride(@IntRange(from = 0) int fpsOverride) {
+            Preconditions.checkArgument(fpsOverride >= 0,
+                    "FPS override should be non-negative");
+            mFpsOverride = fpsOverride;
+            return this;
+        }
+
+        /**
+         * Builds a GameModeConfiguration.
+         */
+        @NonNull
+        public GameModeConfiguration build() {
+            return new GameModeConfiguration(mScalingFactor, mFpsOverride);
+        }
+
+        ;
+        private float mScalingFactor;
+        private int mFpsOverride;
+    }
+
+    GameModeConfiguration(float scalingFactor, int fpsOverride) {
+        this.mScalingFactor = scalingFactor;
+        this.mFpsOverride = fpsOverride;
+    }
+
+    GameModeConfiguration(Parcel in) {
+        this.mScalingFactor = in.readFloat();
+        this.mFpsOverride = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeFloat(mScalingFactor);
+        dest.writeInt(mFpsOverride);
+    }
+
+    /**
+     * Gets the scaling factor used for game resolution downscaling.
+     */
+    public float getScalingFactor() {
+        return mScalingFactor;
+    }
+
+    /**
+     * Gets the FPS override used for frame rate throttling.
+     */
+    public int getFpsOverride() {
+        return mFpsOverride;
+    }
+
+    /**
+     * Converts and returns the game mode config as a new builder.
+     */
+    @NonNull
+    public GameModeConfiguration.Builder toBuilder() {
+        return new GameModeConfiguration.Builder()
+                .setFpsOverride(mFpsOverride)
+                .setScalingFactor(mScalingFactor);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof GameModeConfiguration)) {
+            return false;
+        }
+        GameModeConfiguration config = (GameModeConfiguration) obj;
+        return config.mFpsOverride == this.mFpsOverride
+                && config.mScalingFactor == this.mScalingFactor;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 7;
+        result = 31 * result + mFpsOverride;
+        result = 31 * result + Float.floatToIntBits(mScalingFactor);
+        return result;
+    }
+
+    private final float mScalingFactor;
+    private final int mFpsOverride;
+}
diff --git a/core/java/android/app/GameModeInfo.java b/core/java/android/app/GameModeInfo.java
index fe0ac35..31255c2 100644
--- a/core/java/android/app/GameModeInfo.java
+++ b/core/java/android/app/GameModeInfo.java
@@ -20,9 +20,34 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.Map;
 
 /**
  * GameModeInfo returned from {@link GameManager#getGameModeInfo(String)}.
+ *
+ * Developers can enable game modes or interventions by adding
+ * <pre>{@code
+ * <meta-data android:name="android.game_mode_intervention"
+ *   android:resource="@xml/GAME_MODE_CONFIG_FILE" />
+ * }</pre>
+ * to the <pre>{@code <application>}</pre>, where the GAME_MODE_CONFIG_FILE is an XML file that
+ * specifies the game mode enablement and intervention configuration:
+ * <pre>{@code
+ * <game-mode-config xmlns:android="http://schemas.android.com/apk/res/android"
+ *   android:gameModePerformance="true"
+ *   android:gameModeBattery="false"
+ *   android:allowGameDownscaling="true"
+ *   android:allowGameFpsOverride="false"
+ * />
+ * }</pre>
+ *
  * @hide
  */
 @SystemApi
@@ -40,52 +65,196 @@
         }
     };
 
-    public GameModeInfo(@GameManager.GameMode int activeGameMode,
-            @NonNull @GameManager.GameMode int[] availableGameModes) {
-        mActiveGameMode = activeGameMode;
-        mAvailableGameModes = availableGameModes;
+    /**
+     * Builder for {@link GameModeInfo}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        /** Constructs a new Builder for a game mode info. */
+        public Builder() {
+        }
+
+        /**
+         * Sets the available game modes.
+         */
+        @NonNull
+        public GameModeInfo.Builder setAvailableGameModes(
+                @NonNull @GameManager.GameMode int[] availableGameModes) {
+            mAvailableGameModes = availableGameModes;
+            return this;
+        }
+
+        /**
+         * Sets the opted-in game modes.
+         */
+        @NonNull
+        public GameModeInfo.Builder setOptedInGameModes(
+                @NonNull @GameManager.GameMode int[] optedInGameModes) {
+            mOptedInGameModes = optedInGameModes;
+            return this;
+        }
+
+        /**
+         * Sets the active game mode.
+         */
+        @NonNull
+        public GameModeInfo.Builder setActiveGameMode(
+                @NonNull @GameManager.GameMode int activeGameMode) {
+            mActiveGameMode = activeGameMode;
+            return this;
+        }
+
+        /**
+         * Sets the downscaling intervention flag.
+         */
+        @NonNull
+        public GameModeInfo.Builder setDownscalingAllowed(boolean allowed) {
+            mIsDownscalingAllowed = allowed;
+            return this;
+        }
+
+        /**
+         * Sets the FPS override flag.
+         */
+        @NonNull
+        public GameModeInfo.Builder setFpsOverrideAllowed(boolean allowed) {
+            mIsFpsOverrideAllowed = allowed;
+            return this;
+        }
+
+        /**
+         * Sets the GameModeConfiguration for a game mode.
+         */
+        @NonNull
+        public GameModeInfo.Builder setGameModeConfiguration(
+                @GameManager.GameMode int gameMode,
+                @NonNull GameModeConfiguration gameModeConfiguration) {
+            mConfigMap.put(gameMode, gameModeConfiguration);
+            return this;
+        }
+
+        /**
+         * Builds a GameModeInfo.
+         */
+        @NonNull
+        public GameModeInfo build() {
+            return new GameModeInfo(mActiveGameMode, mAvailableGameModes, mOptedInGameModes,
+                    mIsDownscalingAllowed, mIsFpsOverrideAllowed, mConfigMap);
+        }
+
+        private @GameManager.GameMode int[] mAvailableGameModes = new int[]{};
+        private @GameManager.GameMode int[] mOptedInGameModes = new int[]{};
+        private @GameManager.GameMode int mActiveGameMode;
+        private boolean mIsDownscalingAllowed;
+        private boolean mIsFpsOverrideAllowed;
+        private Map<Integer, GameModeConfiguration> mConfigMap = new ArrayMap<>();
     }
 
-    GameModeInfo(Parcel in) {
+    /**
+     * Creates a game mode info.
+     *
+     * @deprecated Use the {@link Builder} instead.
+     */
+    public GameModeInfo(@GameManager.GameMode int activeGameMode,
+            @NonNull @GameManager.GameMode int[] availableGameModes) {
+        this(activeGameMode, availableGameModes, new int[]{}, true, true, new ArrayMap<>());
+    }
+
+    private GameModeInfo(@GameManager.GameMode int activeGameMode,
+            @NonNull @GameManager.GameMode int[] availableGameModes,
+            @NonNull @GameManager.GameMode int[] optedInGameModes, boolean isDownscalingAllowed,
+            boolean isFpsOverrideAllowed, @NonNull Map<Integer, GameModeConfiguration> configMap) {
+        mActiveGameMode = activeGameMode;
+        mAvailableGameModes = Arrays.copyOf(availableGameModes, availableGameModes.length);
+        mOptedInGameModes = Arrays.copyOf(optedInGameModes, optedInGameModes.length);
+        mIsDownscalingAllowed = isDownscalingAllowed;
+        mIsFpsOverrideAllowed = isFpsOverrideAllowed;
+        mConfigMap = configMap;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public GameModeInfo(Parcel in) {
         mActiveGameMode = in.readInt();
-        final int availableGameModesCount = in.readInt();
-        mAvailableGameModes = new int[availableGameModesCount];
-        in.readIntArray(mAvailableGameModes);
+        mAvailableGameModes = in.createIntArray();
+        mOptedInGameModes = in.createIntArray();
+        mIsDownscalingAllowed = in.readBoolean();
+        mIsFpsOverrideAllowed = in.readBoolean();
+        mConfigMap = new ArrayMap<>();
+        in.readMap(mConfigMap,
+                getClass().getClassLoader(), Integer.class, GameModeConfiguration.class);
     }
 
     /**
      * Returns the {@link GameManager.GameMode} the application is currently using.
-     * Developers can enable game modes by adding
-     * <code>
-     *     <meta-data android:name="android.game_mode_intervention"
-     *             android:resource="@xml/GAME_MODE_CONFIG_FILE" />
-     * </code>
-     * to the {@link <application> tag}, where the GAME_MODE_CONFIG_FILE is an XML file that
-     * specifies the game mode enablement and configuration:
-     * <code>
-     *     <game-mode-config xmlns:android="http://schemas.android.com/apk/res/android"
-     *         android:gameModePerformance="true"
-     *         android:gameModeBattery="false"
-     *     />
-     * </code>
      */
     public @GameManager.GameMode int getActiveGameMode() {
         return mActiveGameMode;
     }
 
     /**
-     * The collection of {@link GameManager.GameMode GameModes} that can be applied to the game.
+     * Gets the collection of {@link GameManager.GameMode} that can be applied to the game.
+     * <p>
+     * Available games include all game modes that are either supported by the OEM in device
+     * config, or opted in by the game developers in game mode config XML, plus the default enabled
+     * modes for any game including {@link GameManager#GAME_MODE_STANDARD} and
+     * {@link GameManager#GAME_MODE_CUSTOM}.
+     * <p>
+     * Also see {@link GameModeInfo}.
      */
     @NonNull
     public @GameManager.GameMode int[] getAvailableGameModes() {
-        return mAvailableGameModes;
+        return Arrays.copyOf(mAvailableGameModes, mAvailableGameModes.length);
     }
 
-    // Ideally there should be callback that the caller can register to know when the available
-    // GameMode and/or the active GameMode is changed, however, there's no concrete use case
-    // at the moment so there's no callback mechanism introduced    .
+    /**
+     * Gets the collection of {@link GameManager.GameMode} that are opted in by the game.
+     * <p>
+     * Also see {@link GameModeInfo}.
+     */
+    @NonNull
+    public @GameManager.GameMode int[] getOptedInGameModes() {
+        return Arrays.copyOf(mOptedInGameModes, mOptedInGameModes.length);
+    }
+
+    /**
+     * Gets the current game mode configuration of a game mode.
+     * <p>
+     * The game mode can be null if it's opted in by the game itself, or not configured in device
+     * config nor set by the user as custom game mode configuration.
+     */
+    public @Nullable GameModeConfiguration getGameModeConfiguration(
+            @GameManager.GameMode int gameMode) {
+        return mConfigMap.get(gameMode);
+    }
+
+    /**
+     * Returns if downscaling is allowed (not opted out) by the game in their Game Mode config.
+     * <p>
+     * Also see {@link GameModeInfo}.
+     */
+    public boolean isDownscalingAllowed() {
+        return mIsDownscalingAllowed;
+    }
+
+    /**
+     * Returns if FPS override is allowed (not opted out) by the game in their Game Mode config.
+     * <p>
+     * Also see {@link GameModeInfo}.
+     */
+    public boolean isFpsOverrideAllowed() {
+        return mIsFpsOverrideAllowed;
+    }
+
+
     private final @GameManager.GameMode int[] mAvailableGameModes;
+    private final @GameManager.GameMode int[] mOptedInGameModes;
     private final @GameManager.GameMode int mActiveGameMode;
+    private final boolean mIsDownscalingAllowed;
+    private final boolean mIsFpsOverrideAllowed;
+    private final Map<Integer, GameModeConfiguration> mConfigMap;
 
     @Override
     public int describeContents() {
@@ -95,7 +264,10 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mActiveGameMode);
-        dest.writeInt(mAvailableGameModes.length);
         dest.writeIntArray(mAvailableGameModes);
+        dest.writeIntArray(mOptedInGameModes);
+        dest.writeBoolean(mIsDownscalingAllowed);
+        dest.writeBoolean(mIsFpsOverrideAllowed);
+        dest.writeMap(mConfigMap);
     }
 }
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 481e7b0..aea097d 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -16,21 +16,37 @@
 
 package android.app;
 
+import android.app.GameModeConfiguration;
 import android.app.GameModeInfo;
 import android.app.GameState;
+import android.app.IGameModeListener;
 
 /**
  * @hide
  */
 interface IGameManagerService {
     int getGameMode(String packageName, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     void setGameMode(String packageName, int gameMode, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     int[] getAvailableGameModes(String packageName);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     boolean isAngleEnabled(String packageName, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     void notifyGraphicsEnvironmentSetup(String packageName, int userId);
     void setGameState(String packageName, in GameState gameState, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     GameModeInfo getGameModeInfo(String packageName, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_GAME_SERVICE)")
     void setGameServiceProvider(String packageName);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     void updateResolutionScalingFactor(String packageName, int gameMode, float scalingFactor, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     float getResolutionScalingFactor(String packageName, int gameMode, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
+    void updateCustomGameModeConfiguration(String packageName, in GameModeConfiguration gameModeConfig, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
+    void addGameModeListener(IGameModeListener gameModeListener);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
+    void removeGameModeListener(IGameModeListener gameModeListener);
 }
diff --git a/core/java/android/app/IGameModeListener.aidl b/core/java/android/app/IGameModeListener.aidl
new file mode 100644
index 0000000..77fcac0
--- /dev/null
+++ b/core/java/android/app/IGameModeListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/** @hide */
+interface IGameModeListener {
+    /**
+     * Called when the game mode of the user has changed.
+     */
+    void onGameModeChanged(String packageName, int gameModeFrom, int gameModeTo, int userId);
+}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 167de46..b1ed152 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -50,9 +50,10 @@
             IWallpaperManagerCallback completion, int userId);
 
     /**
-     * Set the live wallpaper. This only affects the system wallpaper.
+     * Set the live wallpaper.
      */
-    void setWallpaperComponentChecked(in ComponentName name, in String callingPackage, int userId);
+    void setWallpaperComponentChecked(in ComponentName name, in String callingPackage, int which,
+            int userId);
 
     /**
      * Set the live wallpaper. This only affects the system wallpaper.
@@ -89,6 +90,12 @@
     WallpaperInfo getWallpaperInfo(int userId);
 
     /**
+     * If the current wallpaper for destination `which` is a live wallpaper component, return the
+     * information about that wallpaper.  Otherwise, if it is a static image, simply return null.
+     */
+    WallpaperInfo getWallpaperInfoWithFlags(int which, int userId);
+
+    /**
      * Clear the system wallpaper.
      */
     void clearWallpaper(in String callingPackage, int which, int userId);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 6f1737a..b9a7186 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -67,7 +67,6 @@
 import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.SystemProperties;
-import android.service.wallpaper.WallpaperService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -521,10 +520,7 @@
         }
 
         WallpaperColors getWallpaperColors(int which, int userId, int displayId) {
-            if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
-                throw new IllegalArgumentException(
-                        "Must request colors for exactly one kind of wallpaper");
-            }
+            checkExactlyOneWallpaperFlagSet(which);
 
             try {
                 return mService.getWallpaperColors(which, userId, displayId);
@@ -857,9 +853,7 @@
             throw new RuntimeException(new DeadSystemException());
         }
 
-        if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
-            throw new IllegalArgumentException("Must request exactly one kind of wallpaper");
-        }
+        checkExactlyOneWallpaperFlagSet(which);
 
         Resources resources = mContext.getResources();
         horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment));
@@ -1115,6 +1109,19 @@
     }
 
     /**
+     * Like {@link #getDrawable()} but returns a Bitmap.
+     *
+     * @param hardware Asks for a hardware backed bitmap.
+     * @param which Specifies home or lock screen
+     * @see Bitmap.Config#HARDWARE
+     * @hide
+     */
+    @Nullable
+    public Bitmap getBitmap(boolean hardware, @SetWallpaperFlags int which) {
+        return getBitmapAsUser(mContext.getUserId(), hardware, which);
+    }
+
+    /**
      * Like {@link #getDrawable()} but returns a Bitmap for the provided user.
      *
      * @hide
@@ -1125,6 +1132,17 @@
     }
 
     /**
+     * Like {@link #getDrawable()} but returns a Bitmap for the provided user.
+     *
+     * @param which Specifies home or lock screen
+     * @hide
+     */
+    public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
+        final ColorManagementProxy cmProxy = getColorManagementProxy();
+        return sGlobals.peekWallpaperBitmap(mContext, true, which, userId, hardware, cmProxy);
+    }
+
+    /**
      * Peek the dimensions of system wallpaper of the user without decoding it.
      *
      * @return the dimensions of system wallpaper
@@ -1281,9 +1299,7 @@
      */
     @UnsupportedAppUsage
     public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int userId) {
-        if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
-            throw new IllegalArgumentException("Must request exactly one kind of wallpaper");
-        }
+        checkExactlyOneWallpaperFlagSet(which);
 
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperService not running");
@@ -1333,16 +1349,7 @@
      * @hide
      */
     public WallpaperInfo getWallpaperInfoForUser(int userId) {
-        try {
-            if (sGlobals.mService == null) {
-                Log.w(TAG, "WallpaperService not running");
-                throw new RuntimeException(new DeadSystemException());
-            } else {
-                return sGlobals.mService.getWallpaperInfo(userId);
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getWallpaperInfo(FLAG_SYSTEM, userId);
     }
 
     /**
@@ -1353,10 +1360,10 @@
      * @param which Specifies wallpaper to request (home or lock).
      * @throws IllegalArgumentException if {@code which} is not exactly one of
      * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
-     * @hide
      */
+    @Nullable
     public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) {
-        return getWallpaperInfo();
+        return getWallpaperInfo(which, mContext.getUserId());
     }
 
     /**
@@ -1371,7 +1378,17 @@
      * @hide
      */
     public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) {
-        return getWallpaperInfoForUser(userId);
+        checkExactlyOneWallpaperFlagSet(which);
+        try {
+            if (sGlobals.mService == null) {
+                Log.w(TAG, "WallpaperService not running");
+                throw new RuntimeException(new DeadSystemException());
+            } else {
+                return sGlobals.mService.getWallpaperInfoWithFlags(which, userId);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2012,7 +2029,10 @@
     }
 
     /**
-     * Set the live wallpaper.
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, usually in order to set a live wallpaper.
+     *
+     * @param name Name of the component to use.
      *
      * @hide
      */
@@ -2080,43 +2100,72 @@
     }
 
     /**
-     * Set the live wallpaper.
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, usually in order to set a live wallpaper.
      *
      * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
      * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
      * another user's wallpaper.
      *
+     * @param name Name of the component to use.
+     * @param userId User for whom the component should be set.
+     *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
     @UnsupportedAppUsage
     public boolean setWallpaperComponent(ComponentName name, int userId) {
-        if (sGlobals.mService == null) {
-            Log.w(TAG, "WallpaperService not running");
-            throw new RuntimeException(new DeadSystemException());
-        }
-        try {
-            sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),
-                    userId);
-            return true;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return setWallpaperComponentWithFlags(name, FLAG_SYSTEM | FLAG_LOCK, userId);
     }
 
     /**
-     * Set the live wallpaper for the given screen(s).
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, usually in order to set a live wallpaper, for a given wallpaper destination.
      *
      * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
      * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
      * another user's wallpaper.
      *
+     * @param name Name of the component to use.
+     * @param which Specifies wallpaper destination (home and/or lock).
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
+    public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+            @SetWallpaperFlags int which) {
+        return setWallpaperComponentWithFlags(name, which, mContext.getUserId());
+    }
+
+    /**
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, usually in order to set a live wallpaper, for a given wallpaper destination.
+     *
+     * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+     * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
+     * another user's wallpaper.
+     *
+     * @param name Name of the component to use.
+     * @param which Specifies wallpaper destination (home and/or lock).
+     * @param userId User for whom the component should be set.
+     *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
     public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
-            @SetWallpaperFlags int which) {
-        return setWallpaperComponent(name);
+            @SetWallpaperFlags int which, int userId) {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperManagerService not running");
+            throw new RuntimeException(new DeadSystemException());
+        }
+        try {
+            sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),
+                    which, userId);
+            return true;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2440,6 +2489,13 @@
         return mCmProxy;
     }
 
+    private static void checkExactlyOneWallpaperFlagSet(@SetWallpaperFlags int which) {
+        if (which == FLAG_SYSTEM || which == FLAG_LOCK) {
+            return;
+        }
+        throw new IllegalArgumentException("Must specify exactly one kind of wallpaper");
+    }
+
     /**
      * A hidden class to help {@link Globals#getCurrentWallpaperLocked} handle color management.
      * @hide
@@ -2519,7 +2575,7 @@
          *
          * @param colors Wallpaper color info, {@code null} when not available.
          * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM}
-         * @see WallpaperService.Engine#onComputeColors()
+         * @see android.service.wallpaper.WallpaperService.Engine#onComputeColors()
          */
         void onColorsChanged(@Nullable WallpaperColors colors, int which);
 
@@ -2531,7 +2587,7 @@
          * @param colors Wallpaper color info, {@code null} when not available.
          * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM}
          * @param userId Owner of the wallpaper
-         * @see WallpaperService.Engine#onComputeColors()
+         * @see android.service.wallpaper.WallpaperService.Engine#onComputeColors()
          * @hide
          */
         default void onColorsChanged(@Nullable WallpaperColors colors, int which, int userId) {
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 8b6c4dd..bff27d3 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -23,9 +23,11 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PatternMatcher;
+import android.os.PersistableBundle;
 import android.text.TextUtils;
 import android.util.AndroidException;
 import android.util.ArraySet;
@@ -171,6 +173,13 @@
     private static final String NAME_STR = "name";
     private static final String ACTION_STR = "action";
     private static final String AUTO_VERIFY_STR = "autoVerify";
+    private static final String EXTRAS_STR = "extras";
+
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
+    private static final long[] EMPTY_LONG_ARRAY = new long[0];
+    private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+    private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
 
     /**
      * The filter {@link #setPriority} value at which system high-priority
@@ -267,6 +276,11 @@
      * that were not in the Intent.
      */
     public static final int NO_MATCH_CATEGORY = -4;
+    /**
+     * That filter didn't match due to different extras data.
+     * @hide
+     */
+    public static final int NO_MATCH_EXTRAS = -5;
 
     /**
      * HTTP scheme.
@@ -314,6 +328,7 @@
     private ArrayList<String> mMimeGroups = null;
     private boolean mHasStaticPartialTypes = false;
     private boolean mHasDynamicPartialTypes = false;
+    private PersistableBundle mExtras = null;
 
     private static final int STATE_VERIFY_AUTO         = 0x00000001;
     private static final int STATE_NEED_VERIFY         = 0x00000010;
@@ -507,6 +522,9 @@
         if (o.mMimeGroups != null) {
             mMimeGroups = new ArrayList<String>(o.mMimeGroups);
         }
+        if (o.mExtras != null) {
+            mExtras = new PersistableBundle(o.mExtras);
+        }
         mHasStaticPartialTypes = o.mHasStaticPartialTypes;
         mHasDynamicPartialTypes = o.mHasDynamicPartialTypes;
         mVerifyState = o.mVerifyState;
@@ -1768,6 +1786,423 @@
     }
 
     /**
+     * Match this filter against an Intent's extras. An intent must
+     * have all the extras specified by the filter with the same values,
+     * for the match to succeed.
+     *
+     * <p> An intent con have other extras in addition to those specified
+     * by the filter and it would not affect whether the match succeeds or not.
+     *
+     * @param extras The extras included in the intent, as returned by
+     *               Intent.getExtras().
+     *
+     * @return If all extras match (success), null; else the name of the
+     *         first extra that didn't match.
+     *
+     * @hide
+     */
+    private String matchExtras(@Nullable Bundle extras) {
+        if (mExtras == null) {
+            return null;
+        }
+        final Set<String> keys = mExtras.keySet();
+        for (String key : keys) {
+            if (extras == null) {
+                return key;
+            }
+            final Object value = mExtras.get(key);
+            final Object otherValue = extras.get(key);
+            if (otherValue == null || value.getClass() != otherValue.getClass()
+                    || !Objects.deepEquals(value, otherValue)) {
+                return key;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, int value) {
+        Objects.requireNonNull(name);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putInt(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, int)} or
+     *         {@code 0} if no value has been set.
+     * @hide
+     */
+    public final int getIntExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? 0 : mExtras.getInt(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull int[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putIntArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, int[])} or
+     *         an empty int array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final int[] getIntArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_INT_ARRAY : mExtras.getIntArray(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, long value) {
+        Objects.requireNonNull(name);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putLong(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, long)} or
+     *         {@code 0L} if no value has been set.
+     * @hide
+     */
+    public final long getLongExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? 0L : mExtras.getLong(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull long[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putLongArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, long[])} or
+     *         an empty long array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final long[] getLongArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_LONG_ARRAY : mExtras.getLongArray(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, double value) {
+        Objects.requireNonNull(name);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putDouble(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, double)} or
+     *         {@code 0.0} if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final double getDoubleExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? 0.0 : mExtras.getDouble(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull double[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putDoubleArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, double[])} or
+     *         an empty double array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final double[] getDoubleArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_DOUBLE_ARRAY : mExtras.getDoubleArray(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull String value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putString(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, String)} or a
+     *         {@code null} if no value has been set.
+     * @hide
+     */
+    @Nullable
+    public final String getStringExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? null : mExtras.getString(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull String[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putStringArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, String[])} or
+     *         an empty string array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final String[] getStringArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_STRING_ARRAY : mExtras.getStringArray(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, boolean value) {
+        Objects.requireNonNull(name);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putBoolean(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, boolean)} or
+     *         {@code false} if no value has been set.
+     * @hide
+     */
+    public final boolean getBooleanExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? false : mExtras.getBoolean(name);
+    }
+
+    /**
+     * Add a new extra name and value to match against. If an extra is included in the filter,
+     * then an Intent must have an extra with the same {@code name} and {@code value} for it to
+     * match.
+     *
+     * <p> Subsequent calls to this method with the same {@code name} will override any previously
+     * set {@code value}.
+     *
+     * @param name the name of the extra to match against.
+     * @param value the value of the extra to match against.
+     *
+     * @hide
+     */
+    public final void addExtra(@NonNull String name, @NonNull boolean[] value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        if (mExtras == null) {
+            mExtras = new PersistableBundle();
+        }
+        mExtras.putBooleanArray(name, value);
+    }
+
+    /**
+     * Return the value of the extra with {@code name} included in the filter.
+     *
+     * @return the value that was previously set using {@link #addExtra(String, boolean[])} or
+     *         an empty boolean array if no value has been set.
+     * @hide
+     */
+    @NonNull
+    public final boolean[] getBooleanArrayExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? EMPTY_BOOLEAN_ARRAY : mExtras.getBooleanArray(name);
+    }
+
+    /**
+     * Returns whether or not an extra with {@code name} is included in the filter.
+     *
+     * @return {@code true} if an extra with {@code name} is included in the filter.
+     *         Otherwise {@code false}.
+     * @hide
+     */
+    public final boolean hasExtra(@NonNull String name) {
+        Objects.requireNonNull(name);
+        return mExtras == null ? false : mExtras.containsKey(name);
+    }
+
+    /**
+     * Set the intent extras to match against. For this filter to match an
+     * intent, it must contain all the {@code extras} set.
+     *
+     * <p> Subsequent calls to this method  will override any previously set extras.
+     *
+     * @param extras The intent extras to match against.
+     * @hide
+     */
+    @SystemApi
+    public final void setExtras(@NonNull PersistableBundle extras) {
+        mExtras = extras;
+    }
+
+    /**
+     * Return the intent extras included in this filter.
+     *
+     * @return the extras that were previously set using {@link #setExtras(PersistableBundle)} or
+     *         an empty {@link PersistableBundle} object if no extras were set.
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public final PersistableBundle getExtras() {
+        return mExtras == null ? new PersistableBundle() : mExtras;
+    }
+
+    /**
      * Return a {@link Predicate} which tests whether this filter matches the
      * given <var>intent</var>.
      * <p>
@@ -1818,7 +2253,9 @@
             boolean resolve, String logTag) {
         String type = resolve ? intent.resolveType(resolver) : intent.getType();
         return match(intent.getAction(), type, intent.getScheme(),
-                     intent.getData(), intent.getCategories(), logTag);
+                     intent.getData(), intent.getCategories(), logTag,
+                     false /* supportWildcards */, null /* ignoreActions */,
+                     intent.getExtras());
     }
 
     /**
@@ -1868,6 +2305,19 @@
     public final int match(String action, String type, String scheme,
             Uri data, Set<String> categories, String logTag, boolean supportWildcards,
             @Nullable Collection<String> ignoreActions) {
+        return match(action, type, scheme, data, categories, logTag, supportWildcards,
+                ignoreActions, null /* extras */);
+    }
+
+    /**
+     * Variant of {@link #match(String, String, String, Uri, Set, String, boolean, Collection)}
+     * that supports matching the extra values in the intent.
+     *
+     * @hide
+     */
+    public final int match(String action, String type, String scheme,
+            Uri data, Set<String> categories, String logTag, boolean supportWildcards,
+            @Nullable Collection<String> ignoreActions, @Nullable Bundle extras) {
         if (action != null && !matchAction(action, supportWildcards, ignoreActions)) {
             if (false) Log.v(
                 logTag, "No matching action " + action + " for " + this);
@@ -1897,6 +2347,14 @@
             return NO_MATCH_CATEGORY;
         }
 
+        String extraMismatch = matchExtras(extras);
+        if (extraMismatch != null) {
+            if (false) {
+                Log.v(logTag, "Mismatch for extra key " + extraMismatch + " for " + this);
+            }
+            return NO_MATCH_EXTRAS;
+        }
+
         // It would be nice to treat container activities as more
         // important than ones that can be embedded, but this is not the way...
         if (false) {
@@ -1998,6 +2456,15 @@
             }
             serializer.endTag(null, PATH_STR);
         }
+        if (mExtras != null) {
+            serializer.startTag(null, EXTRAS_STR);
+            try {
+                mExtras.saveToXml(serializer);
+            } catch (XmlPullParserException e) {
+                throw new IllegalStateException("Failed to write extras: " + mExtras.toString(), e);
+            }
+            serializer.endTag(null, EXTRAS_STR);
+        }
     }
 
     /**
@@ -2124,6 +2591,8 @@
                 } else if ((path=parser.getAttributeValue(null, SUFFIX_STR)) != null) {
                     addDataPath(path, PatternMatcher.PATTERN_SUFFIX);
                 }
+            } else if (tagName.equals(EXTRAS_STR)) {
+                mExtras = PersistableBundle.restoreFromXml(parser);
             } else {
                 Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
             }
@@ -2187,6 +2656,9 @@
             proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, hasPartialTypes());
         }
         proto.write(IntentFilterProto.GET_AUTO_VERIFY, getAutoVerify());
+        if (mExtras != null) {
+            mExtras.dumpDebug(proto, IntentFilterProto.EXTRAS);
+        }
         proto.end(token);
     }
 
@@ -2297,6 +2769,11 @@
             sb.append(prefix); sb.append("AutoVerify="); sb.append(getAutoVerify());
             du.println(sb.toString());
         }
+        if (mExtras != null) {
+            sb.setLength(0);
+            sb.append(prefix); sb.append("mExtras="); sb.append(mExtras.toString());
+            du.println(sb.toString());
+        }
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<IntentFilter> CREATOR
@@ -2379,6 +2856,12 @@
         dest.writeInt(getAutoVerify() ? 1 : 0);
         dest.writeInt(mInstantAppVisibility);
         dest.writeInt(mOrder);
+        if (mExtras != null) {
+            dest.writeInt(1);
+            mExtras.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     /**
@@ -2459,6 +2942,9 @@
         setAutoVerify(source.readInt() > 0);
         setVisibilityToInstantApp(source.readInt());
         mOrder = source.readInt();
+        if (source.readInt() != 0) {
+            mExtras = PersistableBundle.CREATOR.createFromParcel(source);
+        }
     }
 
     private boolean hasPartialTypes() {
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 93d4def..a41401b 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -47,4 +47,5 @@
     oneway void addLocalColorsAreas(in List<RectF> regions);
     SurfaceControl mirrorSurfaceControl();
     oneway void applyDimming(float dimAmount);
+    oneway void setWallpaperFlags(int which);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 007478a..aebd91a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -166,6 +166,7 @@
     private static final int MSG_SCALE_PREVIEW = 10110;
     private static final int MSG_REPORT_SHOWN = 10150;
     private static final int MSG_UPDATE_DIMMING = 10200;
+    private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210;
     private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
             Float.NEGATIVE_INFINITY);
 
@@ -486,6 +487,13 @@
         }
 
         /**
+         * Returns the current wallpaper flags indicating which screen this Engine is rendering to.
+         */
+        @SetWallpaperFlags public int getWallpaperFlags() {
+            return mIWallpaperEngine.mWhich;
+        }
+
+        /**
          * Convenience for {@link WallpaperManager#getDesiredMinimumWidth()
          * WallpaperManager.getDesiredMinimumWidth()}, returning the width
          * that the system would like this wallpaper to run in.
@@ -795,6 +803,16 @@
         }
 
         /**
+         * Called when the current wallpaper flags change.
+         *
+         * @param which The new flag value
+         * @see #getWallpaperFlags()
+         */
+        @MainThread
+        public void onWallpaperFlagsChanged(@SetWallpaperFlags int which) {
+        }
+
+        /**
          * Called when the zoom level of the wallpaper changed.
          * This method will be called with the initial zoom level when the surface is created.
          *
@@ -2195,11 +2213,12 @@
         private final AtomicBoolean mDetached = new AtomicBoolean();
 
         Engine mEngine;
+        @SetWallpaperFlags int mWhich;
 
         IWallpaperEngineWrapper(WallpaperService context,
                 IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
-                int displayId) {
+                int displayId, @SetWallpaperFlags int which) {
             mWallpaperManager = getSystemService(WallpaperManager.class);
             mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
             mConnection = conn;
@@ -2210,6 +2229,7 @@
             mReqHeight = reqHeight;
             mDisplayPadding.set(padding);
             mDisplayId = displayId;
+            mWhich = which;
 
             // Create a display context before onCreateEngine.
             mDisplayManager = getSystemService(DisplayManager.class);
@@ -2240,6 +2260,16 @@
         }
 
         @Override
+        public void setWallpaperFlags(@SetWallpaperFlags int which) {
+            if (which == mWhich) {
+                return;
+            }
+            mWhich = which;
+            Message msg = mCaller.obtainMessageI(MSG_WALLPAPER_FLAGS_CHANGED, which);
+            mCaller.sendMessage(msg);
+        }
+
+        @Override
         public void setInAmbientMode(boolean inAmbientDisplay, long animationDuration)
                 throws RemoteException {
             Message msg = mCaller.obtainMessageIO(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0,
@@ -2459,6 +2489,9 @@
                     reportShown();
                     Trace.endSection();
                 } break;
+                case MSG_WALLPAPER_FLAGS_CHANGED: {
+                    mEngine.onWallpaperFlagsChanged(message.arg1);
+                } break;
                 default :
                     Log.w(TAG, "Unknown message type " + message.what);
             }
@@ -2483,7 +2516,7 @@
                 int displayId, @SetWallpaperFlags int which) {
             Trace.beginSection("WPMS.ServiceWrapper.attach");
             mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
-                    windowType, isPreview, reqWidth, reqHeight, padding, displayId);
+                    windowType, isPreview, reqWidth, reqHeight, padding, displayId, which);
             Trace.endSection();
         }
 
diff --git a/core/java/com/android/internal/protolog/BaseProtoLogImpl.java b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
index 83309fc..cd55d32 100644
--- a/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
@@ -32,6 +32,7 @@
 import android.annotation.Nullable;
 import android.os.ShellCommand;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -44,7 +45,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.IllegalFormatConversionException;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
 
@@ -108,10 +108,14 @@
             messageString = mViewerConfig.getViewerString(messageHash);
         }
         if (messageString != null) {
-            try {
-                message = String.format(messageString, args);
-            } catch (IllegalFormatConversionException ex) {
-                Slog.w(TAG, "Invalid ProtoLog format string.", ex);
+            if (args != null) {
+                try {
+                    message = TextUtils.formatSimple(messageString, args);
+                } catch (Exception ex) {
+                    Slog.w(TAG, "Invalid ProtoLog format string.", ex);
+                }
+            } else {
+                message = messageString;
             }
         }
         if (message == null) {
diff --git a/core/java/com/android/internal/protolog/common/LogDataType.java b/core/java/com/android/internal/protolog/common/LogDataType.java
index 651932a..c05824a 100644
--- a/core/java/com/android/internal/protolog/common/LogDataType.java
+++ b/core/java/com/android/internal/protolog/common/LogDataType.java
@@ -74,13 +74,10 @@
                         types.add(LogDataType.BOOLEAN);
                         break;
                     case 'd':
-                    case 'o':
                     case 'x':
                         types.add(LogDataType.LONG);
                         break;
                     case 'f':
-                    case 'e':
-                    case 'g':
                         types.add(LogDataType.DOUBLE);
                         break;
                     case 's':
diff --git a/core/java/com/android/internal/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java
index 01cc1ed..93765cd 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLog.java
@@ -22,11 +22,12 @@
  * a messageString, which is a format string for the log message (has to be a string literal or
  * a concatenation of string literals) and a vararg array of parameters for the formatter.
  *
- * The syntax for the message string is a subset of {@code java.util.Formatter} syntax.
+ * The syntax for the message string depends on
+ * {@link android.text.TextUtils#formatSimple(String, Object...)}}.
  * Supported conversions:
  * %b - boolean
- * %d, %o and %x - integral type (Short, Integer or Long)
- * %f, %e and %g - floating point type (Float or Double)
+ * %d %x - integral type (Short, Integer or Long)
+ * %f - floating point type (Float or Double)
  * %s - string
  * %% - a literal percent character
  * The width and precision modifiers are supported, argument_index and flags are not.
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
index 26e7dbb..75e2908 100644
--- a/core/proto/android/content/intent.proto
+++ b/core/proto/android/content/intent.proto
@@ -21,6 +21,7 @@
 
 import "frameworks/base/core/proto/android/content/component_name.proto";
 import "frameworks/base/core/proto/android/os/patternmatcher.proto";
+import "frameworks/base/core/proto/android/os/persistablebundle.proto";
 import "frameworks/base/core/proto/android/privacy.proto";
 
 // Next Tag: 14
@@ -87,6 +88,7 @@
     optional bool has_partial_types = 9;
     optional bool get_auto_verify = 10;
     repeated string mime_groups = 11;
+    optional android.os.PersistableBundleProto extras = 12;
 }
 
 message AuthorityEntryProto {
diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
index 4664ec7..fac3a0e 100644
--- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -19,6 +19,8 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
@@ -48,15 +50,11 @@
         mPackageName = mContext.getPackageName();
 
         // Reset the Game Mode for the test app, since it persists across invocations.
-        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_UNSUPPORTED);
+        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
     }
 
     @Test
     public void testPublicApiGameModeGetterSetter() {
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                mGameManager.getGameMode());
-
-        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
         assertEquals(GameManager.GAME_MODE_STANDARD,
                 mGameManager.getGameMode());
 
@@ -71,10 +69,6 @@
 
     @Test
     public void testPrivilegedGameModeGetterSetter() {
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                mGameManager.getGameMode(mPackageName));
-
-        mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD);
         assertEquals(GameManager.GAME_MODE_STANDARD,
                 mGameManager.getGameMode(mPackageName));
 
@@ -86,4 +80,30 @@
         assertEquals(GameManager.GAME_MODE_CUSTOM,
                 mGameManager.getGameMode(mPackageName));
     }
+
+    @Test
+    public void testUpdateCustomGameModeConfiguration() {
+        GameModeInfo gameModeInfo = mGameManager.getGameModeInfo(mPackageName);
+        assertNotNull(gameModeInfo);
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
+        GameModeConfiguration unsupportedFpsConfig =
+                new GameModeConfiguration.Builder().setFpsOverride(
+                        70).setScalingFactor(0.5f).build();
+        mGameManager.updateCustomGameModeConfiguration(mPackageName, unsupportedFpsConfig);
+        gameModeInfo = mGameManager.getGameModeInfo(mPackageName);
+        assertNotNull(gameModeInfo);
+        // TODO(b/243448953): update to non-zero FPS when matching is implemented
+        assertEquals(new GameModeConfiguration.Builder().setFpsOverride(
+                        GameModeConfiguration.FPS_OVERRIDE_NONE).setScalingFactor(0.5f).build(),
+                gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
+
+        GameModeConfiguration supportedFpsConfig =
+                new GameModeConfiguration.Builder().setFpsOverride(
+                        60).setScalingFactor(0.5f).build();
+        mGameManager.updateCustomGameModeConfiguration(mPackageName, supportedFpsConfig);
+        gameModeInfo = mGameManager.getGameModeInfo(mPackageName);
+        assertNotNull(gameModeInfo);
+        assertEquals(supportedFpsConfig,
+                gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
+    }
 }
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
new file mode 100644
index 0000000..7462bcf
--- /dev/null
+++ b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class GameModeConfigurationTest {
+    @Test
+    public void testEqualsAndHashCode() {
+        GameModeConfiguration config = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).setFpsOverride(10).build();
+        assertTrue(config.equals(config));
+
+        GameModeConfiguration config1 = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).setFpsOverride(10).build();
+        assertTrue(config.equals(config1));
+        assertEquals(config.hashCode(), config1.hashCode());
+
+        GameModeConfiguration config2 = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).build();
+        assertFalse(config.equals(config2));
+        assertNotEquals(config.hashCode(), config2.hashCode());
+
+        GameModeConfiguration config3 = new GameModeConfiguration.Builder()
+                .setFpsOverride(10).build();
+        assertFalse(config.equals(config3));
+        assertNotEquals(config.hashCode(), config3.hashCode());
+        assertFalse(config2.equals(config3));
+        assertNotEquals(config2.hashCode(), config3.hashCode());
+
+        GameModeConfiguration config4 = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.2f).setFpsOverride(10).build();
+        assertFalse(config.equals(config4));
+        assertNotEquals(config.hashCode(), config4.hashCode());
+
+        GameModeConfiguration config5 = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).setFpsOverride(30).build();
+        assertFalse(config.equals(config5));
+        assertNotEquals(config.hashCode(), config5.hashCode());
+
+        GameModeConfiguration config6 = new GameModeConfiguration.Builder()
+                .build();
+        assertFalse(config.equals(config6));
+        assertNotEquals(config.hashCode(), config6.hashCode());
+        assertFalse(config2.equals(config6));
+        assertNotEquals(config2.hashCode(), config6.hashCode());
+        assertFalse(config3.equals(config6));
+        assertNotEquals(config3.hashCode(), config6.hashCode());
+    }
+
+    @Test
+    public void testToBuilder() {
+        GameModeConfiguration config = new GameModeConfiguration
+                .Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
+        GameModeConfiguration newConfig = config.toBuilder().build();
+        assertEquals(config, newConfig);
+    }
+
+    @Test
+    public void testGetters() {
+        GameModeConfiguration config = new GameModeConfiguration.Builder()
+                .setScalingFactor(0.5f).setFpsOverride(10).build();
+        assertEquals(0.5f, config.getScalingFactor(), 0.01f);
+        assertEquals(10, config.getFpsOverride());
+    }
+}
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
new file mode 100644
index 0000000..ecd9b6b8
--- /dev/null
+++ b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link android.app.GameModeInfo}.
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class GameModeInfoTest {
+    @Test
+    public void testParcelable() {
+        int activeGameMode = GameManager.GAME_MODE_PERFORMANCE;
+        int[] availableGameModes =
+                new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_PERFORMANCE,
+                        GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_CUSTOM};
+        int[] optedInGameModes = new int[]{GameManager.GAME_MODE_PERFORMANCE};
+        GameModeConfiguration batteryConfig = new GameModeConfiguration
+                .Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
+        GameModeConfiguration performanceConfig = new GameModeConfiguration
+                .Builder().setFpsOverride(90).setScalingFactor(0.9f).build();
+        GameModeInfo gameModeInfo = new GameModeInfo.Builder()
+                .setActiveGameMode(activeGameMode)
+                .setAvailableGameModes(availableGameModes)
+                .setOptedInGameModes(optedInGameModes)
+                .setDownscalingAllowed(true)
+                .setFpsOverrideAllowed(false)
+                .setGameModeConfiguration(GameManager.GAME_MODE_BATTERY, batteryConfig)
+                .setGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE, performanceConfig)
+                .build();
+
+        assertArrayEquals(availableGameModes, gameModeInfo.getAvailableGameModes());
+        assertArrayEquals(optedInGameModes, gameModeInfo.getOptedInGameModes());
+        assertEquals(activeGameMode, gameModeInfo.getActiveGameMode());
+        assertTrue(gameModeInfo.isDownscalingAllowed());
+        assertFalse(gameModeInfo.isFpsOverrideAllowed());
+        assertEquals(performanceConfig,
+                gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+        assertEquals(batteryConfig,
+                gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+
+        Parcel parcel = Parcel.obtain();
+        gameModeInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GameModeInfo newGameModeInfo = new GameModeInfo(parcel);
+        assertEquals(gameModeInfo.getActiveGameMode(), newGameModeInfo.getActiveGameMode());
+        assertArrayEquals(gameModeInfo.getAvailableGameModes(),
+                newGameModeInfo.getAvailableGameModes());
+        assertArrayEquals(gameModeInfo.getOptedInGameModes(),
+                newGameModeInfo.getOptedInGameModes());
+        assertTrue(newGameModeInfo.isDownscalingAllowed());
+        assertFalse(newGameModeInfo.isFpsOverrideAllowed());
+        assertEquals(performanceConfig,
+                newGameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+        assertEquals(batteryConfig,
+                newGameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 215308d..00b9fced 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -30,6 +30,8 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.wm.shell.transition.Transitions;
+
 /**
  * Wrapper to handle the ActivityEmbedding animation update in one
  * {@link SurfaceControl.Transaction}.
@@ -50,6 +52,16 @@
     /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
     @NonNull
     private final Rect mWholeAnimationBounds = new Rect();
+    /**
+     * Area in absolute coordinate that should represent all the content to show for this window.
+     * This should be the end bounds for opening window, and start bounds for closing window in case
+     * the window is resizing during the open/close transition.
+     */
+    @NonNull
+    private final Rect mContentBounds = new Rect();
+    /** Offset relative to the window parent surface for {@link #mContentBounds}. */
+    @NonNull
+    private final Point mContentRelOffset = new Point();
 
     @NonNull
     final Transformation mTransformation = new Transformation();
@@ -80,6 +92,21 @@
         mChange = change;
         mLeash = leash;
         mWholeAnimationBounds.set(wholeAnimationBounds);
+        if (Transitions.isClosingType(change.getMode())) {
+            // When it is closing, we want to show the content at the start position in case the
+            // window is resizing as well. For example, when the activities is changing from split
+            // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
+            final Rect startBounds = change.getStartAbsBounds();
+            final Rect endBounds = change.getEndAbsBounds();
+            mContentBounds.set(startBounds);
+            mContentRelOffset.set(change.getEndRelOffset());
+            mContentRelOffset.offset(
+                    startBounds.left - endBounds.left,
+                    startBounds.top - endBounds.top);
+        } else {
+            mContentBounds.set(change.getEndAbsBounds());
+            mContentRelOffset.set(change.getEndRelOffset());
+        }
     }
 
     /**
@@ -110,8 +137,7 @@
     /** To be overridden by subclasses to adjust the animation surface change. */
     void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
         // Update the surface position and alpha.
-        final Point offset = mChange.getEndRelOffset();
-        mTransformation.getMatrix().postTranslate(offset.x, offset.y);
+        mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
         t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
         t.setAlpha(mLeash, mTransformation.getAlpha());
 
@@ -119,8 +145,8 @@
         // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
         final int positionX = Math.round(mMatrix[MTRANS_X]);
         final int positionY = Math.round(mMatrix[MTRANS_Y]);
-        final Rect cropRect = new Rect(mChange.getEndAbsBounds());
-        cropRect.offset(positionX - offset.x, positionY - offset.y);
+        final Rect cropRect = new Rect(mContentBounds);
+        cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
 
         // Store the current offset of the surface top left from (0,0) in absolute coordinate.
         final int offsetX = cropRect.left;
@@ -133,7 +159,7 @@
         } else if (mAnimation.hasExtension()) {
             // Allow the surface to be shown in its original bounds in case we want to use edge
             // extensions.
-            cropRect.union(mChange.getEndAbsBounds());
+            cropRect.union(mContentBounds);
         }
 
         // cropRect is in absolute coordinate, so we need to translate it to surface top left.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 921861a..c0a6456 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -304,6 +304,7 @@
         // This is because the TaskFragment surface/change won't contain the Activity's before its
         // reparent.
         Animation changeAnimation = null;
+        Rect parentBounds = new Rect();
         for (TransitionInfo.Change change : info.getChanges()) {
             if (change.getMode() != TRANSIT_CHANGE
                     || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -326,10 +327,15 @@
                 }
             }
 
+            // The TaskFragment may be enter/exit split, so we take the union of both as the parent
+            // size.
+            parentBounds.union(boundsAnimationChange.getStartAbsBounds());
+            parentBounds.union(boundsAnimationChange.getEndAbsBounds());
+
             // There are two animations in the array. The first one is for the start leash
             // (snapshot), and the second one is for the end leash (TaskFragment).
             final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
-                    boundsAnimationChange.getEndAbsBounds());
+                    parentBounds);
             // Keep track as we might need to add background color for the animation.
             // Although there may be multiple change animation, record one of them is sufficient
             // because the background color will be added to the root leash for the whole animation.
@@ -352,6 +358,11 @@
                     animations[1], boundsAnimationChange));
         }
 
+        if (parentBounds.isEmpty()) {
+            throw new IllegalStateException(
+                    "There should be at least one changing window to play the change animation");
+        }
+
         // If there is no corresponding open/close window with the change, we should show background
         // color to cover the empty part of the screen.
         boolean shouldShouldBackgroundColor = true;
@@ -368,10 +379,10 @@
                 // No-op if it will be covered by the changing parent window.
                 animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
             } else if (Transitions.isClosingType(change.getMode())) {
-                animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
+                animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
                 shouldShouldBackgroundColor = false;
             } else {
-                animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
+                animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
                 shouldShouldBackgroundColor = false;
             }
             adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 2bb7369..65a7d09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -21,7 +21,6 @@
 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
 
 import android.content.Context;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -80,15 +79,25 @@
 
     /** Animation for window that is opening in a change transition. */
     @NonNull
-    Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change) {
+    Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
+            @NonNull Rect parentBounds) {
+        // Use end bounds for opening.
         final Rect bounds = change.getEndAbsBounds();
-        final Point offset = change.getEndRelOffset();
-        // The window will be animated in from left or right depends on its position.
-        final int startLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+        final int startLeft;
+        final int startTop;
+        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+            // The window will be animated in from left or right depends on its position.
+            startTop = 0;
+            startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+        } else {
+            // The window will be animated in from top or bottom depends on its position.
+            startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+            startLeft = 0;
+        }
 
         // The position should be 0-based as we will post translate in
         // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
-        final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+        final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
         animation.setInterpolator(mFastOutExtraSlowInInterpolator);
         animation.setDuration(CHANGE_ANIMATION_DURATION);
         animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -98,15 +107,25 @@
 
     /** Animation for window that is closing in a change transition. */
     @NonNull
-    Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change) {
-        final Rect bounds = change.getEndAbsBounds();
-        final Point offset = change.getEndRelOffset();
-        // The window will be animated out to left or right depends on its position.
-        final int endLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+    Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change,
+            @NonNull Rect parentBounds) {
+        // Use start bounds for closing.
+        final Rect bounds = change.getStartAbsBounds();
+        final int endTop;
+        final int endLeft;
+        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+            // The window will be animated out to left or right depends on its position.
+            endTop = 0;
+            endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+        } else {
+            // The window will be animated out to top or bottom depends on its position.
+            endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+            endLeft = 0;
+        }
 
         // The position should be 0-based as we will post translate in
         // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
-        final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+        final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
         animation.setInterpolator(mFastOutExtraSlowInInterpolator);
         animation.setDuration(CHANGE_ANIMATION_DURATION);
         animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 161ea25..a28ea32 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -305,7 +305,7 @@
             currentSystemRoutes = mMediaRouterService.getSystemRoutes();
             currentSystemSessionInfo = mMediaRouterService.getSystemSessionInfo();
         } catch (RemoteException ex) {
-            Log.e(TAG, "Unable to get current system's routes / session info", ex);
+            ex.rethrowFromSystemServer();
         }
 
         if (currentSystemRoutes == null || currentSystemRoutes.isEmpty()) {
@@ -407,14 +407,14 @@
                     mMediaRouterService.registerRouter2(stub, mPackageName);
                     mStub = stub;
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "registerRouteCallback: Unable to register MediaRouter2.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
             if (mStub != null && updateDiscoveryPreferenceIfNeededLocked()) {
                 try {
                     mMediaRouterService.setDiscoveryRequestWithRouter2(mStub, mDiscoveryPreference);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "registerRouteCallback: Unable to set discovery request.", ex);
+                    ex.rethrowFromSystemServer();
                 }
             }
         }
@@ -454,7 +454,7 @@
                 try {
                     mMediaRouterService.unregisterRouter2(mStub);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to unregister media router.", ex);
+                    ex.rethrowFromSystemServer();
                 }
                 mStub = null;
             }
@@ -1769,7 +1769,7 @@
                     try {
                         mMediaRouterService.releaseSessionWithRouter2(mStub, getId());
                     } catch (RemoteException ex) {
-                        Log.e(TAG, "Unable to release session", ex);
+                        ex.rethrowFromSystemServer();
                     }
                 }
 
@@ -1787,7 +1787,7 @@
                     try {
                         mMediaRouterService.unregisterRouter2(mStub);
                     } catch (RemoteException ex) {
-                        Log.e(TAG, "releaseInternal: Unable to unregister media router.", ex);
+                        ex.rethrowFromSystemServer();
                     }
                     mStub = null;
                 }
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index 9f5bfd4..f6458c2 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -41,6 +41,7 @@
         "androidx.lifecycle_lifecycle-livedata",
         "androidx.lifecycle_lifecycle-extensions",
         "androidx.recyclerview_recyclerview",
+        "androidx-constraintlayout_constraintlayout",
         "androidx.appcompat_appcompat",
     ],
 
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_app_streaming.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable-night/ic_apps.xml
rename to packages/CompanionDeviceManager/res/drawable-night/ic_permission_app_streaming.xml
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_calendar.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_calendar.xml
new file mode 100644
index 0000000..d7ea3a2
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_calendar.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M5,22Q4.175,22 3.587,21.413Q3,20.825 3,20V6Q3,5.175 3.587,4.588Q4.175,4 5,4H6V2H8V4H16V2H18V4H19Q19.825,4 20.413,4.588Q21,5.175 21,6V20Q21,20.825 20.413,21.413Q19.825,22 19,22ZM5,20H19Q19,20 19,20Q19,20 19,20V10H5V20Q5,20 5,20Q5,20 5,20Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_contacts.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_contacts.xml
new file mode 100644
index 0000000..41e4044
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_contacts.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M4,23V21H20V23ZM4,3V1H20V3ZM12,13Q13.25,13 14.125,12.125Q15,11.25 15,10Q15,8.75 14.125,7.875Q13.25,7 12,7Q10.75,7 9.875,7.875Q9,8.75 9,10Q9,11.25 9.875,12.125Q10.75,13 12,13ZM4,20Q3.175,20 2.588,19.413Q2,18.825 2,18V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20Q20.825,4 21.413,4.588Q22,5.175 22,6V18Q22,18.825 21.413,19.413Q20.825,20 20,20ZM5.75,18Q6.875,16.6 8.475,15.8Q10.075,15 12,15Q13.925,15 15.525,15.8Q17.125,16.6 18.25,18H20Q20,18 20,18Q20,18 20,18V6Q20,6 20,6Q20,6 20,6H4Q4,6 4,6Q4,6 4,6V18Q4,18 4,18Q4,18 4,18ZM8.7,18H15.3Q14.575,17.5 13.738,17.25Q12.9,17 12,17Q11.1,17 10.263,17.25Q9.425,17.5 8.7,18ZM12,11Q11.575,11 11.288,10.712Q11,10.425 11,10Q11,9.575 11.288,9.287Q11.575,9 12,9Q12.425,9 12.713,9.287Q13,9.575 13,10Q13,10.425 12.713,10.712Q12.425,11 12,11ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
new file mode 100644
index 0000000..1611861
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path android:fillColor="@android:color/system_accent1_200"
+          android:pathData="M12,16.4 L7.6,12 12,7.6 16.4,12ZM13.4,21.375Q13.125,21.65 12.75,21.8Q12.375,21.95 12,21.95Q11.625,21.95 11.25,21.8Q10.875,21.65 10.6,21.375L2.625,13.4Q2.35,13.125 2.2,12.75Q2.05,12.375 2.05,12Q2.05,11.625 2.2,11.25Q2.35,10.875 2.625,10.6L10.575,2.65Q10.875,2.35 11.238,2.2Q11.6,2.05 12,2.05Q12.4,2.05 12.762,2.2Q13.125,2.35 13.425,2.65L21.375,10.6Q21.65,10.875 21.8,11.25Q21.95,11.625 21.95,12Q21.95,12.375 21.8,12.75Q21.65,13.125 21.375,13.4ZM12,19.2 L19.2,12Q19.2,12 19.2,12Q19.2,12 19.2,12L12,4.8Q12,4.8 12,4.8Q12,4.8 12,4.8L4.8,12Q4.8,12 4.8,12Q4.8,12 4.8,12L12,19.2Q12,19.2 12,19.2Q12,19.2 12,19.2Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_notifications.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable-night/ic_notifications.xml
rename to packages/CompanionDeviceManager/res/drawable-night/ic_permission_notifications.xml
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_phone.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_phone.xml
new file mode 100644
index 0000000..49467ed
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_phone.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M19.95,21Q16.725,21 13.663,19.562Q10.6,18.125 8.238,15.762Q5.875,13.4 4.438,10.337Q3,7.275 3,4.05Q3,3.6 3.3,3.3Q3.6,3 4.05,3H8.1Q8.45,3 8.725,3.225Q9,3.45 9.05,3.8L9.7,7.3Q9.75,7.65 9.688,7.937Q9.625,8.225 9.4,8.45L7,10.9Q8.05,12.7 9.625,14.275Q11.2,15.85 13.1,17L15.45,14.65Q15.675,14.425 16.038,14.312Q16.4,14.2 16.75,14.25L20.2,14.95Q20.55,15.025 20.775,15.287Q21,15.55 21,15.9V19.95Q21,20.4 20.7,20.7Q20.4,21 19.95,21Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_sms.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_sms.xml
new file mode 100644
index 0000000..859c06f
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_sms.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_200">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M8,11Q8.425,11 8.713,10.712Q9,10.425 9,10Q9,9.575 8.713,9.287Q8.425,9 8,9Q7.575,9 7.287,9.287Q7,9.575 7,10Q7,10.425 7.287,10.712Q7.575,11 8,11ZM12,11Q12.425,11 12.713,10.712Q13,10.425 13,10Q13,9.575 12.713,9.287Q12.425,9 12,9Q11.575,9 11.288,9.287Q11,9.575 11,10Q11,10.425 11.288,10.712Q11.575,11 12,11ZM16,11Q16.425,11 16.712,10.712Q17,10.425 17,10Q17,9.575 16.712,9.287Q16.425,9 16,9Q15.575,9 15.288,9.287Q15,9.575 15,10Q15,10.425 15.288,10.712Q15.575,11 16,11ZM2,22V4Q2,3.175 2.588,2.587Q3.175,2 4,2H20Q20.825,2 21.413,2.587Q22,3.175 22,4V16Q22,16.825 21.413,17.413Q20.825,18 20,18H6ZM4,16H20Q20,16 20,16Q20,16 20,16V4Q20,4 20,4Q20,4 20,4H4Q4,4 4,4Q4,4 4,4V16ZM4,16V4Q4,4 4,4Q4,4 4,4Q4,4 4,4Q4,4 4,4V16Q4,16 4,16Q4,16 4,16Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_storage.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable-night/ic_storage.xml
rename to packages/CompanionDeviceManager/res/drawable-night/ic_permission_storage.xml
diff --git a/packages/CompanionDeviceManager/res/drawable/btn_expand_less.xml b/packages/CompanionDeviceManager/res/drawable/btn_expand_less.xml
new file mode 100644
index 0000000..99db560
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/btn_expand_less.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_neutral1_500">
+    <path android:fillColor="@android:color/white" android:pathData="M7.4,15.05 L6.35,13.975 12,8.325 17.65,13.975 16.6,15.05 12,10.45Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/btn_expand_more.xml b/packages/CompanionDeviceManager/res/drawable/btn_expand_more.xml
new file mode 100644
index 0000000..8518cfa
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/btn_expand_more.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_neutral1_500">
+    <path android:fillColor="@android:color/white" android:pathData="M12,15.05 L6.35,9.375 7.4,8.325 12,12.925 16.6,8.325 17.65,9.375Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml b/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
index 2a8eb24..15f6987 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
@@ -15,7 +15,8 @@
   ~ limitations under the License.
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp" android:height="24dp"
+        android:width="24dp"
+        android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
         android:tint="?attr/colorControlNormal">
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_apps.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_app_streaming.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable/ic_apps.xml
rename to packages/CompanionDeviceManager/res/drawable/ic_permission_app_streaming.xml
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_calendar.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_calendar.xml
new file mode 100644
index 0000000..3dc53e7
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_calendar.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path android:fillColor="@android:color/white" android:pathData="M5,22Q4.175,22 3.587,21.413Q3,20.825 3,20V6Q3,5.175 3.587,4.588Q4.175,4 5,4H6V2H8V4H16V2H18V4H19Q19.825,4 20.413,4.588Q21,5.175 21,6V20Q21,20.825 20.413,21.413Q19.825,22 19,22ZM5,20H19Q19,20 19,20Q19,20 19,20V10H5V20Q5,20 5,20Q5,20 5,20Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_contacts.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_contacts.xml
new file mode 100644
index 0000000..2dfda8d
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_contacts.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M4,23V21H20V23ZM4,3V1H20V3ZM12,13Q13.25,13 14.125,12.125Q15,11.25 15,10Q15,8.75 14.125,7.875Q13.25,7 12,7Q10.75,7 9.875,7.875Q9,8.75 9,10Q9,11.25 9.875,12.125Q10.75,13 12,13ZM4,20Q3.175,20 2.588,19.413Q2,18.825 2,18V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20Q20.825,4 21.413,4.588Q22,5.175 22,6V18Q22,18.825 21.413,19.413Q20.825,20 20,20ZM5.75,18Q6.875,16.6 8.475,15.8Q10.075,15 12,15Q13.925,15 15.525,15.8Q17.125,16.6 18.25,18H20Q20,18 20,18Q20,18 20,18V6Q20,6 20,6Q20,6 20,6H4Q4,6 4,6Q4,6 4,6V18Q4,18 4,18Q4,18 4,18ZM8.7,18H15.3Q14.575,17.5 13.738,17.25Q12.9,17 12,17Q11.1,17 10.263,17.25Q9.425,17.5 8.7,18ZM12,11Q11.575,11 11.288,10.712Q11,10.425 11,10Q11,9.575 11.288,9.287Q11.575,9 12,9Q12.425,9 12.713,9.287Q13,9.575 13,10Q13,10.425 12.713,10.712Q12.425,11 12,11ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_devices.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_devices.xml
new file mode 100644
index 0000000..49a6fe3
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_nearby_devices.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path android:fillColor="@android:color/system_accent1_600"
+          android:pathData="M12,16.4 L7.6,12 12,7.6 16.4,12ZM13.4,21.375Q13.125,21.65 12.75,21.8Q12.375,21.95 12,21.95Q11.625,21.95 11.25,21.8Q10.875,21.65 10.6,21.375L2.625,13.4Q2.35,13.125 2.2,12.75Q2.05,12.375 2.05,12Q2.05,11.625 2.2,11.25Q2.35,10.875 2.625,10.6L10.575,2.65Q10.875,2.35 11.238,2.2Q11.6,2.05 12,2.05Q12.4,2.05 12.762,2.2Q13.125,2.35 13.425,2.65L21.375,10.6Q21.65,10.875 21.8,11.25Q21.95,11.625 21.95,12Q21.95,12.375 21.8,12.75Q21.65,13.125 21.375,13.4ZM12,19.2 L19.2,12Q19.2,12 19.2,12Q19.2,12 19.2,12L12,4.8Q12,4.8 12,4.8Q12,4.8 12,4.8L4.8,12Q4.8,12 4.8,12Q4.8,12 4.8,12L12,19.2Q12,19.2 12,19.2Q12,19.2 12,19.2Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_notifications.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable/ic_notifications.xml
rename to packages/CompanionDeviceManager/res/drawable/ic_permission_notifications.xml
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_phone.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_phone.xml
new file mode 100644
index 0000000..cc1c5b5
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_phone.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path android:fillColor="@android:color/white" android:pathData="M19.95,21Q16.725,21 13.663,19.562Q10.6,18.125 8.238,15.762Q5.875,13.4 4.438,10.337Q3,7.275 3,4.05Q3,3.6 3.3,3.3Q3.6,3 4.05,3H8.1Q8.45,3 8.725,3.225Q9,3.45 9.05,3.8L9.7,7.3Q9.75,7.65 9.688,7.937Q9.625,8.225 9.4,8.45L7,10.9Q8.05,12.7 9.625,14.275Q11.2,15.85 13.1,17L15.45,14.65Q15.675,14.425 16.038,14.312Q16.4,14.2 16.75,14.25L20.2,14.95Q20.55,15.025 20.775,15.287Q21,15.55 21,15.9V19.95Q21,20.4 20.7,20.7Q20.4,21 19.95,21Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_sms.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_sms.xml
new file mode 100644
index 0000000..7f76a60
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_sms.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M8,11Q8.425,11 8.713,10.712Q9,10.425 9,10Q9,9.575 8.713,9.287Q8.425,9 8,9Q7.575,9 7.287,9.287Q7,9.575 7,10Q7,10.425 7.287,10.712Q7.575,11 8,11ZM12,11Q12.425,11 12.713,10.712Q13,10.425 13,10Q13,9.575 12.713,9.287Q12.425,9 12,9Q11.575,9 11.288,9.287Q11,9.575 11,10Q11,10.425 11.288,10.712Q11.575,11 12,11ZM16,11Q16.425,11 16.712,10.712Q17,10.425 17,10Q17,9.575 16.712,9.287Q16.425,9 16,9Q15.575,9 15.288,9.287Q15,9.575 15,10Q15,10.425 15.288,10.712Q15.575,11 16,11ZM2,22V4Q2,3.175 2.588,2.587Q3.175,2 4,2H20Q20.825,2 21.413,2.587Q22,3.175 22,4V16Q22,16.825 21.413,17.413Q20.825,18 20,18H6ZM4,16H20Q20,16 20,16Q20,16 20,16V4Q20,4 20,4Q20,4 20,4H4Q4,4 4,4Q4,4 4,4V16ZM4,16V4Q4,4 4,4Q4,4 4,4Q4,4 4,4Q4,4 4,4V16Q4,16 4,16Q4,16 4,16Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_storage.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_storage.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable/ic_storage.xml
rename to packages/CompanionDeviceManager/res/drawable/ic_permission_storage.xml
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_watch.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_watch.xml
new file mode 100644
index 0000000..dd247ee
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_watch.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path android:fillColor="@android:color/white" android:pathData="M9,22 L7.65,17.45Q6.45,16.5 5.725,15.075Q5,13.65 5,12Q5,10.35 5.725,8.925Q6.45,7.5 7.65,6.55L9,2H15L16.35,6.55Q17.55,7.5 18.275,8.925Q19,10.35 19,12Q19,13.65 18.275,15.075Q17.55,16.5 16.35,17.45L15,22ZM12,17Q14.075,17 15.538,15.537Q17,14.075 17,12Q17,9.925 15.538,8.462Q14.075,7 12,7Q9.925,7 8.463,8.462Q7,9.925 7,12Q7,14.075 8.463,15.537Q9.925,17 12,17Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 520ade8..22805f6 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -57,36 +57,39 @@
                 android:layout_height="0dp"
                 android:layout_weight="1">
 
-                <LinearLayout
-                    android:id="@+id/multiple_device_list"
+                <androidx.constraintlayout.widget.ConstraintLayout
+                    xmlns:app="http://schemas.android.com/apk/res-auto"
+                    android:id="@+id/constraint_list"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_marginTop="12dp"
-                    android:layout_marginBottom="12dp"
-                    android:orientation="vertical"
                     android:visibility="gone">
 
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/device_list"
+                        android:layout_width="match_parent"
+                        android:layout_height="200dp"
+                        android:scrollbars="vertical"
+                        android:visibility="gone" />
+
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/permission_list"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:scrollbars="vertical"
+                        android:fadeScrollbars="false"
+                        app:layout_constraintHeight_max="220dp"
+                        android:visibility="gone" />
+
                     <View
                         android:id="@+id/border_top"
                         style="@style/DeviceListBorder" />
 
-                    <androidx.recyclerview.widget.RecyclerView
-                        android:id="@+id/device_list"
-                        android:layout_width="match_parent"
-                        android:scrollbars="vertical"
-                        android:layout_marginBottom="12dp"
-                        android:layout_height="200dp" />
-
                     <View
                         android:id="@+id/border_bottom"
+                        app:layout_constraintBottom_toBottomOf="parent"
                         style="@style/DeviceListBorder" />
 
-                </LinearLayout>
-
-                <androidx.recyclerview.widget.RecyclerView
-                    android:id="@+id/permission_list"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
+                </androidx.constraintlayout.widget.ConstraintLayout>
 
                 <ProgressBar
                     android:id="@+id/spinner_multiple_device"
@@ -132,6 +135,8 @@
                     style="@style/NegativeButtonMultipleDevices"
                     android:textColor="?android:textColorPrimary"
                     android:visibility="gone"
+                    android:layout_marginTop="12dp"
+                    android:layout_marginBottom="12dp"
                     android:text="@string/consent_no" />
             </LinearLayout>
 
diff --git a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
index 1f922b9..ddff2cb 100644
--- a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
@@ -46,6 +46,7 @@
                 android:layout_height="wrap_content"
                 android:gravity="center"
                 android:textColor="?android:attr/textColorPrimary"
+                style="@style/TextAppearance"
                 android:textSize="22sp" />
 
             <TextView
@@ -58,6 +59,7 @@
                 android:layout_marginBottom="32dp"
                 android:gravity="center"
                 android:textColor="?android:attr/textColorSecondary"
+                style="@style/TextAppearance"
                 android:textSize="14sp" />
 
             <LinearLayout
@@ -70,6 +72,7 @@
                 <Button
                     android:id="@+id/btn_back"
                     style="@style/VendorHelperBackButton"
+                    android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
                     android:text="@string/consent_back" />
 
             </LinearLayout>
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index d4439f9..ac5294a 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -39,6 +39,6 @@
         android:layout_height="wrap_content"
         android:paddingStart="24dp"
         android:paddingEnd="24dp"
-        android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
+        style="@style/TextAppearance"/>
 
 </LinearLayout>
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index a3d71b9..ab2d815 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -34,26 +34,43 @@
         android:contentDescription="@null"/>
 
     <LinearLayout
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
         android:gravity="center_vertical"
-        android:padding="6dp">
+        android:padding="6dp"
+        android:layout_weight="1">
 
         <TextView
             android:id="@+id/permission_name"
-            android:layout_width="match_parent"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textSize="16sp"
-            android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
+            android:layout_marginTop="2dp"
+            style="@style/TextAppearance"
+            android:textColor="?android:attr/textColorPrimary"/>
 
         <TextView
             android:id="@+id/permission_summary"
-            android:layout_width="match_parent"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textSize="14sp"
+            android:layout_marginTop="2dp"
+            style="@style/TextAppearance"
             android:textColor="?android:attr/textColorSecondary"/>
 
     </LinearLayout>
 
+    <ImageButton
+        android:id="@+id/permission_expand_button"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:src="@drawable/btn_expand_more"
+        android:layout_marginTop="8dp"
+        android:layout_marginStart="24dp"
+        android:background="@android:color/transparent"
+        android:clickable="false"
+        android:importantForAccessibility="no"
+        android:contentDescription="@null"/>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 83dbbf3..97201e2 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -31,16 +31,13 @@
     <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%2$s</xliff:g>&lt;/strong&gt;</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
+    <string name="summary_watch">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
+    <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
 
     <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
 
-    <!-- Apps permission will be granted of APP_STREAMING profile [CHAR LIMIT=30] -->
-    <string name="permission_apps">Apps</string>
-
-    <!-- Description of apps permission of APP_STREAMING profile [CHAR LIMIT=NONE] -->
-    <string name="permission_apps_summary">Stream your phone\u2019s apps</string>
-
     <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
     <string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone</string>
 
@@ -66,18 +63,6 @@
     <!-- Description of the privileges the application will get if associated with the companion device of COMPUTER profile (type) [CHAR LIMIT=NONE] -->
     <string name="summary_computer"></string>
 
-    <!-- Notification permission will be granted of COMPUTER profile [CHAR LIMIT=30] -->
-    <string name="permission_notification">Notifications</string>
-
-    <!-- Description of notification permission of COMPUTER profile [CHAR LIMIT=NONE] -->
-    <string name="permission_notification_summary">Can read all notifications, including information like contacts, messages, and photos</string>
-
-    <!-- Storage permission will be granted of COMPUTER profile [CHAR LIMIT=30] -->
-    <string name="permission_storage">Photos and media</string>
-
-    <!-- Description of storage permission of COMPUTER profile [CHAR LIMIT=NONE] -->
-    <string name="permission_storage_summary"></string>
-
     <!-- Title of the helper dialog for COMPUTER profile [CHAR LIMIT=30]. -->
     <string name="helper_title_computer">Google Play services</string>
 
@@ -117,4 +102,57 @@
     <!--Description for information icon [CHAR LIMIT=30]-->
     <string name="vendor_header_button_description">More Information Button</string>
 
+    <!-- ================= Permissions ================= -->
+
+    <!-- Phone permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_phone">Phone</string>
+
+    <!-- SMS permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_sms">SMS</string>
+
+    <!-- Contacts permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_contacts">Contacts</string>
+
+    <!-- Calendar permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_calendar">Calendar</string>
+
+    <!-- Calendar permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_nearby_devices">Nearby devices</string>
+
+    <!-- Storage permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_storage">Photos and media</string>
+
+    <!-- Notification permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_notification">Notifications</string>
+
+    <!-- Apps permission will be granted of corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_app_streaming">Apps</string>
+
+    <!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_phone_summary">Can access your phone number and network info. Required for making calls and VoIP, voicemail, call redirect, and editing call logs</string>
+
+    <!-- Description of SMS permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <!-- TODO(b/253644212) Need the description for sms permission  -->
+    <string name="permission_sms_summary"></string>
+
+    <!-- Description of contacts permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_contacts_summary">Can read, create, or edit our contact list, as well as access the list of all accounts used on your device</string>
+
+    <!-- Description of calendar permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <!-- TODO(b/253644212) Need the description for calendar permission  -->
+    <string name="permission_calendar_summary"></string>
+
+    <!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <!-- TODO(b/253644212) Need the description for nearby devices' permission  -->
+    <string name="permission_nearby_devices_summary"></string>
+
+    <!-- Description of notification permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_notification_summary">Can read all notifications, including information like contacts, messages, and photos</string>
+
+    <!-- Description of app streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_app_streaming_summary">Stream your phone\u2019s apps</string>
+
+    <!-- Description of storage permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_storage_summary"></string>
+
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 2000d96..3c75cd5 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -36,7 +36,7 @@
     </style>
 
     <style name="DescriptionTitle"
-           parent="@*android:style/TextAppearance.Widget.Toolbar.Title">
+           parent="@android:style/TextAppearance.DeviceDefault.Medium">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:gravity">center</item>
@@ -46,7 +46,8 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
-    <style name="DescriptionSummary">
+    <style name="DescriptionSummary"
+           parent="@android:style/TextAppearance.DeviceDefault.Medium">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginTop">18dp</item>
@@ -61,6 +62,7 @@
         <item name="android:layout_width">70dp</item>
         <item name="android:layout_height">48dp</item>
         <item name="android:textAllCaps">false</item>
+        <item name="android:textSize">14sp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
         <item name="android:background">@drawable/helper_back_button</item>
     </style>
@@ -73,6 +75,7 @@
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
+        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
         <item name="android:background">@drawable/btn_positive_bottom</item>
     </style>
 
@@ -85,6 +88,7 @@
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
         <item name="android:layout_marginTop">4dp</item>
+        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
         <item name="android:background">@drawable/btn_negative_top</item>
     </style>
 
@@ -93,13 +97,17 @@
         <item name="android:layout_width">100dp</item>
         <item name="android:layout_height">36dp</item>
         <item name="android:textAllCaps">false</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
         <item name="android:background">@drawable/btn_negative_multiple_devices</item>
     </style>
 
     <style name="DeviceListBorder">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">1dp</item>
-        <item name="android:background">@android:color/system_accent1_300</item>
+        <item name="android:layout_marginStart">32dp</item>
+        <item name="android:layout_marginEnd">32dp</item>
+        <item name="android:background">@android:color/system_neutral1_200</item>
     </style>
 
     <style name="Spinner"
@@ -115,4 +123,9 @@
         <item name="android:fillViewport">true</item>
         <item name="android:clipChildren">false</item>
     </style>
+
+    <style name="TextAppearance">
+        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 723c59a..3a3a5d2 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -31,9 +31,14 @@
 
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState;
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
-import static com.android.companiondevicemanager.PermissionListAdapter.TYPE_APPS;
-import static com.android.companiondevicemanager.PermissionListAdapter.TYPE_NOTIFICATION;
-import static com.android.companiondevicemanager.PermissionListAdapter.TYPE_STORAGE;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NOTIFICATION;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_PHONE;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_SMS;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_STORAGE;
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
@@ -55,6 +60,9 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.net.MacAddress;
 import android.os.Bundle;
@@ -64,6 +72,7 @@
 import android.text.Spanned;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
@@ -72,12 +81,14 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -140,8 +151,14 @@
     // Present for multiple devices' association requests only.
     private Button mButtonNotAllowMultipleDevices;
 
+    // Present for top and bottom borders for permissions list and device list.
+    private View mBorderTop;
+    private View mBorderBottom;
+
     private LinearLayout mAssociationConfirmationDialog;
-    private LinearLayout mMultipleDeviceList;
+    // Contains device list, permission list and top/bottom borders.
+    private ConstraintLayout mConstraintList;
+    // Only present for self-managed association requests.
     private RelativeLayout mVendorHeader;
 
     // The recycler view is only shown for multiple-device regular association request, after
@@ -150,7 +167,7 @@
     private @Nullable DeviceListAdapter mDeviceAdapter;
 
 
-    // The recycler view is only shown for selfManaged association request.
+    // The recycler view is only shown for selfManaged and singleDevice  association request.
     private @Nullable RecyclerView mPermissionListRecyclerView;
     private @Nullable PermissionListAdapter mPermissionListAdapter;
 
@@ -164,6 +181,8 @@
 
     private @Nullable List<Integer> mPermissionTypes;
 
+    private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         if (DEBUG) Log.d(TAG, "onCreate()");
@@ -212,7 +231,6 @@
         boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
 
         if (forCancelDialog) {
-
             Log.i(TAG, "Cancelling the user confirmation");
 
             cancel(/* discoveryTimeOut */ false,
@@ -285,10 +303,13 @@
 
         setContentView(R.layout.activity_confirmation);
 
-        mMultipleDeviceList = findViewById(R.id.multiple_device_list);
+        mConstraintList = findViewById(R.id.constraint_list);
         mAssociationConfirmationDialog = findViewById(R.id.association_confirmation);
         mVendorHeader = findViewById(R.id.vendor_header);
 
+        mBorderTop = findViewById(R.id.border_top);
+        mBorderBottom = findViewById(R.id.border_bottom);
+
         mTitle = findViewById(R.id.title);
         mSummary = findViewById(R.id.summary);
 
@@ -463,10 +484,11 @@
             return;
         }
 
+        // TODO(b/253644212): Add maps for profile -> title, summary, permissions
         switch (deviceProfile) {
             case DEVICE_PROFILE_APP_STREAMING:
                 title = getHtmlFromResources(this, R.string.title_app_streaming, deviceName);
-                mPermissionTypes.add(TYPE_APPS);
+                mPermissionTypes.add(PERMISSION_APP_STREAMING);
                 break;
 
             case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
@@ -476,25 +498,27 @@
 
             case DEVICE_PROFILE_COMPUTER:
                 title = getHtmlFromResources(this, R.string.title_computer, deviceName);
-                mPermissionTypes.add(TYPE_NOTIFICATION);
-                mPermissionTypes.add(TYPE_STORAGE);
+                mPermissionTypes.addAll(Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_STORAGE));
                 break;
 
             default:
                 throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
 
+        // Summary is not needed for selfManaged dialog.
         mSummary.setVisibility(View.GONE);
 
-        mPermissionListAdapter.setPermissionType(mPermissionTypes);
-        mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
-        mPermissionListRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+        setupPermissionList();
 
         mTitle.setText(title);
         mVendorHeaderName.setText(vendorName);
-        mDeviceListRecyclerView.setVisibility(View.GONE);
-        mProfileIcon.setVisibility(View.GONE);
         mVendorHeader.setVisibility(View.VISIBLE);
+        mVendorHeader.setVisibility(View.VISIBLE);
+        mProfileIcon.setVisibility(View.GONE);
+        mDeviceListRecyclerView.setVisibility(View.GONE);
+        // Top and bottom borders should be gone for selfManaged dialog.
+        mBorderTop.setVisibility(View.GONE);
+        mBorderBottom.setVisibility(View.GONE);
     }
 
     private void initUiForSingleDevice(CharSequence appLabel) {
@@ -502,11 +526,15 @@
 
         final String deviceProfile = mRequest.getDeviceProfile();
 
+        mPermissionTypes = new ArrayList<>();
+
         CompanionDeviceDiscoveryService.getScanResult().observe(this,
                 deviceFilterPairs -> updateSingleDeviceUi(
                         deviceFilterPairs, deviceProfile, appLabel));
 
         mSingleDeviceSpinner.setVisibility(View.VISIBLE);
+        // Hide permission list and confirmation dialog first before the
+        // first matched device is found.
         mPermissionListRecyclerView.setVisibility(View.GONE);
         mDeviceListRecyclerView.setVisibility(View.GONE);
         mAssociationConfirmationDialog.setVisibility(View.GONE);
@@ -537,11 +565,20 @@
             title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, deviceName);
             summary = getHtmlFromResources(this, R.string.summary_generic);
             profileIcon = getIcon(this, R.drawable.ic_device_other);
+            // Summary is not needed for null profile.
             mSummary.setVisibility(View.GONE);
+            mConstraintList.setVisibility(View.GONE);
         } else if (deviceProfile.equals(DEVICE_PROFILE_WATCH)) {
-            title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, profileName);
-            summary = getHtmlFromResources(this, R.string.summary_watch, deviceName, appLabel);
+            title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, deviceName);
+            summary = getHtmlFromResources(
+                    this, R.string.summary_watch_single_device, profileName, appLabel);
             profileIcon = getIcon(this, R.drawable.ic_watch);
+
+            mPermissionTypes.addAll(Arrays.asList(
+                    PERMISSION_NOTIFICATION, PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS,
+                    PERMISSION_CALENDAR, PERMISSION_NEARBY_DEVICES));
+
+            setupPermissionList();
         } else {
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
@@ -596,8 +633,9 @@
         // "Remove" consent button: users would need to click on the list item.
         mButtonAllow.setVisibility(View.GONE);
         mButtonNotAllow.setVisibility(View.GONE);
+        mDeviceListRecyclerView.setVisibility(View.VISIBLE);
         mButtonNotAllowMultipleDevices.setVisibility(View.VISIBLE);
-        mMultipleDeviceList.setVisibility(View.VISIBLE);
+        mConstraintList.setVisibility(View.VISIBLE);
         mMultipleDeviceSpinner.setVisibility(View.VISIBLE);
     }
 
@@ -654,6 +692,80 @@
         return mApproved || mCancelled;
     }
 
+    // Set up the mPermissionListRecyclerView, including set up the adapter,
+    // initiate the layoutManager for the recyclerview, add listeners for monitoring the scrolling
+    // and when mPermissionListRecyclerView is fully populated.
+    // Lastly, disable the Allow and Don't allow buttons.
+    private void setupPermissionList() {
+        mPermissionListAdapter.setPermissionType(mPermissionTypes);
+        mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
+        mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
+
+        disableButtons();
+
+        // Enable buttons once users scroll down to the bottom of the permission list.
+        mPermissionListRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                super.onScrollStateChanged(recyclerView, newState);
+                if (!recyclerView.canScrollVertically(1)) {
+                    enableButtons();
+                }
+            }
+        });
+        // Enable buttons if last item in the permission list is visible to the users when
+        // mPermissionListRecyclerView is fully populated.
+        mPermissionListRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        LinearLayoutManager layoutManager =
+                                (LinearLayoutManager) mPermissionListRecyclerView
+                                        .getLayoutManager();
+                        int lastVisibleItemPosition =
+                                layoutManager.findLastCompletelyVisibleItemPosition();
+                        int numItems = mPermissionListRecyclerView.getAdapter().getItemCount();
+
+                        if (lastVisibleItemPosition >= numItems - 1) {
+                            enableButtons();
+                        }
+
+                        mPermissionListRecyclerView.getViewTreeObserver()
+                                .removeOnGlobalLayoutListener(this);
+                    }
+                });
+
+        mConstraintList.setVisibility(View.VISIBLE);
+        mPermissionListRecyclerView.setVisibility(View.VISIBLE);
+    }
+
+    // Disable and grey out the Allow and Don't allow buttons if the last permission in the
+    // permission list is not visible to the users.
+    private void disableButtons() {
+        mButtonAllow.setEnabled(false);
+        mButtonNotAllow.setEnabled(false);
+        mButtonAllow.setTextColor(
+                getResources().getColor(android.R.color.system_neutral1_400, null));
+        mButtonNotAllow.setTextColor(
+                getResources().getColor(android.R.color.system_neutral1_400, null));
+        mButtonAllow.getBackground().setColorFilter(
+                (new BlendModeColorFilter(Color.LTGRAY,  BlendMode.DARKEN)));
+        mButtonNotAllow.getBackground().setColorFilter(
+                (new BlendModeColorFilter(Color.LTGRAY,  BlendMode.DARKEN)));
+    }
+    // Enable and restore the color for the Allow and Don't allow buttons if the last permission in
+    // the permission list is visible to the users.
+    private void enableButtons() {
+        mButtonAllow.setEnabled(true);
+        mButtonNotAllow.setEnabled(true);
+        mButtonAllow.getBackground().setColorFilter(null);
+        mButtonNotAllow.getBackground().setColorFilter(null);
+        mButtonAllow.setTextColor(
+                getResources().getColor(android.R.color.system_neutral1_900, null));
+        mButtonNotAllow.setTextColor(
+                getResources().getColor(android.R.color.system_neutral1_900, null));
+    }
+
     private final ResultReceiver mOnAssociationCreatedReceiver =
             new ResultReceiver(Handler.getMain()) {
                 @Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index 895b729..0ee94a2 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -27,6 +27,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -37,37 +38,58 @@
 
 class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.ViewHolder> {
     private final Context mContext;
-
     private List<Integer> mPermissions;
+    // Add the expand buttons if permissions are more than PERMISSION_SIZE in the permission list.
+    private static final int PERMISSION_SIZE = 2;
 
-    static final int TYPE_NOTIFICATION = 0;
-    static final int TYPE_STORAGE = 1;
-    static final int TYPE_APPS = 2;
+    static final int PERMISSION_NOTIFICATION = 0;
+    static final int PERMISSION_STORAGE = 1;
+    static final int PERMISSION_APP_STREAMING = 2;
+    static final int PERMISSION_PHONE = 3;
+    static final int PERMISSION_SMS = 4;
+    static final int PERMISSION_CONTACTS = 5;
+    static final int PERMISSION_CALENDAR = 6;
+    static final int PERMISSION_NEARBY_DEVICES = 7;
 
     private static final Map<Integer, Integer> sTitleMap;
     static {
         final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(TYPE_NOTIFICATION, R.string.permission_notification);
-        map.put(TYPE_STORAGE, R.string.permission_storage);
-        map.put(TYPE_APPS, R.string.permission_apps);
+        map.put(PERMISSION_NOTIFICATION, R.string.permission_notification);
+        map.put(PERMISSION_STORAGE, R.string.permission_storage);
+        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming);
+        map.put(PERMISSION_PHONE, R.string.permission_phone);
+        map.put(PERMISSION_SMS, R.string.permission_sms);
+        map.put(PERMISSION_CONTACTS, R.string.permission_contacts);
+        map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
+        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
         sTitleMap = unmodifiableMap(map);
     }
 
     private static final Map<Integer, Integer> sSummaryMap;
     static {
         final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(TYPE_NOTIFICATION, R.string.permission_notification_summary);
-        map.put(TYPE_STORAGE, R.string.permission_storage_summary);
-        map.put(TYPE_APPS, R.string.permission_apps_summary);
+        map.put(PERMISSION_NOTIFICATION, R.string.permission_notification_summary);
+        map.put(PERMISSION_STORAGE, R.string.permission_storage_summary);
+        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary);
+        map.put(PERMISSION_PHONE, R.string.permission_phone_summary);
+        map.put(PERMISSION_SMS, R.string.permission_sms_summary);
+        map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary);
+        map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary);
+        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
         sSummaryMap = unmodifiableMap(map);
     }
 
     private static final Map<Integer, Integer> sIconMap;
     static {
         final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(TYPE_NOTIFICATION, R.drawable.ic_notifications);
-        map.put(TYPE_STORAGE, R.drawable.ic_storage);
-        map.put(TYPE_APPS, R.drawable.ic_apps);
+        map.put(PERMISSION_NOTIFICATION, R.drawable.ic_permission_notifications);
+        map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage);
+        map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming);
+        map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone);
+        map.put(PERMISSION_SMS, R.drawable.ic_permission_sms);
+        map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts);
+        map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar);
+        map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
         sIconMap = unmodifiableMap(map);
     }
 
@@ -82,6 +104,29 @@
         ViewHolder viewHolder = new ViewHolder(view);
         viewHolder.mPermissionIcon.setImageDrawable(getIcon(mContext, sIconMap.get(viewType)));
 
+        if (viewHolder.mExpandButton.getTag() == null) {
+            viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
+        }
+        // Add expand buttons if the permissions are more than PERMISSION_SIZE in this list.
+        if (mPermissions.size() > PERMISSION_SIZE) {
+            view.setOnClickListener(v -> {
+                if ((Integer) viewHolder.mExpandButton.getTag() == R.drawable.btn_expand_more) {
+                    viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_less);
+
+                    if (viewHolder.mSummary != null) {
+                        viewHolder.mPermissionSummary.setText(viewHolder.mSummary);
+                    }
+
+                    viewHolder.mPermissionSummary.setVisibility(View.VISIBLE);
+                    viewHolder.mExpandButton.setTag(R.drawable.btn_expand_less);
+                } else {
+                    viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_more);
+                    viewHolder.mPermissionSummary.setVisibility(View.GONE);
+                    viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
+                }
+            });
+        }
+
         return viewHolder;
     }
 
@@ -91,8 +136,15 @@
         final Spanned title = getHtmlFromResources(mContext, sTitleMap.get(type));
         final Spanned summary = getHtmlFromResources(mContext, sSummaryMap.get(type));
 
+        holder.mSummary = summary;
         holder.mPermissionName.setText(title);
-        holder.mPermissionSummary.setText(summary);
+
+        if (mPermissions.size() <= PERMISSION_SIZE) {
+            holder.mPermissionSummary.setText(summary);
+            holder.mExpandButton.setVisibility(View.GONE);
+        } else {
+            holder.mPermissionSummary.setVisibility(View.GONE);
+        }
     }
 
     @Override
@@ -114,11 +166,14 @@
         private final TextView mPermissionName;
         private final TextView mPermissionSummary;
         private final ImageView mPermissionIcon;
+        private final ImageButton mExpandButton;
+        private Spanned mSummary = null;
         ViewHolder(View itemView) {
             super(itemView);
             mPermissionName = itemView.findViewById(R.id.permission_name);
             mPermissionSummary = itemView.findViewById(R.id.permission_summary);
             mPermissionIcon = itemView.findViewById(R.id.permission_icon);
+            mExpandButton = itemView.findViewById(R.id.permission_expand_button);
         }
     }
 
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index b8fd579..dea9bf4 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -24,7 +24,7 @@
     }
 }
 plugins {
-    id 'com.android.application' version '7.3.0' apply false
-    id 'com.android.library' version '7.3.0' apply false
+    id 'com.android.application' version '7.3.1' apply false
+    id 'com.android.library' version '7.3.1' apply false
     id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
 }
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index dcfc171..69740058 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -27,13 +27,10 @@
     static_libs: [
         "SpaLib",
         "SpaLibTestUtils",
-        "androidx.test.runner",
-        "androidx.test.ext.junit",
         "androidx.compose.runtime_runtime",
-        "androidx.compose.ui_ui-test-junit4",
-        "androidx.compose.ui_ui-test-manifest",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
         "mockito-target-minus-junit4",
-        "truth-prebuilt",
     ],
     kotlincflags: ["-Xjvm-default=all"],
     min_sdk_version: "31",
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 2d501fc..5971895 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -61,8 +61,5 @@
 dependencies {
     androidTestImplementation project(":spa")
     androidTestImplementation project(":testutils")
-    androidTestImplementation "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
-    androidTestImplementation "com.google.truth:truth:1.1.3"
-    androidTestImplementation "org.mockito:mockito-android:3.4.6"
-    androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
+    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
 }
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 68ad414..0f618fa 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -24,7 +24,10 @@
     srcs: ["src/**/*.kt"],
 
     static_libs: [
-        "mockito-target-minus-junit4",
+        "androidx.compose.ui_ui-test-junit4",
+        "androidx.compose.ui_ui-test-manifest",
+        "mockito",
+        "truth-prebuilt",
     ],
     kotlincflags: [
         "-Xjvm-default=all",
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index 3e50b29..58b4d42 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -47,5 +47,8 @@
 }
 
 dependencies {
-    api "org.mockito:mockito-android:3.4.6"
+    api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
+    api "com.google.truth:truth:1.1.3"
+    api "org.mockito:mockito-core:2.21.0"
+    debugApi "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
 }
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
new file mode 100644
index 0000000..a5d1f40
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.spa.testutils
+
+import androidx.compose.ui.test.ComposeTimeoutException
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.hasAnyAncestor
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isDialog
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+
+/** Blocks until the found a semantics node that match the given condition. */
+fun ComposeContentTestRule.waitUntilExists(matcher: SemanticsMatcher) = waitUntil {
+    onAllNodes(matcher).fetchSemanticsNodes().isNotEmpty()
+}
+
+/** Blocks until the timeout is reached. */
+fun ComposeContentTestRule.delay(timeoutMillis: Long = 1_000) = try {
+    waitUntil(timeoutMillis) { false }
+} catch (_: ComposeTimeoutException) {
+    // Expected
+}
+
+/** Finds a text node that within dialog. */
+fun ComposeContentTestRule.onDialogText(text: String): SemanticsNodeInteraction =
+    onNode(hasAnyAncestor(isDialog()) and hasText(text))
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index 5cd74e3..5fa4f77 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -32,11 +32,8 @@
 
     static_libs: [
         "SpaLibTestUtils",
-        "androidx.compose.ui_ui-test-junit4",
-        "androidx.compose.ui_ui-test-manifest",
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "mockito-target-minus-junit4",
-        "truth-prebuilt",
     ],
 }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 785a1d4..4cf09ab 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1377,6 +1377,9 @@
     <!-- QR Code Scanner label, title [CHAR LIMIT=32] -->
     <string name="qr_code_scanner_title">QR code scanner</string>
 
+    <!-- QR Code Scanner Secondary label when GMS Core is Updating -->
+    <string name="qr_code_scanner_updating_secondary_label">Updating</string>
+
     <!-- Name of the work status bar icon. -->
     <string name="status_bar_work">Work profile</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 1a24af1..6d50b56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -117,6 +117,10 @@
         state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
         state.state = mQRCodeScannerController.isAbleToOpenCameraApp() ? Tile.STATE_INACTIVE
                 : Tile.STATE_UNAVAILABLE;
+        // The assumption is that if the OEM has the QR code scanner module enabled then the scanner
+        // would go to "Unavailable" state only when GMS core is updating.
+        state.secondaryLabel = state.state == Tile.STATE_UNAVAILABLE
+                ? mContext.getString(R.string.qr_code_scanner_updating_secondary_label) : null;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index eefceb7..5efd460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -241,6 +241,8 @@
 import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
@@ -251,8 +253,6 @@
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import dagger.Lazy;
-
 /**
  * A class handling initialization and coordination between some of the key central surfaces in
  * System UI: The notification shade, the keyguard (lockscreen), and the status bar.
@@ -4204,7 +4204,7 @@
                 Log.wtf(TAG, "WallpaperManager not supported");
                 return;
             }
-            WallpaperInfo info = mWallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
+            WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser(UserHandle.USER_CURRENT);
             mWallpaperController.onWallpaperInfoUpdated(info);
 
             final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index 6a3f785..a1be2f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -18,6 +18,7 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
 
 import static org.mockito.Mockito.when;
 
@@ -112,6 +113,8 @@
         QSTile.State state = new QSTile.State();
         mTile.handleUpdateState(state, null);
         assertEquals(state.state, Tile.STATE_UNAVAILABLE);
+        assertEquals(state.secondaryLabel.toString(),
+                     mContext.getString(R.string.qr_code_scanner_updating_secondary_label));
     }
 
     @Test
@@ -120,5 +123,6 @@
         QSTile.State state = new QSTile.State();
         mTile.handleUpdateState(state, null);
         assertEquals(state.state, Tile.STATE_INACTIVE);
+        assertNull(state.secondaryLabel);
     }
 }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 7476317..a2755be 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1649,7 +1649,7 @@
 
         final long identityToken = clearCallingIdentity();
         try {
-            return getAuthenticatorTypesInternal(userId);
+            return getAuthenticatorTypesInternal(userId, callingUid);
 
         } finally {
             restoreCallingIdentity(identityToken);
@@ -1659,19 +1659,19 @@
     /**
      * Should only be called inside of a clearCallingIdentity block.
      */
-    private AuthenticatorDescription[] getAuthenticatorTypesInternal(int userId) {
+    private AuthenticatorDescription[] getAuthenticatorTypesInternal(int userId, int callingUid) {
         mAuthenticatorCache.updateServices(userId);
         Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
                 authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
-        AuthenticatorDescription[] types =
-                new AuthenticatorDescription[authenticatorCollection.size()];
-        int i = 0;
+        final List<AuthenticatorDescription> types =
+                new ArrayList<>(authenticatorCollection.size());
         for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
                 : authenticatorCollection) {
-            types[i] = authenticator.type;
-            i++;
+            if (canCallerAccessPackage(authenticator.type.packageName, callingUid, userId)) {
+                types.add(authenticator.type);
+            }
         }
-        return types;
+        return types.toArray(new AuthenticatorDescription[types.size()]);
     }
 
     private boolean isCrossUser(int callingUid, int userId) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 319b5aa..1534e63 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -6112,7 +6112,7 @@
             TimeoutRecord timeoutRecord = TimeoutRecord.forServiceStartWithEndTime(annotation,
                     SystemClock.uptimeMillis());
 
-            timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded();
+            timeoutRecord.mLatencyTracker.waitingOnAMSLockStarted();
             synchronized (mAm) {
                 timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded();
                 if (!r.fgRequired || !r.fgWaiting || r.destroying) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f180ffa..c779ea9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3491,9 +3491,6 @@
             }
 
             final File tracesDir = new File(ANR_TRACE_DIR);
-            // Each set of ANR traces is written to a separate file and dumpstate will process
-            // all such files and add them to a captured bug report if they're recent enough.
-            maybePruneOldTraces(tracesDir);
 
             // NOTE: We should consider creating the file in native code atomically once we've
             // gotten rid of the old scheme of dumping and lot of the code that deals with paths
@@ -3526,6 +3523,9 @@
             if (firstPidEndOffset != null) {
                 firstPidEndOffset.set(firstPidEndPos);
             }
+            // Each set of ANR traces is written to a separate file and dumpstate will process
+            // all such files and add them to a captured bug report if they're recent enough.
+            maybePruneOldTraces(tracesDir);
 
             return tracesFile;
         } finally {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 84d7442..100b2db 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -37,6 +37,7 @@
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
@@ -870,14 +871,34 @@
         }
     }
 
-    public boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
-        final String key = (options != null) ? options.getDeliveryGroupKey() : null;
-        final String otherKey = (other.options != null)
-                ? other.options.getDeliveryGroupKey() : null;
-        if (key == null && otherKey == null) {
-            return intent.filterEquals(other.intent);
+    boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
+        return matchesDeliveryGroup(this, other);
+    }
+
+    private static boolean matchesDeliveryGroup(@NonNull BroadcastRecord newRecord,
+            @NonNull BroadcastRecord oldRecord) {
+        final String newMatchingKey = getDeliveryGroupMatchingKey(newRecord);
+        final String oldMatchingKey = getDeliveryGroupMatchingKey(oldRecord);
+        final IntentFilter newMatchingFilter = getDeliveryGroupMatchingFilter(newRecord);
+        // If neither delivery group key nor matching filter is specified, then use
+        // Intent.filterEquals() to identify the delivery group.
+        if (newMatchingKey == null && oldMatchingKey == null && newMatchingFilter == null) {
+            return newRecord.intent.filterEquals(oldRecord.intent);
         }
-        return Objects.equals(key, otherKey);
+        if (newMatchingFilter != null && !newMatchingFilter.asPredicate().test(oldRecord.intent)) {
+            return false;
+        }
+        return Objects.equals(newMatchingKey, oldMatchingKey);
+    }
+
+    @Nullable
+    private static String getDeliveryGroupMatchingKey(@NonNull BroadcastRecord record) {
+        return record.options == null ? null : record.options.getDeliveryGroupMatchingKey();
+    }
+
+    @Nullable
+    private static IntentFilter getDeliveryGroupMatchingFilter(@NonNull BroadcastRecord record) {
+        return record.options == null ? null : record.options.getDeliveryGroupMatchingFilter();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 64f2aa3..b92c163 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -35,9 +35,11 @@
 import android.app.GameManager;
 import android.app.GameManager.GameMode;
 import android.app.GameManagerInternal;
+import android.app.GameModeConfiguration;
 import android.app.GameModeInfo;
 import android.app.GameState;
 import android.app.IGameManagerService;
+import android.app.IGameModeListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -57,10 +59,12 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManagerInternal;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserManager;
@@ -131,6 +135,7 @@
     private final Context mContext;
     private final Object mLock = new Object();
     private final Object mDeviceConfigLock = new Object();
+    private final Object mGameModeListenerLock = new Object();
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     final Handler mHandler;
     private final PackageManager mPackageManager;
@@ -144,6 +149,9 @@
     private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
     @GuardedBy("mDeviceConfigLock")
     private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
+    // listener to caller uid map
+    @GuardedBy("mGameModeListenerLock")
+    private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
     @Nullable
     private final GameServiceController mGameServiceController;
 
@@ -399,6 +407,7 @@
     // Turn the raw string to the corresponding fps int.
     // Return 0 when disabling, -1 for invalid fps.
     static int getFpsInt(String raw) {
+        // TODO(b/243448953): make sure this translates to proper values based on current display
         switch (raw) {
             case "30":
                 return FrameRate.FPS_30.fps;
@@ -596,6 +605,14 @@
             }
         }
 
+        // used to check if the override package config has any game mode config, if not, it's
+        // considered empty and safe to delete from settings
+        boolean hasActiveGameModeConfig() {
+            synchronized (mModeConfigLock) {
+                return !mModeConfigs.isEmpty();
+            }
+        }
+
         /**
          * GameModeConfiguration contains all the values for all the interventions associated with
          * a game mode.
@@ -691,10 +708,27 @@
             public boolean isActive() {
                 return (mGameMode == GameManager.GAME_MODE_STANDARD
                         || mGameMode == GameManager.GAME_MODE_PERFORMANCE
-                        || mGameMode == GameManager.GAME_MODE_BATTERY)
+                        || mGameMode == GameManager.GAME_MODE_BATTERY
+                        || mGameMode == GameManager.GAME_MODE_CUSTOM)
                         && !willGamePerformOptimizations(mGameMode);
             }
 
+            android.app.GameModeConfiguration toPublicGameModeConfig() {
+                int fpsOverride = getFpsInt(mFps);
+                // TODO(b/243448953): match to proper value in case of display change?
+                fpsOverride = fpsOverride > 0 ? fpsOverride
+                        : android.app.GameModeConfiguration.FPS_OVERRIDE_NONE;
+                final float scaling = mScaling == DEFAULT_SCALING ? 1.0f : mScaling;
+                return new android.app.GameModeConfiguration.Builder()
+                        .setScalingFactor(scaling)
+                        .setFpsOverride(fpsOverride).build();
+            }
+
+            void updateFromPublicGameModeConfig(android.app.GameModeConfiguration config) {
+                mScaling = config.getScalingFactor();
+                mFps = String.valueOf(config.getFpsOverride());
+            }
+
             /**
              * @hide
              */
@@ -723,7 +757,8 @@
         }
 
         private int getAvailableGameModesBitfield() {
-            int field = 0;
+            int field = modeToBitmask(GameManager.GAME_MODE_CUSTOM)
+                    | modeToBitmask(GameManager.GAME_MODE_STANDARD);
             synchronized (mModeConfigLock) {
                 for (final int mode : mModeConfigs.keySet()) {
                     field |= modeToBitmask(mode);
@@ -735,13 +770,6 @@
             if (mPerfModeOptedIn) {
                 field |= modeToBitmask(GameManager.GAME_MODE_PERFORMANCE);
             }
-            // The lowest bit is reserved for UNSUPPORTED, STANDARD is supported if we support any
-            // other mode.
-            if (field > 1) {
-                field |= modeToBitmask(GameManager.GAME_MODE_STANDARD);
-            } else {
-                field |= modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
-            }
             return field;
         }
 
@@ -763,6 +791,21 @@
         }
 
         /**
+         * Get an array of a package's opted-in game modes.
+         */
+        public @GameMode int[] getOptedInGameModes() {
+            if (mBatteryModeOptedIn && mPerfModeOptedIn) {
+                return new int[]{GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE};
+            } else if (mBatteryModeOptedIn) {
+                return new int[]{GameManager.GAME_MODE_BATTERY};
+            } else if (mPerfModeOptedIn) {
+                return new int[]{GameManager.GAME_MODE_PERFORMANCE};
+            } else {
+                return new int[]{};
+            }
+        }
+
+        /**
          * Get a GameModeConfiguration for a given game mode.
          *
          * @return The package's GameModeConfiguration for the provided mode or null if absent
@@ -848,7 +891,7 @@
     private final class LocalService extends GameManagerInternal {
         @Override
         public float getResolutionScalingFactor(String packageName, int userId) {
-            final int gameMode = getGameModeFromSettings(packageName, userId);
+            final int gameMode = getGameModeFromSettingsUnchecked(packageName, userId);
             return getResolutionScalingFactorInternal(packageName, gameMode, userId);
         }
     }
@@ -927,7 +970,7 @@
             config = mConfigs.get(packageName);
         }
         if (config == null) {
-            return new int[]{};
+            return new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM};
         }
         return config.getAvailableGameModes();
     }
@@ -953,12 +996,13 @@
         return getAvailableGameModesUnchecked(packageName);
     }
 
-    private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) {
+    private @GameMode int getGameModeFromSettingsUnchecked(String packageName,
+            @UserIdInt int userId) {
         synchronized (mLock) {
             if (!mSettings.containsKey(userId)) {
                 Slog.d(TAG, "User ID '" + userId + "' does not have a Game Mode"
-                            + " selected for package: '" + packageName + "'");
-                return GameManager.GAME_MODE_UNSUPPORTED;
+                        + " selected for package: '" + packageName + "'");
+                return GameManager.GAME_MODE_STANDARD;
             }
 
             return mSettings.get(userId).getGameModeLocked(packageName);
@@ -991,12 +1035,12 @@
         // return a value if the package name is valid. Next, check if the caller has the necessary
         // permission and return a value. Do this check last, since it can throw an exception.
         if (isValidPackageName(packageName, userId)) {
-            return getGameModeFromSettings(packageName, userId);
+            return getGameModeFromSettingsUnchecked(packageName, userId);
         }
 
         // Since the package name doesn't match, check the caller has the necessary permission.
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-        return getGameModeFromSettings(packageName, userId);
+        return getGameModeFromSettingsUnchecked(packageName, userId);
     }
 
     /**
@@ -1021,10 +1065,34 @@
             return null;
         }
 
-        final @GameMode int activeGameMode = getGameModeFromSettings(packageName, userId);
-        final @GameMode int[] availableGameModes = getAvailableGameModesUnchecked(packageName);
-
-        return new GameModeInfo(activeGameMode, availableGameModes);
+        final @GameMode int activeGameMode = getGameModeFromSettingsUnchecked(packageName, userId);
+        final GamePackageConfiguration config = getConfig(packageName, userId);
+        if (config != null) {
+            final @GameMode int[] optedInGameModes = config.getOptedInGameModes();
+            final @GameMode int[] availableGameModes = config.getAvailableGameModes();
+            GameModeInfo.Builder gameModeInfoBuilder = new GameModeInfo.Builder()
+                    .setActiveGameMode(activeGameMode)
+                    .setAvailableGameModes(availableGameModes)
+                    .setOptedInGameModes(optedInGameModes)
+                    .setDownscalingAllowed(config.mAllowDownscale)
+                    .setFpsOverrideAllowed(config.mAllowFpsOverride);
+            for (int gameMode : availableGameModes) {
+                if (!config.willGamePerformOptimizations(gameMode)) {
+                    GamePackageConfiguration.GameModeConfiguration gameModeConfig =
+                            config.getGameModeConfiguration(gameMode);
+                    if (gameModeConfig != null) {
+                        gameModeInfoBuilder.setGameModeConfiguration(gameMode,
+                                gameModeConfig.toPublicGameModeConfig());
+                    }
+                }
+            }
+            return gameModeInfoBuilder.build();
+        } else {
+            return new GameModeInfo.Builder()
+                    .setActiveGameMode(activeGameMode)
+                    .setAvailableGameModes(getAvailableGameModesUnchecked(packageName))
+                    .build();
+        }
     }
 
     /**
@@ -1037,11 +1105,11 @@
             throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
 
-        if (!isPackageGame(packageName, userId)) {
-            // Restrict to games only.
+        if (!isPackageGame(packageName, userId) || gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+            // Restrict to games and valid game modes only.
             return;
         }
-
+        int fromGameMode;
         synchronized (mLock) {
             userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, false, true, "setGameMode",
@@ -1053,9 +1121,21 @@
                 return;
             }
             GameManagerSettings userSettings = mSettings.get(userId);
+            fromGameMode = userSettings.getGameModeLocked(packageName);
             userSettings.setGameModeLocked(packageName, gameMode);
         }
         updateInterventions(packageName, gameMode, userId);
+        synchronized (mGameModeListenerLock) {
+            for (IGameModeListener listener : mGameModeListeners.keySet()) {
+                Binder.allowBlocking(listener.asBinder());
+                try {
+                    listener.onGameModeChanged(packageName, fromGameMode, gameMode, userId);
+                } catch (RemoteException ex) {
+                    Slog.w(TAG, "Cannot notify game mode change for listener added by "
+                            + mGameModeListeners.get(listener));
+                }
+            }
+        }
         sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
         sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
                 "SET_GAME_MODE", 0 /*delayMillis*/);
@@ -1237,6 +1317,101 @@
     }
 
     /**
+     * Updates the config for the game's {@link GameManager#GAME_MODE_CUSTOM} mode.
+     *
+     * @throws SecurityException        if caller doesn't have
+     *                                  {@link android.Manifest.permission#MANAGE_GAME_MODE}
+     *                                  permission.
+     * @throws IllegalArgumentException if the user ID provided doesn't exist.
+     */
+    @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public void updateCustomGameModeConfiguration(String packageName,
+            GameModeConfiguration gameModeConfig, int userId)
+            throws SecurityException, IllegalArgumentException {
+        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+        synchronized (mLock) {
+            if (!mSettings.containsKey(userId)) {
+                throw new IllegalArgumentException("User " + userId + " wasn't started");
+            }
+        }
+        // TODO(b/243448953): add validation on gameModeConfig provided
+        // Adding game mode config override of the given package name
+        GamePackageConfiguration configOverride;
+        synchronized (mLock) {
+            if (!mSettings.containsKey(userId)) {
+                return;
+            }
+            final GameManagerSettings settings = mSettings.get(userId);
+            // look for the existing GamePackageConfiguration override
+            configOverride = settings.getConfigOverride(packageName);
+            if (configOverride == null) {
+                configOverride = new GamePackageConfiguration(packageName);
+                settings.setConfigOverride(packageName, configOverride);
+            }
+
+        }
+        GamePackageConfiguration.GameModeConfiguration internalConfig =
+                configOverride.getOrAddDefaultGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
+        internalConfig.updateFromPublicGameModeConfig(gameModeConfig);
+
+        Slog.i(TAG, "Updated custom game mode config for package: " + packageName
+                + " with FPS=" + internalConfig.getFps() + ";Scaling="
+                + internalConfig.getScaling());
+    }
+
+    /**
+     * Adds a game mode listener.
+     *
+     * @throws SecurityException if caller doesn't have
+     *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
+     *                           permission.
+     */
+    @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public void addGameModeListener(@NonNull IGameModeListener listener) {
+        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+        try {
+            final IBinder listenerBinder = listener.asBinder();
+            listenerBinder.linkToDeath(new DeathRecipient() {
+                @Override public void binderDied() {
+                    // TODO(b/258851194): add traces on binder death based listener removal
+                    removeGameModeListenerUnchecked(listener);
+                    listenerBinder.unlinkToDeath(this, 0 /*flags*/);
+                }
+            }, 0 /*flags*/);
+            synchronized (mGameModeListenerLock) {
+                mGameModeListeners.put(listener, Binder.getCallingUid());
+            }
+        } catch (RemoteException ex) {
+            Slog.e(TAG,
+                    "Failed to link death recipient for IGameModeListener from caller "
+                            + Binder.getCallingUid() + ", abandoned its listener registration", ex);
+        }
+    }
+
+    /**
+     * Removes a game mode listener.
+     *
+     * @throws SecurityException if caller doesn't have
+     *                           {@link android.Manifest.permission#MANAGE_GAME_MODE}
+     *                           permission.
+     */
+    @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public void removeGameModeListener(@NonNull IGameModeListener listener) {
+        // TODO(b/258851194): add traces on manual listener removal
+        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+        removeGameModeListenerUnchecked(listener);
+    }
+
+    private void removeGameModeListenerUnchecked(IGameModeListener listener) {
+        synchronized (mGameModeListenerLock) {
+            mGameModeListeners.remove(listener);
+        }
+    }
+
+    /**
      * Notified when boot is completed.
      */
     @VisibleForTesting
@@ -1376,7 +1551,8 @@
         final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
         if (gameMode == GameManager.GAME_MODE_STANDARD
                 || gameMode == GameManager.GAME_MODE_UNSUPPORTED || packageConfig == null
-                || packageConfig.willGamePerformOptimizations(gameMode)) {
+                || packageConfig.willGamePerformOptimizations(gameMode)
+                || packageConfig.getGameModeConfiguration(gameMode) == null) {
             resetFps(packageName, userId);
             // resolution scaling does not need to be reset as it's now read dynamically on game
             // restart, see #getResolutionScalingFactor and CompatModePackages#getCompatScale.
@@ -1464,13 +1640,9 @@
                 if (!bitFieldContainsModeBitmask(modesBitfield, gameModeToReset)) {
                     return;
                 }
-                // if the game mode to reset is the only mode other than standard mode or there
-                // is device config, the entire package config override is removed.
-                if (Integer.bitCount(modesBitfield) <= 2 || deviceConfig == null) {
+                configOverride.removeModeConfig(gameModeToReset);
+                if (!configOverride.hasActiveGameModeConfig()) {
                     settings.removeConfigOverride(packageName);
-                } else {
-                    // otherwise we reset the mode by removing the game mode config override
-                    configOverride.removeModeConfig(gameModeToReset);
                 }
             } else {
                 settings.removeConfigOverride(packageName);
@@ -1498,20 +1670,12 @@
             // want to check if we support selectable game modes
             modesBitfield &= ~modeToBitmask(GameManager.GAME_MODE_UNSUPPORTED);
             if (!bitFieldContainsModeBitmask(modesBitfield, gameMode)) {
-                if (bitFieldContainsModeBitmask(modesBitfield,
-                        GameManager.GAME_MODE_STANDARD)) {
-                    // If the current set mode isn't supported,
-                    // but we support STANDARD, then set the mode to STANDARD.
-                    newGameMode = GameManager.GAME_MODE_STANDARD;
-                } else {
-                    // If we don't support any game modes, then set to UNSUPPORTED
-                    newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
-                }
+                // always default to STANDARD if there is no mode config
+                newGameMode = GameManager.GAME_MODE_STANDARD;
             }
-        } else if (gameMode != GameManager.GAME_MODE_UNSUPPORTED) {
-            // If we have no config for the package, but the configured mode is not
-            // UNSUPPORTED, then set to UNSUPPORTED
-            newGameMode = GameManager.GAME_MODE_UNSUPPORTED;
+        } else {
+            // always default to STANDARD if there is no package config
+            newGameMode = GameManager.GAME_MODE_STANDARD;
         }
         return newGameMode;
     }
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 1e68837..638bc4e 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -90,9 +90,14 @@
      */
     int getGameModeLocked(String packageName) {
         if (mGameModes.containsKey(packageName)) {
-            return mGameModes.get(packageName);
+            final int gameMode = mGameModes.get(packageName);
+            if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+                // force replace cached UNSUPPORTED mode with STANDARD starting in U
+                return GameManager.GAME_MODE_STANDARD;
+            }
+            return gameMode;
         }
-        return GameManager.GAME_MODE_UNSUPPORTED;
+        return GameManager.GAME_MODE_STANDARD;
     }
 
     /**
@@ -255,7 +260,7 @@
             XmlUtils.skipCurrentTag(parser);
             return;
         }
-        int gameMode = GameManager.GAME_MODE_UNSUPPORTED;
+        int gameMode;
         try {
             gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
         } catch (XmlPullParserException e) {
@@ -282,7 +287,7 @@
                         + type);
             }
         }
-        if (config.getAvailableGameModes().length > 1) {
+        if (config.hasActiveGameModeConfig()) {
             mConfigOverrides.put(name, config);
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 78b697d..22e6159 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2644,7 +2644,7 @@
         final DisplayPowerControllerInterface displayPowerController;
 
         if (DeviceConfig.getBoolean("display_manager",
-                "use_newly_structured_display_power_controller", false)) {
+                "use_newly_structured_display_power_controller", true)) {
             displayPowerController = new DisplayPowerController2(
                     mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                     mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 94ecebb..c20d880 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -369,7 +369,7 @@
     /** Switch code: Camera lens cover. When set the lens is covered. */
     public static final int SW_CAMERA_LENS_COVER = 0x09;
 
-    /** Switch code: Microphone. When set it is off. */
+    /** Switch code: Microphone. When set, the mic is muted. */
     public static final int SW_MUTE_DEVICE = 0x0e;
 
     public static final int SW_LID_BIT = 1 << SW_LID;
@@ -536,14 +536,14 @@
         // Set the HW mic toggle switch state
         final int micMuteState = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY,
                 SW_MUTE_DEVICE);
-        if (micMuteState != InputManager.SWITCH_STATE_UNKNOWN) {
-            setSensorPrivacy(Sensors.MICROPHONE, micMuteState != InputManager.SWITCH_STATE_OFF);
+        if (micMuteState == InputManager.SWITCH_STATE_ON) {
+            setSensorPrivacy(Sensors.MICROPHONE, true);
         }
         // Set the HW camera toggle switch state
         final int cameraMuteState = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY,
                 SW_CAMERA_LENS_COVER);
-        if (cameraMuteState != InputManager.SWITCH_STATE_UNKNOWN) {
-            setSensorPrivacy(Sensors.CAMERA, cameraMuteState != InputManager.SWITCH_STATE_OFF);
+        if (cameraMuteState == InputManager.SWITCH_STATE_ON) {
+            setSensorPrivacy(Sensors.CAMERA, true);
         }
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 11e2704..a6b7fe2 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -348,6 +348,9 @@
             @NonNull String uniqueSessionId, int volume) {
         Objects.requireNonNull(router, "router must not be null");
         Objects.requireNonNull(uniqueSessionId, "uniqueSessionId must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index 483fa8a..bf28479 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -69,11 +69,11 @@
     public static boolean canQueryAsInstaller(PackageStateInternal querying,
             AndroidPackage potentialTarget) {
         final InstallSource installSource = querying.getInstallSource();
-        if (potentialTarget.getPackageName().equals(installSource.installerPackageName)) {
+        if (potentialTarget.getPackageName().equals(installSource.mInstallerPackageName)) {
             return true;
         }
-        if (!installSource.isInitiatingPackageUninstalled
-                && potentialTarget.getPackageName().equals(installSource.initiatingPackageName)) {
+        if (!installSource.mIsInitiatingPackageUninstalled
+                && potentialTarget.getPackageName().equals(installSource.mInitiatingPackageName)) {
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index d72aacc..dd41830 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -17,8 +17,11 @@
 package com.android.server.pm;
 
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 import static com.android.server.pm.dex.ArtStatsLogUtils.BackgroundDexoptJobStatsLogger;
 
+import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -748,10 +751,21 @@
             return PackageDexOptimizer.DEX_OPT_CANCELLED;
         }
         int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
+        String filter = getCompilerFilterForReason(reason);
         int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_DOWNGRADE;
+
+        if (isProfileGuidedCompilerFilter(filter)) {
+            // We don't expect updates in current profiles to be significant here, but
+            // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
+            // unconditionally enabled for profile guided filters when ART Service is called instead
+            // of the legacy PackageDexOptimizer implementation.
+            dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+        }
+
         if (!isPostBootUpdate) {
             dexoptFlags |= DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
         }
+
         long package_size_before = getPackageSize(snapshot, pkg);
         int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
         if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
@@ -762,10 +776,10 @@
                 // remove their compiler artifacts from dalvik cache.
                 pm.deleteOatArtifactsOfPackage(snapshot, pkg);
             } else {
-                result = performDexOptPrimary(pkg, reason, dexoptFlags);
+                result = performDexOptPrimary(pkg, reason, filter, dexoptFlags);
             }
         } else {
-            result = performDexOptSecondary(pkg, reason, dexoptFlags);
+            result = performDexOptSecondary(pkg, reason, filter, dexoptFlags);
         }
 
         if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -801,32 +815,42 @@
     private int optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate) {
         int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT
                                       : PackageManagerService.REASON_BACKGROUND_DEXOPT;
+        String filter = getCompilerFilterForReason(reason);
+
         int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE;
         if (!isPostBootUpdate) {
             dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
                     | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
         }
 
+        if (isProfileGuidedCompilerFilter(filter)) {
+            // Ensure DEXOPT_CHECK_FOR_PROFILES_UPDATES is enabled if the filter is profile guided,
+            // to replicate behaviour that will be unconditionally enabled when ART Service is
+            // called instead of the legacy PackageDexOptimizer implementation.
+            dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+        }
+
         // System server share the same code path as primary dex files.
         // PackageManagerService will select the right optimization path for it.
         if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
-            return performDexOptPrimary(pkg, reason, dexoptFlags);
+            return performDexOptPrimary(pkg, reason, filter, dexoptFlags);
         } else {
-            return performDexOptSecondary(pkg, reason, dexoptFlags);
+            return performDexOptSecondary(pkg, reason, filter, dexoptFlags);
         }
     }
 
     @DexOptResult
-    private int performDexOptPrimary(String pkg, int reason, int dexoptFlags) {
-        DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, dexoptFlags);
+    private int performDexOptPrimary(String pkg, int reason, String filter, int dexoptFlags) {
+        DexoptOptions dexoptOptions =
+                new DexoptOptions(pkg, reason, filter, /*splitName=*/null, dexoptFlags);
         return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/true,
                 () -> mDexOptHelper.performDexOptWithStatus(dexoptOptions));
     }
 
     @DexOptResult
-    private int performDexOptSecondary(String pkg, int reason, int dexoptFlags) {
-        DexoptOptions dexoptOptions = new DexoptOptions(
-                pkg, reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
+    private int performDexOptSecondary(String pkg, int reason, String filter, int dexoptFlags) {
+        DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, filter, /*splitName=*/null,
+                dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
         return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/false,
                 ()
                         -> mDexOptHelper.performDexOpt(dexoptOptions)
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index a8534b0..8296e91 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -44,6 +44,7 @@
 import static android.content.pm.PackageManager.TYPE_RECEIVER;
 import static android.content.pm.PackageManager.TYPE_SERVICE;
 import static android.content.pm.PackageManager.TYPE_UNKNOWN;
+import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
@@ -2576,7 +2577,7 @@
             }
         }
 
-        return -1;
+        return INVALID_UID;
     }
 
     /**
@@ -4325,18 +4326,18 @@
     @Override
     public int getUidForSharedUser(@NonNull String sharedUserName) {
         if (sharedUserName == null) {
-            return Process.INVALID_UID;
+            return INVALID_UID;
         }
         final int callingUid = Binder.getCallingUid();
         if (getInstantAppPackageName(callingUid) != null) {
-            return Process.INVALID_UID;
+            return INVALID_UID;
         }
         final SharedUserSetting suid = mSettings.getSharedUserFromId(sharedUserName);
         if (suid != null && !shouldFilterApplicationIncludingUninstalled(suid, callingUid,
                 UserHandle.getUserId(callingUid))) {
             return suid.mAppId;
         }
-        return Process.INVALID_UID;
+        return INVALID_UID;
     }
 
     @Override
@@ -4918,7 +4919,7 @@
         if (installSource == null) {
             throw new IllegalArgumentException("Unknown package: " + packageName);
         }
-        String installerPackageName = installSource.installerPackageName;
+        String installerPackageName = installSource.mInstallerPackageName;
         if (installerPackageName != null) {
             final PackageStateInternal ps = mSettings.getPackage(installerPackageName);
             if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid,
@@ -4961,7 +4962,7 @@
             return null;
         }
 
-        installerPackageName = installSource.installerPackageName;
+        installerPackageName = installSource.mInstallerPackageName;
         if (installerPackageName != null) {
             final PackageStateInternal ps = mSettings.getPackage(installerPackageName);
             if (ps == null
@@ -4970,25 +4971,25 @@
             }
         }
 
-        if (installSource.isInitiatingPackageUninstalled) {
+        if (installSource.mIsInitiatingPackageUninstalled) {
             // We can't check visibility in the usual way, since the initiating package is no
             // longer present. So we apply simpler rules to whether to expose the info:
             // 1. Instant apps can't see it.
             // 2. Otherwise only the installed app itself can see it.
             final boolean isInstantApp = getInstantAppPackageName(callingUid) != null;
             if (!isInstantApp && isCallerSameApp(packageName, callingUid)) {
-                initiatingPackageName = installSource.initiatingPackageName;
+                initiatingPackageName = installSource.mInitiatingPackageName;
             } else {
                 initiatingPackageName = null;
             }
         } else {
-            if (Objects.equals(installSource.initiatingPackageName,
-                    installSource.installerPackageName)) {
+            if (Objects.equals(installSource.mInitiatingPackageName,
+                    installSource.mInstallerPackageName)) {
                 // The installer and initiator will often be the same, and when they are
                 // we can skip doing the same check again.
                 initiatingPackageName = installerPackageName;
             } else {
-                initiatingPackageName = installSource.initiatingPackageName;
+                initiatingPackageName = installSource.mInitiatingPackageName;
                 final PackageStateInternal ps = mSettings.getPackage(initiatingPackageName);
                 if (ps == null
                         || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
@@ -4997,7 +4998,7 @@
             }
         }
 
-        originatingPackageName = installSource.originatingPackageName;
+        originatingPackageName = installSource.mOriginatingPackageName;
         if (originatingPackageName != null) {
             final PackageStateInternal ps = mSettings.getPackage(originatingPackageName);
             if (ps == null
@@ -5017,7 +5018,7 @@
         // If you can see the initiatingPackageName, and we have valid signing info for it,
         // then we let you see that too.
         final SigningInfo initiatingPackageSigningInfo;
-        final PackageSignatures signatures = installSource.initiatingPackageSignatures;
+        final PackageSignatures signatures = installSource.mInitiatingPackageSignatures;
         if (initiatingPackageName != null && signatures != null
                 && signatures.mSigningDetails != SigningDetails.UNKNOWN) {
             initiatingPackageSigningInfo = new SigningInfo(signatures.mSigningDetails);
@@ -5026,7 +5027,7 @@
         }
 
         return new InstallSourceInfo(initiatingPackageName, initiatingPackageSigningInfo,
-                originatingPackageName, installerPackageName, installSource.packageSource);
+                originatingPackageName, installerPackageName, installSource.mPackageSource);
     }
 
     @PackageManager.EnabledState
@@ -5246,7 +5247,7 @@
         final int targetAppId = UserHandle.getAppId(
                 getPackageUid(targetPackageName, 0 /* flags */, userId));
         // For update or already installed case, leverage the existing visibility rule.
-        if (targetAppId != Process.INVALID_UID) {
+        if (targetAppId != INVALID_UID) {
             final Object targetSetting = mSettings.getSettingBase(targetAppId);
             if (targetSetting instanceof PackageSetting) {
                 return !shouldFilterApplication(
@@ -5307,7 +5308,7 @@
         }
 
         final PackageStateInternal installerPackageState = getPackageStateInternal(
-                packageState.getInstallSource().installerPackageName);
+                packageState.getInstallSource().mInstallerPackageName);
         return installerPackageState != null
                 && UserHandle.isSameApp(installerPackageState.getAppId(), callingUid);
     }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 095a7f6..6998db7 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -544,7 +544,7 @@
                 outInfo.mDataRemoved = true;
             }
             outInfo.mRemovedPackage = ps.getPackageName();
-            outInfo.mInstallerPackageName = ps.getInstallSource().installerPackageName;
+            outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName;
             outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null;
             outInfo.mRemovedAppId = ps.getAppId();
             outInfo.mRemovedUsers = userIds;
@@ -814,7 +814,7 @@
 
     private boolean isOrphaned(@NonNull Computer snapshot, String packageName) {
         final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
-        return packageState != null && packageState.getInstallSource().isOrphaned;
+        return packageState != null && packageState.getInstallSource().mIsOrphaned;
     }
 
     private boolean isCallerAllowedToSilentlyUninstall(@NonNull Computer snapshot, int callingUid,
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index c4f6836..0066592 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -33,6 +33,8 @@
 import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_APEX_PKG;
 import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
 
+import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -70,8 +72,6 @@
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 
-import dalvik.system.DexFile;
-
 import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -236,20 +236,24 @@
                 mPm.mArtManagerService.compileLayouts(pkg);
             }
 
-            // checkProfiles is false to avoid merging profiles during boot which
-            // might interfere with background compilation (b/28612421).
-            // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
-            // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
-            // trade-off worth doing to save boot time work.
             int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
+
+            String filter = getCompilerFilterForReason(pkgCompilationReason);
+            if (isProfileGuidedCompilerFilter(filter)) {
+                // DEXOPT_CHECK_FOR_PROFILES_UPDATES used to be false to avoid merging profiles
+                // during boot which might interfere with background compilation (b/28612421).
+                // However those problems were related to the verify-profile compiler filter which
+                // doesn't exist any more, so enable it again.
+                dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+            }
+
             if (compilationReason == REASON_FIRST_BOOT) {
                 // TODO: This doesn't cover the upgrade case, we should check for this too.
                 dexoptFlags |= DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
             }
-            int primaryDexOptStatus = performDexOptTraced(new DexoptOptions(
-                    pkg.getPackageName(),
-                    pkgCompilationReason,
-                    dexoptFlags));
+            int primaryDexOptStatus = performDexOptTraced(
+                    new DexoptOptions(pkg.getPackageName(), pkgCompilationReason, filter,
+                            /*splitName*/ null, dexoptFlags));
 
             switch (primaryDexOptStatus) {
                 case PackageDexOptimizer.DEX_OPT_PERFORMED:
@@ -297,7 +301,7 @@
                 SystemProperties.get("dalvik.vm.systemuicompilerfilter", defaultCompilerFilter);
         String compilerFilter;
 
-        if (DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) {
+        if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
             compilerFilter = defaultCompilerFilter;
             File profileFile = new File(getPrebuildProfilePath(pkg));
 
@@ -322,8 +326,16 @@
             compilerFilter = targetCompilerFilter;
         }
 
+        // We don't expect updates in current profiles to be significant here, but
+        // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
+        // unconditionally enabled for profile guided filters when ART Service is called instead of
+        // the legacy PackageDexOptimizer implementation.
+        int dexoptFlags = isProfileGuidedCompilerFilter(compilerFilter)
+                ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+                : 0;
+
         performDexOptTraced(new DexoptOptions(pkg.getPackageName(), REASON_BOOT_AFTER_OTA,
-                compilerFilter, null /* splitName */, 0 /* dexoptFlags */));
+                compilerFilter, null /* splitName */, dexoptFlags));
     }
 
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
@@ -622,16 +634,21 @@
     }
 
     public boolean performDexOptMode(@NonNull Computer snapshot, String packageName,
-            boolean checkProfiles, String targetCompilerFilter, boolean force,
-            boolean bootComplete, String splitName) {
+            String targetCompilerFilter, boolean force, boolean bootComplete, String splitName) {
         if (!PackageManagerServiceUtils.isSystemOrRootOrShell()
                 && !isCallerInstallerForPackage(snapshot, packageName)) {
             throw new SecurityException("performDexOptMode");
         }
 
-        int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0)
-                | (force ? DexoptOptions.DEXOPT_FORCE : 0)
+        int flags = (force ? DexoptOptions.DEXOPT_FORCE : 0)
                 | (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0);
+
+        if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
+            // Set this flag whenever the filter is profile guided, to align with ART Service
+            // behavior.
+            flags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+        }
+
         return performDexOpt(new DexoptOptions(packageName, REASON_CMDLINE,
                 targetCompilerFilter, splitName, flags));
     }
@@ -644,7 +661,7 @@
         final InstallSource installSource = packageState.getInstallSource();
 
         final PackageStateInternal installerPackageState =
-                snapshot.getPackageStateInternal(installSource.installerPackageName);
+                snapshot.getPackageStateInternal(installSource.mInstallerPackageName);
         if (installerPackageState == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 05a0adc..5c3890c 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -57,6 +57,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.permission.PermissionManager;
+import android.util.Log;
 
 import com.android.internal.R;
 import com.android.internal.content.InstallLocationUtils;
@@ -964,8 +965,12 @@
             boolean checkProfiles, String targetCompilerFilter, boolean force,
             boolean bootComplete, String splitName) {
         final Computer snapshot = snapshot();
-        return mDexOptHelper.performDexOptMode(snapshot, packageName, checkProfiles,
-                targetCompilerFilter, force, bootComplete, splitName);
+        if (!checkProfiles) {
+            // There is no longer a flag to skip profile checking.
+            Log.w(PackageManagerService.TAG, "Ignored checkProfiles=false flag");
+        }
+        return mDexOptHelper.performDexOptMode(
+                snapshot, packageName, targetCompilerFilter, force, bootComplete, splitName);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index aedebba..5dd5d81 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -308,9 +308,9 @@
         // previous device state.
         InstallSource installSource = request.getInstallSource();
         if (installSource != null) {
-            if (installSource.initiatingPackageName != null) {
+            if (installSource.mInitiatingPackageName != null) {
                 final PackageSetting ips = mPm.mSettings.getPackageLPr(
-                        installSource.initiatingPackageName);
+                        installSource.mInitiatingPackageName);
                 if (ips != null) {
                     installSource = installSource.setInitiatingPackageSignatures(
                             ips.getSignatures());
@@ -1566,7 +1566,7 @@
                 removedInfo.mUid = oldPackage.getUid();
                 removedInfo.mRemovedPackage = oldPackage.getPackageName();
                 removedInfo.mInstallerPackageName =
-                        ps.getInstallSource().installerPackageName;
+                        ps.getInstallSource().mInstallerPackageName;
                 removedInfo.mIsStaticSharedLib =
                         parsedPackage.getStaticSharedLibraryName() != null;
                 removedInfo.mIsUpdate = true;
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 4e5a6f9..71571dc 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -81,7 +81,7 @@
     /** Package Installed Info */
     @Nullable
     private String mName;
-    private int mUid = -1;
+    private int mUid = INVALID_UID;
     // The set of users that originally had this package installed.
     @Nullable
     private int[] mOrigUsers;
@@ -314,7 +314,12 @@
     @Nullable
     public String getInstallerPackageName() {
         return (mInstallArgs != null && mInstallArgs.mInstallSource != null)
-                ? mInstallArgs.mInstallSource.installerPackageName : null;
+                ? mInstallArgs.mInstallSource.mInstallerPackageName : null;
+    }
+
+    public int getInstallerPackageUid() {
+        return (mInstallArgs != null && mInstallArgs.mInstallSource != null)
+                ? mInstallArgs.mInstallSource.mInstallerPackageUid : INVALID_UID;
     }
 
     public int getDataLoaderType() {
@@ -343,7 +348,7 @@
 
     @Nullable
     public String getSourceInstallerPackageName() {
-        return mInstallArgs.mInstallSource.installerPackageName;
+        return mInstallArgs.mInstallSource.mInstallerPackageName;
     }
 
     public boolean isRollback() {
@@ -608,12 +613,18 @@
         setReturnCode(code);
         setReturnMessage(msg);
         Slog.w(TAG, msg);
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onInstallFailed();
+        }
     }
 
     public void setError(String msg, PackageManagerException e) {
         mReturnCode = e.error;
         setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
         Slog.w(TAG, msg, e);
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onInstallFailed();
+        }
     }
 
     public void setReturnCode(int returnCode) {
@@ -757,10 +768,10 @@
         }
     }
 
-    public void onInstallCompleted(Computer snapshot) {
+    public void onInstallCompleted() {
         if (getReturnCode() == INSTALL_SUCCEEDED) {
             if (mPackageMetrics != null) {
-                mPackageMetrics.onInstallSucceed(snapshot);
+                mPackageMetrics.onInstallSucceed();
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
index e5f7f71..dde9905 100644
--- a/services/core/java/com/android/server/pm/InstallSource.java
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static android.os.Process.INVALID_UID;
+
 import android.annotation.Nullable;
 import android.content.pm.PackageInstaller;
 
@@ -32,27 +34,27 @@
      * An instance of InstallSource representing an absence of knowledge of the source of
      * a package. Used in preference to null.
      */
-    static final InstallSource EMPTY = new InstallSource(null, null, null, null, false, false,
-            null, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
+    static final InstallSource EMPTY = new InstallSource(null, null, null, INVALID_UID, null,
+            false, false, null, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
 
     /** We also memoize this case because it is common - all un-updated system apps. */
     private static final InstallSource EMPTY_ORPHANED = new InstallSource(
-            null, null, null, null, true, false, null,
+            null, null, null, INVALID_UID, null, true, false, null,
             PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
 
     /**
      * The package that requested the installation, if known. May not correspond to a currently
-     * installed package if {@link #isInitiatingPackageUninstalled} is true.
+     * installed package if {@link #mIsInitiatingPackageUninstalled} is true.
      */
     @Nullable
-    final String initiatingPackageName;
+    final String mInitiatingPackageName;
 
     /**
      * The signing details of the initiating package, if known. Always null if
-     * {@link #initiatingPackageName} is null.
+     * {@link #mInitiatingPackageName} is null.
      */
     @Nullable
-    final PackageSignatures initiatingPackageSignatures;
+    final PackageSignatures mInitiatingPackageSignatures;
 
     /**
      * The package on behalf of which the initiating package requested the installation, if any.
@@ -61,64 +63,63 @@
      * verified by the framework.
      */
     @Nullable
-    final String originatingPackageName;
+    final String mOriginatingPackageName;
 
     /**
      * Package name of the app that installed this package (the installer of record). Note that
      * this may be modified.
      */
     @Nullable
-    final String installerPackageName;
+    final String mInstallerPackageName;
 
+    /**
+     * UID of the installer package, corresponding to the {@link #mInstallerPackageName}.
+     */
+    final int mInstallerPackageUid;
 
     /**
      * {@link android.content.Context#getAttributionTag()} of installing context.
      */
     @Nullable
-    final String installerAttributionTag;
+    final String mInstallerAttributionTag;
 
     /** Indicates if the package that was the installerPackageName has been uninstalled. */
-    final boolean isOrphaned;
+    final boolean mIsOrphaned;
 
     /**
      * Indicates if the package in initiatingPackageName has been uninstalled. Always false if
-     * {@link #initiatingPackageName} is null.
+     * {@link #mInitiatingPackageName} is null.
      */
-    final boolean isInitiatingPackageUninstalled;
+    final boolean mIsInitiatingPackageUninstalled;
 
-    final int packageSource;
+    final int mPackageSource;
 
     static InstallSource create(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag) {
-        return create(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
-    }
-
-    static InstallSource create(@Nullable String initiatingPackageName,
-            @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag, boolean isOrphaned,
+            int installerPackageUid, @Nullable String installerAttributionTag, boolean isOrphaned,
             boolean isInitiatingPackageUninstalled) {
         return create(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, isOrphaned,
+                installerPackageUid, installerAttributionTag,
+                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, isOrphaned,
                 isInitiatingPackageUninstalled);
     }
 
     static InstallSource create(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag, int packageSource) {
+            int installerPackageUid, @Nullable String installerAttributionTag, int packageSource) {
         return create(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, packageSource, false, false);
+                installerPackageUid, installerAttributionTag, packageSource, false, false);
     }
 
     static InstallSource create(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag, int packageSource, boolean isOrphaned,
-            boolean isInitiatingPackageUninstalled) {
+            int installerPackageUid, @Nullable String installerAttributionTag, int packageSource,
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled) {
         return createInternal(
                 intern(initiatingPackageName),
                 intern(originatingPackageName),
                 intern(installerPackageName),
+                installerPackageUid,
                 installerAttributionTag,
                 packageSource,
                 isOrphaned, isInitiatingPackageUninstalled, null);
@@ -126,8 +127,8 @@
 
     private static InstallSource createInternal(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            @Nullable String installerAttributionTag, int packageSource, boolean isOrphaned,
-            boolean isInitiatingPackageUninstalled,
+            int installerPackageUid, @Nullable String installerAttributionTag, int packageSource,
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled,
             @Nullable PackageSignatures initiatingPackageSignatures) {
         if (initiatingPackageName == null && originatingPackageName == null
                 && installerPackageName == null && initiatingPackageSignatures == null
@@ -136,13 +137,14 @@
             return isOrphaned ? EMPTY_ORPHANED : EMPTY;
         }
         return new InstallSource(initiatingPackageName, originatingPackageName,
-                installerPackageName, installerAttributionTag, isOrphaned,
+                installerPackageName, installerPackageUid, installerAttributionTag, isOrphaned,
                 isInitiatingPackageUninstalled, initiatingPackageSignatures, packageSource
         );
     }
 
     private InstallSource(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
+            int installerPackageUid,
             @Nullable String installerAttributionTag, boolean isOrphaned,
             boolean isInitiatingPackageUninstalled,
             @Nullable PackageSignatures initiatingPackageSignatures,
@@ -151,53 +153,58 @@
             Preconditions.checkArgument(initiatingPackageSignatures == null);
             Preconditions.checkArgument(!isInitiatingPackageUninstalled);
         }
-        this.initiatingPackageName = initiatingPackageName;
-        this.originatingPackageName = originatingPackageName;
-        this.installerPackageName = installerPackageName;
-        this.installerAttributionTag = installerAttributionTag;
-        this.isOrphaned = isOrphaned;
-        this.isInitiatingPackageUninstalled = isInitiatingPackageUninstalled;
-        this.initiatingPackageSignatures = initiatingPackageSignatures;
-        this.packageSource = packageSource;
+        mInitiatingPackageName = initiatingPackageName;
+        mOriginatingPackageName = originatingPackageName;
+        mInstallerPackageName = installerPackageName;
+        mInstallerPackageUid = installerPackageUid;
+        mInstallerAttributionTag = installerAttributionTag;
+        mIsOrphaned = isOrphaned;
+        mIsInitiatingPackageUninstalled = isInitiatingPackageUninstalled;
+        mInitiatingPackageSignatures = initiatingPackageSignatures;
+        mPackageSource = packageSource;
     }
 
     /**
      * Return an InstallSource the same as this one except with the specified
-     * {@link #installerPackageName}.
+     * {@link #mInstallerPackageName}.
      */
-    InstallSource setInstallerPackage(@Nullable String installerPackageName) {
-        if (Objects.equals(installerPackageName, this.installerPackageName)) {
+    InstallSource setInstallerPackage(@Nullable String installerPackageName,
+            int installerPackageUid) {
+        if (Objects.equals(installerPackageName, mInstallerPackageName)) {
             return this;
         }
-        return createInternal(initiatingPackageName, originatingPackageName,
-                intern(installerPackageName), installerAttributionTag, packageSource, isOrphaned,
-                isInitiatingPackageUninstalled, initiatingPackageSignatures);
+        return createInternal(mInitiatingPackageName, mOriginatingPackageName,
+                intern(installerPackageName), installerPackageUid, mInstallerAttributionTag,
+                mPackageSource, mIsOrphaned, mIsInitiatingPackageUninstalled,
+                mInitiatingPackageSignatures);
     }
 
     /**
      * Return an InstallSource the same as this one except with the specified value for
-     * {@link #isOrphaned}.
+     * {@link #mIsOrphaned}.
      */
     InstallSource setIsOrphaned(boolean isOrphaned) {
-        if (isOrphaned == this.isOrphaned) {
+        if (isOrphaned == mIsOrphaned) {
             return this;
         }
-        return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, packageSource, isOrphaned, isInitiatingPackageUninstalled,
-                initiatingPackageSignatures);
+        return createInternal(mInitiatingPackageName, mOriginatingPackageName,
+                mInstallerPackageName,
+                mInstallerPackageUid, mInstallerAttributionTag, mPackageSource, isOrphaned,
+                mIsInitiatingPackageUninstalled, mInitiatingPackageSignatures);
     }
 
     /**
      * Return an InstallSource the same as this one except with the specified
-     * {@link #initiatingPackageSignatures}.
+     * {@link #mInitiatingPackageSignatures}.
      */
     InstallSource setInitiatingPackageSignatures(@Nullable PackageSignatures signatures) {
-        if (signatures == initiatingPackageSignatures) {
+        if (signatures == mInitiatingPackageSignatures) {
             return this;
         }
-        return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                installerAttributionTag, packageSource, isOrphaned,
-                isInitiatingPackageUninstalled, signatures);
+        return createInternal(mInitiatingPackageName, mOriginatingPackageName,
+                mInstallerPackageName,
+                mInstallerPackageUid, mInstallerAttributionTag, mPackageSource, mIsOrphaned,
+                mIsInitiatingPackageUninstalled, signatures);
     }
 
     /**
@@ -210,12 +217,13 @@
         }
 
         boolean modified = false;
-        boolean isInitiatingPackageUninstalled = this.isInitiatingPackageUninstalled;
-        String originatingPackageName = this.originatingPackageName;
-        String installerPackageName = this.installerPackageName;
-        boolean isOrphaned = this.isOrphaned;
+        boolean isInitiatingPackageUninstalled = mIsInitiatingPackageUninstalled;
+        String originatingPackageName = mOriginatingPackageName;
+        String installerPackageName = mInstallerPackageName;
+        int installerPackageUid = mInstallerPackageUid;
+        boolean isOrphaned = mIsOrphaned;
 
-        if (packageName.equals(this.initiatingPackageName)) {
+        if (packageName.equals(mInitiatingPackageName)) {
             if (!isInitiatingPackageUninstalled) {
                 // In this case we deliberately do not clear the package name (and signatures).
                 // We allow an app to retrieve details of its own install initiator even after
@@ -230,6 +238,7 @@
         }
         if (packageName.equals(installerPackageName)) {
             installerPackageName = null;
+            installerPackageUid = INVALID_UID;
             isOrphaned = true;
             modified = true;
         }
@@ -238,9 +247,9 @@
             return this;
         }
 
-        return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                null, packageSource, isOrphaned,
-                isInitiatingPackageUninstalled, initiatingPackageSignatures);
+        return createInternal(mInitiatingPackageName, originatingPackageName, installerPackageName,
+                installerPackageUid, null, mPackageSource, isOrphaned,
+                isInitiatingPackageUninstalled, mInitiatingPackageSignatures);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index d8494db..69ced1b 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -143,7 +143,7 @@
         mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
         mMoveInfo = null;
         mInstallReason = fixUpInstallReason(
-                installSource.installerPackageName, installerUid, sessionParams.installReason);
+                installSource.mInstallerPackageName, installerUid, sessionParams.installReason);
         mInstallScenario = sessionParams.installScenario;
         mObserver = observer;
         mInstallFlags = sessionParams.installFlags;
@@ -223,7 +223,7 @@
      * policy if needed and then create install arguments based
      * on the install location.
      */
-    private void handleStartCopy() {
+    private void handleStartCopy(InstallRequest request) {
         if ((mInstallFlags & PackageManager.INSTALL_APEX) != 0) {
             mRet = INSTALL_SUCCEEDED;
             return;
@@ -239,6 +239,7 @@
                     pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
             mRet = ret.first;
             if (mRet != INSTALL_SUCCEEDED) {
+                request.setError(mRet, "Failed to verify version code");
                 return;
             }
         }
@@ -258,14 +259,16 @@
         }
         mRet = overrideInstallLocation(pkgLite.packageName, pkgLite.recommendedInstallLocation,
                 pkgLite.installLocation);
+        if (mRet != INSTALL_SUCCEEDED) {
+            request.setError(mRet, "Failed to override installation location");
+        }
     }
 
-    private void handleReturnCode() {
-        processPendingInstall();
+    private void handleReturnCode(InstallRequest installRequest) {
+        processPendingInstall(installRequest);
     }
 
-    private void processPendingInstall() {
-        InstallRequest installRequest = new InstallRequest(this);
+    private void processPendingInstall(InstallRequest installRequest) {
         if (mRet == PackageManager.INSTALL_SUCCEEDED) {
             mRet = copyApk(installRequest);
         }
@@ -302,21 +305,26 @@
                 request.setCodeFile(mOriginInfo.mFile);
                 return PackageManager.INSTALL_SUCCEEDED;
             }
-
+            int ret;
             try {
                 final boolean isEphemeral =
                         (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
                 request.setCodeFile(
                         mPm.mInstallerService.allocateStageDirLegacy(mVolumeUuid, isEphemeral));
             } catch (IOException e) {
-                Slog.w(TAG, "Failed to create copy file: " + e);
-                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                final String errorMessage = "Failed to create copy file";
+                Slog.w(TAG, errorMessage + ": " + e);
+                ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                request.setError(ret, errorMessage);
+                return ret;
             }
 
-            int ret = PackageManagerServiceUtils.copyPackage(
+            ret = PackageManagerServiceUtils.copyPackage(
                     mOriginInfo.mFile.getAbsolutePath(), request.getCodeFile());
             if (ret != PackageManager.INSTALL_SUCCEEDED) {
-                Slog.e(TAG, "Failed to copy package");
+                final String errorMessage = "Failed to copy package";
+                Slog.e(TAG, errorMessage);
+                request.setError(ret, errorMessage);
                 return ret;
             }
 
@@ -329,8 +337,10 @@
                 ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
                         request.getAbiOverride(), isIncremental);
             } catch (IOException e) {
-                Slog.e(TAG, "Copying native libraries failed", e);
+                final String errorMessage = "Copying native libraries failed";
+                Slog.e(TAG, errorMessage, e);
                 ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                request.setError(ret, errorMessage);
             } finally {
                 IoUtils.closeQuietly(handle);
             }
@@ -352,8 +362,11 @@
                         mMoveInfo.mPackageName, mMoveInfo.mAppId, mMoveInfo.mSeInfo,
                         mMoveInfo.mTargetSdkVersion, mMoveInfo.mFromCodePath);
             } catch (Installer.InstallerException e) {
-                Slog.w(TAG, "Failed to move app", e);
-                return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                final String errorMessage = "Failed to move app";
+                final int ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                request.setError(ret, errorMessage);
+                Slog.w(TAG, errorMessage, e);
+                return ret;
             }
         }
 
@@ -456,8 +469,9 @@
         Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                 System.identityHashCode(this));
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startInstall");
-        handleStartCopy();
-        handleReturnCode();
+        InstallRequest installRequest = new InstallRequest(this);
+        handleStartCopy(installRequest);
+        handleReturnCode(installRequest);
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
@@ -518,7 +532,7 @@
             mInstallPackageHelper.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
-                request.onInstallCompleted(mPm.snapshotComputer());
+                request.onInstallCompleted();
                 doPostInstall(request);
             }
         }
@@ -635,11 +649,18 @@
             Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                     System.identityHashCode(this));
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "start");
-            for (InstallingSession childInstallingSession : mChildInstallingSessions) {
-                childInstallingSession.handleStartCopy();
+
+            final int numChildSessions = mChildInstallingSessions.size();
+            final ArrayList<InstallRequest> installRequests = new ArrayList<>(numChildSessions);
+
+            for (int i = 0; i < numChildSessions; i++) {
+                final InstallingSession childSession = mChildInstallingSessions.get(i);
+                final InstallRequest installRequest = new InstallRequest(childSession);
+                installRequests.add(installRequest);
+                childSession.handleStartCopy(installRequest);
             }
-            for (InstallingSession childInstallingSession : mChildInstallingSessions) {
-                childInstallingSession.handleReturnCode();
+            for (int i = 0; i < numChildSessions; i++) {
+                mChildInstallingSessions.get(i).handleReturnCode(installRequests.get(i));
             }
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -647,6 +668,7 @@
         public void tryProcessInstallRequest(InstallRequest request) {
             mCurrentInstallRequests.add(request);
             if (mCurrentInstallRequests.size() != mChildInstallingSessions.size()) {
+                // Wait until all the installRequests have finished copying
                 return;
             }
             int completeStatus = PackageManager.INSTALL_SUCCEEDED;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index cc1d879..653a882 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
+import static android.os.Process.INVALID_UID;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -847,8 +848,18 @@
                 params.forceQueryableOverride = false;
             }
         }
+        int requestedInstallerPackageUid = INVALID_UID;
+        if (requestedInstallerPackageName != null) {
+            requestedInstallerPackageUid = snapshot.getPackageUid(requestedInstallerPackageName,
+                    0 /* flags */, userId);
+        }
+        if (requestedInstallerPackageUid == INVALID_UID) {
+            // Requested installer package is invalid, reset it
+            requestedInstallerPackageName = null;
+        }
+
         InstallSource installSource = InstallSource.create(installerPackageName,
-                originatingPackageName, requestedInstallerPackageName,
+                originatingPackageName, requestedInstallerPackageName, requestedInstallerPackageUid,
                 installerAttributionTag, params.packageSource);
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2e00222..2ee12bf 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -33,6 +33,7 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
+import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDONLY;
 import static android.system.OsConstants.O_WRONLY;
@@ -217,6 +218,7 @@
     private static final String ATTR_SESSION_ID = "sessionId";
     private static final String ATTR_USER_ID = "userId";
     private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
+    private static final String ATTR_INSTALLER_PACKAGE_UID = "installerPackageUid";
     private static final String ATTR_INSTALLER_ATTRIBUTION_TAG = "installerAttributionTag";
     private static final String ATTR_INSTALLER_UID = "installerUid";
     private static final String ATTR_INITIATING_PACKAGE_NAME =
@@ -812,7 +814,7 @@
         // It may wait for a long time to finish {@code dpmi.canSilentlyInstallPackage}.
         // Please don't acquire mLock before calling {@code dpmi.canSilentlyInstallPackage}.
         return dpmi != null && dpmi.canSilentlyInstallPackage(
-                getInstallSource().installerPackageName, mInstallerUid);
+                getInstallSource().mInstallerPackageName, mInstallerUid);
     }
 
     private static final int USER_ACTION_NOT_NEEDED = 0;
@@ -936,7 +938,7 @@
         mOriginalInstallerUid = installerUid;
         mInstallerUid = installerUid;
         mInstallSource = Objects.requireNonNull(installSource);
-        mOriginalInstallerPackageName = mInstallSource.installerPackageName;
+        mOriginalInstallerPackageName = mInstallSource.mInstallerPackageName;
         this.params = params;
         this.createdMillis = createdMillis;
         this.updatedMillis = createdMillis;
@@ -1047,8 +1049,8 @@
         synchronized (mLock) {
             info.sessionId = sessionId;
             info.userId = userId;
-            info.installerPackageName = mInstallSource.installerPackageName;
-            info.installerAttributionTag = mInstallSource.installerAttributionTag;
+            info.installerPackageName = mInstallSource.mInstallerPackageName;
+            info.installerAttributionTag = mInstallSource.mInstallerAttributionTag;
             info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
                     mResolvedBaseFile.getAbsolutePath() : null;
             info.progress = progress;
@@ -1310,10 +1312,10 @@
         }
 
         final String installerPackageName;
-        if (!TextUtils.isEmpty(getInstallSource().initiatingPackageName)) {
-            installerPackageName = getInstallSource().initiatingPackageName;
+        if (!TextUtils.isEmpty(getInstallSource().mInitiatingPackageName)) {
+            installerPackageName = getInstallSource().mInitiatingPackageName;
         } else {
-            installerPackageName = getInstallSource().installerPackageName;
+            installerPackageName = getInstallSource().mInstallerPackageName;
         }
         if (TextUtils.isEmpty(installerPackageName)) {
             throw new IllegalStateException("Installer package is empty.");
@@ -1354,7 +1356,7 @@
             @NonNull IOnChecksumsReadyListener onChecksumsReadyListener) {
         assertCallerIsOwnerRootOrVerifier();
         final File file = new File(stageDir, name);
-        final String installerPackageName = getInstallSource().initiatingPackageName;
+        final String installerPackageName = getInstallSource().mInitiatingPackageName;
         try {
             mPm.requestFileChecksums(file, installerPackageName, optional, required,
                     trustedInstallers, onChecksumsReadyListener);
@@ -2109,8 +2111,8 @@
             }
 
             mInstallerUid = newOwnerAppInfo.uid;
-            mInstallSource = InstallSource.create(packageName, null, packageName, null,
-                    params.packageSource);
+            mInstallSource = InstallSource.create(packageName, null, packageName,
+                    mInstallerUid, null, params.packageSource);
         }
     }
 
@@ -2179,7 +2181,7 @@
         if (isInstallerDeviceOwnerOrAffiliatedProfileOwner()) {
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.INSTALL_PACKAGE)
-                    .setAdmin(getInstallSource().installerPackageName)
+                    .setAdmin(getInstallSource().mInstallerPackageName)
                     .write();
         }
 
@@ -2606,7 +2608,7 @@
         final int packageUid;
         if (returnCode != INSTALL_SUCCEEDED) {
             // Package didn't install; no valid uid
-            packageUid = Process.INVALID_UID;
+            packageUid = INVALID_UID;
         } else {
             packageUid = mPm.snapshotComputer().getPackageUid(packageName, 0, userId);
         }
@@ -3459,11 +3461,11 @@
     }
 
     String getInstallerPackageName() {
-        return getInstallSource().installerPackageName;
+        return getInstallSource().mInstallerPackageName;
     }
 
     String getInstallerAttributionTag() {
-        return getInstallSource().installerAttributionTag;
+        return getInstallSource().mInstallerAttributionTag;
     }
 
     InstallSource getInstallSource() {
@@ -4451,9 +4453,9 @@
         pw.printPair("userId", userId);
         pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
         pw.printPair("mOriginalInstallerPackageName", mOriginalInstallerPackageName);
-        pw.printPair("installerPackageName", mInstallSource.installerPackageName);
-        pw.printPair("installInitiatingPackageName", mInstallSource.initiatingPackageName);
-        pw.printPair("installOriginatingPackageName", mInstallSource.originatingPackageName);
+        pw.printPair("installerPackageName", mInstallSource.mInstallerPackageName);
+        pw.printPair("installInitiatingPackageName", mInstallSource.mInitiatingPackageName);
+        pw.printPair("installOriginatingPackageName", mInstallSource.mOriginatingPackageName);
         pw.printPair("mInstallerUid", mInstallerUid);
         pw.printPair("createdMillis", createdMillis);
         pw.printPair("updatedMillis", updatedMillis);
@@ -4642,14 +4644,15 @@
             out.attributeInt(null, ATTR_SESSION_ID, sessionId);
             out.attributeInt(null, ATTR_USER_ID, userId);
             writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
-                    mInstallSource.installerPackageName);
+                    mInstallSource.mInstallerPackageName);
+            out.attributeInt(null, ATTR_INSTALLER_PACKAGE_UID, mInstallSource.mInstallerPackageUid);
             writeStringAttribute(out, ATTR_INSTALLER_ATTRIBUTION_TAG,
-                    mInstallSource.installerAttributionTag);
+                    mInstallSource.mInstallerAttributionTag);
             out.attributeInt(null, ATTR_INSTALLER_UID, mInstallerUid);
             writeStringAttribute(out, ATTR_INITIATING_PACKAGE_NAME,
-                    mInstallSource.initiatingPackageName);
+                    mInstallSource.mInitiatingPackageName);
             writeStringAttribute(out, ATTR_ORIGINATING_PACKAGE_NAME,
-                    mInstallSource.originatingPackageName);
+                    mInstallSource.mOriginatingPackageName);
             out.attributeLong(null, ATTR_CREATED_MILLIS, createdMillis);
             out.attributeLong(null, ATTR_UPDATED_MILLIS, updatedMillis);
             out.attributeLong(null, ATTR_COMMITTED_MILLIS, committedMillis);
@@ -4806,6 +4809,8 @@
         final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
         final int userId = in.getAttributeInt(null, ATTR_USER_ID);
         final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
+        final int installPackageUid = in.getAttributeInt(null, ATTR_INSTALLER_PACKAGE_UID,
+                INVALID_UID);
         final String installerAttributionTag = readStringAttribute(in,
                 ATTR_INSTALLER_ATTRIBUTION_TAG);
         final int installerUid = in.getAttributeInt(null, ATTR_INSTALLER_UID, pm.snapshotComputer()
@@ -4975,8 +4980,8 @@
         }
 
         InstallSource installSource = InstallSource.create(installInitiatingPackageName,
-                installOriginatingPackageName, installerPackageName, installerAttributionTag,
-                params.packageSource);
+                installOriginatingPackageName, installerPackageName, installPackageUid,
+                installerAttributionTag, params.packageSource);
         return new PackageInstallerSession(callback, context, pm, sessionProvider,
                 silentUpdatePolicy, installerThread, stagingManager, sessionId, userId,
                 installerUid, installSource, params, createdMillis, committedMillis, stageDir,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 16a3ca0..bd58bfb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -29,6 +29,7 @@
 import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
@@ -3068,7 +3069,7 @@
             int userId) {
         final PackageRemovedInfo info = new PackageRemovedInfo(this);
         info.mRemovedPackage = packageName;
-        info.mInstallerPackageName = packageState.getInstallSource().installerPackageName;
+        info.mInstallerPackageName = packageState.getInstallSource().mInstallerPackageName;
         info.mRemovedUsers = new int[] {userId};
         info.mBroadcastUsers = new int[] {userId};
         info.mUid = UserHandle.getUid(userId, packageState.getAppId());
@@ -4492,7 +4493,7 @@
 
                 if (wasNotLaunched) {
                     final String installerPackageName =
-                            packageState.getInstallSource().installerPackageName;
+                            packageState.getInstallSource().mInstallerPackageName;
                     if (installerPackageName != null) {
                         notifyFirstLaunch(packageName, installerPackageName, userId);
                     }
@@ -5476,7 +5477,7 @@
                 }
 
                 if (!Objects.equals(callerPackageName,
-                        packageState.getInstallSource().installerPackageName)) {
+                        packageState.getInstallSource().mInstallerPackageName)) {
                     throw new IllegalArgumentException("Calling package " + callerPackageName
                             + " is not installer for " + packageName);
                 }
@@ -5709,7 +5710,8 @@
         }
 
         @Override
-        public void setInstallerPackageName(String targetPackage, String installerPackageName) {
+        public void setInstallerPackageName(String targetPackage,
+                @Nullable String installerPackageName) {
             final int callingUid = Binder.getCallingUid();
             final int callingUserId = UserHandle.getUserId(callingUid);
             final FunctionalUtils.ThrowingCheckedFunction<Computer, Boolean, RuntimeException>
@@ -5764,7 +5766,7 @@
                 // Verify: if target already has an installer package, it must
                 // be signed with the same cert as the caller.
                 String targetInstallerPackageName =
-                        targetPackageState.getInstallSource().installerPackageName;
+                        targetPackageState.getInstallSource().mInstallerPackageName;
                 PackageStateInternal targetInstallerPkgSetting = targetInstallerPackageName == null
                         ? null : snapshot.getPackageStateInternal(targetInstallerPackageName);
 
@@ -5807,16 +5809,21 @@
             if (allowed) {
                 // TODO: Need to lock around here to handle mSettings.addInstallerPackageNames,
                 //  should find an alternative which avoids any race conditions
+                final int installerPackageUid = installerPackageName == null
+                        ? INVALID_UID : snapshotComputer().getPackageUid(installerPackageName,
+                        0 /* flags */, callingUserId);
                 PackageStateInternal targetPackageState;
                 synchronized (mLock) {
                     PackageStateMutator.Result result = commitPackageStateMutation(initialState,
-                            targetPackage, state -> state.setInstaller(installerPackageName));
+                            targetPackage, state -> state.setInstaller(installerPackageName,
+                                    installerPackageUid));
                     if (result.isPackagesChanged() || result.isStateChanged()) {
                         synchronized (mPackageStateWriteLock) {
                             allowed = implementation.apply(snapshotComputer());
                             if (allowed) {
                                 commitPackageStateMutation(null, targetPackage,
-                                        state -> state.setInstaller(installerPackageName));
+                                        state -> state.setInstaller(installerPackageName,
+                                                installerPackageUid));
                             } else {
                                 return;
                             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 9f21f11..cc1306d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -88,7 +88,6 @@
 import android.os.ServiceSpecificException;
 import android.os.ShellCommand;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -1787,13 +1786,11 @@
 
     private int runCompile() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
         boolean forceCompilation = false;
         boolean allPackages = false;
         boolean clearProfileData = false;
         String compilerFilter = null;
         String compilationReason = null;
-        String checkProfilesRaw = null;
         boolean secondaryDex = false;
         String split = null;
 
@@ -1816,7 +1813,9 @@
                     compilationReason = getNextArgRequired();
                     break;
                 case "--check-prof":
-                    checkProfilesRaw = getNextArgRequired();
+                    getNextArgRequired();
+                    pw.println("Warning: Ignoring obsolete flag --check-prof "
+                            + "- it is unconditionally enabled now");
                     break;
                 case "--reset":
                     forceCompilation = true;
@@ -1835,17 +1834,6 @@
             }
         }
 
-        if (checkProfilesRaw != null) {
-            if ("true".equals(checkProfilesRaw)) {
-                checkProfiles = true;
-            } else if ("false".equals(checkProfilesRaw)) {
-                checkProfiles = false;
-            } else {
-                pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
-                return 1;
-            }
-        }
-
         final boolean compilerFilterGiven = compilerFilter != null;
         final boolean compilationReasonGiven = compilationReason != null;
         // Make sure exactly one of -m, or -r is given.
@@ -1922,11 +1910,10 @@
             }
 
             final boolean result = secondaryDex
-                    ? mInterface.performDexOptSecondary(packageName,
-                            targetCompilerFilter, forceCompilation)
-                    : mInterface.performDexOptMode(packageName,
-                            checkProfiles, targetCompilerFilter, forceCompilation,
-                            true /* bootComplete */, split);
+                    ? mInterface.performDexOptSecondary(
+                            packageName, targetCompilerFilter, forceCompilation)
+                    : mInterface.performDexOptMode(packageName, true /* checkProfiles */,
+                            targetCompilerFilter, forceCompilation, true /* bootComplete */, split);
             if (!result) {
                 failedPackages.add(packageName);
             }
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 0574f73..3dcf926 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -16,8 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.os.Process.INVALID_UID;
-
 import android.annotation.IntDef;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
@@ -26,7 +24,6 @@
 
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
-import com.android.server.pm.pkg.PackageStateInternal;
 
 import java.io.File;
 import java.io.IOException;
@@ -68,32 +65,46 @@
         mInstallRequest = installRequest;
     }
 
-    public void onInstallSucceed(Computer snapshot) {
+    public void onInstallSucceed() {
         // TODO(b/239722919): report to SecurityLog if on work profile or managed device
-        reportInstallationStats(snapshot, true /* success */);
+        reportInstallationStats(true /* success */);
     }
 
-    private void reportInstallationStats(Computer snapshot, boolean success) {
+    public void onInstallFailed() {
+        reportInstallationStats(false /* success */);
+    }
+
+    private void reportInstallationStats(boolean success) {
         UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
-        // TODO(b/249294752): do not log if adb
         final long installDurationMillis =
                 System.currentTimeMillis() - mInstallStartTimestampMillis;
         // write to stats
         final Pair<int[], long[]> stepDurations = getInstallStepDurations();
         final int[] newUsers = mInstallRequest.getNewUsers();
         final int[] originalUsers = mInstallRequest.getOriginUsers();
-        final String packageName = mInstallRequest.getName();
-        final String installerPackageName = mInstallRequest.getInstallerPackageName();
-        final int installerUid = installerPackageName == null ? INVALID_UID
-                : snapshot.getPackageUid(installerPackageName, 0, 0);
-        final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
-        final long versionCode = success ? 0 : ps.getVersionCode();
-        final long apksSize = getApksSize(ps.getPath());
+        final String packageName;
+        // only reporting package name for failed non-adb installations
+        if (success || mInstallRequest.isInstallFromAdb()) {
+            packageName = null;
+        } else {
+            packageName = mInstallRequest.getName();
+        }
+
+        final int installerPackageUid = mInstallRequest.getInstallerPackageUid();
+
+        long versionCode = 0, apksSize = 0;
+        if (success) {
+            final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
+            if (ps != null) {
+                versionCode = ps.getVersionCode();
+                apksSize = getApksSize(ps.getPath());
+            }
+        }
 
         FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
                 mInstallRequest.getSessionId() /* session_id */,
-                success ? null : packageName /* not report package_name on success */,
+                packageName /* package_name */,
                 mInstallRequest.getUid() /* uid */,
                 newUsers /* user_ids */,
                 userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */,
@@ -107,7 +118,7 @@
                 stepDurations.second /* step_duration_millis */,
                 installDurationMillis /* total_duration_millis */,
                 mInstallRequest.getInstallFlags() /* install_flags */,
-                installerUid /* installer_package_uid */,
+                installerPackageUid /* installer_package_uid */,
                 -1 /* original_installer_package_uid */,
                 mInstallRequest.getDataLoaderType() /* data_loader_type */,
                 mInstallRequest.getRequireUserAction() /* user_action_required_type */,
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 28a021b..434a62d 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -483,7 +483,7 @@
             return;
         }
         final String packageName = session.getPackageName();
-        final String installerPackageName = session.getInstallSource().installerPackageName;
+        final String installerPackageName = session.getInstallSource().mInstallerPackageName;
         if (!isApexUpdateAllowed(packageName, installerPackageName)) {
             throw new PackageManagerException(
                     PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 8d6abe0..6d90593 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -277,7 +277,7 @@
         proto.write(PackageProto.UID, mAppId);
         proto.write(PackageProto.VERSION_CODE, versionCode);
         proto.write(PackageProto.UPDATE_TIME_MS, lastUpdateTime);
-        proto.write(PackageProto.INSTALLER_NAME, installSource.installerPackageName);
+        proto.write(PackageProto.INSTALLER_NAME, installSource.mInstallerPackageName);
 
         if (pkg != null) {
             proto.write(PackageProto.VERSION_STRING, pkg.getVersionName());
@@ -297,9 +297,9 @@
 
             long sourceToken = proto.start(PackageProto.INSTALL_SOURCE);
             proto.write(PackageProto.InstallSourceProto.INITIATING_PACKAGE_NAME,
-                    installSource.initiatingPackageName);
+                    installSource.mInitiatingPackageName);
             proto.write(PackageProto.InstallSourceProto.ORIGINATING_PACKAGE_NAME,
-                    installSource.originatingPackageName);
+                    installSource.mOriginatingPackageName);
             proto.end(sourceToken);
         }
         proto.write(PackageProto.StatesProto.IS_LOADING, isLoading());
@@ -360,8 +360,10 @@
         return this;
     }
 
-    public PackageSetting setInstallerPackageName(String packageName) {
-        installSource = installSource.setInstallerPackage(packageName);
+    public PackageSetting setInstallerPackage(@Nullable String installerPackageName,
+            int installerPackageUid) {
+        installSource = installSource.setInstallerPackage(installerPackageName,
+                installerPackageUid);
         onChanged();
         return this;
     }
@@ -372,7 +374,7 @@
         return this;
     }
 
-    PackageSetting removeInstallerPackage(String packageName) {
+    PackageSetting removeInstallerPackage(@Nullable String packageName) {
         installSource = installSource.removeInstallerPackage(packageName);
         onChanged();
         return this;
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7e93673..8c58397 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -269,7 +269,7 @@
         final AndroidPackage deletedPkg = deletedPs.getPkg();
         if (outInfo != null) {
             outInfo.mRemovedPackage = packageName;
-            outInfo.mInstallerPackageName = deletedPs.getInstallSource().installerPackageName;
+            outInfo.mInstallerPackageName = deletedPs.getInstallSource().mInstallerPackageName;
             outInfo.mIsStaticSharedLib = deletedPkg != null
                     && deletedPkg.getStaticSharedLibraryName() != null;
             outInfo.populateUsers(deletedPs.queryInstalledUsers(
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a29fa9b..a40d404 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2923,9 +2923,9 @@
                     sb.append("@system");
                 } else if (pkg.isProduct()) {
                     sb.append("@product");
-                } else if (pkg.getInstallSource().installerPackageName != null
-                           && !pkg.getInstallSource().installerPackageName.isEmpty()) {
-                    sb.append(pkg.getInstallSource().installerPackageName);
+                } else if (pkg.getInstallSource().mInstallerPackageName != null
+                           && !pkg.getInstallSource().mInstallerPackageName.isEmpty()) {
+                    sb.append(pkg.getInstallSource().mInstallerPackageName);
                 } else {
                     sb.append("@null");
                 }
@@ -3016,26 +3016,29 @@
             serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
         }
         InstallSource installSource = pkg.getInstallSource();
-        if (installSource.installerPackageName != null) {
-            serializer.attribute(null, "installer", installSource.installerPackageName);
+        if (installSource.mInstallerPackageName != null) {
+            serializer.attribute(null, "installer", installSource.mInstallerPackageName);
         }
-        if (installSource.installerAttributionTag != null) {
+        if (installSource.mInstallerPackageUid != INVALID_UID) {
+            serializer.attributeInt(null, "installerUid", installSource.mInstallerPackageUid);
+        }
+        if (installSource.mInstallerAttributionTag != null) {
             serializer.attribute(null, "installerAttributionTag",
-                    installSource.installerAttributionTag);
+                    installSource.mInstallerAttributionTag);
         }
         serializer.attributeInt(null, "packageSource",
-                installSource.packageSource);
-        if (installSource.isOrphaned) {
+                installSource.mPackageSource);
+        if (installSource.mIsOrphaned) {
             serializer.attributeBoolean(null, "isOrphaned", true);
         }
-        if (installSource.initiatingPackageName != null) {
-            serializer.attribute(null, "installInitiator", installSource.initiatingPackageName);
+        if (installSource.mInitiatingPackageName != null) {
+            serializer.attribute(null, "installInitiator", installSource.mInitiatingPackageName);
         }
-        if (installSource.isInitiatingPackageUninstalled) {
+        if (installSource.mIsInitiatingPackageUninstalled) {
             serializer.attributeBoolean(null, "installInitiatorUninstalled", true);
         }
-        if (installSource.originatingPackageName != null) {
-            serializer.attribute(null, "installOriginator", installSource.originatingPackageName);
+        if (installSource.mOriginatingPackageName != null) {
+            serializer.attribute(null, "installOriginator", installSource.mOriginatingPackageName);
         }
         if (pkg.getVolumeUuid() != null) {
             serializer.attribute(null, "volumeUuid", pkg.getVolumeUuid());
@@ -3064,8 +3067,8 @@
 
         pkg.getSignatures().writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
 
-        if (installSource.initiatingPackageSignatures != null) {
-            installSource.initiatingPackageSignatures.writeXml(
+        if (installSource.mInitiatingPackageSignatures != null) {
+            installSource.mInitiatingPackageSignatures.writeXml(
                     serializer, "install-initiator-sigs", mPastSignatures.untrackedStorage());
         }
 
@@ -3809,6 +3812,7 @@
         String cpuAbiOverrideString = null;
         String systemStr = null;
         String installerPackageName = null;
+        int installerPackageUid = INVALID_UID;
         String installerAttributionTag = null;
         int packageSource = PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED;
         boolean isOrphaned = false;
@@ -3851,6 +3855,7 @@
 
             versionCode = parser.getAttributeLong(null, "version", 0);
             installerPackageName = parser.getAttributeValue(null, "installer");
+            installerPackageUid = parser.getAttributeInt(null, "installerUid", INVALID_UID);
             installerAttributionTag = parser.getAttributeValue(null, "installerAttributionTag");
             packageSource = parser.getAttributeInt(null, "packageSource",
                     PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
@@ -3993,8 +3998,8 @@
         if (packageSetting != null) {
             InstallSource installSource = InstallSource.create(
                     installInitiatingPackageName, installOriginatingPackageName,
-                    installerPackageName, installerAttributionTag, packageSource, isOrphaned,
-                    installInitiatorUninstalled);
+                    installerPackageName, installerPackageUid, installerAttributionTag,
+                    packageSource, isOrphaned, installInitiatorUninstalled);
             packageSetting.setInstallSource(installSource)
                     .setVolumeUuid(volumeUuid)
                     .setCategoryOverride(categoryHint)
@@ -4138,14 +4143,14 @@
     }
 
     void addInstallerPackageNames(InstallSource installSource) {
-        if (installSource.installerPackageName != null) {
-            mInstallerPackages.add(installSource.installerPackageName);
+        if (installSource.mInstallerPackageName != null) {
+            mInstallerPackages.add(installSource.mInstallerPackageName);
         }
-        if (installSource.initiatingPackageName != null) {
-            mInstallerPackages.add(installSource.initiatingPackageName);
+        if (installSource.mInitiatingPackageName != null) {
+            mInstallerPackages.add(installSource.mInitiatingPackageName);
         }
-        if (installSource.originatingPackageName != null) {
-            mInstallerPackages.add(installSource.originatingPackageName);
+        if (installSource.mOriginatingPackageName != null) {
+            mInstallerPackages.add(installSource.mOriginatingPackageName);
         }
     }
 
@@ -4659,12 +4664,13 @@
             pw.print(",");
             pw.print(ps.getLastUpdateTime());
             pw.print(",");
-            pw.print(ps.getInstallSource().installerPackageName != null
-                    ? ps.getInstallSource().installerPackageName : "?");
-            pw.print(ps.getInstallSource().installerAttributionTag != null
-                    ? "(" + ps.getInstallSource().installerAttributionTag + ")" : "");
+            pw.print(ps.getInstallSource().mInstallerPackageName != null
+                    ? ps.getInstallSource().mInstallerPackageName : "?");
+            pw.print(ps.getInstallSource().mInstallerPackageUid);
+            pw.print(ps.getInstallSource().mInstallerAttributionTag != null
+                    ? "(" + ps.getInstallSource().mInstallerAttributionTag + ")" : "");
             pw.print(",");
-            pw.print(ps.getInstallSource().packageSource);
+            pw.print(ps.getInstallSource().mPackageSource);
             pw.println();
             if (pkg != null) {
                 pw.print(checkinTag); pw.print("-"); pw.print("splt,");
@@ -4936,16 +4942,20 @@
         pw.print(prefix); pw.print("  lastUpdateTime=");
             date.setTime(ps.getLastUpdateTime());
             pw.println(sdf.format(date));
-        if (ps.getInstallSource().installerPackageName != null) {
+        if (ps.getInstallSource().mInstallerPackageName != null) {
             pw.print(prefix); pw.print("  installerPackageName=");
-            pw.println(ps.getInstallSource().installerPackageName);
+            pw.println(ps.getInstallSource().mInstallerPackageName);
         }
-        if (ps.getInstallSource().installerAttributionTag != null) {
+        if (ps.getInstallSource().mInstallerPackageUid != INVALID_UID) {
+            pw.print(prefix); pw.print("  installerPackageUid=");
+            pw.println(ps.getInstallSource().mInstallerPackageUid);
+        }
+        if (ps.getInstallSource().mInstallerAttributionTag != null) {
             pw.print(prefix); pw.print("  installerAttributionTag=");
-            pw.println(ps.getInstallSource().installerAttributionTag);
+            pw.println(ps.getInstallSource().mInstallerAttributionTag);
         }
         pw.print(prefix); pw.print("  packageSource=");
-        pw.println(ps.getInstallSource().packageSource);
+        pw.println(ps.getInstallSource().mPackageSource);
         if (ps.isLoading()) {
             pw.print(prefix); pw.println("  loadingProgress=" +
                     (int) (ps.getLoadingProgress() * 100) + "%");
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 415ddd3..6160519 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -764,7 +764,7 @@
 
     void populateInstallerExtras(Intent intent) {
         intent.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
-                mInstallSource.initiatingPackageName);
+                mInstallSource.mInitiatingPackageName);
 
         if (mVerificationInfo != null) {
             if (mVerificationInfo.mOriginatingUri != null) {
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index f5557c4..411c19f 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -18,6 +18,8 @@
 
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 
+import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
+
 import android.annotation.Nullable;
 
 import com.android.server.art.ReasonMapping;
@@ -26,8 +28,6 @@
 import com.android.server.pm.DexOptHelper;
 import com.android.server.pm.PackageManagerService;
 
-import dalvik.system.DexFile;
-
 /**
  * Options used for dexopt invocations.
  */
@@ -218,12 +218,11 @@
 
         /*@OptimizeFlags*/ int flags = extraFlags;
         if ((mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) == 0
-                && DexFile.isProfileGuidedCompilerFilter(mCompilerFilter)) {
-            // ART Service doesn't support bypassing this, so not setting this flag is not
-            // supported.
-            DexOptHelper.reportArtManagerFallback(mPackageName,
-                    "DEXOPT_CHECK_FOR_PROFILES_UPDATES not set with profile compiler filter");
-            return null;
+                && isProfileGuidedCompilerFilter(mCompilerFilter)) {
+            // ART Service doesn't support bypassing the profile update check when profiles are
+            // used, so not setting this flag is not supported.
+            throw new IllegalArgumentException(
+                    "DEXOPT_CHECK_FOR_PROFILES_UPDATES must be set with profile guided filter");
         }
         if ((mFlags & DEXOPT_FORCE) != 0) {
             flags |= ArtFlags.FLAG_FORCE;
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 951ddfa..e736f43 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -263,9 +263,10 @@
 
         @NonNull
         @Override
-        public PackageStateWrite setInstaller(@NonNull String installerPackageName) {
+        public PackageStateWrite setInstaller(@Nullable String installerPackageName,
+                int installerPackageUid) {
             if (mState != null) {
-                mState.setInstallerPackageName(installerPackageName);
+                mState.setInstallerPackage(installerPackageName, installerPackageUid);
             }
             return this;
         }
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index 1ac0b05..dc9cd3b 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -56,5 +56,5 @@
     PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo);
 
     @NonNull
-    PackageStateWrite setInstaller(@NonNull String installerPackageName);
+    PackageStateWrite setInstaller(@Nullable String installerPackageName, int installerPackageUid);
 }
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index e9827ce..6782229 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -35,7 +35,9 @@
 import java.time.DateTimeException;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -211,7 +213,11 @@
     }
 
     private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+        // Copy the listeners to notify under the "mListeners" lock but don't hold the lock while
+        // delivering the notifications to avoid deadlocks.
+        List<StateChangeListener> listenersToNotify;
         synchronized (mListeners) {
+            listenersToNotify = new ArrayList<>(mListeners.size());
             for (Map.Entry<StateChangeListener, HashSet<String>> listenerEntry
                     : mListeners.entrySet()) {
                 // It's unclear which set of the following two Sets is going to be larger in the
@@ -225,10 +231,14 @@
                 HashSet<String> monitoredKeys = listenerEntry.getValue();
                 Iterable<String> modifiedKeys = properties.getKeyset();
                 if (containsAny(monitoredKeys, modifiedKeys)) {
-                    listenerEntry.getKey().onChange();
+                    listenersToNotify.add(listenerEntry.getKey());
                 }
             }
         }
+
+        for (StateChangeListener listener : listenersToNotify) {
+            listener.onChange();
+        }
     }
 
     private static boolean containsAny(
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 4ef713c..dc2a974 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -166,8 +166,14 @@
         }
     }
 
-    private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (StateChangeListener changeListener : mConfigurationInternalListeners) {
+    private void handleConfigurationInternalChangeOnMainThread() {
+        // Copy the listeners holding the "this" lock but don't hold the lock while delivering the
+        // notifications to avoid deadlocks.
+        List<StateChangeListener> configurationInternalListeners;
+        synchronized (this) {
+            configurationInternalListeners = new ArrayList<>(this.mConfigurationInternalListeners);
+        }
+        for (StateChangeListener changeListener : configurationInternalListeners) {
             changeListener.onChange();
         }
     }
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 64adbb6..9d098c6 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -266,6 +266,8 @@
             for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
                 ITimeDetectorListener listener = mListeners.valueAt(listenerIndex);
                 try {
+                    // No need to surrender the mListeners lock while doing this:
+                    // ITimeDetectorListener is declared "oneway".
                     listener.onChange();
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Unable to notify listener=" + listener, e);
diff --git a/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java
index 8c9bd3b..6d2e723 100644
--- a/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java
@@ -77,12 +77,18 @@
         mListeners.add(listener);
     }
 
-    private synchronized void notifyFlightComplete() {
+    private void notifyFlightComplete() {
         if (DBG) {
             Slog.d(LOG_TAG, "notifyFlightComplete");
         }
 
-        for (Listener listener : mListeners) {
+        // Copy the listeners holding the "this" lock but don't hold the lock while delivering the
+        // notifications to avoid deadlocks.
+        List<Listener> listeners;
+        synchronized (this) {
+            listeners = new ArrayList<>(mListeners);
+        }
+        for (Listener listener : listeners) {
             listener.onFlightComplete();
         }
     }
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index dfb9619..295c5c8a 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -205,8 +205,14 @@
         }
     }
 
-    private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (StateChangeListener changeListener : mConfigurationInternalListeners) {
+    private void handleConfigurationInternalChangeOnMainThread() {
+        // Copy the listeners holding the "this" lock but don't hold the lock while delivering the
+        // notifications to avoid deadlocks.
+        List<StateChangeListener> configurationInternalListeners;
+        synchronized (this) {
+            configurationInternalListeners = new ArrayList<>(this.mConfigurationInternalListeners);
+        }
+        for (StateChangeListener changeListener : configurationInternalListeners) {
             changeListener.onChange();
         }
     }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index eecf0f7..3424251 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -351,6 +351,7 @@
     @GuardedBy("this")
     private void notifyStateChangeListenersAsynchronously() {
         for (StateChangeListener listener : mStateChangeListeners) {
+            // This is queuing asynchronous notification, so no need to surrender the "this" lock.
             mStateChangeHandler.post(listener::onChange);
         }
     }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c875f4a..5d08461 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -270,11 +270,12 @@
                         + " sys=" + sysWallpaperChanged
                         + " lock=" + lockWallpaperChanged
                         + " imagePending=" + wallpaper.imageWallpaperPending
-                        + " whichPending=0x" + Integer.toHexString(wallpaper.whichPending)
+                        + " mWhich=0x" + Integer.toHexString(wallpaper.mWhich)
                         + " written=" + written);
             }
 
             if (moved && lockWallpaperChanged) {
+                // TODO(b/253507223) Start lock screen WallpaperService
                 // We just migrated sys -> lock to preserve imagery for an impending
                 // new system-only wallpaper.  Tell keyguard about it and make sure it
                 // has the right SELinux label.
@@ -329,8 +330,10 @@
                                         false, wallpaper, callback);
                                 notifyColorsWhich |= FLAG_SYSTEM;
                             }
+                            // TODO(b/253507223) Start lock screen WallpaperService if only lock
+                            // screen wp changed
                             if (lockWallpaperChanged
-                                    || (wallpaper.whichPending & FLAG_LOCK) != 0) {
+                                    || (wallpaper.mWhich & FLAG_LOCK) != 0) {
                                 if (DEBUG) {
                                     Slog.i(TAG, "Lock-relevant wallpaper changed");
                                 }
@@ -609,7 +612,7 @@
 
         if (DEBUG) {
             Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
-                    + Integer.toHexString(wallpaper.whichPending)
+                    + Integer.toHexString(wallpaper.mWhich)
                     + " to " + wallpaper.cropFile.getName()
                     + " crop=(" + cropHint.width() + 'x' + cropHint.height()
                     + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
@@ -922,10 +925,9 @@
         boolean imageWallpaperPending;
 
         /**
-         * Which new wallpapers are being written; mirrors the 'which'
-         * selector bit field to setWallpaper().
+         * Which wallpaper is set. Flag values are from {@link SetWallpaperFlags}.
          */
-        int whichPending;
+        int mWhich;
 
         /**
          * Callback once the set + crop is finished
@@ -1166,7 +1168,7 @@
                 try {
                     connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
                             wpdData.mWidth, wpdData.mHeight,
-                            wpdData.mPadding, mDisplayId, FLAG_SYSTEM | FLAG_LOCK);
+                            wpdData.mPadding, mDisplayId, mWallpaper.mWhich);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed attaching wallpaper on display", e);
                     if (wallpaper != null && !wallpaper.wallpaperUpdating
@@ -2415,13 +2417,19 @@
 
     @Override
     public WallpaperInfo getWallpaperInfo(int userId) {
+        return getWallpaperInfoWithFlags(FLAG_SYSTEM, userId);
+    }
+
+    @Override
+    public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
         final boolean allow =
                 hasPermission(READ_WALLPAPER_INTERNAL) || hasPermission(QUERY_ALL_PACKAGES);
         if (allow) {
             userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
             synchronized (mLock) {
-                WallpaperData wallpaper = mWallpaperMap.get(userId);
+                WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+                        : mWallpaperMap.get(userId);
                 if (wallpaper != null && wallpaper.connection != null) {
                     return wallpaper.connection.mInfo;
                 }
@@ -2870,7 +2878,7 @@
                 ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
                 if (pfd != null) {
                     wallpaper.imageWallpaperPending = true;
-                    wallpaper.whichPending = which;
+                    wallpaper.mWhich = which;
                     wallpaper.setComplete = completion;
                     wallpaper.fromForegroundApp = fromForegroundApp;
                     wallpaper.cropHint.set(cropHint);
@@ -2901,6 +2909,7 @@
         lockWP.allowBackup = sysWP.allowBackup;
         lockWP.primaryColors = sysWP.primaryColors;
         lockWP.mWallpaperDimAmount = sysWP.mWallpaperDimAmount;
+        lockWP.mWhich = FLAG_LOCK;
 
         // Migrate the bitmap files outright; no need to copy
         try {
@@ -2953,25 +2962,27 @@
 
     @Override
     public void setWallpaperComponentChecked(ComponentName name, String callingPackage,
-            int userId) {
+            @SetWallpaperFlags int which, int userId) {
 
         if (isWallpaperSupported(callingPackage) && isSetWallpaperAllowed(callingPackage)) {
-            setWallpaperComponent(name, userId);
+            setWallpaperComponent(name, which, userId);
         }
     }
 
     // ToDo: Remove this version of the function
     @Override
     public void setWallpaperComponent(ComponentName name) {
-        setWallpaperComponent(name, UserHandle.getCallingUserId());
+        setWallpaperComponent(name, UserHandle.getCallingUserId(), FLAG_SYSTEM);
     }
 
-    private void setWallpaperComponent(ComponentName name, int userId) {
+    private void setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which,
+            int userId) {
         userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
                 false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
 
-        int which = FLAG_SYSTEM;
+        // TODO(b/253507223) Use passed destination and properly start lock screen LWP
+        int legacyWhich = FLAG_SYSTEM;
         boolean shouldNotifyColors = false;
         WallpaperData wallpaper;
 
@@ -2999,11 +3010,12 @@
 
             // New live wallpaper is also a lock wallpaper if nothing is set
             if (mLockWallpaperMap.get(userId) == null) {
-                which |= FLAG_LOCK;
+                legacyWhich |= FLAG_LOCK;
             }
 
             try {
                 wallpaper.imageWallpaperPending = false;
+                wallpaper.mWhich = which;
                 boolean same = changingToSame(name, wallpaper);
                 if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
                     if (!same) {
@@ -3032,7 +3044,7 @@
         }
 
         if (shouldNotifyColors) {
-            notifyWallpaperColorsChanged(wallpaper, which);
+            notifyWallpaperColorsChanged(wallpaper, legacyWhich);
             notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3ec24d5..97aee0b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2745,12 +2745,6 @@
                 newParent = candidateTf;
             }
         }
-        if (newParent.asTask() == null) {
-            // only collect task-fragments.
-            // TODO(b/258095975): we probably shouldn't ever collect the parent here since it isn't
-            //                    changing. The logic that changes it should collect it.
-            newParent.mTransitionController.collect(newParent);
-        }
         if (mStartActivity.getTaskFragment() == null
                 || mStartActivity.getTaskFragment() == newParent) {
             newParent.addChild(mStartActivity, POSITION_TOP);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b290bec..756e449b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -21,9 +21,6 @@
 import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -3108,20 +3105,6 @@
     }
 
     @Override
-    int getOrientation(int candidate) {
-        return canSpecifyOrientation() ? super.getOrientation(candidate) : SCREEN_ORIENTATION_UNSET;
-    }
-
-    private boolean canSpecifyOrientation() {
-        final int windowingMode = getWindowingMode();
-        final int activityType = getActivityType();
-        return windowingMode == WINDOWING_MODE_FULLSCREEN
-                || activityType == ACTIVITY_TYPE_HOME
-                || activityType == ACTIVITY_TYPE_RECENTS
-                || activityType == ACTIVITY_TYPE_ASSISTANT;
-    }
-
-    @Override
     void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
         final int count = mChildren.size();
         boolean isLeafTask = true;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index c15c57d..f3ed937 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -27,7 +27,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -649,17 +648,6 @@
             }, SCREEN_ORIENTATION_UNSET);
         }
 
-        // Apps and their containers are not allowed to specify an orientation of non floating
-        // visible tasks created by organizer and that has an adjacent task.
-        final Task nonFloatingTopTask =
-                getTask(t -> !t.getWindowConfiguration().tasksAreFloating());
-        if (nonFloatingTopTask != null) {
-            final Task task = nonFloatingTopTask.getCreatedByOrganizerTask();
-            if (task != null && task.getAdjacentTaskFragment() != null && task.isVisible()) {
-                return SCREEN_ORIENTATION_UNSPECIFIED;
-            }
-        }
-
         final int orientation = super.getOrientation(candidate);
         if (orientation != SCREEN_ORIENTATION_UNSET
                 && orientation != SCREEN_ORIENTATION_BEHIND) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f0e3644..91cb037 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -17,7 +17,9 @@
 package com.android.server.wm;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -27,6 +29,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -76,6 +80,7 @@
 import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -1809,6 +1814,51 @@
         }
     }
 
+    @ActivityInfo.ScreenOrientation
+    @Override
+    int getOrientation(@ActivityInfo.ScreenOrientation int candidate) {
+        if (shouldReportOrientationUnspecified()) {
+            return SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+        if (canSpecifyOrientation()) {
+            return super.getOrientation(candidate);
+        }
+        return SCREEN_ORIENTATION_UNSET;
+    }
+
+    /**
+     * Whether or not to allow this container to specify an app requested orientation.
+     *
+     * This is different from {@link #providesOrientation()} that
+     * 1. The container may still provide an orientation even if it can't specify the app requested
+     *    one, such as {@link #shouldReportOrientationUnspecified()}
+     * 2. Even if the container can specify an app requested orientation, it may not be used by the
+     *    parent container if it is {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
+     */
+    boolean canSpecifyOrientation() {
+        final int windowingMode = getWindowingMode();
+        final int activityType = getActivityType();
+        return windowingMode == WINDOWING_MODE_FULLSCREEN
+                || activityType == ACTIVITY_TYPE_HOME
+                || activityType == ACTIVITY_TYPE_RECENTS
+                || activityType == ACTIVITY_TYPE_ASSISTANT;
+    }
+
+    /**
+     * Whether or not the parent container should use the orientation provided by this container
+     * even if it is {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
+     */
+    @Override
+    boolean providesOrientation() {
+        return super.providesOrientation() || shouldReportOrientationUnspecified();
+    }
+
+    private boolean shouldReportOrientationUnspecified() {
+        // Apps and their containers are not allowed to specify orientation from adjacent
+        // TaskFragment.
+        return getAdjacentTaskFragment() != null && isVisibleRequested();
+    }
+
     @Override
     void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
         super.forAllTaskFragments(callback, traverseTopToBottom);
diff --git a/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_battery_opt_in.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_battery_opt_in.xml
new file mode 100644
index 0000000..4f41ee6
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_battery_opt_in.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:supportsPerformanceGameMode="false"
+    android:supportsBatteryGameMode="true"
+    android:allowGameAngleDriver="true"
+    android:allowGameDownscaling="true"
+    android:allowGameFpsOverride="true"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_performance_opt_in.xml b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_performance_opt_in.xml
new file mode 100644
index 0000000..0242aec
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/game_manager_service_metadata_config_interventions_enabled_performance_opt_in.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:supportsPerformanceGameMode="true"
+    android:supportsBatteryGameMode="false"
+    android:allowGameAngleDriver="true"
+    android:allowGameDownscaling="true"
+    android:allowGameFpsOverride="true"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 2f6b07b..66e7ec0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -586,7 +586,7 @@
         final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic();
         optionsMusicVolumeChanged.setDeliveryGroupPolicy(
                 BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
-        optionsMusicVolumeChanged.setDeliveryGroupKey("audio",
+        optionsMusicVolumeChanged.setDeliveryGroupMatchingKey("audio",
                 String.valueOf(AudioManager.STREAM_MUSIC));
 
         final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
@@ -595,7 +595,7 @@
         final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic();
         optionsAlarmVolumeChanged.setDeliveryGroupPolicy(
                 BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
-        optionsAlarmVolumeChanged.setDeliveryGroupKey("audio",
+        optionsAlarmVolumeChanged.setDeliveryGroupMatchingKey("audio",
                 String.valueOf(AudioManager.STREAM_ALARM));
 
         // Halt all processing so that we get a consistent view
@@ -651,7 +651,8 @@
         final BroadcastOptions optionsPackageChangedForUid = BroadcastOptions.makeBasic();
         optionsPackageChangedForUid.setDeliveryGroupPolicy(
                 BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
-        optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID));
+        optionsPackageChangedForUid.setDeliveryGroupMatchingKey("package",
+                String.valueOf(TEST_UID));
         optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
 
         final Intent secondPackageChangedForUid = createPackageChangedIntent(TEST_UID,
@@ -662,7 +663,8 @@
         final BroadcastOptions optionsPackageChangedForUid2 = BroadcastOptions.makeBasic();
         optionsPackageChangedForUid.setDeliveryGroupPolicy(
                 BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
-        optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID2));
+        optionsPackageChangedForUid.setDeliveryGroupMatchingKey("package",
+                String.valueOf(TEST_UID2));
         optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
 
         // Halt all processing so that we get a consistent view
@@ -685,6 +687,75 @@
         verifyPendingRecords(queue, List.of(packageChangedForUid2, expectedPackageChangedForUid));
     }
 
+    @Test
+    public void testDeliveryGroupPolicy_matchingFilter() {
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+        final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic();
+        optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+        final Intent musicVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+        musicVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                AudioManager.STREAM_MUSIC);
+        final IntentFilter filterMusicVolumeChanged = new IntentFilter(
+                AudioManager.VOLUME_CHANGED_ACTION);
+        filterMusicVolumeChanged.addExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                AudioManager.STREAM_MUSIC);
+        final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic();
+        optionsMusicVolumeChanged.setDeliveryGroupPolicy(
+                BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+        optionsMusicVolumeChanged.setDeliveryGroupMatchingFilter(filterMusicVolumeChanged);
+
+        final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+        alarmVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                AudioManager.STREAM_ALARM);
+        final IntentFilter filterAlarmVolumeChanged = new IntentFilter(
+                AudioManager.VOLUME_CHANGED_ACTION);
+        filterAlarmVolumeChanged.addExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                AudioManager.STREAM_ALARM);
+        final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic();
+        optionsAlarmVolumeChanged.setDeliveryGroupPolicy(
+                BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+        optionsAlarmVolumeChanged.setDeliveryGroupMatchingFilter(filterAlarmVolumeChanged);
+
+        // Halt all processing so that we get a consistent view
+        mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+                optionsMusicVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+                optionsAlarmVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+                optionsMusicVolumeChanged));
+
+        final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        // Verify that the older musicVolumeChanged has been removed.
+        verifyPendingRecords(queue,
+                List.of(timeTick, alarmVolumeChanged, musicVolumeChanged));
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+                optionsAlarmVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+                optionsMusicVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+                optionsAlarmVolumeChanged));
+        // Verify that the older alarmVolumeChanged has been removed.
+        verifyPendingRecords(queue,
+                List.of(timeTick, musicVolumeChanged, alarmVolumeChanged));
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
+                optionsMusicVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged,
+                optionsAlarmVolumeChanged));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+        // Verify that the older timeTick has been removed.
+        verifyPendingRecords(queue,
+                List.of(musicVolumeChanged, alarmVolumeChanged, timeTick));
+    }
+
     private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
         final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
         packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 05ed0e2..3864c88 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -36,13 +36,17 @@
 import static org.mockito.Mockito.doReturn;
 
 import android.app.ActivityManagerInternal;
+import android.app.BroadcastOptions;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
@@ -176,9 +180,8 @@
         intent.putExtra(Intent.EXTRA_INDEX, 42);
         final BroadcastRecord r = createBroadcastRecord(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
-                (uid, extras) -> {
-                    return Bundle.EMPTY;
-                });
+                (uid, extras) -> Bundle.EMPTY,
+                null /* options */);
         final Intent actual = r.getReceiverIntent(r.receivers.get(0));
         assertEquals(PACKAGE1, actual.getComponent().getPackageName());
         assertEquals(-1, actual.getIntExtra(Intent.EXTRA_INDEX, -1));
@@ -192,9 +195,8 @@
         intent.putExtra(Intent.EXTRA_INDEX, 42);
         final BroadcastRecord r = createBroadcastRecord(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
-                (uid, extras) -> {
-                    return null;
-                });
+                (uid, extras) -> null,
+                null /* options */);
         final Intent actual = r.getReceiverIntent(r.receivers.get(0));
         assertNull(actual);
         assertNull(r.intent.getComponent());
@@ -290,6 +292,122 @@
         assertEquals(origReceiversSize, br.receivers.size());
     }
 
+    @Test
+    public void testMatchesDeliveryGroup() {
+        final List<ResolveInfo> receivers = List.of(createResolveInfo(PACKAGE1, getAppId(1)));
+
+        final Intent intent1 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent1.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent1.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        final BroadcastOptions options1 = BroadcastOptions.makeBasic();
+        final BroadcastRecord record1 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent1, options1);
+
+        final Intent intent2 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent2.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent2.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+        final BroadcastOptions options2 = BroadcastOptions.makeBasic();
+        final BroadcastRecord record2 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent2, options2);
+
+        assertTrue(record2.matchesDeliveryGroup(record1));
+    }
+
+    @Test
+    public void testMatchesDeliveryGroup_withMatchingKey() {
+        final List<ResolveInfo> receivers = List.of(createResolveInfo(PACKAGE1, getAppId(1)));
+
+        final Intent intent1 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent1.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent1.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        final BroadcastOptions options1 = BroadcastOptions.makeBasic();
+        options1.setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, "key1");
+        final BroadcastRecord record1 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent1, options1);
+
+        final Intent intent2 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent2.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent2.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+        final BroadcastOptions options2 = BroadcastOptions.makeBasic();
+        options2.setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, "key2");
+        final BroadcastRecord record2 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent2, options2);
+
+        final Intent intent3 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent3.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 1);
+        intent3.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 3);
+        final BroadcastOptions options3 = BroadcastOptions.makeBasic();
+        options3.setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, "key1");
+        final BroadcastRecord record3 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent3, options3);
+
+        // record2 and record1 have different matching keys, so their delivery groups
+        // shouldn't match
+        assertFalse(record2.matchesDeliveryGroup(record1));
+        // record3 and record2 have different matching keys, so their delivery groups
+        // shouldn't match
+        assertFalse(record3.matchesDeliveryGroup(record2));
+        // record3 and record1 have same matching keys, so their delivery groups should match even
+        // if the intent has different extras.
+        assertTrue(record3.matchesDeliveryGroup(record1));
+    }
+
+    @Test
+    public void testMatchesDeliveryGroup_withMatchingFilter() {
+        final List<ResolveInfo> receivers = List.of(createResolveInfo(PACKAGE1, getAppId(1)));
+
+        final Intent intent1 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent1.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent1.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        intent1.putExtra(Intent.EXTRA_REASON, "reason1");
+        final IntentFilter filter1 = new IntentFilter(Intent.ACTION_SERVICE_STATE);
+        final PersistableBundle bundle1 = new PersistableBundle();
+        bundle1.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        bundle1.putInt(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        filter1.setExtras(bundle1);
+        final BroadcastOptions options1 = BroadcastOptions.makeBasic();
+        options1.setDeliveryGroupMatchingFilter(filter1);
+        final BroadcastRecord record1 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent1, options1);
+
+        final Intent intent2 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent2.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        intent2.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+        intent2.putExtra(Intent.EXTRA_REASON, "reason2");
+        final IntentFilter filter2 = new IntentFilter(Intent.ACTION_SERVICE_STATE);
+        final PersistableBundle bundle2 = new PersistableBundle();
+        bundle2.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        bundle2.putInt(SubscriptionManager.EXTRA_SLOT_INDEX, 2);
+        filter2.setExtras(bundle2);
+        final BroadcastOptions options2 = BroadcastOptions.makeBasic();
+        options2.setDeliveryGroupMatchingFilter(filter2);
+        final BroadcastRecord record2 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent2, options2);
+
+        final Intent intent3 = new Intent(Intent.ACTION_SERVICE_STATE);
+        intent3.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 1);
+        intent3.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 3);
+        intent3.putExtra(Intent.EXTRA_REASON, "reason3");
+        final IntentFilter filter3 = new IntentFilter(Intent.ACTION_SERVICE_STATE);
+        final PersistableBundle bundle3 = new PersistableBundle();
+        bundle3.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 0);
+        bundle3.putInt(SubscriptionManager.EXTRA_SLOT_INDEX, 1);
+        filter3.setExtras(bundle3);
+        final BroadcastOptions options3 = BroadcastOptions.makeBasic();
+        options3.setDeliveryGroupMatchingFilter(filter3);
+        final BroadcastRecord record3 = createBroadcastRecord(receivers, UserHandle.USER_ALL,
+                intent3, options3);
+
+        // record2's matchingFilter doesn't match record1's intent, so their delivery groups
+        // shouldn't match
+        assertFalse(record2.matchesDeliveryGroup(record1));
+        // record3's matchingFilter doesn't match record2's intent, so their delivery groups
+        // shouldn't match
+        assertFalse(record3.matchesDeliveryGroup(record2));
+        // record3's matchingFilter matches record1's intent, so their delivery groups should match.
+        assertTrue(record3.matchesDeliveryGroup(record1));
+    }
+
     private BroadcastRecord createBootCompletedBroadcastRecord(String action) {
         final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, USER_LIST);
         final BroadcastRecord br = createBroadcastRecord(receivers, UserHandle.USER_ALL,
@@ -539,11 +657,19 @@
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
             Intent intent) {
-        return createBroadcastRecord(receivers, userId, intent, null);
+        return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
+                null /* options */);
     }
 
     private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
-            Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+            Intent intent, BroadcastOptions options) {
+        return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
+                options);
+    }
+
+    private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+            Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            BroadcastOptions options) {
         return new BroadcastRecord(
                 mQueue /* queue */,
                 intent,
@@ -558,7 +684,7 @@
                 null /* excludedPermissions */,
                 null /* excludedPackages */,
                 0 /* appOp */,
-                null /* options */,
+                options,
                 new ArrayList<>(receivers), // Make a copy to not affect the original list.
                 null /* resultToApp */,
                 null /* resultTo */,
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 24e5175..dc77762 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -19,6 +19,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -32,6 +33,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -39,8 +41,10 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.GameManager;
+import android.app.GameModeConfiguration;
 import android.app.GameModeInfo;
 import android.app.GameState;
+import android.app.IGameModeListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -55,12 +59,13 @@
 import android.content.res.XmlResourceParser;
 import android.hardware.power.Mode;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.PowerManagerInternal;
+import android.os.RemoteException;
 import android.os.UserManager;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
-import android.util.ArraySet;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -73,7 +78,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoSession;
@@ -82,6 +89,7 @@
 import java.io.File;
 import java.nio.file.Files;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.function.Supplier;
@@ -108,6 +116,9 @@
     private UserManager mMockUserManager;
     private BroadcastReceiver mShutDownActionReceiver;
 
+    @Captor
+    ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;
+
     // Stolen from ConnectivityServiceTest.MockContext
     class MockContext extends ContextWrapper {
         private static final String TAG = "MockContext";
@@ -178,7 +189,7 @@
 
         @Override
         public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
-            mShutDownActionReceiver =  receiver;
+            mShutDownActionReceiver = receiver;
             return null;
         }
     }
@@ -387,6 +398,17 @@
                 .thenReturn(applicationInfo);
     }
 
+    private void mockInterventionsEnabledBatteryOptInFromXml() throws Exception {
+        seedGameManagerServiceMetaDataFromFile(mPackageName, 123, "res/xml/"
+                + "game_manager_service_metadata_config_interventions_enabled_battery_opt_in.xml");
+    }
+
+    private void mockInterventionsEnabledPerformanceOptInFromXml() throws Exception {
+        seedGameManagerServiceMetaDataFromFile(mPackageName, 123, "res/xml/"
+                + "game_manager_service_metadata_config_interventions_enabled_performance_opt_in"
+                + ".xml");
+    }
+
     private void mockInterventionsEnabledNoOptInFromXml() throws Exception {
         seedGameManagerServiceMetaDataFromFile(mPackageName, 123,
                 "res/xml/game_manager_service_metadata_config_interventions_enabled_no_opt_in.xml");
@@ -431,7 +453,7 @@
     }
 
     /**
-     * By default game mode is not supported.
+     * By default game mode is set to STANDARD
      */
     @Test
     public void testGameModeDefaultValue() {
@@ -441,7 +463,7 @@
         startUser(gameManagerService, USER_ID_1);
         mockModifyGameModeGranted();
 
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+        assertEquals(GameManager.GAME_MODE_STANDARD,
                 gameManagerService.getGameMode(mPackageName, USER_ID_1));
     }
 
@@ -457,7 +479,7 @@
         mockModifyGameModeGranted();
 
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+        assertEquals(GameManager.GAME_MODE_STANDARD,
                 gameManagerService.getGameMode(mPackageName, USER_ID_2));
     }
 
@@ -473,7 +495,7 @@
         startUser(gameManagerService, USER_ID_1);
         gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
         mockModifyGameModeGranted();
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+        assertEquals(GameManager.GAME_MODE_STANDARD,
                 gameManagerService.getGameMode(mPackageName, USER_ID_1));
         // We need to make sure the mode is supported before setting it.
         mockDeviceConfigAll();
@@ -580,31 +602,42 @@
                 gameManagerService.getGameMode(mPackageName, USER_ID_2));
     }
 
-    private void checkReportedModes(GameManagerService gameManagerService, int ...requiredModes) {
-        if (gameManagerService == null) {
-            gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper());
-            startUser(gameManagerService, USER_ID_1);
-            gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-        }
-        ArraySet<Integer> reportedModes = new ArraySet<>();
-        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
-        for (int mode : modes) {
-            reportedModes.add(mode);
-        }
-        assertEquals(requiredModes.length, reportedModes.size());
-        for (int requiredMode : requiredModes) {
-            assertTrue("Required game mode not supported: " + requiredMode,
-                    reportedModes.contains(requiredMode));
-        }
+    private GameManagerService createServiceAndStartUser(int userId) {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
+        startUser(gameManagerService, userId);
+        return gameManagerService;
+    }
+
+    private void checkReportedAvailableGameModes(GameManagerService gameManagerService,
+            int... requiredAvailableModes) {
+        Arrays.sort(requiredAvailableModes);
+        // check getAvailableGameModes
+        int[] reportedAvailableModes = gameManagerService.getAvailableGameModes(mPackageName);
+        Arrays.sort(reportedAvailableModes);
+        assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
+
+        // check GetModeInfo.getAvailableGameModes
+        GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertNotNull(info);
+        reportedAvailableModes = info.getAvailableGameModes();
+        Arrays.sort(reportedAvailableModes);
+        assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
+    }
+
+    private void checkReportedOptedInGameModes(GameManagerService gameManagerService,
+            int... requiredOptedInModes) {
+        Arrays.sort(requiredOptedInModes);
+        // check GetModeInfo.getOptedInGameModes
+        GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertNotNull(info);
+        int[] optedInModes = info.getOptedInGameModes();
+        Arrays.sort(optedInModes);
+        assertArrayEquals(requiredOptedInModes, optedInModes);
     }
 
     private void checkDownscaling(GameManagerService gameManagerService,
-                int gameMode, float scaling) {
-        if (gameManagerService == null) {
-            gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper());
-            startUser(gameManagerService, USER_ID_1);
-            gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-        }
+            int gameMode, float scaling) {
         GameManagerService.GamePackageConfiguration config =
                 gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(scaling, config.getGameModeConfiguration(gameMode).getScaling(), 0.01f);
@@ -625,8 +658,6 @@
 
     private void checkLoadingBoost(GameManagerService gameManagerService, int gameMode,
             int loadingBoost) {
-        gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-
         // Validate GamePackageConfiguration returns the correct value.
         GameManagerService.GamePackageConfiguration config =
                 gameManagerService.getConfig(mPackageName, USER_ID_1);
@@ -639,11 +670,6 @@
     }
 
     private void checkFps(GameManagerService gameManagerService, int gameMode, int fps) {
-        if (gameManagerService == null) {
-            gameManagerService = new GameManagerService(mMockContext, mTestLooper.getLooper());
-            startUser(gameManagerService, USER_ID_1);
-            gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-        }
         GameManagerService.GamePackageConfiguration config =
                 gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
@@ -662,7 +688,8 @@
     public void testDeviceConfigDefault() {
         mockDeviceConfigDefault();
         mockModifyGameModeGranted();
-        checkReportedModes(null);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -672,38 +699,8 @@
     public void testDeviceConfigNone() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        checkReportedModes(null);
-    }
-
-    /**
-     * Phenotype device config for performance mode exists and is valid.
-     */
-    @Test
-    public void testDeviceConfigPerformance() {
-        mockDeviceConfigPerformance();
-        mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD);
-    }
-
-    /**
-     * Phenotype device config for battery mode exists and is valid.
-     */
-    @Test
-    public void testDeviceConfigBattery() {
-        mockDeviceConfigBattery();
-        mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
-    }
-
-    /**
-     * Phenotype device configs for both battery and performance modes exists and are valid.
-     */
-    @Test
-    public void testDeviceConfigAll() {
-        mockDeviceConfigAll();
-        mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -713,7 +710,8 @@
     public void testDeviceConfigInvalid() {
         mockDeviceConfigInvalid();
         mockModifyGameModeGranted();
-        checkReportedModes(null);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -723,7 +721,8 @@
     public void testDeviceConfigMalformed() {
         mockDeviceConfigMalformed();
         mockModifyGameModeGranted();
-        checkReportedModes(null);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -740,8 +739,8 @@
         gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_PERFORMANCE, "120", "0.3");
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120);
     }
@@ -760,8 +759,8 @@
         gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_BATTERY, "60", "0.5");
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.5f);
         checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 60);
     }
@@ -782,8 +781,9 @@
         gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_BATTERY, "60", "0.5");
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.5f);
@@ -876,8 +876,8 @@
         gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_PERFORMANCE);
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
     }
@@ -899,8 +899,8 @@
         gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_BATTERY);
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f);
         checkFps(gameManagerService, GameManager.GAME_MODE_BATTERY, 30);
     }
@@ -923,8 +923,9 @@
 
         gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1, -1);
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f);
@@ -952,8 +953,9 @@
         gameManagerService.resetGameModeConfigOverride(mPackageName, USER_ID_1,
                 GameManager.GAME_MODE_BATTERY);
 
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
-                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0.3f);
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 120);
         checkDownscaling(gameManagerService, GameManager.GAME_MODE_BATTERY, 0.7f);
@@ -968,12 +970,12 @@
         mockGameModeOptInAll();
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
 
-
     /**
      * BATTERY game mode is available through the app manifest opt-in.
      */
@@ -982,7 +984,9 @@
         mockGameModeOptInBattery();
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -993,7 +997,9 @@
         mockGameModeOptInPerformance();
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -1005,8 +1011,9 @@
         mockGameModeOptInBattery();
         mockDeviceConfigPerformance();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -1018,8 +1025,9 @@
         mockGameModeOptInPerformance();
         mockDeviceConfigBattery();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
-                GameManager.GAME_MODE_STANDARD);
+        checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1),
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
     }
 
     /**
@@ -1029,7 +1037,8 @@
     public void testInterventionAllowScalingDefault() throws Exception {
         mockDeviceConfigPerformance();
         mockModifyGameModeGranted();
-        checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
+        checkDownscaling(createServiceAndStartUser(USER_ID_1), GameManager.GAME_MODE_PERFORMANCE,
+                0.5f);
     }
 
     /**
@@ -1040,7 +1049,8 @@
         mockDeviceConfigPerformance();
         mockInterventionAllowDownscaleFalse();
         mockModifyGameModeGranted();
-        checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, -1.0f);
+        checkDownscaling(createServiceAndStartUser(USER_ID_1), GameManager.GAME_MODE_PERFORMANCE,
+                -1.0f);
     }
 
     /**
@@ -1052,7 +1062,8 @@
         mockDeviceConfigPerformance();
         mockInterventionAllowDownscaleTrue();
         mockModifyGameModeGranted();
-        checkDownscaling(null, GameManager.GAME_MODE_PERFORMANCE, 0.5f);
+        checkDownscaling(createServiceAndStartUser(USER_ID_1), GameManager.GAME_MODE_PERFORMANCE,
+                0.5f);
     }
 
     /**
@@ -1060,12 +1071,9 @@
      */
     @Test
     public void testInterventionAllowAngleDefault() throws Exception {
-        GameManagerService gameManagerService = new GameManagerService(
-                mMockContext, mTestLooper.getLooper());
-
-        startUser(gameManagerService, USER_ID_1);
         mockDeviceConfigPerformance();
         mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
     }
 
@@ -1075,12 +1083,9 @@
      */
     @Test
     public void testInterventionAllowLoadingBoostDefault() throws Exception {
-        GameManagerService gameManagerService = new GameManagerService(
-                mMockContext, mTestLooper.getLooper());
-
-        startUser(gameManagerService, USER_ID_1);
         mockDeviceConfigPerformance();
         mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         checkLoadingBoost(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, -1);
     }
 
@@ -1089,12 +1094,10 @@
      */
     @Test
     public void testInterventionAllowAngleFalse() throws Exception {
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-        startUser(gameManagerService, USER_ID_1);
         mockDeviceConfigPerformanceEnableAngle();
         mockInterventionAllowAngleFalse();
         mockModifyGameModeGranted();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
     }
 
@@ -1171,8 +1174,9 @@
     public void testInterventionFps() throws Exception {
         mockDeviceConfigAll();
         mockModifyGameModeGranted();
-        checkFps(null, GameManager.GAME_MODE_PERFORMANCE, 90);
-        checkFps(null, GameManager.GAME_MODE_BATTERY, 30);
+        GameManagerService service = createServiceAndStartUser(USER_ID_1);
+        checkFps(service, GameManager.GAME_MODE_PERFORMANCE, 90);
+        checkFps(service, GameManager.GAME_MODE_BATTERY, 30);
     }
 
     /**
@@ -1195,7 +1199,7 @@
 
     /**
      * Ensure that, if a game no longer supports any game modes, we set the game mode to
-     * UNSUPPORTED
+     * STANDARD
      */
     @Test
     public void testUnsetInvalidGameMode() throws Exception {
@@ -1206,7 +1210,7 @@
         startUser(gameManagerService, USER_ID_1);
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
         gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+        assertEquals(GameManager.GAME_MODE_STANDARD,
                 gameManagerService.getGameMode(mPackageName, USER_ID_1));
     }
 
@@ -1247,6 +1251,7 @@
     static {
         System.loadLibrary("mockingservicestestjni");
     }
+
     @Test
     public void testGetGameModeInfoPermissionDenied() {
         mockDeviceConfigAll();
@@ -1261,30 +1266,30 @@
     }
 
     @Test
-    public void testGetGameModeInfoWithAllGameModesDefault() {
-        mockDeviceConfigAll();
-        mockModifyGameModeGranted();
-        GameManagerService gameManagerService =
-                new GameManagerService(mMockContext, mTestLooper.getLooper());
-        startUser(gameManagerService, USER_ID_1);
-        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
-
-        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
-        assertEquals(3, gameModeInfo.getAvailableGameModes().length);
-    }
-
-    @Test
     public void testGetGameModeInfoWithAllGameModes() {
         mockDeviceConfigAll();
         mockModifyGameModeGranted();
         GameManagerService gameManagerService =
                 new GameManagerService(mMockContext, mTestLooper.getLooper());
         startUser(gameManagerService, USER_ID_1);
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+        assertTrue(gameModeInfo.isDownscalingAllowed());
+        assertTrue(gameModeInfo.isFpsOverrideAllowed());
 
-        assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode());
-        assertEquals(3, gameModeInfo.getAvailableGameModes().length);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        checkReportedOptedInGameModes(gameManagerService);
+
+        assertEquals(new GameModeConfiguration.Builder()
+                .setFpsOverride(30)
+                .setScalingFactor(0.7f)
+                .build(), gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertEquals(new GameModeConfiguration.Builder()
+                .setFpsOverride(90)
+                .setScalingFactor(0.5f)
+                .build(), gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
     }
 
     @Test
@@ -1298,7 +1303,14 @@
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
 
         assertEquals(GameManager.GAME_MODE_BATTERY, gameModeInfo.getActiveGameMode());
-        assertEquals(2, gameModeInfo.getAvailableGameModes().length);
+
+        checkReportedAvailableGameModes(gameManagerService,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        checkReportedOptedInGameModes(gameManagerService);
+
+        assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
     }
 
     @Test
@@ -1312,11 +1324,17 @@
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
 
         assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode());
-        assertEquals(2, gameModeInfo.getAvailableGameModes().length);
+        checkReportedAvailableGameModes(gameManagerService,
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        checkReportedOptedInGameModes(gameManagerService);
+
+        assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
     }
 
     @Test
-    public void testGetGameModeInfoWithUnsupportedGameMode() {
+    public void testGetGameModeInfoWithDefaultGameModes() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
         GameManagerService gameManagerService =
@@ -1324,8 +1342,103 @@
         startUser(gameManagerService, USER_ID_1);
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
 
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode());
-        assertEquals(0, gameModeInfo.getAvailableGameModes().length);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_STANDARD));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+    }
+
+    @Test
+    public void testGetGameModeInfoWithAllGameModesOptedIn_noDeviceConfig()
+            throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsEnabledAllOptInFromXml();
+        mockDeviceConfigNone();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+        verifyAllModesOptedInAndInterventionsAvailable(gameManagerService, gameModeInfo);
+
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+    }
+
+    @Test
+    public void testGetGameModeInfoWithAllGameModesOptedIn_allDeviceConfig()
+            throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsEnabledAllOptInFromXml();
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+        verifyAllModesOptedInAndInterventionsAvailable(gameManagerService, gameModeInfo);
+
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+    }
+
+    private void verifyAllModesOptedInAndInterventionsAvailable(
+            GameManagerService gameManagerService,
+            GameModeInfo gameModeInfo) {
+        checkReportedAvailableGameModes(gameManagerService,
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
+        checkReportedOptedInGameModes(gameManagerService,
+                GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY);
+        assertTrue(gameModeInfo.isFpsOverrideAllowed());
+        assertTrue(gameModeInfo.isDownscalingAllowed());
+    }
+
+    @Test
+    public void testGetGameModeInfoWithBatteryModeOptedIn_withBatteryDeviceConfig()
+            throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsEnabledBatteryOptInFromXml();
+        mockDeviceConfigBattery();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
+                GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
+        checkReportedOptedInGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY);
+
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+    }
+
+    @Test
+    public void testGetGameModeInfoWithPerformanceModeOptedIn_withAllDeviceConfig()
+            throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsEnabledPerformanceOptInFromXml();
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_CUSTOM);
+        checkReportedOptedInGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE);
+
+        assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
+        assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
+    }
+
+    @Test
+    public void testGetGameModeInfoWithInterventionsDisabled() throws Exception {
+        mockModifyGameModeGranted();
+        mockInterventionsDisabledAllOptInFromXml();
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+        assertFalse(gameModeInfo.isFpsOverrideAllowed());
+        assertFalse(gameModeInfo.isDownscalingAllowed());
     }
 
     @Test
@@ -1381,7 +1494,7 @@
     }
 
     private void mockInterventionListForMultipleUsers() {
-        final String[] packageNames = new String[] {"com.android.app0",
+        final String[] packageNames = new String[]{"com.android.app0",
                 "com.android.app1", "com.android.app2"};
 
         final ApplicationInfo[] applicationInfos = new ApplicationInfo[3];
@@ -1424,8 +1537,8 @@
         final Context context = InstrumentationRegistry.getContext();
         GameManagerService gameManagerService =
                 new GameManagerService(mMockContext,
-                                       mTestLooper.getLooper(),
-                                       context.getFilesDir());
+                        mTestLooper.getLooper(),
+                        context.getFilesDir());
         startUser(gameManagerService, USER_ID_1);
         startUser(gameManagerService, USER_ID_2);
 
@@ -1437,14 +1550,14 @@
 
         /* Expected fileOutput (order may vary)
          # user 1001:
-         com.android.app2 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.5,fps=60
+         com.android.app2 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.5,fps=60
          com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
-         com.android.app0 <UID>   0   2   angle=0,scaling=0.6,fps=120 3   angle=0,scaling=0.7,fps=30
+         com.android.app0 <UID>   1   2   angle=0,scaling=0.6,fps=120 3   angle=0,scaling=0.7,fps=30
 
          # user 1002:
-         com.android.app2 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
+         com.android.app2 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
          com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
-         com.android.app0 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
+         com.android.app0 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
          The current game mode would only be set to non-zero if the current user have that game
          installed.
         */
@@ -1461,7 +1574,7 @@
         assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60");
         splitLine = fileOutput.get(1).split("\\s+");
         assertEquals(splitLine[0], "com.android.app1");
-        assertEquals(splitLine[2], "0");
+        assertEquals(splitLine[2], "1");
         assertEquals(splitLine[3], "2");
         assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
         assertEquals(splitLine[5], "3");
@@ -1484,7 +1597,7 @@
 
         splitLine = fileOutput.get(0).split("\\s+");
         assertEquals(splitLine[0], "com.android.app2");
-        assertEquals(splitLine[2], "0");
+        assertEquals(splitLine[2], "1");
         assertEquals(splitLine[3], "2");
         assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
         assertEquals(splitLine[5], "3");
@@ -1498,7 +1611,7 @@
         assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
         splitLine = fileOutput.get(2).split("\\s+");
         assertEquals(splitLine[0], "com.android.app0");
-        assertEquals(splitLine[2], "0");
+        assertEquals(splitLine[2], "1");
         assertEquals(splitLine[3], "2");
         assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
         assertEquals(splitLine[5], "3");
@@ -1518,8 +1631,8 @@
         startUser(gameManagerService, USER_ID_1);
         startUser(gameManagerService, USER_ID_2);
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
-                GameManager.GAME_MODE_BATTERY);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_CUSTOM);
         assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
                 GameManager.GAME_MODE_BATTERY);
 
@@ -1527,16 +1640,16 @@
         switchUser(gameManagerService, USER_ID_1, USER_ID_2);
         assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_2),
                 GameManager.GAME_MODE_STANDARD);
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
-                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_CUSTOM);
 
         switchUser(gameManagerService, USER_ID_2, USER_ID_1);
-        checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
-                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+                GameManager.GAME_MODE_BATTERY);
+        checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_CUSTOM);
     }
 
     @Test
@@ -1640,6 +1753,33 @@
     }
 
     @Test
+    public void testUpdateCustomGameModeConfiguration_permissionDenied() {
+        mockModifyGameModeDenied();
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        assertThrows(SecurityException.class, () -> {
+            gameManagerService.updateCustomGameModeConfiguration(mPackageName,
+                    new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(),
+                    USER_ID_1);
+        });
+    }
+
+    @Test
+    public void testUpdateCustomGameModeConfiguration_noUserId() {
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_2);
+        assertThrows(IllegalArgumentException.class, () -> {
+            gameManagerService.updateCustomGameModeConfiguration(mPackageName,
+                    new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(),
+                    USER_ID_1);
+        });
+    }
+
+    @Test
     public void testWritingSettingFile_onShutdown() throws InterruptedException {
         mockModifyGameModeGranted();
         mockDeviceConfigAll();
@@ -1743,6 +1883,53 @@
                 ArgumentMatchers.eq(0.0f));
     }
 
+    @Test
+    public void testAddGameModeListener() throws RemoteException {
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        mockDeviceConfigAll();
+        startUser(gameManagerService, USER_ID_1);
+        mockModifyGameModeGranted();
+
+        IGameModeListener mockListener = Mockito.mock(IGameModeListener.class);
+        IBinder binder = Mockito.mock(IBinder.class);
+        when(mockListener.asBinder()).thenReturn(binder);
+        gameManagerService.addGameModeListener(mockListener);
+        verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        verify(mockListener).onGameModeChanged(mPackageName, GameManager.GAME_MODE_STANDARD,
+                GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        reset(mockListener);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        verify(mockListener).onGameModeChanged(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+                GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        reset(mockListener);
+
+        mDeathRecipientCaptor.getValue().binderDied();
+        verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt());
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_CUSTOM, USER_ID_1);
+        verify(mockListener, never()).onGameModeChanged(anyString(), anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testRemoveGameModeListener() throws RemoteException {
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        mockDeviceConfigAll();
+        startUser(gameManagerService, USER_ID_1);
+        mockModifyGameModeGranted();
+
+        IGameModeListener mockListener = Mockito.mock(IGameModeListener.class);
+        IBinder binder = Mockito.mock(IBinder.class);
+        when(mockListener.asBinder()).thenReturn(binder);
+
+        gameManagerService.addGameModeListener(mockListener);
+        gameManagerService.removeGameModeListener(mockListener);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        verify(mockListener, never()).onGameModeChanged(anyString(), anyInt(), anyInt(), anyInt());
+    }
+
     private static void deleteFolder(File folder) {
         File[] files = folder.listFiles();
         if (files != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 1464405..cfd5279 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -723,7 +723,7 @@
         params.isStaged = true;
 
         InstallSource installSource = InstallSource.create("testInstallInitiator",
-                "testInstallOriginator", "testInstaller", "testAttributionTag",
+                "testInstallOriginator", "testInstaller", 100, "testAttributionTag",
                 PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
 
         PackageInstallerSession session = new PackageInstallerSession(
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index e8dd541..582c78b 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -192,7 +192,7 @@
         final Context context = InstrumentationRegistry.getContext();
         GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
         assertTrue(settings.readPersistentDataLocked());
-        assertEquals(0, settings.getGameModeLocked(PACKAGE_NAME_1));
+        assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_1));
         assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
         assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
index 1a8ef9e..3575b57 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
@@ -17,6 +17,8 @@
 package com.android.server.pm;
 
 
+import static android.os.Process.INVALID_UID;
+
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
 import static org.junit.Assert.assertFalse;
@@ -1014,7 +1016,7 @@
                 DUMMY_TARGET_APPID);
         PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
                 DUMMY_CALLING_APPID,
-                withInstallSource(target.getPackageName(), null, null, null, false));
+                withInstallSource(target.getPackageName(), null, null, INVALID_UID, null, false));
 
         assertFalse(
                 appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
@@ -1033,7 +1035,7 @@
                 DUMMY_TARGET_APPID);
         PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
                 DUMMY_CALLING_APPID,
-                withInstallSource(target.getPackageName(), null, null, null, true));
+                withInstallSource(target.getPackageName(), null, null, INVALID_UID, null, true));
 
         assertTrue(
                 appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
@@ -1056,8 +1058,8 @@
                 DUMMY_TARGET_APPID);
         watcher.verifyChangeReported("add package");
         PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
-                DUMMY_CALLING_APPID, withInstallSource(null, target.getPackageName(), null, null,
-                        false));
+                DUMMY_CALLING_APPID, withInstallSource(null, target.getPackageName(), null,
+                        INVALID_UID, null, false));
         watcher.verifyChangeReported("add package");
 
         assertTrue(
@@ -1082,8 +1084,8 @@
                 DUMMY_TARGET_APPID);
         watcher.verifyChangeReported("add package");
         PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"),
-                DUMMY_CALLING_APPID, withInstallSource(null, null, target.getPackageName(), null,
-                        false));
+                DUMMY_CALLING_APPID, withInstallSource(null, null, target.getPackageName(),
+                        DUMMY_TARGET_APPID, null, false));
         watcher.verifyChangeReported("add package");
 
         assertFalse(
@@ -1668,11 +1670,11 @@
     }
 
     private WithSettingBuilder withInstallSource(String initiatingPackageName,
-            String originatingPackageName, String installerPackageName,
+            String originatingPackageName, String installerPackageName, int installerPackageUid,
             String installerAttributionTag, boolean isInitiatingPackageUninstalled) {
         final InstallSource installSource = InstallSource.create(initiatingPackageName,
-                originatingPackageName, installerPackageName, installerAttributionTag,
-                /* isOrphaned= */ false, isInitiatingPackageUninstalled);
+                originatingPackageName, installerPackageName, installerPackageUid,
+                installerAttributionTag, /* isOrphaned= */ false, isInitiatingPackageUninstalled);
         return setting -> setting.setInstallSource(installSource);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 648f895..4da082e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -167,7 +167,7 @@
             params.isMultiPackage = true;
         }
         InstallSource installSource = InstallSource.create("testInstallInitiator",
-                "testInstallOriginator", "testInstaller", "testAttributionTag",
+                "testInstallOriginator", "testInstaller", -1, "testAttributionTag",
                 PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
         return new PackageInstallerSession(
                 /* callback */ null,
@@ -335,8 +335,8 @@
     }
 
     private void assertInstallSourcesEquivalent(InstallSource expected, InstallSource actual) {
-        assertEquals(expected.installerPackageName, actual.installerPackageName);
-        assertEquals(expected.initiatingPackageName, actual.initiatingPackageName);
-        assertEquals(expected.originatingPackageName, actual.originatingPackageName);
+        assertEquals(expected.mInstallerPackageName, actual.mInstallerPackageName);
+        assertEquals(expected.mInitiatingPackageName, actual.mInitiatingPackageName);
+        assertEquals(expected.mOriginatingPackageName, actual.mOriginatingPackageName);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 48d6d90..ce0b3a2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -493,7 +493,7 @@
 
         final ScanResult scanResult = executeScan(scanRequest);
 
-        assertThat(scanResult.mPkgSetting.getInstallSource().isOrphaned, is(true));
+        assertThat(scanResult.mPkgSetting.getInstallSource().mIsOrphaned, is(true));
     }
 
     private static Matcher<Integer> hasFlag(final int flag) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
index 578a43c..af0d32c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -50,17 +50,17 @@
         ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
         runWith(mockedProtoLog, this::testProtoLog);
         verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(ProtoLogGroup.TEST_GROUP),
-                anyInt(), eq(0b0010101001010111),
+                anyInt(), eq(0b0010010111),
                 eq(com.android.internal.protolog.ProtoLogGroup.TEST_GROUP.isLogToLogcat()
-                        ? "Test completed successfully: %b %d %o %x %e %g %f %% %s"
+                        ? "Test completed successfully: %b %d %x %f %% %s"
                         : null),
-                eq(new Object[]{true, 1L, 2L, 3L, 0.4, 0.5, 0.6, "ok"}));
+                eq(new Object[]{true, 1L, 2L, 0.3, "ok"}));
     }
 
     private void testProtoLog() {
         ProtoLog.e(ProtoLogGroup.TEST_GROUP,
-                "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
-                true, 1, 2, 3, 0.4, 0.5, 0.6, "ok");
+                "Test completed successfully: %b %d %x %f %% %s",
+                true, 1, 2, 0.3, "ok");
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 3ff2c0e..97e5755 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -23,6 +23,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.os.Process.FIRST_APPLICATION_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -547,4 +548,31 @@
         activity0.moveFocusableActivityToTop("test");
         assertEquals(activity0, mDisplayContent.mFocusedApp);
     }
+
+    @Test
+    public void testIsVisibleWithAdjacent_reportOrientationUnspecified() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf0 = createTaskFragmentWithParentTask(task);
+        final TaskFragment tf1 = createTaskFragmentWithParentTask(task);
+        tf0.setAdjacentTaskFragment(tf1);
+        tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        task.setBounds(0, 0, 1200, 1000);
+        tf0.setBounds(0, 0, 600, 1000);
+        tf1.setBounds(600, 0, 1200, 1000);
+        final ActivityRecord activity0 = tf0.getTopMostActivity();
+        final ActivityRecord activity1 = tf1.getTopMostActivity();
+        doReturn(true).when(activity0).isVisibleRequested();
+        doReturn(true).when(activity1).isVisibleRequested();
+
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf0.getOrientation(SCREEN_ORIENTATION_UNSET));
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf1.getOrientation(SCREEN_ORIENTATION_UNSET));
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, task.getOrientation(SCREEN_ORIENTATION_UNSET));
+
+        activity0.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf0.getOrientation(SCREEN_ORIENTATION_UNSET));
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf1.getOrientation(SCREEN_ORIENTATION_UNSET));
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, task.getOrientation(SCREEN_ORIENTATION_UNSET));
+    }
 }
diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml
index f5fe8f2..2d183bc 100644
--- a/tests/InputMethodStressTest/AndroidManifest.xml
+++ b/tests/InputMethodStressTest/AndroidManifest.xml
@@ -16,11 +16,11 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.inputmethod.stresstest">
+          package="com.android.inputmethod.stresstest">
 
     <application>
-        <activity android:name=".AutoShowTest$TestActivity"/>
-        <activity android:name=".ImeOpenCloseStressTest$TestActivity"/>
+        <activity android:name=".ImeStressTestUtil$TestActivity"
+                  android:configChanges="orientation|screenSize"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING
index ad07205..06e2ce8 100644
--- a/tests/InputMethodStressTest/TEST_MAPPING
+++ b/tests/InputMethodStressTest/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "presubmit": [
+  "presubmit-large": [
     {
       "name": "InputMethodStressTest"
     }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index 92ea029..8e4ecf1 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -16,24 +16,32 @@
 
 package com.android.inputmethod.stresstest;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.UNFOCUSABLE_VIEW;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
-import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
 
-import android.app.Activity;
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.Instrumentation;
 import android.content.Intent;
-import android.os.Bundle;
+import android.os.SystemClock;
 import android.platform.test.annotations.RootPermissionTest;
 import android.platform.test.rule.UnlockScreenRule;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
 import android.view.WindowManager;
 import android.widget.EditText;
-import android.widget.LinearLayout;
 
-import androidx.annotation.Nullable;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Rule;
@@ -41,135 +49,428 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
- * Tests to verify the "auto show" behavior in {@code InputMethodManagerService} when the window
+ * Tests to verify the "auto-show" behavior in {@code InputMethodManagerService} when the window
  * gaining the focus to start the input.
  */
 @RootPermissionTest
 @RunWith(Parameterized.class)
 public final class AutoShowTest {
 
-    @Rule
-    public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+    @Rule public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
 
     @Rule
     public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
 
-    private static final int[] SOFT_INPUT_VISIBILITY_FLAGS =
-            new int[] {
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN,
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN,
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE,
-                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE,
-            };
-
-    private static final int[] SOFT_INPUT_ADJUST_FLAGS =
-            new int[] {
-                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED,
-                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
-                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN,
-                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
-            };
-
     // TODO(b/240359838): add test case {@code Configuration.SCREENLAYOUT_SIZE_LARGE}.
     @Parameterized.Parameters(
-            name =
-                    "softInputVisibility={0}, softInputAdjustment={1},"
-                            + " softInputModeIsForwardNavigation={2}")
-    public static List<Object[]> softInputModeConfigs() {
-        ArrayList<Object[]> params = new ArrayList<>();
-        for (int softInputVisibility : SOFT_INPUT_VISIBILITY_FLAGS) {
-            for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
-                params.add(new Object[] {softInputVisibility, softInputAdjust, true});
-                params.add(new Object[] {softInputVisibility, softInputAdjust, false});
-            }
-        }
-        return params;
+            name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}")
+    public static List<Object[]> windowAndSoftInputFlagParameters() {
+        return getWindowAndSoftInputFlagParameters();
     }
 
-    private static final String SOFT_INPUT_FLAGS = "soft_input_flags";
+    private final int mSoftInputFlags;
+    private final int mWindowFocusFlags;
+    private final Instrumentation mInstrumentation;
 
-    private final int mSoftInputVisibility;
-    private final int mSoftInputAdjustment;
-    private final boolean mSoftInputIsForwardNavigation;
+    public AutoShowTest(int windowFocusFlags, int softInputVisibility, int softInputAdjustment) {
+        mSoftInputFlags = softInputVisibility | softInputAdjustment;
+        mWindowFocusFlags = windowFocusFlags;
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
 
-    public AutoShowTest(
-            int softInputVisibility,
-            int softInputAdjustment,
-            boolean softInputIsForwardNavigation) {
-        mSoftInputVisibility = softInputVisibility;
-        mSoftInputAdjustment = softInputAdjustment;
-        mSoftInputIsForwardNavigation = softInputIsForwardNavigation;
+    /**
+     * Test auto-show IME behavior when the {@link EditText} is focusable ({@link
+     * EditText#isFocusableInTouchMode} is {@code true}) and has called {@link
+     * EditText#requestFocus}.
+     */
+    @Test
+    public void autoShow_hasFocusedView_requestFocus() {
+        // request focus at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+
+        verifyAutoShowBehavior_forwardWithKeyboardOff(activity);
+    }
+
+    /**
+     * Test auto-show IME behavior when the {@link EditText} is focusable ({@link
+     * EditText#isFocusableInTouchMode} is {@code true}) and {@link EditText#requestFocus} is not
+     * called. The IME should never be shown because there is no focused editor in the window.
+     */
+    @Test
+    public void autoShow_hasFocusedView_notRequestFocus() {
+        // request focus not set
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        EditText editText = activity.getEditText();
+
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus.
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+        } else {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+        }
+        // IME is always hidden because there is no view focus.
+        verifyImeIsAlwaysHidden(editText);
+    }
+
+    /**
+     * Test auto-show IME behavior when the {@link EditText} is not focusable ({@link
+     * EditText#isFocusableInTouchMode} is {@code false}) and {@link EditText#requestFocus} is not
+     * called. The IME should never be shown because there is no focusable editor in the window.
+     */
+    @Test
+    public void autoShow_notFocusedView_notRequestFocus() {
+        // Unfocusable view, request focus not set
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(UNFOCUSABLE_VIEW));
+        TestActivity activity = TestActivity.start(intent);
+        EditText editText = activity.getEditText();
+
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus.
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+        } else {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+        }
+        // IME is always hidden because there is no focused view.
+        verifyImeIsAlwaysHidden(editText);
+    }
+
+    /**
+     * Test auto-show IME behavior when the activity is navigated forward from another activity with
+     * keyboard off.
+     */
+    @Test
+    public void autoShow_forwardWithKeyboardOff() {
+        // Create first activity with keyboard off
+        Intent intent1 =
+                createIntent(
+                        0x0 /* No window focus flags */,
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                        Collections.emptyList());
+        TestActivity firstActivity = TestActivity.start(intent1);
+
+        // Create second activity with parameterized flags:
+        Intent intent2 =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2);
+
+        // The auto-show behavior should be the same as opening the app
+        verifyAutoShowBehavior_forwardWithKeyboardOff(secondActivity);
+    }
+
+    /**
+     * Test auto-show IME behavior when the activity is navigated forward from another activity with
+     * keyboard on.
+     */
+    @Test
+    public void autoShow_forwardWithKeyboardOn() {
+        // Create first activity with keyboard on
+        Intent intent1 =
+                createIntent(
+                        0x0 /* No window focus flags */,
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity firstActivity = TestActivity.start(intent1);
+        // Show Ime with InputMethodManager to ensure the keyboard is on.
+        boolean succ = callOnMainSync(firstActivity::showImeWithInputMethodManager);
+        assertThat(succ).isTrue();
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+
+        // Create second activity with parameterized flags:
+        Intent intent2 =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2);
+
+        // The auto-show behavior should be the same as open app
+        verifyAutoShowBehavior_forwardWithKeyboardOn(secondActivity);
+    }
+
+    /**
+     * Test auto-show IME behavior when the activity is navigated back from another activity with
+     * keyboard off.
+     */
+    @Test
+    public void autoShow_backwardWithKeyboardOff() {
+        // Not request focus at onCreate() to avoid triggering auto-show behavior
+        Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity firstActivity = TestActivity.start(intent1);
+        // Request view focus after app starts
+        mInstrumentation.runOnMainSync(firstActivity::requestFocus);
+
+        Intent intent2 =
+                createIntent(
+                        0x0 /* No window focus flags */,
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                        Collections.emptyList());
+        TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2);
+        secondActivity.finish();
+        mInstrumentation.waitForIdleSync();
+
+        // When activity is navigated back from another activity with keyboard off, the keyboard
+        // will not show except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_VISIBLE.
+        verifyAutoShowBehavior_backwardWithKeyboardOff(firstActivity);
+    }
+
+    /**
+     * Test auto-show IME behavior when the activity is navigated back from another activity with
+     * keyboard on.
+     */
+    @Test
+    public void autoShow_backwardWithKeyboardOn() {
+        // Not request focus at onCreate() to avoid triggering auto-show behavior
+        Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent1);
+        // Request view focus after app starts
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        // Create second TestActivity
+        Intent intent2 =
+                createIntent(
+                        0x0 /* No window focus flags */,
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        ImeStressTestUtil.TestActivity secondActivity = activity.startSecondTestActivity(intent2);
+        // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
+        boolean succ = callOnMainSync(secondActivity::showImeWithInputMethodManager);
+        assertThat(succ).isTrue();
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+        // Close the second activity
+        secondActivity.finish();
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+        // When activity is navigated back from another activity with keyboard on, the keyboard
+        // will not hide except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_HIDDEN.
+        verifyAutoShowBehavior_backwardWithKeyboardOn(activity);
     }
 
     @Test
-    public void autoShow() {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        int flags = mSoftInputVisibility | mSoftInputAdjustment;
-        if (mSoftInputIsForwardNavigation) {
-            flags |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+    public void clickFocusableView_requestFocus() {
+        if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request view focus after app starts
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        // Find the editText and click it
+        UiObject2 editTextUiObject =
+                UiDevice.getInstance(mInstrumentation)
+                        .wait(Until.findObject(By.clazz(EditText.class)), 5000);
+        assertThat(editTextUiObject).isNotNull();
+        editTextUiObject.click();
+
+        // Ime will show unless window flag is set
+        verifyClickBehavior(activity);
+    }
+
+    @Test
+    public void clickFocusableView_notRequestFocus() {
+        if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set
+            return;
+        }
+        // Not request focus
+        Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent1);
+
+        // Find the editText and click it
+        UiObject2 editTextUiObject =
+                UiDevice.getInstance(mInstrumentation)
+                        .wait(Until.findObject(By.clazz(EditText.class)), 5000);
+        assertThat(editTextUiObject).isNotNull();
+        editTextUiObject.click();
+
+        // Ime will show unless window flag is set
+        verifyClickBehavior(activity);
+    }
+
+    public static void verifyAutoShowBehavior_forwardWithKeyboardOff(TestActivity activity) {
+        // public: also used by ImeOpenCloseStressTest
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
         }
 
-        Intent intent =
-                new Intent()
-                        .setAction(Intent.ACTION_MAIN)
-                        .setClass(instrumentation.getContext(), TestActivity.class)
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                        .putExtra(SOFT_INPUT_FLAGS, flags);
-        TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
+        int softInputMode = activity.getWindow().getAttributes().softInputMode;
+        int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+        int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
         EditText editText = activity.getEditText();
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
 
-        if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
-                || mSoftInputVisibility
-                        == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) {
-            // IME will be auto-shown if softInputMode is set with flag:
-            // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
-            waitOnMainUntilImeIsShown(editText);
-        } else if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
-                || mSoftInputVisibility
-                        == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
-            // IME will be not be shown if softInputMode is set with flag:
-            // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN
-            verifyImeIsAlwaysHidden(editText);
-        } else {
-            // The current system behavior will choose to show IME automatically when navigating
-            // forward to an app that has no visibility state specified  (i.e.
-            // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag.
-            if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
-                    && mSoftInputAdjustment == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
-                    && mSoftInputIsForwardNavigation) {
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        switch (softInputVisibility) {
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: {
+                // IME will be auto-shown if softInputMode is set with flag:
+                // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
                 waitOnMainUntilImeIsShown(editText);
+                break;
             }
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: {
+                // IME will be not be auto-shown if softInputMode is set with flag:
+                // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+                // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED
+                verifyImeIsAlwaysHidden(editText);
+                break;
+            }
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: {
+                if (softInputAdjustment
+                        == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
+                    // The current system behavior will choose to show IME automatically when
+                    // navigating forward to an app that has no visibility state specified
+                    // (i.e. SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE
+                    // flag.
+                    waitOnMainUntilImeIsShown(editText);
+                } else {
+                    verifyImeIsAlwaysHidden(editText);
+                }
+                break;
+            }
+            default:
+                break;
         }
     }
 
-    public static class TestActivity extends Activity {
-        private EditText mEditText;
+    private static void verifyAutoShowBehavior_forwardWithKeyboardOn(TestActivity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        int softInputMode = activity.getWindow().getAttributes().softInputMode;
+        int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+        int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+        EditText editText = activity.getEditText();
 
-        @Override
-        protected void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            int flags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0);
-            getWindow().setSoftInputMode(flags);
-            LinearLayout rootView = new LinearLayout(this);
-            rootView.setOrientation(LinearLayout.VERTICAL);
-            mEditText = new EditText(this);
-            rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
-            setContentView(rootView);
-            // Ensure the focused view is a text editor (View#onCheckIsTextEditor() returns true) to
-            // automatically display a soft input window.
-            mEditText.requestFocus();
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. The IME
+            // will always be hidden even though the view can get focus itself.
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ true);
+            // TODO(b/252192121): Ime should be hidden but is shown.
+            // waitOnMainUntilImeIsHidden(editText);
+            return;
+        } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+            // When FLAG_ALT_FOCUSABLE_IM or FLAG_LOCAL_FOCUS_MODE is set, the view can gain both
+            // window focus and view focus but not IME focus. The IME will always be hidden.
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+            // TODO(b/252192121): Ime should be hidden but is shown.
+            // waitOnMainUntilImeIsHidden(editText);
+            return;
         }
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        switch (softInputVisibility) {
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: {
+                // IME will be auto-shown if softInputMode is set with flag:
+                // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
+                waitOnMainUntilImeIsShown(editText);
+                break;
+            }
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: {
+                // IME will be not be auto-shown if softInputMode is set with flag:
+                // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN
+                // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED
+                verifyImeIsAlwaysHidden(editText);
+                break;
+            }
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: {
+                if (softInputAdjustment
+                        == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
+                    // The current system behavior will choose to show IME automatically when
+                    // navigating
+                    // forward to an app that has no visibility state specified  (i.e.
+                    // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag.
+                    waitOnMainUntilImeIsShown(editText);
+                } else {
+                    verifyImeIsAlwaysHidden(editText);
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
 
-        public EditText getEditText() {
-            return mEditText;
+    private static void verifyAutoShowBehavior_backwardWithKeyboardOff(TestActivity activity) {
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
+        }
+        int softInputMode = activity.getWindow().getAttributes().softInputMode;
+        int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) {
+            waitOnMainUntilImeIsShown(editText);
+        } else {
+            verifyImeIsAlwaysHidden(editText);
+        }
+    }
+
+    private static void verifyAutoShowBehavior_backwardWithKeyboardOn(TestActivity activity) {
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
+        }
+        int softInputMode = activity.getWindow().getAttributes().softInputMode;
+        int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
+            verifyImeIsAlwaysHidden(editText);
+        } else {
+            waitOnMainUntilImeIsShown(editText);
+        }
+    }
+
+    private static void verifyClickBehavior(TestActivity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+            verifyImeIsAlwaysHidden(editText);
+        } else {
+            waitOnMainUntilImeIsShown(editText);
         }
     }
 }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 8419276..82acfb6 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -16,224 +16,535 @@
 
 package com.android.inputmethod.stresstest;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
-
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_HIDE_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_SHOW_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.isImeShown;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
 
-import android.app.Activity;
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.Instrumentation;
 import android.content.Intent;
-import android.os.Bundle;
+import android.os.Build;
 import android.os.SystemClock;
 import android.platform.test.annotations.RootPermissionTest;
 import android.platform.test.rule.UnlockScreenRule;
+import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
-import android.view.WindowInsets;
-import android.view.WindowInsetsAnimation;
-import android.view.inputmethod.InputMethodManager;
+import android.view.WindowManager;
 import android.widget.EditText;
-import android.widget.LinearLayout;
 
-import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 @RootPermissionTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public final class ImeOpenCloseStressTest {
 
     private static final String TAG = "ImeOpenCloseStressTest";
     private static final int NUM_TEST_ITERATIONS = 10;
 
-    @Rule
-    public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+    @Rule public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
 
     @Rule
     public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
-    private Instrumentation mInstrumentation;
 
-    @Before
-    public void setUp() {
+    private final Instrumentation mInstrumentation;
+    private final int mSoftInputFlags;
+    private final int mWindowFocusFlags;
+
+    @Parameterized.Parameters(
+            name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}")
+    public static List<Object[]> windowAndSoftInputFlagParameters() {
+        return getWindowAndSoftInputFlagParameters();
+    }
+
+    public ImeOpenCloseStressTest(
+            int windowFocusFlags, int softInputVisibility, int softInputAdjustment) {
+        mSoftInputFlags = softInputVisibility | softInputAdjustment;
+        mWindowFocusFlags = windowFocusFlags;
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
     }
 
     @Test
-    public void testShowHide_waitingVisibilityChange() {
-        TestActivity activity = TestActivity.start();
-        EditText editText = activity.getEditText();
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
-        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+    public void testShowHideWithInputMethodManager_waitingVisibilityChange() {
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+        // Test only once if window flags set to save time.
+        int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
+        for (int i = 0; i < iterNum; i++) {
             String msgPrefix = "Iteration #" + i + " ";
             Log.i(TAG, msgPrefix + "start");
-            mInstrumentation.runOnMainSync(activity::showIme);
-            waitOnMainUntil(msgPrefix + "IME should be visible", () -> isImeShown(editText));
-            mInstrumentation.runOnMainSync(activity::hideIme);
-            waitOnMainUntil(msgPrefix + "IME should be hidden", () -> !isImeShown(editText));
+            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+            assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+            verifyShowBehavior(activity);
+
+            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
+            assertThat(hideResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+
+            verifyHideBehavior(activity);
         }
     }
 
     @Test
-    public void testShowHide_waitingAnimationEnd() {
-        TestActivity activity = TestActivity.start();
+    public void testShowHideWithInputMethodManager_waitingAnimationEnd() {
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
         activity.enableAnimationMonitoring();
         EditText editText = activity.getEditText();
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
             String msgPrefix = "Iteration #" + i + " ";
             Log.i(TAG, msgPrefix + "start");
-            mInstrumentation.runOnMainSync(activity::showIme);
-            waitOnMainUntil(msgPrefix + "IME should be visible",
+            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+            assertThat(showResult).isTrue();
+            waitOnMainUntil(
+                    msgPrefix + "IME should be visible",
                     () -> !activity.isAnimating() && isImeShown(editText));
-            mInstrumentation.runOnMainSync(activity::hideIme);
-            waitOnMainUntil(msgPrefix + "IME should be hidden",
+
+            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
+            assertThat(hideResult).isTrue();
+            waitOnMainUntil(
+                    msgPrefix + "IME should be hidden",
                     () -> !activity.isAnimating() && !isImeShown(editText));
         }
     }
 
     @Test
-    public void testShowHide_intervalAfterHide() {
+    public void testShowHideWithInputMethodManager_intervalAfterHide() {
         // Regression test for b/221483132
-        TestActivity activity = TestActivity.start();
-        EditText editText = activity.getEditText();
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
         // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
         List<Integer> intervals = new ArrayList<>();
         for (int i = 10; i < 100; i += 10) intervals.add(i);
         for (int i = 100; i < 1000; i += 50) intervals.add(i);
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+        boolean firstHide = false;
         for (int intervalMillis : intervals) {
             String msgPrefix = "Interval = " + intervalMillis + " ";
             Log.i(TAG, msgPrefix + " start");
-            mInstrumentation.runOnMainSync(activity::hideIme);
+            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
+            assertThat(hideResult).isEqualTo(firstHide);
+            firstHide = true;
             SystemClock.sleep(intervalMillis);
-            mInstrumentation.runOnMainSync(activity::showIme);
-            waitOnMainUntil(msgPrefix + "IME should be visible",
-                    () -> isImeShown(editText));
+
+            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+            assertThat(showResult).isTrue();
+            verifyShowBehavior(activity);
         }
     }
 
     @Test
-    public void testShowHideInSameFrame() {
-        TestActivity activity = TestActivity.start();
-        activity.enableAnimationMonitoring();
-        EditText editText = activity.getEditText();
-        waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+    public void testShowHideWithInputMethodManager_inSameFrame() {
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
 
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
         // hidden -> show -> hide
-        mInstrumentation.runOnMainSync(() -> {
-            Log.i(TAG, "Calling showIme() and hideIme()");
-            activity.showIme();
-            activity.hideIme();
-        });
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Log.i(TAG, "Calling showIme() and hideIme()");
+                    activity.showImeWithInputMethodManager();
+                    activity.hideImeWithInputMethodManager();
+                });
         // Wait until IMMS / IMS handles messages.
         SystemClock.sleep(1000);
         mInstrumentation.waitForIdleSync();
-        waitOnMainUntil("IME should be invisible after show/hide", () -> !isImeShown(editText));
+        verifyHideBehavior(activity);
 
-        mInstrumentation.runOnMainSync(activity::showIme);
-        waitOnMainUntil("IME should be visible",
-                () -> !activity.isAnimating() && isImeShown(editText));
+        mInstrumentation.runOnMainSync(activity::showImeWithInputMethodManager);
+        verifyShowBehavior(activity);
         mInstrumentation.waitForIdleSync();
 
         // shown -> hide -> show
-        mInstrumentation.runOnMainSync(() -> {
-            Log.i(TAG, "Calling hideIme() and showIme()");
-            activity.hideIme();
-            activity.showIme();
-        });
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Log.i(TAG, "Calling hideIme() and showIme()");
+                    activity.hideImeWithInputMethodManager();
+                    activity.showImeWithInputMethodManager();
+                });
         // Wait until IMMS / IMS handles messages.
         SystemClock.sleep(1000);
         mInstrumentation.waitForIdleSync();
-        waitOnMainUntil("IME should be visible after hide/show",
-                () -> !activity.isAnimating() && isImeShown(editText));
+        verifyShowBehavior(activity);
     }
 
-    public static class TestActivity extends Activity {
+    @Test
+    public void testShowHideWithInputMethodManager_onCreate() {
+        // Show and hide with InputMethodManager at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Arrays.asList(
+                                REQUEST_FOCUS_ON_CREATE,
+                                INPUT_METHOD_MANAGER_SHOW_ON_CREATE,
+                                INPUT_METHOD_MANAGER_HIDE_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
 
-        private EditText mEditText;
-        private boolean mIsAnimating;
+        // TODO: The Ime is expected to show first and then hide. But show or hide
+        // with InputMethodManager at onCreate() would always fail because the window
+        // has not gained focus, so the actual behavior will be the same as auto-show.
+        // verifyHideBehavior(activity);
+    }
 
-        private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
-                new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
-                    @Override
-                    public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation,
-                            WindowInsetsAnimation.Bounds bounds) {
-                        mIsAnimating = true;
-                        return super.onStart(animation, bounds);
-                    }
+    @Test
+    public void testShowWithInputMethodManager_notRequestFocus() {
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
 
-                    @Override
-                    public void onEnd(WindowInsetsAnimation animation) {
-                        super.onEnd(animation);
-                        mIsAnimating = false;
-                    }
+        // Show InputMethodManager without requesting focus
+        boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+        assertThat(showResult).isFalse();
 
-                    @Override
-                    public WindowInsets onProgress(WindowInsets insets,
-                            List<WindowInsetsAnimation> runningAnimations) {
-                        return insets;
-                    }
-                };
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        EditText editText = activity.getEditText();
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+        } else {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+        }
+        // The Ime should always be hidden because view never gains focus.
+        verifyImeIsAlwaysHidden(editText);
+    }
 
-        public static TestActivity start() {
-            Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-            Intent intent = new Intent()
-                    .setAction(Intent.ACTION_MAIN)
-                    .setClass(instrumentation.getContext(), TestActivity.class)
-                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            return (TestActivity) instrumentation.startActivitySync(intent);
+    @Test
+    public void testShowHideWithWindowInsetsController_waitingVisibilityChange() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+        // Test only once if window flags set to save time.
+        int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
+        for (int i = 0; i < iterNum; i++) {
+            String msgPrefix = "Iteration #" + i + " ";
+            Log.i(TAG, msgPrefix + "start");
+            mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+            verifyShowBehavior(activity);
+            mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
+            verifyHideBehavior(activity);
+        }
+    }
+
+    @Test
+    public void testShowHideWithWindowInsetsController_waitingAnimationEnd() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
+        activity.enableAnimationMonitoring();
+        EditText editText = activity.getEditText();
+        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+            String msgPrefix = "Iteration #" + i + " ";
+            Log.i(TAG, msgPrefix + "start");
+            mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+            waitOnMainUntil(
+                    msgPrefix + "IME should be visible",
+                    () -> !activity.isAnimating() && isImeShown(editText));
+
+            mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
+            waitOnMainUntil(
+                    msgPrefix + "IME should be hidden",
+                    () -> !activity.isAnimating() && !isImeShown(editText));
+        }
+    }
+
+    @Test
+    public void testShowHideWithWindowInsetsController_intervalAfterHide() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
+        // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
+        List<Integer> intervals = new ArrayList<>();
+        for (int i = 10; i < 100; i += 10) intervals.add(i);
+        for (int i = 100; i < 1000; i += 50) intervals.add(i);
+        for (int intervalMillis : intervals) {
+            String msgPrefix = "Interval = " + intervalMillis + " ";
+            Log.i(TAG, msgPrefix + " start");
+            mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
+            SystemClock.sleep(intervalMillis);
+
+            mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+            verifyShowBehavior(activity);
+        }
+    }
+
+    @Test
+    public void testShowHideWithWindowInsetsController_inSameFrame() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        // Request focus after app starts to avoid triggering auto-show behavior.
+        mInstrumentation.runOnMainSync(activity::requestFocus);
+
+        if (hasUnfocusableWindowFlags(activity)) {
+            return; // Skip to save time.
+        }
+        // hidden -> show -> hide
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Log.i(TAG, "Calling showIme() and hideIme()");
+                    activity.showImeWithWindowInsetsController();
+                    activity.hideImeWithWindowInsetsController();
+                });
+        // Wait until IMMS / IMS handles messages.
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+        // TODO(b/248456059): Ime should be hidden but is shown.
+        // verifyHideBehavior(activity);
+
+        mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+        verifyShowBehavior(activity);
+        mInstrumentation.waitForIdleSync();
+
+        // shown -> hide -> show
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    Log.i(TAG, "Calling hideIme() and showIme()");
+                    activity.hideImeWithWindowInsetsController();
+                    activity.showImeWithWindowInsetsController();
+                });
+        // Wait until IMMS / IMS handles messages.
+        SystemClock.sleep(1000);
+        mInstrumentation.waitForIdleSync();
+        verifyShowBehavior(activity);
+    }
+
+    @Test
+    public void testShowWithWindowInsetsController_onCreate_requestFocus() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        // Show with InputMethodManager at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Arrays.asList(
+                                REQUEST_FOCUS_ON_CREATE, WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+
+        verifyShowBehavior(activity);
+    }
+
+    @Test
+    public void testShowWithWindowInsetsController_onCreate_notRequestFocus() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        // Show and hide with InputMethodManager at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+
+        // Ime is shown but with a fallback InputConnection
+        verifyShowBehaviorNotRequestFocus(activity);
+    }
+
+    @Test
+    public void testShowWithWindowInsetsController_afterStart_notRequestFocus() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        // Show and hide with InputMethodManager at onCreate()
+        Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+        TestActivity activity = TestActivity.start(intent);
+        mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+
+        // Ime is shown but with a fallback InputConnection
+        verifyShowBehaviorNotRequestFocus(activity);
+    }
+
+    @Test
+    public void testHideWithWindowInsetsController_onCreate_requestFocus() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            return;
+        }
+        // Show and hide with InputMethodManager at onCreate()
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Arrays.asList(
+                                REQUEST_FOCUS_ON_CREATE,
+                                WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE,
+                                WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+
+        // TODO(b/248456059): Ime should be hidden but is shown.
+        //verifyHideBehavior(activity);
+    }
+
+    @Test
+    public void testScreenOffOn() throws Exception {
+        Intent intent1 =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent1);
+        // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
+        boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+        assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+
+        Thread.sleep(1000);
+        verifyShowBehavior(activity);
+
+        UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
+
+        if (uiDevice.isScreenOn()) {
+            uiDevice.sleep();
+        }
+        Thread.sleep(1000);
+        if (!uiDevice.isScreenOn()) {
+            uiDevice.wakeUp();
         }
 
-        @Override
-        protected void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            LinearLayout rootView = new LinearLayout(this);
-            rootView.setOrientation(LinearLayout.VERTICAL);
-            mEditText = new EditText(this);
-            rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
-            setContentView(rootView);
-        }
+        verifyShowBehavior(activity);
+    }
 
-        public EditText getEditText() {
-            return mEditText;
-        }
+    @Test
+    public void testRotateScreenWithKeyboardOn() throws Exception {
+        // TODO(b/256739702): Keyboard disappears after rotating screen to landscape mode if
+        // android:configChanges="orientation|screenSize" is not set
+        Intent intent =
+                createIntent(
+                        mWindowFocusFlags,
+                        mSoftInputFlags,
+                        Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+        TestActivity activity = TestActivity.start(intent);
+        // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
+        boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+        assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+        Thread.sleep(2000);
+        verifyShowBehavior(activity);
 
-        public void showIme() {
-            Log.i(TAG, "TestActivity.showIme");
-            mEditText.requestFocus();
-            InputMethodManager imm = getSystemService(InputMethodManager.class);
-            imm.showSoftInput(mEditText, 0);
-        }
+        UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
 
-        public void hideIme() {
-            Log.i(TAG, "TestActivity.hideIme");
-            InputMethodManager imm = getSystemService(InputMethodManager.class);
-            imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
-        }
+        uiDevice.freezeRotation();
+        uiDevice.setOrientationRight();
+        uiDevice.waitForIdle();
+        Thread.sleep(1000);
+        Log.i(TAG, "Rotate screen right");
+        assertThat(uiDevice.isNaturalOrientation()).isFalse();
+        verifyShowBehavior(activity);
 
-        public void enableAnimationMonitoring() {
-            // Enable WindowInsetsAnimation.
-            // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner.
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-                getWindow().setDecorFitsSystemWindows(false);
-                mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
-            });
-        }
+        uiDevice.setOrientationLeft();
+        uiDevice.waitForIdle();
+        Thread.sleep(1000);
+        Log.i(TAG, "Rotate screen left");
+        assertThat(uiDevice.isNaturalOrientation()).isFalse();
+        verifyShowBehavior(activity);
 
-        public boolean isAnimating() {
-            return mIsAnimating;
+        uiDevice.setOrientationNatural();
+        uiDevice.waitForIdle();
+        uiDevice.unfreezeRotation();
+    }
+
+    private static void verifyShowBehavior(TestActivity activity) {
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
+        }
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        waitOnMainUntilImeIsShown(editText);
+    }
+
+    private static void verifyHideBehavior(TestActivity activity) {
+        if (hasUnfocusableWindowFlags(activity)) {
+            verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+            return;
+        }
+        EditText editText = activity.getEditText();
+
+        verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+        waitOnMainUntilImeIsHidden(editText);
+    }
+
+    private static void verifyShowBehaviorNotRequestFocus(TestActivity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        EditText editText = activity.getEditText();
+
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+            verifyImeIsAlwaysHidden(editText);
+        } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+            verifyImeIsAlwaysHidden(editText);
+        } else {
+            verifyWindowAndViewFocus(
+                    editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+            // Ime is shown but with a fallback InputConnection
+            waitOnMainUntilImeIsShown(editText);
         }
     }
 }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index b6d462c..e16c915 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -16,17 +16,37 @@
 
 package com.android.inputmethod.stresstest;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+
 import static com.android.compatibility.common.util.SystemUtil.eventually;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
 import android.view.View;
 import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
 
+import androidx.annotation.Nullable;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.ThrowingRunnable;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -34,28 +54,96 @@
 /** Utility methods for IME stress test. */
 public final class ImeStressTestUtil {
 
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
-    private static final long VERIFY_DURATION = TimeUnit.SECONDS.toMillis(2);
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(3);
 
-    private ImeStressTestUtil() {
+    private ImeStressTestUtil() {}
+
+    private static final int[] WINDOW_FOCUS_FLAGS =
+            new int[] {
+                LayoutParams.FLAG_NOT_FOCUSABLE,
+                LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                LayoutParams.FLAG_LOCAL_FOCUS_MODE
+            };
+
+    private static final int[] SOFT_INPUT_VISIBILITY_FLAGS =
+            new int[] {
+                LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
+                LayoutParams.SOFT_INPUT_STATE_UNCHANGED,
+                LayoutParams.SOFT_INPUT_STATE_HIDDEN,
+                LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+                LayoutParams.SOFT_INPUT_STATE_VISIBLE,
+                LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE,
+            };
+
+    private static final int[] SOFT_INPUT_ADJUST_FLAGS =
+            new int[] {
+                LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED,
+                LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                LayoutParams.SOFT_INPUT_ADJUST_PAN,
+                LayoutParams.SOFT_INPUT_ADJUST_NOTHING
+            };
+
+    public static final String SOFT_INPUT_FLAGS = "soft_input_flags";
+    public static final String WINDOW_FLAGS = "window_flags";
+    public static final String UNFOCUSABLE_VIEW = "unfocusable_view";
+    public static final String REQUEST_FOCUS_ON_CREATE = "request_focus_on_create";
+    public static final String INPUT_METHOD_MANAGER_SHOW_ON_CREATE =
+            "input_method_manager_show_on_create";
+    public static final String INPUT_METHOD_MANAGER_HIDE_ON_CREATE =
+            "input_method_manager_hide_on_create";
+    public static final String WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE =
+            "window_insets_controller_show_on_create";
+    public static final String WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE =
+            "window_insets_controller_hide_on_create";
+
+    /** Parameters for show/hide ime parameterized tests. */
+    public static ArrayList<Object[]> getWindowAndSoftInputFlagParameters() {
+        ArrayList<Object[]> params = new ArrayList<>();
+
+        // Set different window focus flags and keep soft input flags as default values (4 cases)
+        for (int windowFocusFlags : WINDOW_FOCUS_FLAGS) {
+            params.add(
+                    new Object[] {
+                        windowFocusFlags,
+                        LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
+                        LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+                    });
+        }
+        // Set the combinations of different softInputVisibility, softInputAdjustment flags,
+        // keep the window focus flag as default value ( 6 * 4 = 24 cases)
+        for (int softInputVisibility : SOFT_INPUT_VISIBILITY_FLAGS) {
+            for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
+                params.add(
+                        new Object[] {
+                            0x0 /* No window focus flags */, softInputVisibility, softInputAdjust
+                        });
+            }
+        }
+        return params;
     }
 
     /** Checks if the IME is shown on the window that the given view belongs to. */
     public static boolean isImeShown(View view) {
         WindowInsets insets = view.getRootWindowInsets();
+        if (insets == null) {
+            return false;
+        }
         return insets.isVisible(WindowInsets.Type.ime());
     }
 
     /** Calls the callable on the main thread and returns the result. */
     public static <V> V callOnMainSync(Callable<V> callable) {
         AtomicReference<V> result = new AtomicReference<>();
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            try {
-                result.set(callable.call());
-            } catch (Exception e) {
-                throw new RuntimeException("Exception was thrown", e);
-            }
-        });
+        InstrumentationRegistry.getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            try {
+                                result.set(callable.call());
+                            } catch (Exception e) {
+                                throw new RuntimeException("Exception was thrown", e);
+                            }
+                        });
         return result.get();
     }
 
@@ -70,15 +158,42 @@
 
     /** Waits until IME is shown, or throws on timeout. */
     public static void waitOnMainUntilImeIsShown(View view) {
-        eventually(() -> assertWithMessage("IME should be shown").that(
-                callOnMainSync(() -> isImeShown(view))).isTrue(), TIMEOUT);
+        eventually(
+                () ->
+                        assertWithMessage("IME should be shown")
+                                .that(callOnMainSync(() -> isImeShown(view)))
+                                .isTrue(),
+                TIMEOUT);
     }
 
     /** Waits until IME is hidden, or throws on timeout. */
     public static void waitOnMainUntilImeIsHidden(View view) {
-        //eventually(() -> assertThat(callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
-        eventually(() -> assertWithMessage("IME should be hidden").that(
-                callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
+        eventually(
+                () ->
+                        assertWithMessage("IME should be hidden")
+                                .that(callOnMainSync(() -> isImeShown(view)))
+                                .isFalse(),
+                TIMEOUT);
+    }
+
+    /** Waits until window get focus, or throws on timeout. */
+    public static void waitOnMainUntilWindowGainsFocus(View view) {
+        eventually(
+                () ->
+                        assertWithMessage("Window should gain focus")
+                                .that(callOnMainSync(view::hasWindowFocus))
+                                .isTrue(),
+                TIMEOUT);
+    }
+
+    /** Waits until view get focus, or throws on timeout. */
+    public static void waitOnMainUntilViewGainsFocus(View view) {
+        eventually(
+                () ->
+                        assertWithMessage("View should gain focus")
+                                .that(callOnMainSync(view::hasFocus))
+                                .isTrue(),
+                TIMEOUT);
     }
 
     /** Verify IME is always hidden within the given time duration. */
@@ -88,7 +203,27 @@
                         assertWithMessage("IME should be hidden")
                                 .that(callOnMainSync(() -> isImeShown(view)))
                                 .isFalse(),
-                VERIFY_DURATION);
+                TIMEOUT);
+    }
+
+    /** Verify the window never gains focus within the given time duration. */
+    public static void verifyWindowNeverGainsFocus(View view) {
+        always(
+                () ->
+                        assertWithMessage("window should never gain focus")
+                                .that(callOnMainSync(view::hasWindowFocus))
+                                .isFalse(),
+                TIMEOUT);
+    }
+
+    /** Verify the view never gains focus within the given time duration. */
+    public static void verifyViewNeverGainsFocus(View view) {
+        always(
+                () ->
+                        assertWithMessage("view should never gain ime focus")
+                                .that(callOnMainSync(view::hasFocus))
+                                .isFalse(),
+                TIMEOUT);
     }
 
     /**
@@ -117,4 +252,232 @@
             }
         }
     }
+
+    public static boolean hasUnfocusableWindowFlags(Activity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        return (windowFlags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0
+                || (windowFlags & LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
+    }
+
+    public static void verifyWindowAndViewFocus(
+            View view, boolean expectWindowFocus, boolean expectViewFocus) {
+        if (expectWindowFocus) {
+            waitOnMainUntilWindowGainsFocus(view);
+        } else {
+            verifyWindowNeverGainsFocus(view);
+        }
+        if (expectViewFocus) {
+            waitOnMainUntilViewGainsFocus(view);
+        } else {
+            verifyViewNeverGainsFocus(view);
+        }
+    }
+
+    public static void verifyImeAlwaysHiddenWithWindowFlagSet(TestActivity activity) {
+        int windowFlags = activity.getWindow().getAttributes().flags;
+        View view = activity.getEditText();
+        if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+            // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. The IME
+            // will always be hidden even though the view can get focus itself.
+            verifyWindowAndViewFocus(view, /*expectWindowFocus*/ false, /*expectViewFocus*/ true);
+            verifyImeIsAlwaysHidden(view);
+        } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+                || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+            // When FLAG_ALT_FOCUSABLE_IM or FLAG_LOCAL_FOCUS_MODE is set, the view can gain both
+            // window focus and view focus but not IME focus. The IME will always be hidden.
+            verifyWindowAndViewFocus(view, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+            verifyImeIsAlwaysHidden(view);
+        }
+    }
+
+    /** Activity to help test show/hide behavior of IME. */
+    public static class TestActivity extends Activity {
+        private static final String TAG = "ImeStressTestUtil.TestActivity";
+        private EditText mEditText;
+        private boolean mIsAnimating;
+
+        private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
+                new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+                    @Override
+                    public WindowInsetsAnimation.Bounds onStart(
+                            WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
+                        mIsAnimating = true;
+                        return super.onStart(animation, bounds);
+                    }
+
+                    @Override
+                    public void onEnd(WindowInsetsAnimation animation) {
+                        super.onEnd(animation);
+                        mIsAnimating = false;
+                    }
+
+                    @Override
+                    public WindowInsets onProgress(
+                            WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) {
+                        return insets;
+                    }
+                };
+
+        /** Create intent with extras. */
+        public static Intent createIntent(
+                int windowFlags, int softInputFlags, List<String> extras) {
+            Intent intent =
+                    new Intent()
+                            .putExtra(WINDOW_FLAGS, windowFlags)
+                            .putExtra(SOFT_INPUT_FLAGS, softInputFlags);
+            for (String extra : extras) {
+                intent.putExtra(extra, true);
+            }
+            return intent;
+        }
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            Log.i(TAG, "onCreate()");
+            boolean isUnfocusableView = getIntent().getBooleanExtra(UNFOCUSABLE_VIEW, false);
+            boolean requestFocus = getIntent().getBooleanExtra(REQUEST_FOCUS_ON_CREATE, false);
+            int softInputFlags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0);
+            int windowFlags = getIntent().getIntExtra(WINDOW_FLAGS, 0);
+            boolean showWithInputMethodManagerOnCreate =
+                    getIntent().getBooleanExtra(INPUT_METHOD_MANAGER_SHOW_ON_CREATE, false);
+            boolean hideWithInputMethodManagerOnCreate =
+                    getIntent().getBooleanExtra(INPUT_METHOD_MANAGER_HIDE_ON_CREATE, false);
+            boolean showWithWindowInsetsControllerOnCreate =
+                    getIntent().getBooleanExtra(WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE, false);
+            boolean hideWithWindowInsetsControllerOnCreate =
+                    getIntent().getBooleanExtra(WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE, false);
+
+            getWindow().addFlags(windowFlags);
+            getWindow().setSoftInputMode(softInputFlags);
+
+            LinearLayout rootView = new LinearLayout(this);
+            rootView.setOrientation(LinearLayout.VERTICAL);
+            mEditText = new EditText(this);
+            if (isUnfocusableView) {
+                mEditText.setFocusableInTouchMode(false);
+            }
+            rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+            setContentView(rootView);
+
+            if (requestFocus) {
+                requestFocus();
+            }
+            if (showWithInputMethodManagerOnCreate) {
+                showImeWithInputMethodManager();
+            }
+            if (hideWithInputMethodManagerOnCreate) {
+                hideImeWithInputMethodManager();
+            }
+            if (showWithWindowInsetsControllerOnCreate) {
+                showImeWithWindowInsetsController();
+            }
+            if (hideWithWindowInsetsControllerOnCreate) {
+                hideImeWithWindowInsetsController();
+            }
+        }
+
+        /** Show IME with InputMethodManager. */
+        public boolean showImeWithInputMethodManager() {
+            boolean showResult =
+                    getInputMethodManager()
+                            .showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
+            if (showResult) {
+                Log.i(TAG, "IMM#showSoftInput successfully");
+            } else {
+                Log.i(TAG, "IMM#showSoftInput failed");
+            }
+            return showResult;
+        }
+
+        /** Hide IME with InputMethodManager. */
+        public boolean hideImeWithInputMethodManager() {
+            boolean hideResult =
+                    getInputMethodManager().hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+            if (hideResult) {
+                Log.i(TAG, "IMM#hideSoftInput successfully");
+            } else {
+                Log.i(TAG, "IMM#hideSoftInput failed");
+            }
+            return hideResult;
+        }
+
+        /** Show IME with WindowInsetsController */
+        public void showImeWithWindowInsetsController() {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+                return;
+            }
+            Log.i(TAG, "showImeWithWIC()");
+            WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+            assertWithMessage("WindowInsetsController shouldn't be null.")
+                    .that(windowInsetsController)
+                    .isNotNull();
+            windowInsetsController.show(WindowInsets.Type.ime());
+        }
+
+        /** Hide IME with WindowInsetsController. */
+        public void hideImeWithWindowInsetsController() {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+                return;
+            }
+            Log.i(TAG, "hideImeWithWIC()");
+            WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+            assertWithMessage("WindowInsetsController shouldn't be null.")
+                    .that(windowInsetsController)
+                    .isNotNull();
+            windowInsetsController.hide(WindowInsets.Type.ime());
+        }
+
+        private InputMethodManager getInputMethodManager() {
+            return getSystemService(InputMethodManager.class);
+        }
+
+        public EditText getEditText() {
+            return mEditText;
+        }
+
+        /** Start TestActivity with intent. */
+        public static TestActivity start(Intent intent) {
+            Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+            intent.setAction(Intent.ACTION_MAIN)
+                    .setClass(instrumentation.getContext(), TestActivity.class)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            return (TestActivity) instrumentation.startActivitySync(intent);
+        }
+
+        /** Start the second TestActivity with intent. */
+        public TestActivity startSecondTestActivity(Intent intent) {
+            Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+            intent.setClass(TestActivity.this, TestActivity.class);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            return (TestActivity) instrumentation.startActivitySync(intent);
+        }
+
+        public void enableAnimationMonitoring() {
+            // Enable WindowInsetsAnimation.
+            // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner.
+            InstrumentationRegistry.getInstrumentation()
+                    .runOnMainSync(
+                            () -> {
+                                getWindow().setDecorFitsSystemWindows(false);
+                                mEditText.setWindowInsetsAnimationCallback(
+                                        mWindowInsetsAnimationCallback);
+                            });
+        }
+
+        public boolean isAnimating() {
+            return mIsAnimating;
+        }
+
+        public void requestFocus() {
+            boolean requestFocusResult = getEditText().requestFocus();
+            if (requestFocusResult) {
+                Log.i(TAG, "Request focus successfully");
+            } else {
+                Log.i(TAG, "Request focus failed");
+            }
+        }
+    }
 }
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
index 3db0116..2cdb945 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -201,24 +201,24 @@
 
     @Test
     public void log_logcatEnabledExternalMessage() {
-        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f");
+        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% 0x%x %s %f");
         ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
 
         implSpy.log(
                 ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
-                new Object[]{true, 10000, 20000, 30000, 0.0001, 0.00002, "test", 0.000003});
+                new Object[]{true, 10000, 30000, "test", 0.000003});
 
         verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
                 ProtoLogImpl.LogLevel.INFO),
-                eq("test true 10000 % 47040 7530 1.000000e-04 2.00000e-05 test 0.000003"));
+                eq("test true 10000 % 0x7530 test 3.0E-6"));
         verify(mReader).getViewerString(eq(1234));
     }
 
     @Test
     public void log_logcatEnabledInvalidMessage() {
-        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f");
+        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %x %s %f");
         ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
diff --git a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
index e20ca3d..9c2f74ea 100644
--- a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java
@@ -36,15 +36,12 @@
 public class LogDataTypeTest {
     @Test
     public void parseFormatString() {
-        String str = "%b %d %o %x %f %e %g %s %%";
+        String str = "%b %d %x %f %s %%";
         List<Integer> out = LogDataType.parseFormatString(str);
         assertEquals(Arrays.asList(
                 LogDataType.BOOLEAN,
                 LogDataType.LONG,
                 LogDataType.LONG,
-                LogDataType.LONG,
-                LogDataType.DOUBLE,
-                LogDataType.DOUBLE,
                 LogDataType.DOUBLE,
                 LogDataType.STRING
         ), out);
diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
index 67a31da..512d90c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
@@ -84,8 +84,8 @@
 
     @Test
     fun parse_formatting() {
-        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
-                " %x %e %g %s %f", "ERROR", "WindowManager")
+        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %%" +
+                " %x %s %f", "ERROR", "WindowManager")
 
         val logBuilder = ProtoLogFileProto.newBuilder()
         val logMessageBuilder = ProtoLogMessage.newBuilder()
@@ -93,21 +93,21 @@
                 .setMessageHash(123)
                 .setElapsedRealtimeNanos(0)
                 .addBooleanParams(true)
-                .addAllSint64Params(listOf(1000, 20000, 300000))
-                .addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
+                .addAllSint64Params(listOf(1000, 20000))
+                .addDoubleParams(1000.1)
                 .addStrParams("test")
         logBuilder.addLog(logMessageBuilder.build())
 
         parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
 
         assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: " +
-                "true 1000 % 47040 493e0 1.000000e-01 1.00000e-05 test 1000.100000\n",
+                "true 1000 % 4e20 test 1000.100000\n",
                 outStream.toString())
     }
 
     @Test
     fun parse_invalidParamsTooMany() {
-        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o",
+        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %%",
                 "ERROR", "WindowManager")
 
         val logBuilder = ProtoLogFileProto.newBuilder()
@@ -129,8 +129,8 @@
 
     @Test
     fun parse_invalidParamsNotEnough() {
-        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
-                " %x %e %g %s %f", "ERROR", "WindowManager")
+        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %%" +
+                " %x %s %f", "ERROR", "WindowManager")
 
         val logBuilder = ProtoLogFileProto.newBuilder()
         val logMessageBuilder = ProtoLogMessage.newBuilder()