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);