Merge changes from topic "camera-compat" into sc-v2-dev am: 3486eacda3 am: 02a17e7b1f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16468379

Change-Id: Ic9916a3883eef0148a86c86149aa2e883fc56d1b
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index db7ab1a..eb4a355 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PersistableBundle;
@@ -498,6 +499,28 @@
         }
     }
 
+    /**
+     * Shows or hides a Camera app compat toggle for stretched issues with the requested state.
+     *
+     * @param token The token for the window that needs a control.
+     * @param showControl Whether the control should be shown or hidden.
+     * @param transformationApplied Whether the treatment is already applied.
+     * @param callback The callback executed when the user clicks on a control.
+     */
+    void requestCompatCameraControl(Resources res, IBinder token, boolean showControl,
+            boolean transformationApplied, ICompatCameraControlCallback callback) {
+        if (!res.getBoolean(com.android.internal.R.bool
+                .config_isCameraCompatControlForStretchedIssuesEnabled)) {
+            return;
+        }
+        try {
+            getActivityClientController().requestCompatCameraControl(
+                    token, showControl, transformationApplied, callback);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     public static ActivityClient getInstance() {
         return sInstance.get();
     }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7eb92a7..a8894dc 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -561,8 +561,8 @@
         private Configuration mPendingOverrideConfig;
         // Used for consolidating configs before sending on to Activity.
         private Configuration tmpConfig = new Configuration();
-        // Callback used for updating activity override config.
-        ViewRootImpl.ActivityConfigCallback configCallback;
+        // Callback used for updating activity override config and camera compat control state.
+        ViewRootImpl.ActivityConfigCallback activityConfigCallback;
         ActivityClientRecord nextIdle;
 
         // Indicates whether this activity is currently the topmost resumed one in the system.
@@ -660,13 +660,30 @@
             stopped = false;
             hideForNow = false;
             nextIdle = null;
-            configCallback = (Configuration overrideConfig, int newDisplayId) -> {
-                if (activity == null) {
-                    throw new IllegalStateException(
-                            "Received config update for non-existing activity");
+            activityConfigCallback = new ViewRootImpl.ActivityConfigCallback() {
+                @Override
+                public void onConfigurationChanged(Configuration overrideConfig,
+                        int newDisplayId) {
+                    if (activity == null) {
+                        throw new IllegalStateException(
+                                "Received config update for non-existing activity");
+                    }
+                    activity.mMainThread.handleActivityConfigurationChanged(
+                            ActivityClientRecord.this, overrideConfig, newDisplayId);
                 }
-                activity.mMainThread.handleActivityConfigurationChanged(this, overrideConfig,
-                        newDisplayId);
+
+                @Override
+                public void requestCompatCameraControl(boolean showControl,
+                        boolean transformationApplied, ICompatCameraControlCallback callback) {
+                    if (activity == null) {
+                        throw new IllegalStateException(
+                                "Received camera compat control update for non-existing activity");
+                    }
+                    ActivityClient.getInstance().requestCompatCameraControl(
+                            activity.getResources(), token, showControl, transformationApplied,
+                            callback);
+                }
+
             };
         }
 
@@ -3672,7 +3689,7 @@
                 activity.attach(appContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         r.embeddedID, r.lastNonConfigurationInstances, config,
-                        r.referrer, r.voiceInteractor, window, r.configCallback,
+                        r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
                         r.assistToken, r.shareableActivityToken);
 
                 if (customIntent != null) {
@@ -5513,8 +5530,8 @@
                 } else {
                     final ViewRootImpl viewRoot = v.getViewRootImpl();
                     if (viewRoot != null) {
-                        // Clear the callback to avoid the destroyed activity from receiving
-                        // configuration changes that are no longer effective.
+                        // Clear callbacks to avoid the destroyed activity from receiving
+                        // configuration or camera compat changes that are no longer effective.
                         viewRoot.setActivityConfigCallback(null);
                     }
                     wm.removeViewImmediate(v);
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index aba6eb9..83c57c5 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.app.ActivityManager;
+import android.app.ICompatCameraControlCallback;
 import android.app.IRequestFinishCallback;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
@@ -143,4 +144,15 @@
 
     /** Reports that the splash screen view has attached to activity.  */
     oneway void splashScreenAttached(in IBinder token);
+
+    /**
+     * Shows or hides a Camera app compat toggle for stretched issues with the requested state.
+     *
+     * @param token The token for the window that needs a control.
+     * @param showControl Whether the control should be shown or hidden.
+     * @param transformationApplied Whether the treatment is already applied.
+     * @param callback The callback executed when the user clicks on a control.
+     */
+    oneway void requestCompatCameraControl(in IBinder token, boolean showControl,
+            boolean transformationApplied, in ICompatCameraControlCallback callback);
 }
diff --git a/core/java/android/app/ICompatCameraControlCallback.aidl b/core/java/android/app/ICompatCameraControlCallback.aidl
new file mode 100644
index 0000000..1a7f210
--- /dev/null
+++ b/core/java/android/app/ICompatCameraControlCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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;
+
+/**
+ * This callback allows ActivityRecord to ask the calling View to apply the treatment for stretched
+ * issues affecting camera viewfinders when the user clicks on the camera compat control.
+ *
+ * {@hide}
+ */
+oneway interface ICompatCameraControlCallback {
+
+    void applyCameraCompatTreatment();
+
+    void revertCameraCompatTreatment();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 370031a..eb4585d 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1259,7 +1259,7 @@
                 info, title, parent, id,
                 (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
                 new Configuration(), null /* referrer */, null /* voiceInteractor */,
-                null /* window */, null /* activityConfigCallback */, null /*assistToken*/,
+                null /* window */, null /* activityCallback */, null /*assistToken*/,
                 null /*shareableActivityToken*/);
         return activity;
     }
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 95b00c1..18f9379 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
@@ -39,6 +40,8 @@
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -274,6 +277,51 @@
      */
     public boolean isSleeping;
 
+    /**
+     * Camera compat control isn't shown because it's not requested by heuristics.
+     * @hide
+     */
+    public static final int CAMERA_COMPAT_CONTROL_HIDDEN = 0;
+
+    /**
+     * Camera compat control is shown with the treatment suggested.
+     * @hide
+     */
+    public static final int CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED = 1;
+
+    /**
+     * Camera compat control is shown to allow reverting the applied treatment.
+     * @hide
+     */
+    public static final int CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED = 2;
+
+    /**
+     * Camera compat control is dismissed by user.
+     * @hide
+     */
+    public static final int CAMERA_COMPAT_CONTROL_DISMISSED = 3;
+
+    /**
+     * Enum for the Camera app compat control states.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "CAMERA_COMPAT_CONTROL_" }, value = {
+            CAMERA_COMPAT_CONTROL_HIDDEN,
+            CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED,
+            CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED,
+            CAMERA_COMPAT_CONTROL_DISMISSED,
+    })
+    public @interface CameraCompatControlState {};
+
+    /**
+     * State of the Camera app compat control which is used to correct stretched viewfinder
+     * in apps that don't handle all possible configurations and changes between them correctly.
+     * @hide
+     */
+    @CameraCompatControlState
+    public int cameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
+
     TaskInfo() {
         // Do nothing
     }
@@ -342,6 +390,17 @@
         launchCookies.add(cookie);
     }
 
+    /** @hide */
+    public boolean hasCameraCompatControl() {
+        return cameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
+                && cameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
+    }
+
+    /** @hide */
+    public boolean hasCompatUI() {
+        return hasCameraCompatControl() || topActivityInSizeCompat;
+    }
+
     /**
      * @return {@code true} if this task contains the launch cookie.
      * @hide
@@ -394,19 +453,20 @@
      * @return {@code true} if parameters that are important for size compat have changed.
      * @hide
      */
-    public boolean equalsForSizeCompat(@Nullable TaskInfo that) {
+    public boolean equalsForCompatUi(@Nullable TaskInfo that) {
         if (that == null) {
             return false;
         }
         return displayId == that.displayId
                 && taskId == that.taskId
                 && topActivityInSizeCompat == that.topActivityInSizeCompat
-                // Bounds are important if top activity is in size compat
-                && (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds()
+                && cameraCompatControlState == that.cameraCompatControlState
+                // Bounds are important if top activity has compat controls.
+                && (!hasCompatUI() || configuration.windowConfiguration.getBounds()
                     .equals(that.configuration.windowConfiguration.getBounds()))
-                && (!topActivityInSizeCompat || configuration.getLayoutDirection()
+                && (!hasCompatUI() || configuration.getLayoutDirection()
                     == that.configuration.getLayoutDirection())
-                && (!topActivityInSizeCompat || isVisible == that.isVisible);
+                && (!hasCompatUI() || isVisible == that.isVisible);
     }
 
     /**
@@ -449,6 +509,7 @@
         topActivityInSizeCompat = source.readBoolean();
         mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
         displayAreaFeatureId = source.readInt();
+        cameraCompatControlState = source.readInt();
     }
 
     /**
@@ -492,6 +553,7 @@
         dest.writeBoolean(topActivityInSizeCompat);
         dest.writeTypedObject(mTopActivityLocusId, flags);
         dest.writeInt(displayAreaFeatureId);
+        dest.writeInt(cameraCompatControlState);
     }
 
     @Override
@@ -525,6 +587,22 @@
                 + " topActivityInSizeCompat=" + topActivityInSizeCompat
                 + " locusId=" + mTopActivityLocusId
                 + " displayAreaFeatureId=" + displayAreaFeatureId
+                + " cameraCompatControlState="
+                        + cameraCompatControlStateToString(cameraCompatControlState)
                 + "}";
     }
+
+    /** @hide */
+    public static String cameraCompatControlStateToString(
+            @CameraCompatControlState int cameraCompatControlState) {
+        switch (cameraCompatControlState) {
+            case CAMERA_COMPAT_CONTROL_HIDDEN: return "hidden";
+            case CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED: return "treatment-suggested";
+            case CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED: return "treatment-applied";
+            case CAMERA_COMPAT_CONTROL_DISMISSED: return "dismissed";
+            default:
+                throw new AssertionError(
+                    "Unexpected camera compat control state: " + cameraCompatControlState);
+        }
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 70505fc..c371202 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -93,6 +93,7 @@
 import android.annotation.UiContext;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
+import android.app.ICompatCameraControlCallback;
 import android.app.ResourcesManager;
 import android.app.WindowConfiguration;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -317,7 +318,7 @@
     private static final ArrayList<ConfigChangedCallback> sConfigCallbacks = new ArrayList<>();
 
     /**
-     * Callback for notifying activities about override configuration changes.
+     * Callback for notifying activities.
      */
     public interface ActivityConfigCallback {
 
@@ -327,11 +328,23 @@
          * @param newDisplayId New display id, {@link Display#INVALID_DISPLAY} if not changed.
          */
         void onConfigurationChanged(Configuration overrideConfig, int newDisplayId);
+
+        /**
+         * Notify the corresponding activity about the request to show or hide a camera compat
+         * control for stretched issues in the viewfinder.
+         *
+         * @param showControl Whether the control should be shown or hidden.
+         * @param transformationApplied Whether the treatment is already applied.
+         * @param callback The callback executed when the user clicks on a control.
+         */
+        void requestCompatCameraControl(boolean showControl, boolean transformationApplied,
+                ICompatCameraControlCallback callback);
     }
 
     /**
-     * Callback used to notify corresponding activity about override configuration change and make
-     * sure that all resources are set correctly before updating the ViewRootImpl's internal state.
+     * Callback used to notify corresponding activity about camera compat control changes, override
+     * configuration change and make sure that all resources are set correctly before updating the
+     * ViewRootImpl's internal state.
      */
     private ActivityConfigCallback mActivityConfigCallback;
 
@@ -865,7 +878,10 @@
         }
     }
 
-    /** Add activity config callback to be notified about override config changes. */
+    /**
+     * Add activity config callback to be notified about override config changes and camera
+     * compat control state updates.
+     */
     public void setActivityConfigCallback(ActivityConfigCallback callback) {
         mActivityConfigCallback = callback;
     }
@@ -10498,6 +10514,20 @@
     }
 
     /**
+     * Shows or hides a Camera app compat toggle for stretched issues with the requested state
+     * for the corresponding activity.
+     *
+     * @param showControl Whether the control should be shown or hidden.
+     * @param transformationApplied Whether the treatment is already applied.
+     * @param callback The callback executed when the user clicks on a control.
+    */
+    public void requestCompatCameraControl(boolean showControl, boolean transformationApplied,
+                ICompatCameraControlCallback callback) {
+        mActivityConfigCallback.requestCompatCameraControl(
+                showControl, transformationApplied, callback);
+    }
+
+    /**
      * Redirect the next draw of this ViewRoot (from the UI thread perspective)
      * to the passed in consumer. This can be used to create P2P synchronization
      * between ViewRoot's however it comes with many caveats.
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index a833600..022d05d 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -66,4 +66,7 @@
      * Restarts the top activity in the given task by killing its process if it is visible.
      */
     void restartTaskTopActivityProcessIfVisible(in WindowContainerToken task);
+
+    /** Updates a state of camera compat control for stretched issues in the viewfinder. */
+    void updateCameraCompatControlState(in WindowContainerToken task, int state);
 }
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 27c7d315..3ec18db 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -24,6 +24,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.SurfaceControl;
@@ -238,6 +239,20 @@
     }
 
     /**
+     * Updates a state of camera compat control for stretched issues in the viewfinder.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+    public void updateCameraCompatControlState(@NonNull WindowContainerToken task,
+            @CameraCompatControlState int state) {
+        try {
+            mTaskOrganizerController.updateCameraCompatControlState(task, state);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the executor to run callbacks on.
      * @hide
      */
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 050d20e..be32c42 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5068,6 +5068,10 @@
         If given value is outside of this range, the option 1 (center) is assummed. -->
     <integer name="config_letterboxDefaultPositionForReachability">1</integer>
 
+    <!-- Whether a camera compat controller is enabled to allow the user to apply or revert
+         treatment for stretched issues in camera viewfinder. -->
+    <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
+
     <!-- If true, hide the display cutout with display area -->
     <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 504ad23..527865f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4294,6 +4294,7 @@
   <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
   <java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
+  <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
 
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 6bfbd8d..9584994 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1201,6 +1201,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-846931068": {
+      "message": "Update camera compat control state to %s for taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
     "-846078709": {
       "message": "Configuration doesn't matter in finishing %s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml b/libs/WindowManager/Shell/res/color/compat_background_ripple.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml
rename to libs/WindowManager/Shell/res/color/compat_background_ripple.xml
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml
new file mode 100644
index 0000000..1c8cb91
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml
@@ -0,0 +1,33 @@
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="43dp"
+        android:viewportWidth="48"
+        android:viewportHeight="43">
+    <group>
+        <clip-path
+                android:pathData="M48,43l-48,-0l-0,-43l48,-0z"/>
+        <path
+                android:pathData="M24,43C37.2548,43 48,32.2548 48,19L48,0L0,-0L0,19C0,32.2548 10.7452,43 24,43Z"
+                android:fillColor="@color/compat_controls_background"
+                android:strokeAlpha="0.8"
+                android:fillAlpha="0.8"/>
+        <path
+                android:pathData="M31,12.41L29.59,11L24,16.59L18.41,11L17,12.41L22.59,18L17,23.59L18.41,25L24,19.41L29.59,25L31,23.59L25.41,18L31,12.41Z"
+                android:fillColor="@color/compat_controls_text"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml
new file mode 100644
index 0000000..c810139
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/compat_background_ripple">
+    <item android:drawable="@drawable/camera_compat_dismiss_button"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml
new file mode 100644
index 0000000..c796b59
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml
@@ -0,0 +1,32 @@
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="43dp"
+        android:viewportWidth="48"
+        android:viewportHeight="43">
+    <path
+            android:pathData="M24,0C10.7452,0 0,10.7452 0,24V43H48V24C48,10.7452 37.2548,0 24,0Z"
+            android:fillColor="@color/compat_controls_background"
+            android:strokeAlpha="0.8"
+            android:fillAlpha="0.8"/>
+    <path
+            android:pathData="M32,17H28.83L27,15H21L19.17,17H16C14.9,17 14,17.9 14,19V31C14,32.1 14.9,33 16,33H32C33.1,33 34,32.1 34,31V19C34,17.9 33.1,17 32,17ZM32,31H16V19H32V31Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M24.6618,22C23.0436,22 21.578,22.6187 20.4483,23.625L18.25,21.375V27H23.7458L21.5353,24.7375C22.3841,24.0125 23.4649,23.5625 24.6618,23.5625C26.8235,23.5625 28.6616,25.0062 29.3028,27L30.75,26.5125C29.9012,23.8938 27.5013,22 24.6618,22Z"
+            android:fillColor="@color/compat_controls_text"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml
new file mode 100644
index 0000000..3e9fe6d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/compat_background_ripple">
+    <item android:drawable="@drawable/camera_compat_treatment_applied_button"/>
+</ripple>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml
new file mode 100644
index 0000000..af505d1
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml
@@ -0,0 +1,53 @@
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="43dp"
+        android:viewportWidth="48"
+        android:viewportHeight="43">
+    <path
+            android:pathData="M24,0C10.7452,0 0,10.7452 0,24V43H48V24C48,10.7452 37.2548,0 24,0Z"
+            android:fillColor="@color/compat_controls_background"
+            android:strokeAlpha="0.8"
+            android:fillAlpha="0.8"/>
+    <path
+            android:pathData="M32,17H28.83L27,15H21L19.17,17H16C14.9,17 14,17.9 14,19V31C14,32.1 14.9,33 16,33H32C33.1,33 34,32.1 34,31V19C34,17.9 33.1,17 32,17ZM32,31H16V19H32V31Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M18,29L18,25.5L19.5,25.5L19.5,29L18,29Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M30,29L30,25.5L28.5,25.5L28.5,29L30,29Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M30,21L30,24.5L28.5,24.5L28.5,21L30,21Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M18,21L18,24.5L19.5,24.5L19.5,21L18,21Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M18,27.5L21.5,27.5L21.5,29L18,29L18,27.5Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M30,27.5L26.5,27.5L26.5,29L30,29L30,27.5Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M30,22.5L26.5,22.5L26.5,21L30,21L30,22.5Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M18,22.5L21.5,22.5L21.5,21L18,21L18,22.5Z"
+            android:fillColor="@color/compat_controls_text"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml
new file mode 100644
index 0000000..c0f1c89
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/compat_background_ripple">
+    <item android:drawable="@drawable/camera_compat_treatment_suggested_button"/>
+</ripple>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index ab74e43..e6ae282 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -21,7 +21,9 @@
         android:viewportHeight="48">
     <path
         android:fillColor="@color/compat_controls_background"
-        android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" />
+        android:strokeAlpha="0.8"
+        android:fillAlpha="0.8"
+        android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
     <group
         android:translateX="12"
         android:translateY="12">
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
index 95decff..6551edf 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
@@ -15,6 +15,6 @@
   ~ limitations under the License.
   -->
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/size_compat_background_ripple">
+        android:color="@color/compat_background_ripple">
     <item android:drawable="@drawable/size_compat_restart_button"/>
 </ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
index c04e258e..4ac972c 100644
--- a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
@@ -16,7 +16,7 @@
 -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:clipToPadding="false"
@@ -26,7 +26,7 @@
 
     <TextView
         android:id="@+id/compat_mode_hint_text"
-        android:layout_width="188dp"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:lineSpacingExtra="4sp"
         android:background="@drawable/compat_hint_bubble"
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index 6f946b2..c99f3fe 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -21,24 +21,51 @@
     android:orientation="vertical"
     android:gravity="bottom|end">
 
-    <include android:id="@+id/size_compat_hint"
-         layout="@layout/compat_mode_hint"/>
+    <include android:id="@+id/camera_compat_hint"
+        android:visibility="gone"
+        android:layout_width="@dimen/camera_compat_hint_width"
+        android:layout_height="wrap_content"
+        layout="@layout/compat_mode_hint"/>
 
-    <FrameLayout
-        android:layout_width="@dimen/size_compat_button_width"
-        android:layout_height="@dimen/size_compat_button_height"
+    <LinearLayout
+        android:id="@+id/camera_compat_control"
+        android:visibility="gone"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:clipToPadding="false"
-        android:paddingBottom="16dp">
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="16dp"
+        android:orientation="vertical">
 
         <ImageButton
-            android:id="@+id/size_compat_restart_button"
+            android:id="@+id/camera_compat_treatment_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:src="@drawable/size_compat_restart_button_ripple"
-            android:background="@android:color/transparent"
-            android:contentDescription="@string/restart_button_description"/>
+            android:background="@android:color/transparent"/>
 
-    </FrameLayout>
+        <ImageButton
+            android:id="@+id/camera_compat_dismiss_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/camera_compat_dismiss_ripple"
+            android:background="@android:color/transparent"
+            android:contentDescription="@string/camera_compat_dismiss_button_description"/>
+
+    </LinearLayout>
+
+    <include android:id="@+id/size_compat_hint"
+        android:visibility="gone"
+        android:layout_width="@dimen/size_compat_hint_width"
+        android:layout_height="wrap_content"
+        layout="@layout/compat_mode_hint"/>
+
+    <ImageButton
+        android:id="@+id/size_compat_restart_button"
+        android:visibility="gone"
+        android:layout_width="@dimen/size_compat_button_width"
+        android:layout_height="@dimen/size_compat_button_height"
+        android:src="@drawable/size_compat_restart_button_ripple"
+        android:background="@android:color/transparent"
+        android:contentDescription="@string/restart_button_description"/>
 
 </com.android.wm.shell.compatui.CompatUILayout>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 18e91f4..d338e3b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -216,6 +216,12 @@
          - compat_hint_corner_radius - compat_hint_point_width /2). -->
     <dimen name="compat_hint_padding_end">7dp</dimen>
 
+    <!-- The width of the size compat hint. -->
+    <dimen name="size_compat_hint_width">188dp</dimen>
+
+    <!-- The width of the camera compat hint. -->
+    <dimen name="camera_compat_hint_width">143dp</dimen>
+
     <!-- The width of the brand image on staring surface. -->
     <dimen name="starting_surface_brand_image_width">200dp</dimen>
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index c88fc16..ab0013a 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -158,4 +158,17 @@
 
     <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
     <string name="restart_button_description">Tap to restart this app and go full screen.</string>
+
+    <!-- Description of the camera compat button for applying stretched issues treatment in the hint for
+         compatibility control. [CHAR LIMIT=NONE] -->
+    <string name="camera_compat_treatment_suggested_button_description">Camera issues?\nTap to refit</string>
+
+    <!-- Description of the camera compat button for reverting stretched issues treatment in the hint for
+         compatibility control. [CHAR LIMIT=NONE] -->
+    <string name="camera_compat_treatment_applied_button_description">Didn\u2019t fix it?\nTap to revert</string>
+
+    <!-- Accessibillity description of the camera dismiss button for stretched issues in the hint for
+         compatibility control. [CHAR LIMIT=NONE] -->
+    <string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string>
+
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 8b3a356..91ea436 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -458,7 +458,7 @@
                 newListener.onTaskInfoChanged(taskInfo);
             }
             notifyLocusVisibilityIfNeeded(taskInfo);
-            if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
+            if (updated || !taskInfo.equalsForCompatUi(data.getTaskInfo())) {
                 // Notify the compat UI if the listener or task info changed.
                 notifyCompatUI(taskInfo, newListener);
             }
@@ -607,6 +607,19 @@
         restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
     }
 
