summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mariia Sandrikova <mariiasand@google.com> 2022-08-22 09:47:45 +0000
committer Mariia Sandrikova <mariiasand@google.com> 2022-12-21 20:29:45 +0000
commitad26f036f810835b07fa3ebaec881499ce55e7b0 (patch)
treecd1aeebdd5f6c0cc9bb38b5fa259d9a61261d476
parentda4c552378996238e899a84ddcc26be3ebb6ca29 (diff)
[3/n] Camera Compat: Refresh activity
Introduce RefreshCallbackItem that allows to "refresh" activity by going through "stopped -> resumed" or "paused -> resumed" cycle. This allows to clear cached values in apps (e.g. display or camera rotation) that influence camera preview and can lead to sideways or stretching issues persisting even after force rotation in DisplayRotationCompatPolicy. Using "stop -> resumed" cycle by default due to higher success rate confirmed with app compatibility testing. But introduce ADB commands that allow trying both. Bug: 218352945 Test: atest WmTests:DisplayRotationCompatPolicyTests Change-Id: Iaca54b197937147f93d52297543b106fd5b97322
-rw-r--r--core/java/android/app/ActivityClient.java9
-rw-r--r--core/java/android/app/ActivityThread.java5
-rw-r--r--core/java/android/app/ClientTransactionHandler.java3
-rw-r--r--core/java/android/app/IActivityClientController.aidl1
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionItem.java3
-rw-r--r--core/java/android/app/servertransaction/RefreshCallbackItem.java145
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java11
-rw-r--r--data/etc/services.core.protolog.json18
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java29
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java134
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java51
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java18
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java165
17 files changed, 587 insertions, 52 deletions
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 324b8e7a784f..0074a0dd4c96 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -58,6 +58,15 @@ public class ActivityClient {
}
}
+ /** Reports {@link android.app.servertransaction.RefreshCallbackItem} is executed. */
+ public void activityRefreshed(IBinder token) {
+ try {
+ getActivityClientController().activityRefreshed(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Reports after {@link Activity#onTopResumedActivityChanged(boolean)} is called for losing the
* top most position.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ef6c5a6c8272..2beb64d98c99 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5249,6 +5249,11 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
+ @Override
+ public void reportRefresh(ActivityClientRecord r) {
+ ActivityClient.getInstance().activityRefreshed(r.token);
+ }
+
private void handleSetCoreSettings(Bundle coreSettings) {
synchronized (mCoreSettingsLock) {
mCoreSettings = coreSettings;
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index a7566fdaae64..2c70c4e90157 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -140,6 +140,9 @@ public abstract class ClientTransactionHandler {
/** Restart the activity after it was stopped. */
public abstract void performRestartActivity(@NonNull ActivityClientRecord r, boolean start);
+ /** Report that activity was refreshed to server. */
+ public abstract void reportRefresh(@NonNull ActivityClientRecord r);
+
/** Set pending activity configuration in case it will be updated by other transaction item. */
public abstract void updatePendingActivityConfiguration(@NonNull IBinder token,
Configuration overrideConfig);
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 8b655b9bf315..969f97577c5a 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -38,6 +38,7 @@ import com.android.internal.policy.IKeyguardDismissCallback;
interface IActivityClientController {
oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling);
oneway void activityResumed(in IBinder token, in boolean handleSplashScreenExit);
+ oneway void activityRefreshed(in IBinder token);
/**
* This call is not one-way because {@link #activityPaused()) is not one-way, or
* the top-resumed-lost could be reported after activity paused.
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index d94f08b6aac1..b159f336cd85 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -38,6 +38,9 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel
return UNDEFINED;
}
+ boolean shouldHaveDefinedPreExecutionState() {
+ return true;
+ }
// Parcelable
diff --git a/core/java/android/app/servertransaction/RefreshCallbackItem.java b/core/java/android/app/servertransaction/RefreshCallbackItem.java
new file mode 100644
index 000000000000..74abab22b0a6
--- /dev/null
+++ b/core/java/android/app/servertransaction/RefreshCallbackItem.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Callback that allows to {@link TransactionExecutor#cycleToPath} to {@link ON_PAUSE} or
+ * {@link ON_STOP} in {@link TransactionExecutor#executeCallbacks} for activity "refresh" flow
+ * that goes through "paused -> resumed" or "stopped -> resumed" cycle.
+ *
+ * <p>This is used in combination with {@link com.android.server.wm.DisplayRotationCompatPolicy}
+ * for camera compatibility treatment that handles orientation mismatch between camera buffers and
+ * an app window. This allows to clear cached values in apps (e.g. display or camera rotation) that
+ * influence camera preview and can lead to sideways or stretching issues.
+ *
+ * @hide
+ */
+public class RefreshCallbackItem extends ActivityTransactionItem {
+
+ // Whether refresh should happen using the "stopped -> resumed" cycle or
+ // "paused -> resumed" cycle.
+ @LifecycleState
+ private int mPostExecutionState;
+
+ @Override
+ public void execute(@NonNull ClientTransactionHandler client,
+ @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions) {}
+
+ @Override
+ public void postExecute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ final ActivityClientRecord r = getActivityClientRecord(client, token);
+ client.reportRefresh(r);
+ }
+
+ @Override
+ public int getPostExecutionState() {
+ return mPostExecutionState;
+ }
+
+ @Override
+ boolean shouldHaveDefinedPreExecutionState() {
+ return false;
+ }
+
+ // ObjectPoolItem implementation
+
+ @Override
+ public void recycle() {
+ ObjectPool.recycle(this);
+ }
+
+ /**
+ * Obtain an instance initialized with provided params.
+ * @param postExecutionState indicating whether refresh should happen using the
+ * "stopped -> resumed" cycle or "paused -> resumed" cycle.
+ */
+ public static RefreshCallbackItem obtain(@LifecycleState int postExecutionState) {
+ if (postExecutionState != ON_STOP && postExecutionState != ON_PAUSE) {
+ throw new IllegalArgumentException(
+ "Only ON_STOP or ON_PAUSE are allowed as a post execution state for "
+ + "RefreshCallbackItem but got " + postExecutionState);
+ }
+ RefreshCallbackItem instance =
+ ObjectPool.obtain(RefreshCallbackItem.class);
+ if (instance == null) {
+ instance = new RefreshCallbackItem();
+ }
+ instance.mPostExecutionState = postExecutionState;
+ return instance;
+ }
+
+ private RefreshCallbackItem() {}
+
+ // Parcelable implementation
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mPostExecutionState);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final RefreshCallbackItem other = (RefreshCallbackItem) o;
+ return mPostExecutionState == other.mPostExecutionState;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mPostExecutionState;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "RefreshCallbackItem{mPostExecutionState=" + mPostExecutionState + "}";
+ }
+
+ private RefreshCallbackItem(Parcel in) {
+ mPostExecutionState = in.readInt();
+ }
+
+ public static final @NonNull Creator<RefreshCallbackItem> CREATOR =
+ new Creator<RefreshCallbackItem>() {
+
+ public RefreshCallbackItem createFromParcel(Parcel in) {
+ return new RefreshCallbackItem(in);
+ }
+
+ public RefreshCallbackItem[] newArray(int size) {
+ return new RefreshCallbackItem[size];
+ }
+ };
+}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index de1d38a64163..1ff0b796fb1e 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -126,10 +126,13 @@ public class TransactionExecutor {
final ClientTransactionItem item = callbacks.get(i);
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
final int postExecutionState = item.getPostExecutionState();
- final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
- item.getPostExecutionState());
- if (closestPreExecutionState != UNDEFINED) {
- cycleToPath(r, closestPreExecutionState, transaction);
+
+ if (item.shouldHaveDefinedPreExecutionState()) {
+ final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+ item.getPostExecutionState());
+ if (closestPreExecutionState != UNDEFINED) {
+ cycleToPath(r, closestPreExecutionState, transaction);
+ }
}
item.execute(mTransactionHandler, token, mPendingActions);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f55a91e9f1bf..f47d9c6e0c2d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -487,6 +487,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1631991057": {
+ "message": "Display id=%d is notified that Camera %s is closed but activity is still refreshing. Rescheduling an update.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-1630752478": {
"message": "removeLockedTask: removed %s",
"level": "DEBUG",
@@ -625,6 +631,12 @@
"group": "WM_DEBUG_WINDOW_INSETS",
"at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
},
+ "-1480918485": {
+ "message": "Refreshed activity: %s",
+ "level": "INFO",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1480772131": {
"message": "No app or window is requesting an orientation, return %d for display id=%d",
"level": "VERBOSE",
@@ -4225,6 +4237,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "1967643923": {
+ "message": "Refershing activity for camera compatibility treatment, activityRecord=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"1967975839": {
"message": "Changing app %s visible=%b performLayout=%b",
"level": "VERBOSE",
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index da6e7e8436eb..725254513519 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -163,6 +163,15 @@ class ActivityClientController extends IActivityClientController.Stub {
}
@Override
+ public void activityRefreshed(IBinder token) {
+ final long origId = Binder.clearCallingIdentity();
+ synchronized (mGlobalLock) {
+ ActivityRecord.activityRefreshedLocked(token);
+ }
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ @Override
public void activityTopResumedStateLost() {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 57eeb9a0a905..50169b4ba667 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -736,7 +736,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
private boolean mWillCloseOrEnterPip;
- @VisibleForTesting
final LetterboxUiController mLetterboxUiController;
/**
@@ -6111,6 +6110,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
r.mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(r);
}
+ static void activityRefreshedLocked(IBinder token) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ ProtoLog.i(WM_DEBUG_STATES, "Refreshed activity: %s", r);
+ if (r == null) {
+ // In case the record on server side has been removed (e.g. destroy timeout)
+ // and the token could be null.
+ return;
+ }
+ if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) {
+ r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r);
+ }
+ }
+
static void splashScreenAttachedLocked(IBinder token) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
@@ -9143,6 +9155,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else {
scheduleConfigurationChanged(newMergedOverrideConfig);
}
+ notifyDisplayCompatPolicyAboutConfigurationChange(
+ mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
return true;
}
@@ -9211,11 +9225,24 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else {
scheduleConfigurationChanged(newMergedOverrideConfig);
}
+ notifyDisplayCompatPolicyAboutConfigurationChange(
+ mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
+
stopFreezingScreenLocked(false);
return true;
}
+ private void notifyDisplayCompatPolicyAboutConfigurationChange(
+ Configuration newConfig, Configuration lastReportedConfig) {
+ if (mDisplayContent.mDisplayRotationCompatPolicy == null
+ || !shouldBeResumed(/* activeActivity */ null)) {
+ return;
+ }
+ mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging(
+ this, newConfig, lastReportedConfig);
+ }
+
/** Get process configuration, or global config if the process is not set. */
private Configuration getProcessGlobalConfiguration() {
return app != null ? app.getConfiguration() : mAtmService.getGlobalConfiguration();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c97d7a97d886..36f86d10e2c5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -431,7 +431,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
private final DisplayPolicy mDisplayPolicy;
private final DisplayRotation mDisplayRotation;
- @Nullable private final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
+ @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
DisplayFrames mDisplayFrames;
private final RemoteCallbackList<ISystemGestureExclusionListener>
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 34bdb7a2f416..cf3a6880e712 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1805,6 +1805,7 @@ public class DisplayRotation {
final int mHalfFoldSavedRotation;
final boolean mInHalfFoldTransition;
final DeviceStateController.FoldState mFoldState;
+ @Nullable final String mDisplayRotationCompatPolicySummary;
Record(DisplayRotation dr, int fromRotation, int toRotation) {
mFromRotation = fromRotation;
@@ -1839,6 +1840,10 @@ public class DisplayRotation {
mInHalfFoldTransition = false;
mFoldState = DeviceStateController.FoldState.UNKNOWN;
}
+ mDisplayRotationCompatPolicySummary = dc.mDisplayRotationCompatPolicy == null
+ ? null
+ : dc.mDisplayRotationCompatPolicy
+ .getSummaryForDisplayRotationHistoryRecord();
}
void dump(String prefix, PrintWriter pw) {
@@ -1861,6 +1866,9 @@ public class DisplayRotation {
+ " mInHalfFoldTransition=" + mInHalfFoldTransition
+ " mFoldState=" + mFoldState);
}
+ if (mDisplayRotationCompatPolicySummary != null) {
+ pw.println(prefix + mDisplayRotationCompatPolicySummary);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 0d3f784849ba..18c5c3b82b19 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -16,10 +16,13 @@
package com.android.server.wm;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -27,12 +30,18 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.Display.TYPE_INTERNAL;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
+import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -65,11 +74,15 @@ final class DisplayRotationCompatPolicy {
private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
// Delay for updating display rotation after Camera connection is opened. This delay is
// selected to be long enough to avoid conflicts with transitions on the app's side.
- // Using half CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
+ // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
// is flipping between front and rear cameras (in case requested orientation changes at
// runtime at the same time) or when size compat mode is restarted.
private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
+ // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
+ // client process may not always report the event back to the server, such as process is
+ // crashed or got killed.
+ private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
private final DisplayContent mDisplayContent;
private final WindowManagerService mWmService;
@@ -99,6 +112,9 @@ final class DisplayRotationCompatPolicy {
}
};
+ @ScreenOrientation
+ private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
+
DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent) {
this(displayContent, displayContent.mWmService.mH);
}
@@ -132,7 +148,13 @@ final class DisplayRotationCompatPolicy {
* #shouldComputeCameraCompatOrientation} for conditions enabling the treatment.
*/
@ScreenOrientation
- synchronized int getOrientation() {
+ int getOrientation() {
+ mLastReportedOrientation = getOrientationInternal();
+ return mLastReportedOrientation;
+ }
+
+ @ScreenOrientation
+ private synchronized int getOrientationInternal() {
if (!isTreatmentEnabledForDisplay()) {
return SCREEN_ORIENTATION_UNSPECIFIED;
}
@@ -169,6 +191,73 @@ final class DisplayRotationCompatPolicy {
}
/**
+ * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+ * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+ * camera preview and can lead to sideways or stretching issues persisting even after force
+ * rotation.
+ */
+ void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
+ Configuration lastReportedConfig) {
+ if (!isTreatmentEnabledForDisplay()
+ || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
+ || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
+ return;
+ }
+ boolean cycleThroughStop = mWmService.mLetterboxConfiguration
+ .isCameraCompatRefreshCycleThroughStopEnabled();
+ try {
+ activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
+ ProtoLog.v(WM_DEBUG_STATES,
+ "Refershing activity for camera compatibility treatment, "
+ + "activityRecord=%s", activity);
+ final ClientTransaction transaction = ClientTransaction.obtain(
+ activity.app.getThread(), activity.token);
+ transaction.addCallback(
+ RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
+ transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
+ activity.mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+ mHandler.postDelayed(
+ () -> onActivityRefreshed(activity),
+ REFRESH_CALLBACK_TIMEOUT_MS);
+ } catch (RemoteException e) {
+ activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
+ }
+ }
+
+ void onActivityRefreshed(@NonNull ActivityRecord activity) {
+ activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
+ }
+
+ String getSummaryForDisplayRotationHistoryRecord() {
+ String summaryIfEnabled = "";
+ if (isTreatmentEnabledForDisplay()) {
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ summaryIfEnabled =
+ " mLastReportedOrientation="
+ + screenOrientationToString(mLastReportedOrientation)
+ + " topActivity="
+ + (topActivity == null ? "null" : topActivity.shortComponentName)
+ + " isTreatmentEnabledForActivity="
+ + isTreatmentEnabledForActivity(topActivity)
+ + " CameraIdPackageNameBiMap="
+ + mCameraIdPackageBiMap.getSummaryForDisplayRotationHistoryRecord();
+ }
+ return "DisplayRotationCompatPolicy{"
+ + " isTreatmentEnabledForDisplay=" + isTreatmentEnabledForDisplay()
+ + summaryIfEnabled
+ + " }";
+ }
+
+ // Refreshing only when configuration changes after rotation.
+ private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
+ Configuration lastReportedConfig) {
+ return newConfig.windowConfiguration.getDisplayRotation()
+ != lastReportedConfig.windowConfiguration.getDisplayRotation()
+ && isTreatmentEnabledForActivity(activity);
+ }
+
+ /**
* Whether camera compat treatment is enabled for the display.
*
* <p>Conditions that need to be met:
@@ -221,8 +310,6 @@ final class DisplayRotationCompatPolicy {
mHandler.postDelayed(
() -> delayedUpdateOrientationWithWmLock(cameraId, packageName),
CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS);
- // TODO(b/218352945): Restart activity after forced rotation to avoid issues cased by
- // in-app caching of pre-rotation display / camera properties.
}
private void updateOrientationWithWmLock() {
@@ -251,8 +338,12 @@ final class DisplayRotationCompatPolicy {
mScheduledToBeRemovedCameraIdSet.add(cameraId);
// No need to update orientation for this camera if it's already closed.
mScheduledOrientationUpdateCameraIdSet.remove(cameraId);
- // Delay is needed to avoid rotation flickering when an app is flipping between front and
- // rear cameras or when size compat mode is restarted.
+ scheduleRemoveCameraId(cameraId);
+ }
+
+ // Delay is needed to avoid rotation flickering when an app is flipping between front and
+ // rear cameras, when size compat mode is restarted or activity is being refreshed.
+ private void scheduleRemoveCameraId(@NonNull String cameraId) {
mHandler.postDelayed(
() -> removeCameraId(cameraId),
CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS);
@@ -264,6 +355,15 @@ final class DisplayRotationCompatPolicy {
// Already reconnected to this camera, no need to clean up.
return;
}
+ if (isActivityForCameraIdRefreshing(cameraId)) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is notified that Camera %s is closed but activity is"
+ + " still refreshing. Rescheduling an update.",
+ mDisplayContent.mDisplayId, cameraId);
+ mScheduledToBeRemovedCameraIdSet.add(cameraId);
+ scheduleRemoveCameraId(cameraId);
+ return;
+ }
mCameraIdPackageBiMap.removeCameraId(cameraId);
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -272,6 +372,19 @@ final class DisplayRotationCompatPolicy {
updateOrientationWithWmLock();
}
+ private boolean isActivityForCameraIdRefreshing(String cameraId) {
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (!isTreatmentEnabledForActivity(topActivity)) {
+ return false;
+ }
+ String activeCameraId = mCameraIdPackageBiMap.getCameraId(topActivity.packageName);
+ if (activeCameraId == null || activeCameraId != cameraId) {
+ return false;
+ }
+ return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
+ }
+
private static class CameraIdPackageNameBiMap {
private final Map<String, String> mPackageToCameraIdMap = new ArrayMap<>();
@@ -290,6 +403,11 @@ final class DisplayRotationCompatPolicy {
return mPackageToCameraIdMap.containsKey(packageName);
}
+ @Nullable
+ String getCameraId(String packageName) {
+ return mPackageToCameraIdMap.get(packageName);
+ }
+
void removeCameraId(String cameraId) {
String packageName = mCameraIdToPackageMap.get(cameraId);
if (packageName == null) {
@@ -299,6 +417,10 @@ final class DisplayRotationCompatPolicy {
mCameraIdToPackageMap.remove(cameraId, packageName);
}
+ String getSummaryForDisplayRotationHistoryRecord() {
+ return "{ mPackageToCameraIdMap=" + mPackageToCameraIdMap + " }";
+ }
+
private void removePackageName(String packageName) {
String cameraId = mPackageToCameraIdMap.get(packageName);
if (cameraId == null) {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 793a352aeec6..a7bf595fa673 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -195,6 +195,16 @@ final class LetterboxConfiguration {
// See DisplayRotationCompatPolicy for context.
private final boolean mIsCameraCompatTreatmentEnabled;
+ // Whether activity "refresh" in camera compatibility treatment is enabled.
+ // See RefreshCallbackItem for context.
+ private boolean mIsCameraCompatTreatmentRefreshEnabled = true;
+
+ // Whether activity "refresh" in camera compatibility treatment should happen using the
+ // "stopped -> resumed" cycle rather than "paused -> resumed" cycle. Using "stop -> resumed"
+ // cycle by default due to higher success rate confirmed with app compatibility testing.
+ // See RefreshCallbackItem for context.
+ private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+
LetterboxConfiguration(Context systemUiContext) {
this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
() -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
@@ -973,4 +983,45 @@ final class LetterboxConfiguration {
"enable_camera_compat_treatment", false);
}
+ /** Whether camera compatibility refresh is enabled. */
+ boolean isCameraCompatRefreshEnabled() {
+ return mIsCameraCompatTreatmentRefreshEnabled;
+ }
+
+ /** Overrides whether camera compatibility treatment is enabled. */
+ void setCameraCompatRefreshEnabled(boolean enabled) {
+ mIsCameraCompatTreatmentRefreshEnabled = enabled;
+ }
+
+ /**
+ * Resets whether camera compatibility treatment is enabled to {@code true}.
+ */
+ void resetCameraCompatRefreshEnabled() {
+ mIsCameraCompatTreatmentRefreshEnabled = true;
+ }
+
+ /**
+ * Whether activity "refresh" in camera compatibility treatment should happen using the
+ * "stopped -> resumed" cycle rather than "paused -> resumed" cycle.
+ */
+ boolean isCameraCompatRefreshCycleThroughStopEnabled() {
+ return mIsCameraCompatRefreshCycleThroughStopEnabled;
+ }
+
+ /**
+ * Overrides whether activity "refresh" in camera compatibility treatment should happen using
+ * "stopped -> resumed" cycle rather than "paused -> resumed" cycle.
+ */
+ void setCameraCompatRefreshCycleThroughStopEnabled(boolean enabled) {
+ mIsCameraCompatRefreshCycleThroughStopEnabled = enabled;
+ }
+
+ /**
+ * Resets whether activity "refresh" in camera compatibility treatment should happen using
+ * "stopped -> resumed" cycle rather than "paused -> resumed" cycle to {@code true}.
+ */
+ void resetCameraCompatRefreshCycleThroughStopEnabled() {
+ mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9cb94c68583f..fd7e082beed4 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -80,6 +80,7 @@ import java.io.PrintWriter;
// SizeCompatTests and LetterboxTests but not all.
// TODO(b/185264020): Consider making LetterboxUiController applicable to any level of the
// hierarchy in addition to ActivityRecord (Task, DisplayArea, ...).
+// TODO(b/263021211): Consider renaming to more generic CompatUIController.
final class LetterboxUiController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
@@ -125,6 +126,11 @@ final class LetterboxUiController {
@Nullable
private Letterbox mLetterbox;
+ // Whether activity "refresh" was requested but not finished in
+ // ActivityRecord#activityResumedLocked following the camera compat force rotation in
+ // DisplayRotationCompatPolicy.
+ private boolean mIsRefreshAfterRotationRequested;
+
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
mLetterboxConfiguration = wmService.mLetterboxConfiguration;
// Given activityRecord may not be fully constructed since LetterboxUiController
@@ -147,6 +153,18 @@ final class LetterboxUiController {
}
}
+ /**
+ * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
+ * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
+ */
+ boolean isRefreshAfterRotationRequested() {
+ return mIsRefreshAfterRotationRequested;
+ }
+
+ void setIsRefreshAfterRotationRequested(boolean isRequested) {
+ mIsRefreshAfterRotationRequested = isRequested;
+ }
+
boolean hasWallpaperBackgroundForLetterbox() {
return mShowWallpaperForLetterboxBackground;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 554f271a31a7..aef6d1d15510 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -959,6 +959,14 @@ public class WindowManagerShellCommand extends ShellCommand {
runSetBooleanFlag(pw, mLetterboxConfiguration
::setTranslucentLetterboxingOverrideEnabled);
break;
+ case "--isCameraCompatRefreshEnabled":
+ runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
+ .setCameraCompatRefreshEnabled(enabled));
+ break;
+ case "--isCameraCompatRefreshCycleThroughStopEnabled":
+ runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
+ .setCameraCompatRefreshCycleThroughStopEnabled(enabled));
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1025,6 +1033,13 @@ public class WindowManagerShellCommand extends ShellCommand {
case "isTranslucentLetterboxingEnabled":
mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
break;
+ case "isCameraCompatRefreshEnabled":
+ mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
+ break;
+ case "isCameraCompatRefreshCycleThroughStopEnabled":
+ mLetterboxConfiguration
+ .resetCameraCompatRefreshCycleThroughStopEnabled();
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1126,6 +1141,8 @@ public class WindowManagerShellCommand extends ShellCommand {
mLetterboxConfiguration.resetIsEducationEnabled();
mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+ mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
+ mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
}
}
@@ -1171,6 +1188,11 @@ public class WindowManagerShellCommand extends ShellCommand {
+ mLetterboxConfiguration
.getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
+ pw.println(" Is activity \"refresh\" in camera compatibility treatment enabled: "
+ + mLetterboxConfiguration.isCameraCompatRefreshEnabled());
+ pw.println(" Refresh using \"stopped -> resumed\" cycle: "
+ + mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled());
+
pw.println("Background type: "
+ LetterboxConfiguration.letterboxBackgroundTypeToString(
mLetterboxConfiguration.getLetterboxBackgroundType()));
@@ -1380,6 +1402,12 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" unresizable apps.");
pw.println(" --isTranslucentLetterboxingEnabled [true|1|false|0]");
pw.println(" Whether letterboxing for translucent activities is enabled.");
+ pw.println(" --isCameraCompatRefreshEnabled [true|1|false|0]");
+ pw.println(" Whether camera compatibility refresh is enabled.");
+ pw.println(" --isCameraCompatRefreshCycleThroughStopEnabled [true|1|false|0]");
+ pw.println(" Whether activity \"refresh\" in camera compatibility treatment should");
+ pw.println(" happen using the \"stopped -> resumed\" cycle rather than");
+ pw.println(" \"paused -> resumed\" cycle.");
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier");
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index d66215051d62..d1234e3de81a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
@@ -23,6 +25,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,15 +38,23 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
import android.content.ComponentName;
import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration;
import android.content.res.Configuration.Orientation;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
+import android.view.Surface.Rotation;
import androidx.test.filters.SmallTest;
@@ -85,6 +97,10 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ anyBoolean()))
.thenReturn(true);
+ when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+ .thenReturn(true);
+ when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+ .thenReturn(true);
mMockCameraManager = mock(CameraManager.class);
doAnswer(invocation -> {
@@ -112,35 +128,34 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
}
@Test
- public void testGetOrientation_treatmentNotEnabled_returnUnspecified() {
+ public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ anyBoolean()))
.thenReturn(false);
- mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent);
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
SCREEN_ORIENTATION_UNSPECIFIED);
+
+ assertNoForceRotationOrRefresh();
}
@Test
- public void testGetOrientation_treatmentDisabledViaDeviceConfig_returnUnspecified() {
+ public void testTreatmentDisabledViaDeviceConfig_noForceRotationOrRefresh() throws Exception {
when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ true))
.thenReturn(false);
- mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent);
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
}
@Test
- public void testGetOrientation_multiWindowMode_returnUnspecified() {
+ public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mDisplayContent);
mActivity.getTask().reparent(organizer.mPrimary, WindowContainer.POSITION_TOP,
@@ -149,53 +164,46 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mActivity.inMultiWindowMode());
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
}
@Test
- public void testGetOrientation_orientationUnspecified_returnUnspecified() {
+ public void testOrientationUnspecified_noForceRotationOrRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
-
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
}
@Test
- public void testGetOrientation_orientationLocked_returnUnspecified() {
+ public void testOrientationLocked_noForceRotationOrRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_LOCKED);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
}
@Test
- public void testGetOrientation_orientationNoSensor_returnUnspecified() {
+ public void testOrientationNoSensor_noForceRotationOrRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_NOSENSOR);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
}
@Test
- public void testGetOrientation_ignoreOrientationRequestIsFalse_returnUnspecified() {
+ public void testIgnoreOrientationRequestIsFalse_noForceRotationOrRefresh() throws Exception {
mDisplayContent.setIgnoreOrientationRequest(false);
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
}
@Test
- public void testGetOrientation_displayNotInternal_returnUnspecified() {
+ public void testDisplayNotInternal_noForceRotationOrRefresh() throws Exception {
Display display = mDisplayContent.getDisplay();
spyOn(display);
@@ -203,52 +211,51 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
when(display.getType()).thenReturn(Display.TYPE_EXTERNAL);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
when(display.getType()).thenReturn(Display.TYPE_WIFI);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
when(display.getType()).thenReturn(Display.TYPE_OVERLAY);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
when(display.getType()).thenReturn(Display.TYPE_VIRTUAL);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
}
@Test
- public void testGetOrientation_noCameraConnection_returnUnspecified() {
+ public void testNoCameraConnection_noForceRotationOrRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
}
@Test
- public void testGetOrientation_cameraReconnected_returnNotUnspecified() {
+ public void testCameraReconnected_forceRotationAndRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
SCREEN_ORIENTATION_PORTRAIT);
+ assertActivityRefreshRequested(/* refreshRequested */ true);
}
@Test
- public void testGetOrientation_reconnectedToDifferentCamera_returnNotUnspecified() {
+ public void testReconnectedToDifferentCamera_forceRotationAndRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
SCREEN_ORIENTATION_PORTRAIT);
+ assertActivityRefreshRequested(/* refreshRequested */ true);
}
@Test
@@ -267,13 +274,12 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
}
@Test
- public void testGetOrientation_cameraOpenedForDifferentPackage_returnUnspecified() {
+ public void testCameraOpenedForDifferentPackage_noForceRotationOrRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
- assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
- SCREEN_ORIENTATION_UNSPECIFIED);
+ assertNoForceRotationOrRefresh();
}
@Test
@@ -320,6 +326,42 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
expectedOrientation);
}
+ @Test
+ public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception {
+ when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertActivityRefreshRequested(/* refreshRequested */ false);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_displayRotationNotChanging_noRefresh()
+ throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ false);
+
+ assertActivityRefreshRequested(/* refreshRequested */ false);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
+ when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+ .thenReturn(false);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ }
+
private void configureActivity(@ScreenOrientation int activityOrientation) {
configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
}
@@ -337,7 +379,50 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
.setTask(mTask)
.build();
+ spyOn(mActivity.mAtmService.getLifecycleManager());
+ spyOn(mActivity.mLetterboxUiController);
+
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
}
+
+ private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
+ assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+ }
+
+ private void assertActivityRefreshRequested(boolean refreshRequested,
+ boolean cycleThroughStop) throws Exception {
+ verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
+ .setIsRefreshAfterRotationRequested(true);
+
+ final ClientTransaction transaction = ClientTransaction.obtain(
+ mActivity.app.getThread(), mActivity.token);
+ transaction.addCallback(RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
+ transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
+
+ verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
+ .scheduleTransaction(eq(transaction));
+ }
+
+ private void assertNoForceRotationOrRefresh() throws Exception {
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ assertActivityRefreshRequested(/* refreshRequested */ false);
+ }
+
+ private void callOnActivityConfigurationChanging(
+ ActivityRecord activity, boolean isDisplayRotationChanging) {
+ mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity,
+ /* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0),
+ /* newConfig */ createConfigurationWithDisplayRotation(
+ isDisplayRotationChanging ? ROTATION_90 : ROTATION_0));
+ }
+
+ private static Configuration createConfigurationWithDisplayRotation(@Rotation int rotation) {
+ final Configuration config = new Configuration();
+ config.windowConfiguration.setDisplayRotation(rotation);
+ return config;
+ }
}