Merge "Ambient Activation p2.1" into main
diff --git a/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java b/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java
new file mode 100644
index 0000000..9699757
--- /dev/null
+++ b/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.os;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Mode Manager local service interface.
+ * Example usage: LocalServices.get(WearModeManagerInternal.class).
+ *
+ * TODO(b/288115060): consolidate with {@link com.android.server.policy.WearModeServiceInternal}
+ *
+ * @hide
+ */
+public interface WearModeManagerInternal {
+
+    /**
+     * Mode manager quick doze request identifier.
+     *
+     * <p>Unique identifier that can be used as identifier parameter in
+     * registerInternalStateObserver
+     * to listen to changes in quick doze request state from mode manager.
+     *
+     * TODO(b/288276510): convert to int constant
+     */
+    String QUICK_DOZE_REQUEST_IDENTIFIER = "quick_doze_request";
+
+    /**
+     * StringDef for Mode manager identifiers.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({
+            QUICK_DOZE_REQUEST_IDENTIFIER
+    })
+    @Target(ElementType.TYPE_USE)
+    @interface Identifier {
+    }
+
+    /**
+     * Method to register a callback in Mode manager.
+     *
+     * <p>Callback is executed when there is a change of active state for the
+     * provided identifier.
+     *
+     * <p>Mode manager has active states and configured states where active state is the state of a
+     * mode/feature as reflected on the device,
+     * configured state refers to the configured value of the state of the mode / feature.
+     * For e.g.: Quick doze might be configured to be disabled by default but in certain modes, it
+     * can be overridden to be enabled. At that point active=enabled, configured=disabled.
+     *
+     * <p>
+     *
+     * @param identifier Observer listens for changes to this {@link Identifier}
+     * @param executor   Executor used to execute the callback.
+     * @param callback   Boolean consumer callback.
+     */
+    <T> void addActiveStateChangeListener(@NonNull @Identifier String identifier,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<T> callback);
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 36c2f01..3392d25 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7662,7 +7662,7 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable(int);
     method public void removeOnColorsChangedListener(@NonNull android.app.WallpaperManager.OnColorsChangedListener);
-    method public void sendWallpaperCommand(android.os.IBinder, String, int, int, int, android.os.Bundle);
+    method @RequiresPermission(value="android.permission.ALWAYS_UPDATE_WALLPAPER", conditional=true) public void sendWallpaperCommand(android.os.IBinder, String, int, int, int, android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean, int) throws java.io.IOException;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0185080..a02fd84 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -36,6 +36,7 @@
     field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
     field public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS";
     field public static final String ALLOW_SLIPPERY_TOUCHES = "android.permission.ALLOW_SLIPPERY_TOUCHES";
+    field public static final String ALWAYS_UPDATE_WALLPAPER = "android.permission.ALWAYS_UPDATE_WALLPAPER";
     field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
     field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
     field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES";
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 5d7993d..c31aa01 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2670,6 +2670,8 @@
      * @param z Arbitrary integer argument based on command.
      * @param extras Optional additional information for the command, or null.
      */
+    @RequiresPermission(value = android.Manifest.permission.ALWAYS_UPDATE_WALLPAPER,
+            conditional = true)
     public void sendWallpaperCommand(IBinder windowToken, String action,
             int x, int y, int z, Bundle extras) {
         try {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b29a4e6..86cf80e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4032,6 +4032,13 @@
     <permission android:name="android.permission.READ_WALLPAPER_INTERNAL"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allow apps to always update wallpaper by sending data.
+        @SystemApi
+        @hide
+    -->
+    <permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER"
+        android:protectionLevel="internal|role" />
+
     <!-- ===================================================== -->
     <!-- Permissions for changing the system clock / time zone -->
     <!-- ===================================================== -->
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index eaadc20..c3db87b 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -45,29 +45,29 @@
     <!-- Component name of the activity used to inform a user about a sensory being blocked because
      of privacy settings. -->
     <string name="config_sensorUseStartedActivity" translatable="false">
-        com.android.systemui/com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity
+        com.android.systemui/com.android.systemui.tv.sensorprivacy.TvUnblockSensorActivity
     </string>
 
     <!-- Component name of the activity used to inform a user about a sensory being blocked because
      of hardware privacy switches. -->
     <string name="config_sensorUseStartedActivity_hwToggle" translatable="false">
-        com.android.systemui/com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity
+        com.android.systemui/com.android.systemui.tv.sensorprivacy.TvUnblockSensorActivity
     </string>
 
     <!-- Component name of the activity used to inform a user about a sensor privacy update from
      SW/HW privacy switches. -->
     <string name="config_sensorStateChangedActivity" translatable="false">
-        com.android.systemui/com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity
+        com.android.systemui/com.android.systemui.tv.sensorprivacy.TvSensorPrivacyChangedActivity
     </string>
 
     <!-- Component name of the activity that shows the request for access to a usb device. -->
     <string name="config_usbPermissionActivity" translatable="false">
-        com.android.systemui/com.android.systemui.usb.tv.TvUsbPermissionActivity
+        com.android.systemui/com.android.systemui.tv.usb.TvUsbPermissionActivity
     </string>
 
     <!-- Component name of the activity that confirms the activity to start when a usb device is
      plugged in. -->
     <string name="config_usbConfirmActivity" translatable="false">
-        com.android.systemui/com.android.systemui.usb.tv.TvUsbConfirmActivity
+        com.android.systemui/com.android.systemui.tv.usb.TvUsbConfirmActivity
     </string>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 38da090..71630cb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3212,8 +3212,9 @@
 
     <!-- Component name of the activity used to ask a user to confirm system language change after
          receiving <Set Menu Language> CEC message. -->
-    <string name="config_hdmiCecSetMenuLanguageActivity"
-            >com.android.systemui/com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity</string>
+    <string name="config_hdmiCecSetMenuLanguageActivity">
+        com.android.systemui/com.android.systemui.tv.hdmi.HdmiCecSetMenuLanguageActivity
+    </string>
 
     <!-- Name of the dialog that is used to request the user's consent for a Platform VPN -->
     <string name="config_platformVpnConfirmDialogComponent" translatable="false"
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg.xml
new file mode 100644
index 0000000..d3d324b
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_enabled="false"
+        android:drawable="@drawable/settingslib_switch_bar_bg_disabled"/>
+    <item
+        android:state_activated="true"
+        android:drawable="@drawable/settingslib_switch_bar_bg_on"/>
+    <item
+        android:state_activated="false"
+        android:drawable="@drawable/settingslib_switch_bar_bg_off"/>
+</selector>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
index b1c26e8..e3f8fbb 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
@@ -32,7 +32,8 @@
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
         android:paddingStart="@dimen/settingslib_switchbar_padding_left"
-        android:paddingEnd="@dimen/settingslib_switchbar_padding_right">
+        android:paddingEnd="@dimen/settingslib_switchbar_padding_right"
+        android:background="@drawable/settingslib_switch_bar_bg">
 
         <TextView
             android:id="@+id/switch_text"
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
index d2e9fbe..255b2c9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
@@ -32,7 +32,8 @@
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
         android:paddingStart="@dimen/settingslib_switchbar_padding_left"
-        android:paddingEnd="@dimen/settingslib_switchbar_padding_right">
+        android:paddingEnd="@dimen/settingslib_switchbar_padding_right"
+        android:background="@drawable/settingslib_switch_bar_bg">
 
         <TextView
             android:id="@+id/switch_text"
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 864a8bb..d1703c3 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
@@ -52,9 +51,6 @@
 
     protected TextView mTextView;
     protected Switch mSwitch;
-    private Drawable mBackgroundOn;
-    private Drawable mBackgroundOff;
-    private Drawable mBackgroundDisabled;
     private View mFrameView;
 
     public MainSwitchBar(Context context) {
@@ -89,12 +85,6 @@
         mFrameView = findViewById(R.id.frame);
         mTextView = (TextView) findViewById(R.id.switch_text);
         mSwitch = (Switch) findViewById(android.R.id.switch_widget);
-        if (BuildCompatUtils.isAtLeastS()) {
-            mBackgroundOn = getContext().getDrawable(R.drawable.settingslib_switch_bar_bg_on);
-            mBackgroundOff = getContext().getDrawable(R.drawable.settingslib_switch_bar_bg_off);
-            mBackgroundDisabled = getContext().getDrawable(
-                    R.drawable.settingslib_switch_bar_bg_disabled);
-        }
         addOnSwitchChangeListener((switchView, isChecked) -> setChecked(isChecked));
 
         if (mSwitch.getVisibility() == VISIBLE) {
@@ -217,17 +207,15 @@
     /**
      * Enable or disable the text and switch.
      */
+    @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
         mTextView.setEnabled(enabled);
         mSwitch.setEnabled(enabled);
 
         if (BuildCompatUtils.isAtLeastS()) {
-            if (enabled) {
-                mFrameView.setBackground(isChecked() ? mBackgroundOn : mBackgroundOff);
-            } else {
-                mFrameView.setBackground(mBackgroundDisabled);
-            }
+            mFrameView.setEnabled(enabled);
+            mFrameView.setActivated(isChecked());
         }
     }
 
@@ -244,7 +232,7 @@
         if (!BuildCompatUtils.isAtLeastS()) {
             setBackgroundColor(isChecked ? mBackgroundActivatedColor : mBackgroundColor);
         } else {
-            mFrameView.setBackground(isChecked ? mBackgroundOn : mBackgroundOff);
+            mFrameView.setActivated(isChecked);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 3011d31..8d03f70 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -80,6 +80,7 @@
     private Bitmap mSavedPhoto;
     private String mSavedName;
     private Drawable mSavedDrawable;
+    private String mCachedDrawablePath;
     private String mUserName;
     private Drawable mNewUserIcon;
     private Boolean mIsAdmin;
@@ -117,6 +118,7 @@
         mUserNameView = null;
         mSuccessCallback = null;
         mCancelCallback = null;
+        mCachedDrawablePath = null;
         mCurrentState = INITIAL_DIALOG;
     }
 
@@ -124,13 +126,7 @@
      * Notifies that the containing activity or fragment was reinitialized.
      */
     public void onRestoreInstanceState(Bundle savedInstanceState) {
-        String pendingPhoto = savedInstanceState.getString(KEY_SAVED_PHOTO);
-        if (pendingPhoto != null) {
-            ThreadUtils.postOnBackgroundThread(() -> {
-                mSavedPhoto = EditUserPhotoController.loadNewUserPhotoBitmap(
-                        new File(pendingPhoto));
-            });
-        }
+        mCachedDrawablePath = savedInstanceState.getString(KEY_SAVED_PHOTO);
         mCurrentState = savedInstanceState.getInt(KEY_CURRENT_STATE);
         if (savedInstanceState.containsKey(KEY_IS_ADMIN)) {
             mIsAdmin = savedInstanceState.getBoolean(KEY_IS_ADMIN);
@@ -143,15 +139,12 @@
      * Notifies that the containing activity or fragment is saving its state for later use.
      */
     public void onSaveInstanceState(Bundle savedInstanceState) {
-        if (mUserCreationDialog != null && mEditUserPhotoController != null) {
-            // Bitmap cannot be stored into bundle because it may exceed parcel limit
-            // Store it in a temporary file instead
-            ThreadUtils.postOnBackgroundThread(() -> {
-                File file = mEditUserPhotoController.saveNewUserPhotoBitmap();
-                if (file != null) {
-                    savedInstanceState.putString(KEY_SAVED_PHOTO, file.getPath());
-                }
-            });
+        if (mUserCreationDialog != null && mEditUserPhotoController != null
+                && mCachedDrawablePath == null) {
+            mCachedDrawablePath = mEditUserPhotoController.getCachedDrawablePath();
+        }
+        if (mCachedDrawablePath != null) {
+            savedInstanceState.putString(KEY_SAVED_PHOTO, mCachedDrawablePath);
         }
         if (mIsAdmin != null) {
             savedInstanceState.putBoolean(KEY_IS_ADMIN, Boolean.TRUE.equals(mIsAdmin));
@@ -271,9 +264,10 @@
                 mGrantAdminView.setVisibility(View.GONE);
                 break;
             case CREATE_USER_AND_CLOSE:
-                mNewUserIcon = mEditUserPhotoController != null
+                mNewUserIcon = (mEditUserPhotoController != null
+                        && mEditUserPhotoController.getNewUserPhotoDrawable() != null)
                         ? mEditUserPhotoController.getNewUserPhotoDrawable()
-                        : null;
+                        : mSavedDrawable;
 
                 String newName = mUserNameView.getText().toString().trim();
                 String defaultName = mActivity.getString(R.string.user_new_user_name);
@@ -295,12 +289,17 @@
         }
     }
 
-    private Drawable getUserIcon(Drawable defaultUserIcon) {
-        if (mSavedPhoto != null) {
-            mSavedDrawable = CircleFramedDrawable.getInstance(mActivity, mSavedPhoto);
-            return mSavedDrawable;
+    private void setUserIcon(Drawable defaultUserIcon, ImageView userPhotoView) {
+        if (mCachedDrawablePath != null) {
+            ThreadUtils.postOnBackgroundThread(() -> {
+                mSavedPhoto = EditUserPhotoController.loadNewUserPhotoBitmap(
+                        new File(mCachedDrawablePath));
+                mSavedDrawable = CircleFramedDrawable.getInstance(mActivity, mSavedPhoto);
+                ThreadUtils.postOnMainThread(() -> userPhotoView.setImageDrawable(mSavedDrawable));
+            });
+        } else {
+            userPhotoView.setImageDrawable(defaultUserIcon);
         }
-        return defaultUserIcon;
     }
 
     private void addUserInfoEditView() {
@@ -312,10 +311,7 @@
         // if oldUserIcon param is null then we use a default gray user icon
         Drawable defaultUserIcon = UserIcons.getDefaultUserIcon(
                 mActivity.getResources(), UserHandle.USER_NULL, false);
-        // in case a new photo was selected and the activity got recreated we have to load the image
-        Drawable userIcon = getUserIcon(defaultUserIcon);
-        userPhotoView.setImageDrawable(userIcon);
-
+        setUserIcon(defaultUserIcon, userPhotoView);
         if (isChangePhotoRestrictedByBase(mActivity)) {
             // some users can't change their photos so we need to remove the suggestive icon
             mEditUserInfoView.findViewById(R.id.add_a_photo_icon).setVisibility(View.GONE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index 38cf383..3fb2f60 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -60,6 +60,7 @@
     private final File mImagesDir;
     private Bitmap mNewUserPhotoBitmap;
     private Drawable mNewUserPhotoDrawable;
+    private String mCachedDrawablePath;
 
     public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
             ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority) {
@@ -156,6 +157,9 @@
     private void onPhotoProcessed(Bitmap bitmap) {
         if (bitmap != null) {
             mNewUserPhotoBitmap = bitmap;
+            ThreadUtils.postOnBackgroundThread(() -> {
+                mCachedDrawablePath = saveNewUserPhotoBitmap().getPath();
+            });
             mNewUserPhotoDrawable = CircleFramedDrawable
                     .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
             mImageView.setImageDrawable(mNewUserPhotoDrawable);
@@ -186,4 +190,8 @@
     void removeNewUserPhotoBitmapFile() {
         new File(mImagesDir, NEW_USER_PHOTO_FILE_NAME).delete();
     }
+
+    String getCachedDrawablePath() {
+        return mCachedDrawablePath;
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 00f2d0e..ffaebf4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -40,8 +40,12 @@
 
 import com.android.internal.util.FastPrintWriter;
 
+import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.InputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.reflect.Field;
@@ -49,14 +53,22 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Scanner;
 
 /**
  * Receives shell commands from the command line related to device config flags, and dispatches them
  * to the SettingsProvider.
  */
 public final class DeviceConfigService extends Binder {
+    private static final List<String> aconfigTextProtoFilesOnDevice = List.of(
+        "/system/etc/aconfig_flags.textproto",
+        "/system_ext/etc/aconfig_flags.textproto",
+        "/system_ext/etc/aconfig_flags.textproto",
+        "/vendor/etc/aconfig_flags.textproto");
+
     final SettingsProvider mProvider;
 
     public DeviceConfigService(SettingsProvider provider) {
@@ -78,10 +90,61 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
       final IContentProvider iprovider = mProvider.getIContentProvider();
-      pw.println("device config properties:");
+      pw.println("DeviceConfig flags:");
       for (String line : MyShellCommand.listAll(iprovider)) {
         pw.println(line);
       }
+
+      ArrayList<String> missingFiles = new ArrayList<String>();
+      for (String fileName : aconfigTextProtoFilesOnDevice) {
+        File aconfigFile = new File(fileName);
+        if (!aconfigFile.exists()) {
+          missingFiles.add(fileName);
+        }
+      }
+
+      if (missingFiles.isEmpty()) {
+        pw.println("\nAconfig flags:");
+        for (String name : MyShellCommand.listAllAconfigFlags(iprovider)) {
+          pw.println(name);
+        }
+      } else {
+        pw.println("\nFailed to dump aconfig flags due to missing files:");
+        for (String fileName : missingFiles) {
+          pw.println(fileName);
+        }
+      }
+    }
+
+    private static HashSet<String> getAconfigFlagNamesInDeviceConfig() {
+        HashSet<String> nameSet = new HashSet<String>();
+        for (String fileName : aconfigTextProtoFilesOnDevice) {
+          try{
+            File aconfigFile = new File(fileName);
+            String packageName = "";
+            String namespace = "";
+            String name = "";
+
+            try (Scanner scanner = new Scanner(aconfigFile)) {
+              while (scanner.hasNextLine()) {
+                String data = scanner.nextLine().replaceAll("\\s+","");
+                if (data.startsWith("package:\"")) {
+                  packageName = data.substring(9, data.length()-1);
+                } else if (data.startsWith("name:\"")) {
+                  name = data.substring(6, data.length()-1);
+                } else if (data.startsWith("namespace:\"")) {
+                  namespace = data.substring(11, data.length()-1);
+                  nameSet.add(namespace + "/" + packageName + "." + name);
+                }
+              }
+            }
+
+          } catch (FileNotFoundException e) {
+            continue;
+          }
+        }
+
+      return nameSet;
     }
 
     private void callUpdableDeviceConfigShellCommandHandler(FileDescriptor in, FileDescriptor out,
@@ -120,31 +183,51 @@
             mProvider = provider;
         }
 
-        public static List<String> listAll(IContentProvider provider) {
-            final ArrayList<String> lines = new ArrayList<>();
-
-            try {
-                Bundle args = new Bundle();
-                args.putInt(Settings.CALL_METHOD_USER_KEY,
-                        ActivityManager.getService().getCurrentUser().id);
-                Bundle b = provider.call(new AttributionSource(Process.myUid(),
-                                resolveCallingPackage(), null), Settings.AUTHORITY,
-                        Settings.CALL_METHOD_LIST_CONFIG, null, args);
-                if (b != null) {
-                    Map<String, String> flagsToValues =
-                            (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
-                    for (String key : flagsToValues.keySet()) {
-                        lines.add(key + "=" + flagsToValues.get(key));
-                    }
-                }
-
-                Collections.sort(lines);
-            } catch (RemoteException e) {
-                throw new RuntimeException("Failed in IPC", e);
+      public static HashMap<String, String> getAllFlags(IContentProvider provider) {
+        HashMap<String, String> allFlags = new HashMap<String, String>();
+        try {
+            Bundle args = new Bundle();
+            args.putInt(Settings.CALL_METHOD_USER_KEY,
+                ActivityManager.getService().getCurrentUser().id);
+            Bundle b = provider.call(new AttributionSource(Process.myUid(),
+                    resolveCallingPackage(), null), Settings.AUTHORITY,
+                    Settings.CALL_METHOD_LIST_CONFIG, null, args);
+            if (b != null) {
+                Map<String, String> flagsToValues =
+                    (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
+                allFlags.putAll(flagsToValues);
             }
-            return lines;
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed in IPC", e);
         }
 
+        return allFlags;
+      }
+
+      public static List<String> listAll(IContentProvider provider) {
+        HashMap<String, String> allFlags = getAllFlags(provider);
+        final ArrayList<String> lines = new ArrayList<>();
+        for (String key : allFlags.keySet()) {
+          lines.add(key + "=" + allFlags.get(key));
+        }
+        Collections.sort(lines);
+        return lines;
+      }
+
+      public static List<String> listAllAconfigFlags(IContentProvider provider) {
+        HashMap<String, String> allFlags = getAllFlags(provider);
+        HashSet<String> aconfigFlagNames = getAconfigFlagNamesInDeviceConfig();
+        final ArrayList<String> lines = new ArrayList<>();
+        for (String key : aconfigFlagNames) {
+          String val = allFlags.get(key);
+          if (val != null) {
+            lines.add(key + "=" + val);
+          }
+        }
+        Collections.sort(lines);
+        return lines;
+      }
+
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @Override
         public int onCommand(String cmd) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 215cc8e..323f65f 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -849,11 +849,15 @@
     <!-- Permission required for accessing all content provider mime types -->
     <uses-permission android:name="android.permission.GET_ANY_PROVIDER_TYPE" />
 
+    <!-- Permission required for CTS test - CtsWallpaperTestCases -->
+    <uses-permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
         android:defaultToDeviceProtectedStorage="true"
         android:directBootAware="true">
+
         <provider
             android:name="androidx.core.content.FileProvider"
             android:authorities="com.android.shell"
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b49c5fb..1845ae8 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.ALWAYS_UPDATE_WALLPAPER;
 import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
@@ -112,6 +113,7 @@
     final boolean mCanCreateSystemApplicationOverlay;
     final boolean mCanHideNonSystemOverlayWindows;
     final boolean mCanSetUnrestrictedGestureExclusion;
+    final boolean mCanAlwaysUpdateWallpaper;
     private AlertWindowNotification mAlertWindowNotification;
     private boolean mShowingAlertWindowNotificationAllowed;
     private boolean mClientDead = false;
@@ -144,6 +146,9 @@
         mCanSetUnrestrictedGestureExclusion =
                 service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_GESTURE_EXCLUSION)
                         == PERMISSION_GRANTED;
+        mCanAlwaysUpdateWallpaper =
+                service.mContext.checkCallingOrSelfPermission(ALWAYS_UPDATE_WALLPAPER)
+                        == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
         mDragDropController = mService.mDragDropController;
         StringBuilder sb = new StringBuilder();
@@ -621,8 +626,15 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 final WindowState windowState = mService.windowForClientLocked(this, window, true);
-                return windowState.getDisplayContent().mWallpaperController
-                        .sendWindowWallpaperCommand(windowState, action, x, y, z, extras, sync);
+                WallpaperController wallpaperController =
+                        windowState.getDisplayContent().mWallpaperController;
+                if (mCanAlwaysUpdateWallpaper
+                        || windowState == wallpaperController.getWallpaperTarget()
+                        || windowState == wallpaperController.getPrevWallpaperTarget()) {
+                    return wallpaperController.sendWindowWallpaperCommandUnchecked(
+                            windowState, action, x, y, z, extras, sync);
+                }
+                return null;
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index af770e2..7e5dabb 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -298,6 +298,10 @@
         return mWallpaperTarget;
     }
 
+    WindowState getPrevWallpaperTarget() {
+        return mPrevWallpaperTarget;
+    }
+
     boolean isWallpaperTarget(WindowState win) {
         return win == mWallpaperTarget;
     }
@@ -565,12 +569,10 @@
         }
     }
 
-    Bundle sendWindowWallpaperCommand(
-            WindowState window, String action, int x, int y, int z, Bundle extras, boolean sync) {
-        if (window == mWallpaperTarget || window == mPrevWallpaperTarget) {
-            sendWindowWallpaperCommand(action, x, y, z, extras, sync);
-        }
-
+    Bundle sendWindowWallpaperCommandUnchecked(
+            WindowState window, String action, int x, int y, int z,
+            Bundle extras, boolean sync) {
+        sendWindowWallpaperCommand(action, x, y, z, extras, sync);
         return null;
     }
 
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index ce3d514..2a2fa7a 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -315,11 +315,22 @@
         std::unique_lock lock(mMutex);
         for (;;) {
             const TimePoint nextJobTs = mJobs.empty() ? kInfinityTs : mJobs.begin()->when;
-            mCondition.wait_until(lock, nextJobTs, [this, oldNextJobTs = nextJobTs]() {
+            auto conditionPredicate = [this, oldNextJobTs = nextJobTs]() {
                 const auto now = Clock::now();
                 const auto newFirstJobTs = !mJobs.empty() ? mJobs.begin()->when : kInfinityTs;
                 return newFirstJobTs <= now || newFirstJobTs < oldNextJobTs || !mRunning;
-            });
+            };
+            // libcxx's implementation of wait_until() recalculates the 'until' time into
+            // the wait duration and then goes back to the absolute timestamp when calling
+            // pthread_cond_timedwait(); this back-and-forth calculation sometimes loses
+            // the 'infinity' value because enough time passes in between, and instead
+            // passes incorrect timestamp into the syscall, causing a crash.
+            // Mitigating it by explicitly calling the non-timed wait here.
+            if (mJobs.empty()) {
+                mCondition.wait(lock, conditionPredicate);
+            } else {
+                mCondition.wait_until(lock, nextJobTs, conditionPredicate);
+            }
             if (!mRunning) {
                 return;
             }
diff --git a/services/tests/powerservicetests/Android.bp b/services/tests/powerservicetests/Android.bp
index 9384015..7351fc5f 100644
--- a/services/tests/powerservicetests/Android.bp
+++ b/services/tests/powerservicetests/Android.bp
@@ -17,6 +17,7 @@
         "services.core",
         "servicestests-utils",
         "testables",
+        "TestParameterInjector",
     ],
 
     libs: [
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 9bce536..0b1e338 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -111,6 +111,9 @@
 import com.android.server.power.batterysaver.BatterySavingStats;
 import com.android.server.testutils.OffsettableClock;
 
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
@@ -119,6 +122,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
@@ -140,6 +144,7 @@
  *  atest FrameworksServicesTests:PowerManagerServiceTest
  */
 @SuppressWarnings("GuardedBy")
+@RunWith(TestParameterInjector.class)
 public class PowerManagerServiceTest {
     private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
     private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason";
@@ -2768,12 +2773,13 @@
     }
 
     @Test
-    public void testFeatureEnabledProcStateUncachedToCached_fullWakeLockDisabled() {
+    public void testFeatureEnabledProcStateUncachedToCached_screenWakeLockDisabled(
+            @TestParameter PowerManagerServiceTest.ScreenWakeLockTestParameter param) {
         doReturn(true).when(mDeviceParameterProvider)
                 .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
         createService();
         startSystem();
-        WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK);
+        WakeLock wakeLock = acquireWakeLock(param.mDescr, param.mFlags);
         setUncachedUidProcState(wakeLock.mOwnerUid);
 
         setCachedUidProcState(wakeLock.mOwnerUid);
@@ -2781,12 +2787,13 @@
     }
 
     @Test
-    public void testFeatureDisabledProcStateUncachedToCached_fullWakeLockEnabled() {
+    public void testFeatureDisabledProcStateUncachedToCached_screenWakeLockEnabled(
+            @TestParameter PowerManagerServiceTest.ScreenWakeLockTestParameter param) {
         doReturn(false).when(mDeviceParameterProvider)
                 .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
         createService();
         startSystem();
-        WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK);
+        WakeLock wakeLock = acquireWakeLock(param.mDescr, param.mFlags);
         setUncachedUidProcState(wakeLock.mOwnerUid);
 
         setCachedUidProcState(wakeLock.mOwnerUid);
@@ -2794,68 +2801,27 @@
     }
 
     @Test
-    public void testFeatureEnabledProcStateUncachedToCached_screenBrightWakeLockDisabled() {
+    public void testFeatureEnabledProcStateCachedToUncached_screenWakeLockEnabled(
+            @TestParameter PowerManagerServiceTest.ScreenWakeLockTestParameter param) {
         doReturn(true).when(mDeviceParameterProvider)
                 .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
         createService();
         startSystem();
-        WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock",
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK);
-        setUncachedUidProcState(wakeLock.mOwnerUid);
-
+        WakeLock wakeLock = acquireWakeLock(param.mDescr, param.mFlags);
         setCachedUidProcState(wakeLock.mOwnerUid);
-        assertThat(wakeLock.mDisabled).isTrue();
+
+        setUncachedUidProcState(wakeLock.mOwnerUid);
+        assertThat(wakeLock.mDisabled).isFalse();
     }
 
     @Test
-    public void testFeatureDisabledProcStateUncachedToCached_screenBrightWakeLockEnabled() {
+    public void testFeatureDisabledProcStateCachedToUncached_screenWakeLockEnabled(
+            @TestParameter PowerManagerServiceTest.ScreenWakeLockTestParameter param) {
         doReturn(false).when(mDeviceParameterProvider)
                 .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
         createService();
         startSystem();
-        WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock",
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK);
-        setUncachedUidProcState(wakeLock.mOwnerUid);
-
-        setCachedUidProcState(wakeLock.mOwnerUid);
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testFeatureEnabledProcStateUncachedToCached_screenDimWakeLockDisabled() {
-        doReturn(true).when(mDeviceParameterProvider)
-                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("screenDimWakeLock",
-                PowerManager.SCREEN_DIM_WAKE_LOCK);
-        setUncachedUidProcState(wakeLock.mOwnerUid);
-
-        setCachedUidProcState(wakeLock.mOwnerUid);
-        assertThat(wakeLock.mDisabled).isTrue();
-    }
-
-    @Test
-    public void testFeatureDisabledProcStateUncachedToCached_screenDimWakeLockEnabled() {
-        doReturn(false).when(mDeviceParameterProvider)
-                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("screenDimWakeLock",
-                PowerManager.SCREEN_DIM_WAKE_LOCK);
-        setUncachedUidProcState(wakeLock.mOwnerUid);
-
-        setCachedUidProcState(wakeLock.mOwnerUid);
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testFeatureEnabledProcStateCachedToUncached_fullWakeLockEnabled() {
-        doReturn(true).when(mDeviceParameterProvider)
-                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK);
+        WakeLock wakeLock = acquireWakeLock(param.mDescr, param.mFlags);
         setCachedUidProcState(wakeLock.mOwnerUid);
 
         setUncachedUidProcState(wakeLock.mOwnerUid);
@@ -2863,76 +2829,7 @@
     }
 
     @Test
-    public void testFeatureDisabledProcStateCachedToUncached_fullWakeLockEnabled() {
-        doReturn(false).when(mDeviceParameterProvider)
-                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK);
-        setCachedUidProcState(wakeLock.mOwnerUid);
-
-        setUncachedUidProcState(wakeLock.mOwnerUid);
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testFeatureEnabledProcStateCachedToUncached_screenBrightWakeLockEnabled() {
-        doReturn(true).when(mDeviceParameterProvider)
-                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock",
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK);
-        setCachedUidProcState(wakeLock.mOwnerUid);
-
-        setUncachedUidProcState(wakeLock.mOwnerUid);
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testFeatureDisabledProcStateCachedToUncached_screenBrightWakeLockEnabled() {
-        doReturn(false).when(mDeviceParameterProvider)
-                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock",
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK);
-        setCachedUidProcState(wakeLock.mOwnerUid);
-
-        setUncachedUidProcState(wakeLock.mOwnerUid);
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testFeatureEnabledProcStateCachedToUncached_screenDimWakeLockEnabled() {
-        doReturn(true).when(mDeviceParameterProvider)
-                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("screenDimWakeLock",
-                PowerManager.SCREEN_DIM_WAKE_LOCK);
-        setCachedUidProcState(wakeLock.mOwnerUid);
-
-        setUncachedUidProcState(wakeLock.mOwnerUid);
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testFeatureDisabledProcStateCachedToUncached_screenDimWakeLockEnabled() {
-        doReturn(false).when(mDeviceParameterProvider)
-                .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("screenDimWakeLock",
-                PowerManager.SCREEN_DIM_WAKE_LOCK);
-        setCachedUidProcState(wakeLock.mOwnerUid);
-
-        setUncachedUidProcState(wakeLock.mOwnerUid);
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testFeatureDynamicallyDisabledProcStateUncachedToCached_fullWakeLockEnabled() {
+    public void testFeatureDynamicallyDisabledProcStateUncachedToCached_screenWakeLockEnabled() {
         doReturn(true).when(mDeviceParameterProvider)
                 .isDisableScreenWakeLocksWhileCachedFeatureEnabled();
         ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> listenerCaptor =
@@ -2960,4 +2857,18 @@
     private void setUncachedUidProcState(int uid) {
         mService.updateUidProcStateInternal(uid, PROCESS_STATE_RECEIVER);
     }
+
+    private enum ScreenWakeLockTestParameter {
+        FULL_WAKE_LOCK("fullWakeLock", PowerManager.FULL_WAKE_LOCK),
+        SCREEN_BRIGHT_WAKE_LOCK("screenBrightWakeLock", PowerManager.SCREEN_BRIGHT_WAKE_LOCK),
+        SCREEN_DIM_WAKE_LOCK("screenDimWakeLock", PowerManager.SCREEN_DIM_WAKE_LOCK);
+
+        final String mDescr;
+        final int mFlags;
+
+        ScreenWakeLockTestParameter(String descr, int flags) {
+            this.mDescr = descr;
+            this.mFlags = flags;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
index 4f45d5c2..9a93746 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
@@ -39,6 +39,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.wm.utils.CommonUtils;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -131,18 +133,29 @@
         final Activity activity = instrumentation.startActivitySync(intent);
         final SurfaceView sv = new SurfaceView(activity);
         final AtomicInteger surfaceChangedCount = new AtomicInteger();
+        final boolean[] unexpectedTransformHint = new boolean[1];
         instrumentation.runOnMainSync(() -> activity.setContentView(sv));
         sv.getHolder().addCallback(new SurfaceHolder.Callback() {
+            int mInitialTransformHint = -1;
+
             @Override
             public void surfaceCreated(@NonNull SurfaceHolder holder) {
             }
             @Override
             public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
                     int height) {
+                final int transformHint =
+                        sv.getViewRootImpl().getSurfaceControl().getTransformHint();
+                if (mInitialTransformHint == -1) {
+                    mInitialTransformHint = transformHint;
+                } else if (mInitialTransformHint == transformHint) {
+                    // For example, the initial hint is from portrait, so the later changes from
+                    // landscape should not receive the same hint.
+                    unexpectedTransformHint[0] = true;
+                }
                 surfaceChangedCount.getAndIncrement();
                 Log.i("surfaceChanged", "width=" + width + " height=" + height
-                        + " getTransformHint="
-                        + sv.getViewRootImpl().getSurfaceControl().getTransformHint());
+                        + " transformHint=" + transformHint);
             }
             @Override
             public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
@@ -160,7 +173,7 @@
                 .windowConfiguration.getRotation();
         if (rotation == newRotation) {
             // The device might not support requested orientation.
-            activity.finishAndRemoveTask();
+            CommonUtils.waitUntilActivityRemoved(activity);
             return;
         }
         final int count = surfaceChangedCount.get();
@@ -169,11 +182,12 @@
         context.startActivity(intent);
         instrumentation.getUiAutomation().syncInputTransactions();
         final int countAfterToFront = count - surfaceChangedCount.get();
-        activity.finishAndRemoveTask();
+        CommonUtils.waitUntilActivityRemoved(activity);
 
         // The first count is triggered from creation, so the target number is 2.
-        if (count > 2) {
-            fail("More than once surfaceChanged for rotation change: " + count);
+        if (count > 2 && unexpectedTransformHint[0]) {
+            fail("Received transform hint in previous orientation with more than once"
+                    + " surfaceChanged for rotation change: " + count);
         }
         if (countAfterToFront > 1) {
             fail("More than once surfaceChanged for app transition with rotation change: "
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
index df11a44..f173d66 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
@@ -30,6 +30,9 @@
 
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
+import com.android.server.wm.utils.CommonUtils;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -61,6 +64,11 @@
         mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
     }
 
+    @After
+    public void tearDown() {
+        CommonUtils.waitUntilActivityRemoved(mActivity);
+    }
+
     @Test
     public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
         boolean[] results = new boolean[1];