+    @Override
+    public void onCameraControlStateUpdated(
+            int taskId, @TaskInfo.CameraCompatControlState int state) {
+        final TaskAppearedInfo info;
+        synchronized (mLock) {
+            info = mTasks.get(taskId);
+        }
+        if (info == null) {
+            return;
+        }
+        updateCameraCompatControlState(info.getTaskInfo().token, state);
+    }
+
     private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
             int event) {
         ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -633,14 +646,11 @@
         // The task is vanished or doesn't support compat UI, notify to remove compat UI
         // on this Task if there is any.
         if (taskListener == null || !taskListener.supportCompatUI()
-                || !taskInfo.topActivityInSizeCompat || !taskInfo.isVisible) {
-            mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
-                    null /* taskConfig */, null /* taskListener */);
+                || !taskInfo.hasCompatUI() || !taskInfo.isVisible) {
+            mCompatUI.onCompatInfoChanged(taskInfo, null /* taskListener */);
             return;
         }
-
-        mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
-                taskInfo.configuration, taskListener);
+        mCompatUI.onCompatInfoChanged(taskInfo, taskListener);
     }
 
     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index e0b2387..8f4cfb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -17,6 +17,8 @@
 package com.android.wm.shell.compatui;
 
 import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
@@ -53,12 +55,14 @@
 public class CompatUIController implements OnDisplaysChangedListener,
         DisplayImeController.ImePositionProcessor {
 
-    /** Callback for size compat UI interaction. */
+    /** Callback for compat UI interaction. */
     public interface CompatUICallback {
         /** Called when the size compat restart button appears. */
         void onSizeCompatRestartButtonAppeared(int taskId);
         /** Called when the size compat restart button is clicked. */
         void onSizeCompatRestartButtonClicked(int taskId);
+        /** Called when the camera compat control state is updated. */
+        void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state);
     }
 
     private static final String TAG = "CompatUIController";
@@ -86,10 +90,12 @@
 
     private CompatUICallback mCallback;
 
-    /** Only show once automatically in the process life. */
-    private boolean mHasShownHint;
-    /** Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't
-     * be shown. */
+    // Only show once automatically in the process life.
+    private boolean mHasShownSizeCompatHint;
+    private boolean mHasShownCameraCompatHint;
+
+    // Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't
+    // be shown.
     private boolean mKeyguardOccluded;
 
     public CompatUIController(Context context,
@@ -122,23 +128,20 @@
      * Called when the Task info changed. Creates and updates the compat UI if there is an
      * activity in size compat, or removes the UI if there is no size compat activity.
      *
-     * @param displayId display the task and activity are in.
-     * @param taskId task the activity is in.
-     * @param taskConfig task config to place the compat UI with.
+     * @param taskInfo {@link TaskInfo} task the activity is in.
      * @param taskListener listener to handle the Task Surface placement.
      */
-    public void onCompatInfoChanged(int displayId, int taskId,
-            @Nullable Configuration taskConfig,
+    public void onCompatInfoChanged(TaskInfo taskInfo,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
-        if (taskConfig == null || taskListener == null) {
+        if (taskInfo.configuration == null || taskListener == null) {
             // Null token means the current foreground activity is not in compatibility mode.
-            removeLayout(taskId);
-        } else if (mActiveLayouts.contains(taskId)) {
+            removeLayout(taskInfo.taskId);
+        } else if (mActiveLayouts.contains(taskInfo.taskId)) {
             // UI already exists, update the UI layout.
-            updateLayout(taskId, taskConfig, taskListener);
+            updateLayout(taskInfo, taskListener);
         } else {
             // Create a new compat UI.
-            createLayout(displayId, taskId, taskConfig, taskListener);
+            createLayout(taskInfo, taskListener);
         }
     }
 
@@ -215,38 +218,45 @@
         return mDisplaysWithIme.contains(displayId);
     }
 
-    private void createLayout(int displayId, int taskId, Configuration taskConfig,
-            ShellTaskOrganizer.TaskListener taskListener) {
-        final Context context = getOrCreateDisplayContext(displayId);
+    private void createLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
         if (context == null) {
-            Log.e(TAG, "Cannot get context for display " + displayId);
+            Log.e(TAG, "Cannot get context for display " + taskInfo.displayId);
             return;
         }
 
         final CompatUIWindowManager compatUIWindowManager =
-                createLayout(context, displayId, taskId, taskConfig, taskListener);
-        mActiveLayouts.put(taskId, compatUIWindowManager);
-        compatUIWindowManager.createLayout(showOnDisplay(displayId));
+                createLayout(context, taskInfo, taskListener);
+        mActiveLayouts.put(taskInfo.taskId, compatUIWindowManager);
+        compatUIWindowManager.createLayout(showOnDisplay(taskInfo.displayId),
+                taskInfo.topActivityInSizeCompat, taskInfo.cameraCompatControlState);
     }
 
     @VisibleForTesting
-    CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
-            Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
+    CompatUIWindowManager createLayout(Context context, TaskInfo taskInfo,
+            ShellTaskOrganizer.TaskListener taskListener) {
         final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context,
-                taskConfig, mSyncQueue, mCallback, taskId, taskListener,
-                mDisplayController.getDisplayLayout(displayId), mHasShownHint);
-        // Only show hint for the first time.
-        mHasShownHint = true;
+                taskInfo.configuration, mSyncQueue, mCallback, taskInfo.taskId, taskListener,
+                mDisplayController.getDisplayLayout(taskInfo.displayId), mHasShownSizeCompatHint,
+                mHasShownCameraCompatHint);
+        // Only show hints for the first time.
+        if (taskInfo.topActivityInSizeCompat) {
+            mHasShownSizeCompatHint = true;
+        }
+        if (taskInfo.hasCameraCompatControl()) {
+            mHasShownCameraCompatHint = true;
+        }
         return compatUIWindowManager;
     }
 
-    private void updateLayout(int taskId, Configuration taskConfig,
-            ShellTaskOrganizer.TaskListener taskListener) {
-        final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
+    private void updateLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+        final CompatUIWindowManager layout = mActiveLayouts.get(taskInfo.taskId);
         if (layout == null) {
             return;
         }
-        layout.updateCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId()));
+        layout.updateCompatInfo(taskInfo.configuration, taskListener,
+                showOnDisplay(layout.getDisplayId()), taskInfo.topActivityInSizeCompat,
+                taskInfo.cameraCompatControlState);
     }
 
     private void removeLayout(int taskId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index ea4f209..29b2baa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -16,6 +16,9 @@
 
 package com.android.wm.shell.compatui;
 
+import android.annotation.IdRes;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
@@ -53,6 +56,53 @@
         mWindowManager = windowManager;
     }
 
+    void updateCameraTreatmentButton(@CameraCompatControlState int newState) {
+        int buttonBkgId = newState == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+                ? R.drawable.camera_compat_treatment_suggested_ripple
+                : R.drawable.camera_compat_treatment_applied_ripple;
+        int hintStringId = newState == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+                ? R.string.camera_compat_treatment_suggested_button_description
+                : R.string.camera_compat_treatment_applied_button_description;
+        final ImageButton button = findViewById(R.id.camera_compat_treatment_button);
+        button.setImageResource(buttonBkgId);
+        button.setContentDescription(getResources().getString(hintStringId));
+        final LinearLayout hint = findViewById(R.id.camera_compat_hint);
+        ((TextView) hint.findViewById(R.id.compat_mode_hint_text)).setText(hintStringId);
+    }
+
+    void setSizeCompatHintVisibility(boolean show) {
+        setViewVisibility(R.id.size_compat_hint, show);
+    }
+
+    void setCameraCompatHintVisibility(boolean show) {
+        setViewVisibility(R.id.camera_compat_hint, show);
+    }
+
+    void setRestartButtonVisibility(boolean show) {
+        setViewVisibility(R.id.size_compat_restart_button, show);
+        // Hint should never be visible without button.
+        if (!show) {
+            setSizeCompatHintVisibility(/* show= */ false);
+        }
+    }
+
+    void setCameraControlVisibility(boolean show) {
+        setViewVisibility(R.id.camera_compat_control, show);
+        // Hint should never be visible without button.
+        if (!show) {
+            setCameraCompatHintVisibility(/* show= */ false);
+        }
+    }
+
+    private void setViewVisibility(@IdRes int resId, boolean show) {
+        final View view = findViewById(resId);
+        int visibility = show ? View.VISIBLE : View.GONE;
+        if (view.getVisibility() == visibility) {
+            return;
+        }
+        view.setVisibility(visibility);
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -61,15 +111,6 @@
         mWindowManager.relayout();
     }
 
-    void setSizeCompatHintVisibility(boolean show) {
-        final LinearLayout sizeCompatHint = findViewById(R.id.size_compat_hint);
-        int visibility = show ? View.VISIBLE : View.GONE;
-        if (sizeCompatHint.getVisibility() == visibility) {
-            return;
-        }
-        sizeCompatHint.setVisibility(visibility);
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -85,5 +126,26 @@
         ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
                 .setText(R.string.restart_button_description);
         sizeCompatHint.setOnClickListener(view -> setSizeCompatHintVisibility(/* show= */ false));
+
+        final ImageButton cameraTreatmentButton =
+                findViewById(R.id.camera_compat_treatment_button);
+        cameraTreatmentButton.setOnClickListener(
+                view -> mWindowManager.onCameraTreatmentButtonClicked());
+        cameraTreatmentButton.setOnLongClickListener(view -> {
+            mWindowManager.onCameraButtonLongClicked();
+            return true;
+        });
+
+        final ImageButton cameraDismissButton = findViewById(R.id.camera_compat_dismiss_button);
+        cameraDismissButton.setOnClickListener(
+                view -> mWindowManager.onCameraDismissButtonClicked());
+        cameraDismissButton.setOnLongClickListener(view -> {
+            mWindowManager.onCameraButtonLongClicked();
+            return true;
+        });
+
+        final LinearLayout cameraCompatHint = findViewById(R.id.camera_compat_hint);
+        cameraCompatHint.setOnClickListener(
+                view -> setCameraCompatHintVisibility(/* show= */ false));
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 997ad04..44526b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -16,6 +16,10 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
@@ -23,6 +27,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import android.annotation.Nullable;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
@@ -63,8 +68,17 @@
     private ShellTaskOrganizer.TaskListener mTaskListener;
     private DisplayLayout mDisplayLayout;
 
+    // Remember the last reported states in case visibility changes due to keyguard or
+    // IME updates.
     @VisibleForTesting
-    boolean mShouldShowHint;
+    boolean mHasSizeCompat;
+    @CameraCompatControlState
+    private int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
+
+    @VisibleForTesting
+    boolean mShouldShowSizeCompatHint;
+    @VisibleForTesting
+    boolean mShouldShowCameraCompatHint;
 
     @Nullable
     @VisibleForTesting
@@ -78,7 +92,7 @@
     CompatUIWindowManager(Context context, Configuration taskConfig,
             SyncTransactionQueue syncQueue, CompatUIController.CompatUICallback callback,
             int taskId, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
-            boolean hasShownHint) {
+             boolean hasShownSizeCompatHint, boolean hasShownCameraCompatHint) {
         super(taskConfig, null /* rootSurface */, null /* hostInputToken */);
         mContext = context;
         mSyncQueue = syncQueue;
@@ -88,7 +102,8 @@
         mTaskId = taskId;
         mTaskListener = taskListener;
         mDisplayLayout = displayLayout;
-        mShouldShowHint = !hasShownHint;
+        mShouldShowSizeCompatHint = !hasShownSizeCompatHint;
+        mShouldShowCameraCompatHint = !hasShownCameraCompatHint;
         mStableBounds = new Rect();
         mDisplayLayout.getStableBounds(mStableBounds);
     }
@@ -113,7 +128,10 @@
     }
 
     /** Creates the layout for compat controls. */
-    void createLayout(boolean show) {
+    void createLayout(boolean show, boolean hasSizeCompat,
+            @CameraCompatControlState int cameraCompatControlState) {
+        mHasSizeCompat = hasSizeCompat;
+        mCameraCompatControlState = cameraCompatControlState;
         if (!show || mCompatUILayout != null) {
             // Wait until compat controls should be visible.
             return;
@@ -122,16 +140,27 @@
         initCompatUi();
         updateSurfacePosition();
 
-        mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+        if (hasSizeCompat) {
+            mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+        }
+    }
+
+    private void createLayout(boolean show) {
+        createLayout(show, mHasSizeCompat, mCameraCompatControlState);
     }
 
     /** Called when compat info changed. */
     void updateCompatInfo(Configuration taskConfig,
-            ShellTaskOrganizer.TaskListener taskListener, boolean show) {
+            ShellTaskOrganizer.TaskListener taskListener, boolean show, boolean hasSizeCompat,
+            @CameraCompatControlState int cameraCompatControlState) {
         final Configuration prevTaskConfig = mTaskConfig;
         final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
         mTaskConfig = taskConfig;
         mTaskListener = taskListener;
+        final boolean prevHasSizeCompat = mHasSizeCompat;
+        final int prevCameraCompatControlState = mCameraCompatControlState;
+        mHasSizeCompat = hasSizeCompat;
+        mCameraCompatControlState = cameraCompatControlState;
 
         // Update configuration.
         mContext = mContext.createConfigurationContext(taskConfig);
@@ -144,6 +173,11 @@
             return;
         }
 
+        if (prevHasSizeCompat != mHasSizeCompat
+                || prevCameraCompatControlState != mCameraCompatControlState) {
+            updateVisibilityOfViews();
+        }
+
         if (!taskConfig.windowConfiguration.getBounds()
                 .equals(prevTaskConfig.windowConfiguration.getBounds())) {
             // Reposition the UI surfaces.
@@ -155,6 +189,7 @@
             mCompatUILayout.setLayoutDirection(taskConfig.getLayoutDirection());
             updateSurfacePosition();
         }
+
     }
 
     /** Called when the visibility of the UI should change. */
@@ -195,6 +230,34 @@
         mCallback.onSizeCompatRestartButtonClicked(mTaskId);
     }
 
+    /** Called when the camera treatment button is clicked. */
+    void onCameraTreatmentButtonClicked() {
+        if (!shouldShowCameraControl()) {
+            Log.w(TAG, "Camera compat shouldn't receive clicks in the hidden state.");
+            return;
+        }
+        // When a camera control is shown, only two states are allowed: "treament applied" and
+        // "treatment suggested". Clicks on the conrol's treatment button toggle between these
+        // two states.
+        mCameraCompatControlState =
+                mCameraCompatControlState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+                        ? CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED
+                        : CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        mCallback.onCameraControlStateUpdated(mTaskId, mCameraCompatControlState);
+        mCompatUILayout.updateCameraTreatmentButton(mCameraCompatControlState);
+    }
+
+    /** Called when the camera dismiss button is clicked. */
+    void onCameraDismissButtonClicked() {
+        if (!shouldShowCameraControl()) {
+            Log.w(TAG, "Camera compat shouldn't receive clicks in the hidden state.");
+            return;
+        }
+        mCameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED;
+        mCallback.onCameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED);
+        mCompatUILayout.setCameraControlVisibility(/* show= */ false);
+    }
+
     /** Called when the restart button is long clicked. */
     void onRestartButtonLongClicked() {
         if (mCompatUILayout == null) {
@@ -203,6 +266,14 @@
         mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true);
     }
 
+    /** Called when either dismiss or treatment camera buttons is long clicked. */
+    void onCameraButtonLongClicked() {
+        if (mCompatUILayout == null) {
+            return;
+        }
+        mCompatUILayout.setCameraCompatHintVisibility(/* show= */ true);
+    }
+
     int getDisplayId() {
         return mDisplayId;
     }
@@ -213,6 +284,8 @@
 
     /** Releases the surface control and tears down the view hierarchy. */
     void release() {
+        // Hiding before releasing to avoid flickering when transitioning to the Home screen.
+        mCompatUILayout.setVisibility(View.GONE);
         mCompatUILayout = null;
 
         if (mViewHost != null) {
@@ -283,12 +356,35 @@
         mCompatUILayout = inflateCompatUILayout();
         mCompatUILayout.inject(this);
 
-        mCompatUILayout.setSizeCompatHintVisibility(mShouldShowHint);
+        updateVisibilityOfViews();
 
         mViewHost.setView(mCompatUILayout, getWindowLayoutParams());
+    }
 
-        // Only show by default for the first time.
-        mShouldShowHint = false;
+    private void updateVisibilityOfViews() {
+        // Size Compat mode restart button.
+        mCompatUILayout.setRestartButtonVisibility(mHasSizeCompat);
+        if (mHasSizeCompat && mShouldShowSizeCompatHint) {
+            mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true);
+            // Only show by default for the first time.
+            mShouldShowSizeCompatHint = false;
+        }
+
+        // Camera control for stretched issues.
+        mCompatUILayout.setCameraControlVisibility(shouldShowCameraControl());
+        if (shouldShowCameraControl() && mShouldShowCameraCompatHint) {
+            mCompatUILayout.setCameraCompatHintVisibility(/* show= */ true);
+            // Only show by default for the first time.
+            mShouldShowCameraCompatHint = false;
+        }
+        if (shouldShowCameraControl()) {
+            mCompatUILayout.updateCameraTreatmentButton(mCameraCompatControlState);
+        }
+    }
+
+    private boolean shouldShowCameraControl() {
+        return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
+                && mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index a3b98a8f..825320b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
 import android.content.Context;
 import android.content.LocusId;
 import android.content.pm.ParceledListSlice;
@@ -334,8 +335,7 @@
         mOrganizer.onTaskAppeared(taskInfo1, null);
 
         // sizeCompatActivity is null if top activity is not in size compat.
-        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                null /* taskConfig */, null /* taskListener */);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
 
         // sizeCompatActivity is non-null if top activity is in size compat.
         clearInvocations(mCompatUI);
@@ -345,8 +345,7 @@
         taskInfo2.topActivityInSizeCompat = true;
         taskInfo2.isVisible = true;
         mOrganizer.onTaskInfoChanged(taskInfo2);
-        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                taskInfo1.configuration, taskListener);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
 
         // Not show size compat UI if task is not visible.
         clearInvocations(mCompatUI);
@@ -356,13 +355,82 @@
         taskInfo3.topActivityInSizeCompat = true;
         taskInfo3.isVisible = false;
         mOrganizer.onTaskInfoChanged(taskInfo3);
-        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                null /* taskConfig */, null /* taskListener */);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
 
         clearInvocations(mCompatUI);
         mOrganizer.onTaskVanished(taskInfo1);
-        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                null /* taskConfig */, null /* taskListener */);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+    }
+
+    @Test
+    public void testOnCameraCompatActivityChanged() {
+        final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
+        taskInfo1.displayId = DEFAULT_DISPLAY;
+        taskInfo1.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+        final TrackingTaskListener taskListener = new TrackingTaskListener();
+        mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+        mOrganizer.onTaskAppeared(taskInfo1, null);
+
+        // Task listener sent to compat UI is null if top activity doesn't request a camera
+        // compat control.
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+
+        // Task linster is non-null when request a camera compat control for a visible task.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo2 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo2.displayId = taskInfo1.displayId;
+        taskInfo2.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        taskInfo2.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo2);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+
+        // CompatUIController#onCompatInfoChanged is called when requested state for a camera
+        // compat control changes for a visible task.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo3 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo3.displayId = taskInfo1.displayId;
+        taskInfo3.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+        taskInfo3.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo3);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo3, taskListener);
+
+        // CompatUIController#onCompatInfoChanged is called when a top activity goes in size compat
+        // mode for a visible task that has a compat control.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo4 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo4.displayId = taskInfo1.displayId;
+        taskInfo4.topActivityInSizeCompat = true;
+        taskInfo4.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+        taskInfo4.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo4);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo4, taskListener);
+
+        // Task linster is null when a camera compat control is dimissed for a visible task.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo5 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo5.displayId = taskInfo1.displayId;
+        taskInfo5.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+        taskInfo5.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo5);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo5, null /* taskListener */);
+
+        // Task linster is null when request a camera compat control for a invisible task.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo6 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo6.displayId = taskInfo1.displayId;
+        taskInfo6.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        taskInfo6.isVisible = false;
+        mOrganizer.onTaskInfoChanged(taskInfo6);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo6, null /* taskListener */);
+
+        clearInvocations(mCompatUI);
+        mOrganizer.onTaskVanished(taskInfo1);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index f622edb..4352fd3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -29,6 +33,9 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
@@ -90,8 +97,8 @@
         mController = new CompatUIController(mContext, mMockDisplayController,
                 mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) {
             @Override
-            CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
-                    Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
+            CompatUIWindowManager createLayout(Context context, TaskInfo taskInfo,
+                    ShellTaskOrganizer.TaskListener taskListener) {
                 return mMockLayout;
             }
         };
@@ -106,23 +113,59 @@
 
     @Test
     public void testOnCompatInfoChanged() {
-        final Configuration taskConfig = new Configuration();
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // Verify that the restart button is added with non-null size compat info.
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig),
-                eq(mMockTaskListener));
+        verify(mController).createLayout(any(), eq(taskInfo), eq(mMockTaskListener));
 
         // Verify that the restart button is updated with non-null new size compat info.
-        final Configuration newTaskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN),
+                mMockTaskListener);
 
-        verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
-                true /* show */);
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
-        // Verify that the restart button is removed with null size compat info.
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener);
+        // Verify that the restart button is updated with new camera state.
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED),
+                mMockTaskListener);
+
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED),
+                mMockTaskListener);
+
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Verify that compat controls are removed with null compat info.
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN),
+                null /* taskListener */);
+
+        verify(mMockLayout).release();
+
+        clearInvocations(mMockLayout);
+        clearInvocations(mController);
+        // Verify that compat controls are removed with dismissed camera state.
+        taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+        verify(mController).createLayout(any(), eq(taskInfo), eq(mMockTaskListener));
+
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_DISMISSED),
+                null /* taskListener */);
 
         verify(mMockLayout).release();
     }
@@ -139,8 +182,8 @@
     @Test
     public void testOnDisplayRemoved() {
         mController.onDisplayAdded(DISPLAY_ID);
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN),
                 mMockTaskListener);
 
         mController.onDisplayRemoved(DISPLAY_ID + 1);
@@ -157,16 +200,14 @@
 
     @Test
     public void testOnDisplayConfigurationChanged() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
-                mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
-        final Configuration newTaskConfig = new Configuration();
-        mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, newTaskConfig);
+        mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration());
 
         verify(mMockLayout, never()).updateDisplayLayout(any());
 
-        mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig);
+        mController.onDisplayConfigurationChanged(DISPLAY_ID, new Configuration());
 
         verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout);
     }
@@ -174,9 +215,8 @@
     @Test
     public void testInsetsChanged() {
         mController.onDisplayAdded(DISPLAY_ID);
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
-                mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
         InsetsState insetsState = new InsetsState();
         InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
         insetsSource.setFrame(0, 0, 1000, 1000);
@@ -196,8 +236,8 @@
 
     @Test
     public void testChangeButtonVisibilityOnImeShowHide() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         // Verify that the restart button is hidden after IME is showing.
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
@@ -205,10 +245,11 @@
         verify(mMockLayout).updateVisibility(false);
 
         // Verify button remains hidden while IME is showing.
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
-        verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
-                false /* show */);
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                false /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // Verify button is shown after IME is hidden.
         mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
@@ -218,8 +259,8 @@
 
     @Test
     public void testChangeButtonVisibilityOnKeyguardOccludedChanged() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         // Verify that the restart button is hidden after keyguard becomes occluded.
         mController.onKeyguardOccludedChanged(true);
@@ -227,10 +268,11 @@
         verify(mMockLayout).updateVisibility(false);
 
         // Verify button remains hidden while keyguard is occluded.
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
-        verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
-                false /* show */);
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                false /* show */,  true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // Verify button is shown after keyguard becomes not occluded.
         mController.onKeyguardOccludedChanged(false);
@@ -240,8 +282,8 @@
 
     @Test
     public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
         mController.onKeyguardOccludedChanged(true);
@@ -263,8 +305,8 @@
 
     @Test
     public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
         mController.onKeyguardOccludedChanged(true);
@@ -283,4 +325,14 @@
 
         verify(mMockLayout).updateVisibility(true);
     }
+
+    private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
+            @CameraCompatControlState int cameraCompatControlState) {
+        RunningTaskInfo taskInfo = new RunningTaskInfo();
+        taskInfo.taskId = taskId;
+        taskInfo.displayId = displayId;
+        taskInfo.topActivityInSizeCompat = hasSizeCompat;
+        taskInfo.cameraCompatControlState = cameraCompatControlState;
+        return taskInfo;
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 2c3987b..353d8fe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -16,6 +16,11 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.mockito.Mockito.doNothing;
@@ -69,7 +74,7 @@
 
         mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
                 mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
-                false /* hasShownHint */);
+                false /* hasShownSizeCompatHint */, false /* hasShownCameraCompatHint */);
 
         mCompatUILayout = (CompatUILayout)
                 LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
@@ -78,6 +83,7 @@
         spyOn(mWindowManager);
         spyOn(mCompatUILayout);
         doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+        doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
     }
 
     @Test
@@ -86,7 +92,6 @@
         button.performClick();
 
         verify(mWindowManager).onRestartButtonClicked();
-        doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
         verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
     }
 
@@ -102,10 +107,92 @@
 
     @Test
     public void testOnClickForSizeCompatHint() {
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
         final LinearLayout sizeCompatHint = mCompatUILayout.findViewById(R.id.size_compat_hint);
         sizeCompatHint.performClick();
 
         verify(mCompatUILayout).setSizeCompatHintVisibility(/* show= */ false);
     }
+
+    @Test
+    public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+        final ImageButton button =
+                mCompatUILayout.findViewById(R.id.camera_compat_treatment_button);
+        button.performClick();
+
+        verify(mWindowManager).onCameraTreatmentButtonClicked();
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        button.performClick();
+
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+    }
+
+    @Test
+    public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        final ImageButton button =
+                mCompatUILayout.findViewById(R.id.camera_compat_treatment_button);
+        button.performClick();
+
+        verify(mWindowManager).onCameraTreatmentButtonClicked();
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        button.performClick();
+
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+    }
+
+    @Test
+    public void testOnCameraDismissButtonClicked() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        final ImageButton button =
+                mCompatUILayout.findViewById(R.id.camera_compat_dismiss_button);
+        button.performClick();
+
+        verify(mWindowManager).onCameraDismissButtonClicked();
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+        verify(mCompatUILayout).setCameraControlVisibility(/* show */ false);
+    }
+
+    @Test
+    public void testOnLongClickForCameraTreatementButton() {
+        doNothing().when(mWindowManager).onCameraButtonLongClicked();
+
+        final ImageButton button =
+                mCompatUILayout.findViewById(R.id.camera_compat_treatment_button);
+        button.performLongClick();
+
+        verify(mWindowManager).onCameraButtonLongClicked();
+    }
+
+    @Test
+    public void testOnLongClickForCameraDismissButton() {
+        doNothing().when(mWindowManager).onCameraButtonLongClicked();
+
+        final ImageButton button = mCompatUILayout.findViewById(R.id.camera_compat_dismiss_button);
+        button.performLongClick();
+
+        verify(mWindowManager).onCameraButtonLongClicked();
+    }
+
+    @Test
+    public void testOnClickForCameraCompatHint() {
+        mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        final LinearLayout hint = mCompatUILayout.findViewById(R.id.camera_compat_hint);
+        hint.performClick();
+
+        verify(mCompatUILayout).setCameraCompatHintVisibility(/* show= */ false);
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index d5dcf2e..11c7973 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -23,6 +27,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -81,7 +86,7 @@
 
         mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
                 mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
-                false /* hasShownHint */);
+                false /* hasShownSizeCompatHint */, false /* hasShownSizeCompatHint */);
 
         spyOn(mWindowManager);
         doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
@@ -91,31 +96,35 @@
     @Test
     public void testCreateSizeCompatButton() {
         // Not create layout if show is false.
-        mWindowManager.createLayout(false /* show */);
+        mWindowManager.createLayout(false /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager, never()).inflateCompatUILayout();
 
         // Not create hint popup.
-        mWindowManager.mShouldShowHint = false;
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.mShouldShowSizeCompatHint = false;
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).inflateCompatUILayout();
-        verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+        verify(mCompatUILayout, never()).setSizeCompatHintVisibility(true /* show */);
 
         // Create hint popup.
         mWindowManager.release();
-        mWindowManager.mShouldShowHint = true;
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.mShouldShowSizeCompatHint = true;
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager, times(2)).inflateCompatUILayout();
         assertNotNull(mCompatUILayout);
         verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
-        assertFalse(mWindowManager.mShouldShowHint);
+        assertFalse(mWindowManager.mShouldShowSizeCompatHint);
     }
 
     @Test
     public void testRelease() {
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).inflateCompatUILayout();
 
@@ -126,32 +135,60 @@
 
     @Test
     public void testUpdateCompatInfo() {
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // No diff
         clearInvocations(mWindowManager);
-        mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */);
+        mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager, never()).updateSurfacePosition();
         verify(mWindowManager, never()).release();
-        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mWindowManager, never()).createLayout(anyBoolean(), anyBoolean(), anyInt());
 
         // Change task listener, recreate button.
         clearInvocations(mWindowManager);
         final ShellTaskOrganizer.TaskListener newTaskListener = mock(
                 ShellTaskOrganizer.TaskListener.class);
         mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener,
-                true /* show */);
+                true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).release();
-        verify(mWindowManager).createLayout(anyBoolean());
+        verify(mWindowManager).createLayout(anyBoolean(), anyBoolean(), anyInt());
+
+        // Change Camera Compat state, show a control.
+        mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, true /* show */,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        verify(mCompatUILayout).setCameraControlVisibility(/* show */ true);
+        verify(mCompatUILayout).updateCameraTreatmentButton(
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        clearInvocations(mWindowManager);
+        clearInvocations(mCompatUILayout);
+        // Change Camera Compat state, update a control.
+        mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, true /* show */,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mCompatUILayout).setCameraControlVisibility(/* show */ true);
+        verify(mCompatUILayout).updateCameraTreatmentButton(
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        clearInvocations(mWindowManager);
+        clearInvocations(mCompatUILayout);
+        // Change Camera Compat state to hidden, hide a control.
+        mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener,
+                true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        verify(mCompatUILayout).setCameraControlVisibility(/* show */ false);
 
         // Change task bounds, update position.
         clearInvocations(mWindowManager);
         final Configuration newTaskConfiguration = new Configuration();
         newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
         mWindowManager.updateCompatInfo(newTaskConfiguration, newTaskListener,
-                true /* show */);
+                true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).updateSurfacePosition();
     }
@@ -201,23 +238,25 @@
     public void testUpdateVisibility() {
         // Create button if it is not created.
         mWindowManager.mCompatUILayout = null;
+        mWindowManager.mHasSizeCompat = true;
         mWindowManager.updateVisibility(true /* show */);
 
-        verify(mWindowManager).createLayout(true /* show */);
+        verify(mWindowManager).createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // Hide button.
         clearInvocations(mWindowManager);
         doReturn(View.VISIBLE).when(mCompatUILayout).getVisibility();
         mWindowManager.updateVisibility(false /* show */);
 
-        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mWindowManager, never()).createLayout(anyBoolean(), anyBoolean(), anyInt());
         verify(mCompatUILayout).setVisibility(View.GONE);
 
         // Show button.
         doReturn(View.GONE).when(mCompatUILayout).getVisibility();
         mWindowManager.updateVisibility(true /* show */);
 
-        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mWindowManager, never()).createLayout(anyBoolean(), anyBoolean(), anyInt());
         verify(mCompatUILayout).setVisibility(View.VISIBLE);
     }
 
@@ -230,6 +269,37 @@
     }
 
     @Test
+    public void testOnCameraDismissButtonClicked() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        clearInvocations(mCompatUILayout);
+        mWindowManager.onCameraDismissButtonClicked();
+
+        verify(mCallback).onCameraControlStateUpdated(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+        verify(mCompatUILayout).setCameraControlVisibility(/* show= */ false);
+    }
+
+    @Test
+    public void testOnCameraTreatmentButtonClicked() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        clearInvocations(mCompatUILayout);
+        mWindowManager.onCameraTreatmentButtonClicked();
+
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+        verify(mCompatUILayout).updateCameraTreatmentButton(
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        mWindowManager.onCameraTreatmentButtonClicked();
+
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        verify(mCompatUILayout).updateCameraTreatmentButton(
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+    }
+
+    @Test
     public void testOnRestartButtonClicked() {
         mWindowManager.onRestartButtonClicked();
 
@@ -239,15 +309,60 @@
     @Test
     public void testOnRestartButtonLongClicked_showHint() {
        // Not create hint popup.
-        mWindowManager.mShouldShowHint = false;
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.mShouldShowSizeCompatHint = false;
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).inflateCompatUILayout();
-        verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+        verify(mCompatUILayout, never()).setSizeCompatHintVisibility(true /* show */);
 
         mWindowManager.onRestartButtonLongClicked();
 
         verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
     }
 
+    @Test
+    public void testOnCamerControlLongClicked_showHint() {
+       // Not create hint popup.
+        mWindowManager.mShouldShowCameraCompatHint = false;
+        mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mWindowManager).inflateCompatUILayout();
+        verify(mCompatUILayout, never()).setCameraCompatHintVisibility(true /* show */);
+
+        mWindowManager.onCameraButtonLongClicked();
+
+        verify(mCompatUILayout).setCameraCompatHintVisibility(true /* show */);
+    }
+
+    @Test
+    public void testCreateCameraCompatControl() {
+        // Not create layout if show is false.
+        mWindowManager.createLayout(false /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mWindowManager, never()).inflateCompatUILayout();
+
+        // Not create hint popup.
+        mWindowManager.mShouldShowCameraCompatHint = false;
+        mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mWindowManager).inflateCompatUILayout();
+        verify(mCompatUILayout, never()).setCameraCompatHintVisibility(true /* show */);
+        verify(mCompatUILayout).setCameraControlVisibility(true /* show */);
+
+        // Create hint popup.
+        mWindowManager.release();
+        mWindowManager.mShouldShowCameraCompatHint = true;
+        mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mWindowManager, times(2)).inflateCompatUILayout();
+        assertNotNull(mCompatUILayout);
+        verify(mCompatUILayout, times(2)).setCameraControlVisibility(true /* show */);
+        assertFalse(mWindowManager.mShouldShowCameraCompatHint);
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6d78b9f..ce571e5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -39,11 +39,13 @@
 import android.app.ActivityOptions;
 import android.app.ExitTransitionCoordinator;
 import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
+import android.app.ICompatCameraControlCallback;
 import android.app.Notification;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
@@ -72,6 +74,7 @@
 import android.view.ScrollCaptureResponse;
 import android.view.SurfaceControl;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowInsets;
@@ -595,20 +598,35 @@
         withWindowAttached(() -> {
             requestScrollCapture();
             mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
-                    (overrideConfig, newDisplayId) -> {
-                        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
-                            // Hide the scroll chip until we know it's available in this orientation
-                            mScreenshotView.hideScrollChip();
-                            // Delay scroll capture eval a bit to allow the underlying activity
-                            // to set up in the new orientation.
-                            mScreenshotHandler.postDelayed(this::requestScrollCapture, 150);
-                            mScreenshotView.updateInsets(
-                                    mWindowManager.getCurrentWindowMetrics().getWindowInsets());
-                            // screenshot animation calculations won't be valid anymore, so just end
-                            if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
-                                mScreenshotAnimation.end();
+                    new ViewRootImpl.ActivityConfigCallback() {
+                        @Override
+                        public void onConfigurationChanged(Configuration overrideConfig,
+                                int newDisplayId) {
+                            if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+                                // Hide the scroll chip until we know it's available in this
+                                // orientation
+                                mScreenshotView.hideScrollChip();
+                                // Delay scroll capture eval a bit to allow the underlying activity
+                                // to set up in the new orientation.
+                                mScreenshotHandler.postDelayed(
+                                        ScreenshotController.this::requestScrollCapture, 150);
+                                mScreenshotView.updateInsets(
+                                        mWindowManager.getCurrentWindowMetrics()
+                                                .getWindowInsets());
+                                // Screenshot animation calculations won't be valid anymore,
+                                // so just end
+                                if (mScreenshotAnimation != null
+                                        && mScreenshotAnimation.isRunning()) {
+                                    mScreenshotAnimation.end();
+                                }
                             }
                         }
+                        @Override
+                        public void requestCompatCameraControl(boolean showControl,
+                                boolean transformationApplied,
+                                ICompatCameraControlCallback callback) {
+                            Log.w(TAG, "Unexpected requestCompatCameraControl callback");
+                        }
                     });
         });
 
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 6c9f1e5..7164c6c 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -49,6 +49,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IActivityClientController;
+import android.app.ICompatCameraControlCallback;
 import android.app.IRequestFinishCallback;
 import android.app.PictureInPictureParams;
 import android.app.PictureInPictureUiState;
@@ -766,6 +767,22 @@
         Binder.restoreCallingIdentity(origId);
     }
 
+    @Override
+    public void requestCompatCameraControl(IBinder token, boolean showControl,
+            boolean transformationApplied, ICompatCameraControlCallback callback) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
+                if (r != null) {
+                    r.updateCameraCompatState(showControl, transformationApplied, callback);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     /**
      * Checks the state of the system and the activity associated with the given {@param token} to
      * verify that picture-in-picture is supported for that activity.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 447f4be..cde5273 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -232,9 +232,12 @@
 import android.app.Activity;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
+import android.app.ICompatCameraControlCallback;
 import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
 import android.app.ResultInfo;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.app.WaitResult;
 import android.app.WindowConfiguration;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
@@ -712,6 +715,20 @@
     @Nullable
     private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
 
+    // State of the Camera app compat control which is used to correct stretched viewfinder
+    // in apps that don't handle all possible configurations and changes between them correctly.
+    @CameraCompatControlState
+    private int mCameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+
+
+    // The callback that allows to ask the calling View to apply the treatment for stretched
+    // issues affecting camera viewfinders when the user clicks on the camera compat control.
+    @Nullable
+    private ICompatCameraControlCallback mCompatCameraControlCallback;
+
+    private final boolean mCameraCompatControlEnabled;
+    private boolean mCameraCompatControlClickedByUser;
+
     // activity is not displayed?
     // TODO: rename to mNoDisplay
     @VisibleForTesting
@@ -1167,6 +1184,10 @@
         }
 
         mLetterboxUiController.dump(pw, prefix);
+
+        pw.println(prefix + "mCameraCompatControlState="
+                + TaskInfo.cameraCompatControlStateToString(mCameraCompatControlState));
+        pw.println(prefix + "mCameraCompatControlEnabled=" + mCameraCompatControlEnabled);
     }
 
     static boolean dumpActivity(FileDescriptor fd, PrintWriter pw, int index, ActivityRecord r,
@@ -1572,6 +1593,91 @@
         mLetterboxUiController.getLetterboxInnerBounds(outBounds);
     }
 
+    void updateCameraCompatState(boolean showControl, boolean transformationApplied,
+            ICompatCameraControlCallback callback) {
+        if (!isCameraCompatControlEnabled()) {
+            // Feature is disabled by config_isCameraCompatControlForStretchedIssuesEnabled.
+            return;
+        }
+        if (mCameraCompatControlClickedByUser && (showControl
+                || mCameraCompatControlState == TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED)) {
+            // The user already applied treatment on this activity or dismissed control.
+            // Respecting their choice.
+            return;
+        }
+        mCompatCameraControlCallback = callback;
+        int newCameraCompatControlState = !showControl
+                ? TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN
+                : transformationApplied
+                        ? TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED
+                        : TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        boolean changed = setCameraCompatControlState(newCameraCompatControlState);
+        if (!changed) {
+            return;
+        }
+        if (newCameraCompatControlState == TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN) {
+            mCameraCompatControlClickedByUser = false;
+            mCompatCameraControlCallback = null;
+        }
+        // Trigger TaskInfoChanged to update the camera compat UI.
+        getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+    }
+
+    void updateCameraCompatStateFromUser(@CameraCompatControlState int state) {
+        if (!isCameraCompatControlEnabled()) {
+            // Feature is disabled by config_isCameraCompatControlForStretchedIssuesEnabled.
+            return;
+        }
+        if (state == TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN) {
+            Slog.w(TAG, "Unexpected hidden state in updateCameraCompatState");
+            return;
+        }
+        boolean changed = setCameraCompatControlState(state);
+        mCameraCompatControlClickedByUser = true;
+        if (!changed) {
+            return;
+        }
+        if (state == TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED) {
+            mCompatCameraControlCallback = null;
+            return;
+        }
+        if (mCompatCameraControlCallback == null) {
+            Slog.w(TAG, "Callback for a camera compat control is null");
+            return;
+        }
+        try {
+            if (state == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED) {
+                mCompatCameraControlCallback.applyCameraCompatTreatment();
+            } else {
+                mCompatCameraControlCallback.revertCameraCompatTreatment();
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to apply or revert camera compat treatment", e);
+        }
+    }
+
+    private boolean setCameraCompatControlState(@CameraCompatControlState int state) {
+        if (!isCameraCompatControlEnabled()) {
+            // Feature is disabled by config_isCameraCompatControlForStretchedIssuesEnabled.
+            return false;
+        }
+        if (mCameraCompatControlState != state) {
+            mCameraCompatControlState = state;
+            return true;
+        }
+        return false;
+    }
+
+    @CameraCompatControlState
+    int getCameraCompatControlState() {
+        return mCameraCompatControlState;
+    }
+
+    @VisibleForTesting
+    boolean isCameraCompatControlEnabled() {
+        return mCameraCompatControlEnabled;
+    }
+
     /**
      * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
      *     when the current activity is displayed.
@@ -1794,6 +1900,8 @@
         taskDescription = _taskDescription;
 
         mLetterboxUiController = new LetterboxUiController(mWmService, this);
+        mCameraCompatControlEnabled = mWmService.mContext.getResources()
+                .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
 
         if (_createTime > 0) {
             createTime = _createTime;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3e55811..7c3432e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3395,11 +3395,18 @@
         info.topActivityInfo = mReuseActivitiesReport.top != null
                 ? mReuseActivitiesReport.top.info
                 : null;
+
+        boolean isTopActivityResumed = mReuseActivitiesReport.top != null
+                 && mReuseActivitiesReport.top.getOrganizedTask() == this
+                 && mReuseActivitiesReport.top.isState(RESUMED);
         // Whether the direct top activity is in size compat mode on foreground.
-        info.topActivityInSizeCompat = mReuseActivitiesReport.top != null
-                && mReuseActivitiesReport.top.getOrganizedTask() == this
-                && mReuseActivitiesReport.top.inSizeCompatMode()
-                && mReuseActivitiesReport.top.isState(RESUMED);
+        info.topActivityInSizeCompat = isTopActivityResumed
+                && mReuseActivitiesReport.top.inSizeCompatMode();
+        // Whether the direct top activity requested showing camera compat control.
+        info.cameraCompatControlState = isTopActivityResumed
+                ? mReuseActivitiesReport.top.getCameraCompatControlState()
+                : TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+
         info.launchCookies.clear();
         info.addLaunchCookie(mLaunchCookie);
         forAllActivities(r -> {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 3d5f988..037d582 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.app.TaskInfo.cameraCompatControlStateToString;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
@@ -931,6 +933,35 @@
         }
     }
 
+    @Override
+    public void updateCameraCompatControlState(WindowContainerToken token, int state) {
+        enforceTaskPermission("updateCameraCompatControlState()");
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final WindowContainer wc = WindowContainer.fromBinder(token.asBinder());
+                if (wc == null) {
+                    Slog.w(TAG, "Could not resolve window from token");
+                    return;
+                }
+                final Task task = wc.asTask();
+                if (task == null) {
+                    Slog.w(TAG, "Could not resolve task from token");
+                    return;
+                }
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+                        "Update camera compat control state to %s for taskId=%d",
+                        cameraCompatControlStateToString(state), task.mTaskId);
+                final ActivityRecord activity = task.getTopNonFinishingActivity();
+                if (activity != null) {
+                    activity.updateCameraCompatStateFromUser(state);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     public boolean handleInterceptBackPressedOnTaskRoot(Task task) {
         if (task == null || !task.isOrganized()
                 || !mInterceptBackPressedOnRootTasks.contains(task.mTaskId)) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4a8e121..2e62286 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -16,6 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -103,6 +107,7 @@
 import static org.mockito.Mockito.never;
 
 import android.app.ActivityOptions;
+import android.app.ICompatCameraControlCallback;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.DestroyActivityItem;
@@ -3084,6 +3089,188 @@
                 eq(null));
     }
 
+    @Test
+    public void testUpdateCameraCompatState_flagIsEnabled_controlStateIsUpdated() {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being enabled.
+        doReturn(true).when(activity).isCameraCompatControlEnabled();
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+    }
+
+    @Test
+    public void testUpdateCameraCompatState_flagIsDisabled_controlStateIsHidden() {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being disabled.
+        doReturn(false).when(activity).isCameraCompatControlEnabled();
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+    }
+
+    @Test
+    public void testUpdateCameraCompatStateFromUser_clickedOnDismiss() throws RemoteException {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being enabled.
+        doReturn(true).when(activity).isCameraCompatControlEnabled();
+
+        ICompatCameraControlCallback callback = getCompatCameraControlCallback();
+        spyOn(callback);
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, callback);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Clicking on the button.
+        activity.updateCameraCompatStateFromUser(CAMERA_COMPAT_CONTROL_DISMISSED);
+
+        verify(callback, never()).revertCameraCompatTreatment();
+        verify(callback, never()).applyCameraCompatTreatment();
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_DISMISSED);
+
+        // All following updates are ignored.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_DISMISSED);
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_DISMISSED);
+
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_DISMISSED);
+    }
+
+    @Test
+    public void testUpdateCameraCompatStateFromUser_clickedOnApplyTreatment()
+            throws RemoteException {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being enabled.
+        doReturn(true).when(activity).isCameraCompatControlEnabled();
+
+        ICompatCameraControlCallback callback = getCompatCameraControlCallback();
+        spyOn(callback);
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, callback);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Clicking on the button.
+        activity.updateCameraCompatStateFromUser(CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        verify(callback, never()).revertCameraCompatTreatment();
+        verify(callback).applyCameraCompatTreatment();
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        // Request from the client to show the control are ignored respecting the user choice.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        // Request from the client to hide the control is respected.
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        // Request from the client to show the control again is respected.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+    }
+
+    @Test
+    public void testUpdateCameraCompatStateFromUser_clickedOnRevertTreatment()
+            throws RemoteException {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being enabled.
+        doReturn(true).when(activity).isCameraCompatControlEnabled();
+
+        ICompatCameraControlCallback callback = getCompatCameraControlCallback();
+        spyOn(callback);
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, callback);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        // Clicking on the button.
+        activity.updateCameraCompatStateFromUser(CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(callback).revertCameraCompatTreatment();
+        verify(callback, never()).applyCameraCompatTreatment();
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Request from the client to show the control are ignored respecting the user choice.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Request from the client to hide the control is respected.
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        // Request from the client to show the control again is respected.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+    }
+
+    private ICompatCameraControlCallback getCompatCameraControlCallback() {
+        return new ICompatCameraControlCallback.Stub() {
+            @Override
+            public void applyCameraCompatTreatment() {}
+
+            @Override
+            public void revertCameraCompatTreatment() {}
+        };
+    }
+
     private void assertHasStartingWindow(ActivityRecord atoken) {
         assertNotNull(atoken.mStartingSurface);
         assertNotNull(atoken.mStartingData);