Merge changes from topic "camera-compat-force-rotation" into tm-qpr-dev
* changes: [3/n] Camera Compat: Refresh activity [2/n] Camera Compat: Add DeviceConfig flag [1/n] Camera Compat: Force rotate activities
This commit is contained in:
committed by
Android (Google) Code Review
commit
13459ada3a
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -38,6 +38,9 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
boolean shouldHaveDefinedPreExecutionState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parcelable
|
||||
|
||||
|
||||
145
core/java/android/app/servertransaction/RefreshCallbackItem.java
Normal file
145
core/java/android/app/servertransaction/RefreshCallbackItem.java
Normal file
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -5344,6 +5344,12 @@
|
||||
TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
|
||||
<bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
|
||||
|
||||
<!-- Whether camera compat treatment is enabled for issues caused by orientation mismatch
|
||||
between camera buffers and an app window. This includes force rotation of fixed
|
||||
orientation activities connected to the camera in fullscreen and showing a tooltip in
|
||||
split screen. -->
|
||||
<bool name="config_isWindowManagerCameraCompatTreatmentEnabled">false</bool>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
@@ -4460,6 +4460,7 @@
|
||||
<java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
|
||||
<java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
|
||||
<java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
|
||||
<java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
|
||||
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
|
||||
|
||||
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
|
||||
|
||||
@@ -295,6 +295,12 @@
|
||||
"group": "WM_DEBUG_IME",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-1812743677": {
|
||||
"message": "Display id=%d is ignoring all orientation requests, camera is active and the top activity is eligible for force rotation, return %s,portrait activity: %b, is natural orientation portrait: %b.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_ORIENTATION",
|
||||
"at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
|
||||
},
|
||||
"-1810446914": {
|
||||
"message": "Trying to update display configuration for system\/invalid process.",
|
||||
"level": "WARN",
|
||||
@@ -481,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",
|
||||
@@ -619,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",
|
||||
@@ -1321,6 +1339,12 @@
|
||||
"group": "WM_DEBUG_CONFIGURATION",
|
||||
"at": "com\/android\/server\/wm\/ActivityRecord.java"
|
||||
},
|
||||
"-799396645": {
|
||||
"message": "Display id=%d is notified that Camera %s is closed, updating rotation.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_ORIENTATION",
|
||||
"at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
|
||||
},
|
||||
"-799003045": {
|
||||
"message": "Set animatingExit: reason=remove\/replaceWindow win=%s",
|
||||
"level": "VERBOSE",
|
||||
@@ -1543,6 +1567,12 @@
|
||||
"group": "WM_DEBUG_SCREEN_ON",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-627759820": {
|
||||
"message": "Display id=%d is notified that Camera %s is open for package %s",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_ORIENTATION",
|
||||
"at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
|
||||
},
|
||||
"-622997754": {
|
||||
"message": "postWindowRemoveCleanupLocked: %s",
|
||||
"level": "VERBOSE",
|
||||
@@ -2101,6 +2131,12 @@
|
||||
"group": "WM_SHOW_TRANSACTIONS",
|
||||
"at": "com\/android\/server\/wm\/Session.java"
|
||||
},
|
||||
"-81260230": {
|
||||
"message": "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_ORIENTATION",
|
||||
"at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
|
||||
},
|
||||
"-81121442": {
|
||||
"message": "ImeContainer just became organized but it doesn't have a parent or the parent doesn't have a surface control. mSurfaceControl=%s imeParentSurfaceControl=%s",
|
||||
"level": "ERROR",
|
||||
@@ -4201,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",
|
||||
|
||||
@@ -74,6 +74,7 @@ import android.view.Surface;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import com.android.framework.protobuf.nano.MessageNano;
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.server.LocalServices;
|
||||
@@ -389,6 +390,16 @@ public class CameraServiceProxy extends SystemService
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
// When config_isWindowManagerCameraCompatTreatmentEnabled is true,
|
||||
// DisplayRotationCompatPolicy in WindowManager force rotates fullscreen activities with
|
||||
// fixed orientation to align them with the natural orientation of the device.
|
||||
if (ctx.getResources().getBoolean(
|
||||
R.bool.config_isWindowManagerCameraCompatTreatmentEnabled)) {
|
||||
Slog.v(TAG, "Disable Rotate and Crop to avoid conflicts with"
|
||||
+ " WM force rotation treatment.");
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
// External cameras do not need crop-rotate-scale.
|
||||
if (lensFacing != CameraMetadata.LENS_FACING_FRONT
|
||||
&& lensFacing != CameraMetadata.LENS_FACING_BACK) {
|
||||
|
||||
@@ -162,6 +162,15 @@ class ActivityClientController extends IActivityClientController.Stub {
|
||||
Binder.restoreCallingIdentity(origId);
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -431,6 +431,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
|
||||
private final DisplayPolicy mDisplayPolicy;
|
||||
private final DisplayRotation mDisplayRotation;
|
||||
@Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
|
||||
DisplayFrames mDisplayFrames;
|
||||
|
||||
private final RemoteCallbackList<ISystemGestureExclusionListener>
|
||||
@@ -1158,6 +1159,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
onDisplayChanged(this);
|
||||
updateDisplayAreaOrganizers();
|
||||
|
||||
mDisplayRotationCompatPolicy =
|
||||
// Not checking DeviceConfig value here to allow enabling via DeviceConfig
|
||||
// without the need to restart the device.
|
||||
mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
|
||||
/* checkDeviceConfig */ false)
|
||||
? new DisplayRotationCompatPolicy(this) : null;
|
||||
|
||||
mInputMonitor = new InputMonitor(mWmService, this);
|
||||
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
|
||||
mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
|
||||
@@ -2704,6 +2712,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
}
|
||||
}
|
||||
|
||||
if (mDisplayRotationCompatPolicy != null) {
|
||||
int compatOrientation = mDisplayRotationCompatPolicy.getOrientation();
|
||||
if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
|
||||
mLastOrientationSource = null;
|
||||
return compatOrientation;
|
||||
}
|
||||
}
|
||||
|
||||
final int orientation = super.getOrientation();
|
||||
|
||||
if (!handlesOrientationChangeFromDescendant(orientation)) {
|
||||
@@ -3260,6 +3276,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
// on the next traversal if it's removed from RootWindowContainer child list.
|
||||
getPendingTransaction().apply();
|
||||
mWmService.mWindowPlacerLocked.requestTraversal();
|
||||
|
||||
if (mDisplayRotationCompatPolicy != null) {
|
||||
mDisplayRotationCompatPolicy.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if a removal action is still being deferred. */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.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;
|
||||
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;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Controls camera compatibility treatment that handles orientation mismatch between camera
|
||||
* buffers and an app window for a particular display that can lead to camera issues like sideways
|
||||
* or stretched viewfinder.
|
||||
*
|
||||
* <p>This includes force rotation of fixed orientation activities connected to the camera.
|
||||
*
|
||||
* <p>The treatment is enabled for internal displays that have {@code ignoreOrientationRequest}
|
||||
* display setting enabled and when {@code
|
||||
* R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
|
||||
*/
|
||||
// TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
|
||||
final class DisplayRotationCompatPolicy {
|
||||
|
||||
// Delay for updating display rotation after Camera connection is closed. Needed to avoid
|
||||
// rotation flickering when an app is flipping between front and rear cameras or when size
|
||||
// compat mode is restarted.
|
||||
// TODO(b/263114289): Consider associating this delay with a specific activity so that if
|
||||
// the new non-camera activity started on top of the camer one we can rotate faster.
|
||||
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 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;
|
||||
private final CameraManager mCameraManager;
|
||||
private final Handler mHandler;
|
||||
|
||||
// Bi-directional map between package names and active camera IDs since we need to 1) get a
|
||||
// camera id by a package name when determining rotation; 2) get a package name by a camera id
|
||||
// when camera connection is closed and we need to clean up our records.
|
||||
@GuardedBy("this")
|
||||
private final CameraIdPackageNameBiMap mCameraIdPackageBiMap = new CameraIdPackageNameBiMap();
|
||||
@GuardedBy("this")
|
||||
private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();
|
||||
@GuardedBy("this")
|
||||
private final Set<String> mScheduledOrientationUpdateCameraIdSet = new ArraySet<>();
|
||||
|
||||
private final CameraManager.AvailabilityCallback mAvailabilityCallback =
|
||||
new CameraManager.AvailabilityCallback() {
|
||||
@Override
|
||||
public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
|
||||
notifyCameraOpened(cameraId, packageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraClosed(@NonNull String cameraId) {
|
||||
notifyCameraClosed(cameraId);
|
||||
}
|
||||
};
|
||||
|
||||
@ScreenOrientation
|
||||
private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
|
||||
|
||||
DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent) {
|
||||
this(displayContent, displayContent.mWmService.mH);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler) {
|
||||
// This constructor is called from DisplayContent constructor. Don't use any fields in
|
||||
// DisplayContent here since they aren't guaranteed to be set.
|
||||
mHandler = handler;
|
||||
mDisplayContent = displayContent;
|
||||
mWmService = displayContent.mWmService;
|
||||
mCameraManager = mWmService.mContext.getSystemService(CameraManager.class);
|
||||
mCameraManager.registerAvailabilityCallback(
|
||||
mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines orientation for Camera compatibility.
|
||||
*
|
||||
* <p>The goal of this function is to compute a orientation which would align orientations of
|
||||
* portrait app window and natural orientation of the device and set opposite to natural
|
||||
* orientation for a landscape app window. This is one of the strongest assumptions that apps
|
||||
* make when they implement camera previews. Since app and natural display orientations aren't
|
||||
* guaranteed to match, the rotation can cause letterboxing.
|
||||
*
|
||||
* <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link
|
||||
* #shouldComputeCameraCompatOrientation} for conditions enabling the treatment.
|
||||
*/
|
||||
@ScreenOrientation
|
||||
int getOrientation() {
|
||||
mLastReportedOrientation = getOrientationInternal();
|
||||
return mLastReportedOrientation;
|
||||
}
|
||||
|
||||
@ScreenOrientation
|
||||
private synchronized int getOrientationInternal() {
|
||||
if (!isTreatmentEnabledForDisplay()) {
|
||||
return SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
}
|
||||
ActivityRecord topActivity = mDisplayContent.topRunningActivity(
|
||||
/* considerKeyguardState= */ true);
|
||||
if (!isTreatmentEnabledForActivity(topActivity)) {
|
||||
return SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
}
|
||||
boolean isPortraitActivity =
|
||||
topActivity.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
|
||||
boolean isNaturalDisplayOrientationPortrait =
|
||||
mDisplayContent.getNaturalOrientation() == ORIENTATION_PORTRAIT;
|
||||
// Rotate portrait-only activity in the natural orientation of the displays (and in the
|
||||
// opposite to natural orientation for landscape-only) since many apps assume that those
|
||||
// are aligned when they compute orientation of the preview.
|
||||
// This means that even for a landscape-only activity and a device with landscape natural
|
||||
// orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that
|
||||
// natural orientation = portrait window = portait camera is the main wrong assumption
|
||||
// that apps make when they implement camera previews so landscape windows need be
|
||||
// rotated in the orientation oposite to the natural one even if it's portrait.
|
||||
// TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions
|
||||
// of the portrait and landscape orientation requests.
|
||||
int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait)
|
||||
|| (!isPortraitActivity && !isNaturalDisplayOrientationPortrait)
|
||||
? SCREEN_ORIENTATION_PORTRAIT
|
||||
: SCREEN_ORIENTATION_LANDSCAPE;
|
||||
ProtoLog.v(WM_DEBUG_ORIENTATION,
|
||||
"Display id=%d is ignoring all orientation requests, camera is active "
|
||||
+ "and the top activity is eligible for force rotation, return %s,"
|
||||
+ "portrait activity: %b, is natural orientation portrait: %b.",
|
||||
mDisplayContent.mDisplayId, screenOrientationToString(orientation),
|
||||
isPortraitActivity, isNaturalDisplayOrientationPortrait);
|
||||
return orientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* "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:
|
||||
* <ul>
|
||||
* <li>{@code R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
|
||||
* <li>Setting {@code ignoreOrientationRequest} is enabled for the display.
|
||||
* <li>Associated {@link DisplayContent} is for internal display. See b/225928882
|
||||
* that tracks supporting external displays in the future.
|
||||
* </ul>
|
||||
*/
|
||||
private boolean isTreatmentEnabledForDisplay() {
|
||||
return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
|
||||
/* checkDeviceConfig */ true)
|
||||
&& mDisplayContent.getIgnoreOrientationRequest()
|
||||
// TODO(b/225928882): Support camera compat rotation for external displays
|
||||
&& mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether camera compat treatment is applicable for the given activity.
|
||||
*
|
||||
* <p>Conditions that need to be met:
|
||||
* <ul>
|
||||
* <li>{@link #isCameraActiveForPackage} is {@code true} for the activity.
|
||||
* <li>The activity is in fullscreen
|
||||
* <li>The activity has fixed orientation but not "locked" or "nosensor" one.
|
||||
* </ul>
|
||||
*/
|
||||
private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
|
||||
return activity != null && !activity.inMultiWindowMode()
|
||||
&& activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
|
||||
// "locked" and "nosensor" values are often used by camera apps that can't
|
||||
// handle dynamic changes so we shouldn't force rotate them.
|
||||
&& activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
|
||||
&& activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
|
||||
&& mCameraIdPackageBiMap.containsPackageName(activity.packageName);
|
||||
}
|
||||
|
||||
private synchronized void notifyCameraOpened(
|
||||
@NonNull String cameraId, @NonNull String packageName) {
|
||||
// If an activity is restarting or camera is flipping, the camera connection can be
|
||||
// quickly closed and reopened.
|
||||
mScheduledToBeRemovedCameraIdSet.remove(cameraId);
|
||||
ProtoLog.v(WM_DEBUG_ORIENTATION,
|
||||
"Display id=%d is notified that Camera %s is open for package %s",
|
||||
mDisplayContent.mDisplayId, cameraId, packageName);
|
||||
// Some apps can’t handle configuration changes coming at the same time with Camera setup
|
||||
// so delaying orientation update to accomadate for that.
|
||||
mScheduledOrientationUpdateCameraIdSet.add(cameraId);
|
||||
mHandler.postDelayed(
|
||||
() -> delayedUpdateOrientationWithWmLock(cameraId, packageName),
|
||||
CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS);
|
||||
}
|
||||
|
||||
private void updateOrientationWithWmLock() {
|
||||
synchronized (mWmService.mGlobalLock) {
|
||||
mDisplayContent.updateOrientation();
|
||||
}
|
||||
}
|
||||
|
||||
private void delayedUpdateOrientationWithWmLock(
|
||||
@NonNull String cameraId, @NonNull String packageName) {
|
||||
synchronized (this) {
|
||||
if (!mScheduledOrientationUpdateCameraIdSet.remove(cameraId)) {
|
||||
// Orientation update has happened already or was cancelled because
|
||||
// camera was closed.
|
||||
return;
|
||||
}
|
||||
mCameraIdPackageBiMap.put(packageName, cameraId);
|
||||
}
|
||||
updateOrientationWithWmLock();
|
||||
}
|
||||
|
||||
private synchronized void notifyCameraClosed(@NonNull String cameraId) {
|
||||
ProtoLog.v(WM_DEBUG_ORIENTATION,
|
||||
"Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
|
||||
mDisplayContent.mDisplayId, cameraId);
|
||||
mScheduledToBeRemovedCameraIdSet.add(cameraId);
|
||||
// No need to update orientation for this camera if it's already closed.
|
||||
mScheduledOrientationUpdateCameraIdSet.remove(cameraId);
|
||||
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);
|
||||
}
|
||||
|
||||
private void removeCameraId(String cameraId) {
|
||||
synchronized (this) {
|
||||
if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) {
|
||||
// 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,
|
||||
"Display id=%d is notified that Camera %s is closed, updating rotation.",
|
||||
mDisplayContent.mDisplayId, cameraId);
|
||||
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<>();
|
||||
private final Map<String, String> mCameraIdToPackageMap = new ArrayMap<>();
|
||||
|
||||
void put(String packageName, String cameraId) {
|
||||
// Always using the last connected camera ID for the package even for the concurrent
|
||||
// camera use case since we can't guess which camera is more important anyway.
|
||||
removePackageName(packageName);
|
||||
removeCameraId(cameraId);
|
||||
mPackageToCameraIdMap.put(packageName, cameraId);
|
||||
mCameraIdToPackageMap.put(cameraId, packageName);
|
||||
}
|
||||
|
||||
boolean containsPackageName(String packageName) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
mPackageToCameraIdMap.remove(packageName, cameraId);
|
||||
mCameraIdToPackageMap.remove(cameraId, packageName);
|
||||
}
|
||||
|
||||
String getSummaryForDisplayRotationHistoryRecord() {
|
||||
return "{ mPackageToCameraIdMap=" + mPackageToCameraIdMap + " }";
|
||||
}
|
||||
|
||||
private void removePackageName(String packageName) {
|
||||
String cameraId = mPackageToCameraIdMap.get(packageName);
|
||||
if (cameraId == null) {
|
||||
return;
|
||||
}
|
||||
mPackageToCameraIdMap.remove(packageName, cameraId);
|
||||
mCameraIdToPackageMap.remove(cameraId, packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,6 +191,20 @@ final class LetterboxConfiguration {
|
||||
// Allows to enable letterboxing strategy for translucent activities ignoring flags.
|
||||
private boolean mTranslucentLetterboxingOverrideEnabled;
|
||||
|
||||
// Whether camera compatibility treatment is enabled.
|
||||
// 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,
|
||||
@@ -241,6 +255,8 @@ final class LetterboxConfiguration {
|
||||
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
|
||||
mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
|
||||
R.bool.config_letterboxIsEnabledForTranslucentActivities);
|
||||
mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
|
||||
R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
|
||||
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
|
||||
mLetterboxConfigurationPersister.start();
|
||||
}
|
||||
@@ -947,9 +963,65 @@ final class LetterboxConfiguration {
|
||||
isDeviceInTabletopMode, nextVerticalPosition);
|
||||
}
|
||||
|
||||
// TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener
|
||||
// TODO(b/262378106): Cache a runtime flag and implement
|
||||
// DeviceConfig.OnPropertiesChangedListener
|
||||
static boolean isTranslucentLetterboxingAllowed() {
|
||||
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
|
||||
"enable_translucent_activity_letterbox", false);
|
||||
}
|
||||
|
||||
/** Whether camera compatibility treatment is enabled. */
|
||||
boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
|
||||
return mIsCameraCompatTreatmentEnabled
|
||||
&& (!checkDeviceConfig || isCameraCompatTreatmentAllowed());
|
||||
}
|
||||
|
||||
// TODO(b/262977416): Cache a runtime flag and implement
|
||||
// DeviceConfig.OnPropertiesChangedListener
|
||||
private static boolean isCameraCompatTreatmentAllowed() {
|
||||
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
|
||||
"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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilit
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
@@ -806,54 +807,6 @@ public class WindowManagerShellCommand extends ShellCommand {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int runSetLetterboxIsHorizontalReachabilityEnabled(PrintWriter pw)
|
||||
throws RemoteException {
|
||||
String arg = getNextArg();
|
||||
final boolean enabled;
|
||||
switch (arg) {
|
||||
case "true":
|
||||
case "1":
|
||||
enabled = true;
|
||||
break;
|
||||
case "false":
|
||||
case "0":
|
||||
enabled = false;
|
||||
break;
|
||||
default:
|
||||
getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
synchronized (mInternal.mGlobalLock) {
|
||||
mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(enabled);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int runSetLetterboxIsVerticalReachabilityEnabled(PrintWriter pw)
|
||||
throws RemoteException {
|
||||
String arg = getNextArg();
|
||||
final boolean enabled;
|
||||
switch (arg) {
|
||||
case "true":
|
||||
case "1":
|
||||
enabled = true;
|
||||
break;
|
||||
case "false":
|
||||
case "0":
|
||||
enabled = false;
|
||||
break;
|
||||
default:
|
||||
getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
synchronized (mInternal.mGlobalLock) {
|
||||
mLetterboxConfiguration.setIsVerticalReachabilityEnabled(enabled);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int runSetLetterboxDefaultPositionForHorizontalReachability(PrintWriter pw)
|
||||
throws RemoteException {
|
||||
@LetterboxHorizontalReachabilityPosition final int position;
|
||||
@@ -916,32 +869,13 @@ public class WindowManagerShellCommand extends ShellCommand {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException {
|
||||
String arg = getNextArg();
|
||||
final boolean enabled;
|
||||
switch (arg) {
|
||||
case "true":
|
||||
case "1":
|
||||
enabled = true;
|
||||
break;
|
||||
case "false":
|
||||
case "0":
|
||||
enabled = false;
|
||||
break;
|
||||
default:
|
||||
getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
synchronized (mInternal.mGlobalLock) {
|
||||
mLetterboxConfiguration.setIsEducationEnabled(enabled);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(PrintWriter pw)
|
||||
private int runSetBooleanFlag(PrintWriter pw, Consumer<Boolean> setter)
|
||||
throws RemoteException {
|
||||
String arg = getNextArg();
|
||||
if (arg == null) {
|
||||
getErrPrintWriter().println("Error: expected true, 1, false, 0, but got empty input.");
|
||||
return -1;
|
||||
}
|
||||
final boolean enabled;
|
||||
switch (arg) {
|
||||
case "true":
|
||||
@@ -958,30 +892,7 @@ public class WindowManagerShellCommand extends ShellCommand {
|
||||
}
|
||||
|
||||
synchronized (mInternal.mGlobalLock) {
|
||||
mLetterboxConfiguration.setIsSplitScreenAspectRatioForUnresizableAppsEnabled(enabled);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) {
|
||||
String arg = getNextArg();
|
||||
final boolean enabled;
|
||||
switch (arg) {
|
||||
case "true":
|
||||
case "1":
|
||||
enabled = true;
|
||||
break;
|
||||
case "false":
|
||||
case "0":
|
||||
enabled = false;
|
||||
break;
|
||||
default:
|
||||
getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
synchronized (mInternal.mGlobalLock) {
|
||||
mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled);
|
||||
setter.accept(enabled);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1024,10 +935,12 @@ public class WindowManagerShellCommand extends ShellCommand {
|
||||
runSetLetterboxVerticalPositionMultiplier(pw);
|
||||
break;
|
||||
case "--isHorizontalReachabilityEnabled":
|
||||
runSetLetterboxIsHorizontalReachabilityEnabled(pw);
|
||||
runSetBooleanFlag(pw, mLetterboxConfiguration
|
||||
::setIsHorizontalReachabilityEnabled);
|
||||
break;
|
||||
case "--isVerticalReachabilityEnabled":
|
||||
runSetLetterboxIsVerticalReachabilityEnabled(pw);
|
||||
runSetBooleanFlag(pw, mLetterboxConfiguration
|
||||
::setIsVerticalReachabilityEnabled);
|
||||
break;
|
||||
case "--defaultPositionForHorizontalReachability":
|
||||
runSetLetterboxDefaultPositionForHorizontalReachability(pw);
|
||||
@@ -1036,13 +949,23 @@ public class WindowManagerShellCommand extends ShellCommand {
|
||||
runSetLetterboxDefaultPositionForVerticalReachability(pw);
|
||||
break;
|
||||
case "--isEducationEnabled":
|
||||
runSetLetterboxIsEducationEnabled(pw);
|
||||
runSetBooleanFlag(pw, mLetterboxConfiguration::setIsEducationEnabled);
|
||||
break;
|
||||
case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":
|
||||
runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);
|
||||
runSetBooleanFlag(pw, mLetterboxConfiguration
|
||||
::setIsSplitScreenAspectRatioForUnresizableAppsEnabled);
|
||||
break;
|
||||
case "--isTranslucentLetterboxingEnabled":
|
||||
runSetTranslucentLetterboxingEnabled(pw);
|
||||
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(
|
||||
@@ -1089,27 +1012,34 @@ public class WindowManagerShellCommand extends ShellCommand {
|
||||
mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
|
||||
break;
|
||||
case "isHorizontalReachabilityEnabled":
|
||||
mLetterboxConfiguration.getIsHorizontalReachabilityEnabled();
|
||||
mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
|
||||
break;
|
||||
case "isVerticalReachabilityEnabled":
|
||||
mLetterboxConfiguration.getIsVerticalReachabilityEnabled();
|
||||
mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
|
||||
break;
|
||||
case "defaultPositionForHorizontalReachability":
|
||||
mLetterboxConfiguration.getDefaultPositionForHorizontalReachability();
|
||||
mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
|
||||
break;
|
||||
case "defaultPositionForVerticalReachability":
|
||||
mLetterboxConfiguration.getDefaultPositionForVerticalReachability();
|
||||
mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
|
||||
break;
|
||||
case "isEducationEnabled":
|
||||
mLetterboxConfiguration.getIsEducationEnabled();
|
||||
mLetterboxConfiguration.resetIsEducationEnabled();
|
||||
break;
|
||||
case "isSplitScreenAspectRatioForUnresizableAppsEnabled":
|
||||
mLetterboxConfiguration
|
||||
.getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
|
||||
.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
|
||||
break;
|
||||
case "isTranslucentLetterboxingEnabled":
|
||||
mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
|
||||
break;
|
||||
case "isCameraCompatRefreshEnabled":
|
||||
mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
|
||||
break;
|
||||
case "isCameraCompatRefreshCycleThroughStopEnabled":
|
||||
mLetterboxConfiguration
|
||||
.resetCameraCompatRefreshCycleThroughStopEnabled();
|
||||
break;
|
||||
default:
|
||||
getErrPrintWriter().println(
|
||||
"Error: Unrecognized letterbox style option: " + arg);
|
||||
@@ -1211,6 +1141,8 @@ public class WindowManagerShellCommand extends ShellCommand {
|
||||
mLetterboxConfiguration.resetIsEducationEnabled();
|
||||
mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
|
||||
mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
|
||||
mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
|
||||
mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1255,6 +1187,12 @@ public class WindowManagerShellCommand extends ShellCommand {
|
||||
pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
|
||||
+ 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()));
|
||||
@@ -1464,7 +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");
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.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_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;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
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;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Tests for {@link DisplayRotationCompatPolicy}.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest WmTests:DisplayRotationCompatPolicyTests
|
||||
*/
|
||||
@SmallTest
|
||||
@Presubmit
|
||||
@RunWith(WindowTestRunner.class)
|
||||
public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
|
||||
|
||||
private static final String TEST_PACKAGE_1 = "com.test.package.one";
|
||||
private static final String TEST_PACKAGE_2 = "com.test.package.two";
|
||||
private static final String CAMERA_ID_1 = "camera-1";
|
||||
private static final String CAMERA_ID_2 = "camera-2";
|
||||
|
||||
private CameraManager mMockCameraManager;
|
||||
private Handler mMockHandler;
|
||||
private LetterboxConfiguration mLetterboxConfiguration;
|
||||
|
||||
private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
|
||||
private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
|
||||
|
||||
private ActivityRecord mActivity;
|
||||
private Task mTask;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
|
||||
spyOn(mLetterboxConfiguration);
|
||||
when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
|
||||
/* checkDeviceConfig */ anyBoolean()))
|
||||
.thenReturn(true);
|
||||
when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
|
||||
.thenReturn(true);
|
||||
when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
|
||||
.thenReturn(true);
|
||||
|
||||
mMockCameraManager = mock(CameraManager.class);
|
||||
doAnswer(invocation -> {
|
||||
mCameraAvailabilityCallback = invocation.getArgument(1);
|
||||
return null;
|
||||
}).when(mMockCameraManager).registerAvailabilityCallback(
|
||||
any(Executor.class), any(CameraManager.AvailabilityCallback.class));
|
||||
|
||||
spyOn(mContext);
|
||||
when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager);
|
||||
|
||||
spyOn(mDisplayContent);
|
||||
|
||||
mDisplayContent.setIgnoreOrientationRequest(true);
|
||||
|
||||
mMockHandler = mock(Handler.class);
|
||||
|
||||
when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
|
||||
invocation -> {
|
||||
((Runnable) invocation.getArgument(0)).run();
|
||||
return null;
|
||||
});
|
||||
mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
|
||||
mDisplayContent, mMockHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
|
||||
when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
|
||||
/* checkDeviceConfig */ anyBoolean()))
|
||||
.thenReturn(false);
|
||||
|
||||
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
|
||||
|
||||
assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
|
||||
SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatmentDisabledViaDeviceConfig_noForceRotationOrRefresh() throws Exception {
|
||||
when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
|
||||
/* checkDeviceConfig */ true))
|
||||
.thenReturn(false);
|
||||
|
||||
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
|
||||
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
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,
|
||||
false /* moveParents */, "test" /* reason */);
|
||||
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
|
||||
|
||||
assertTrue(mActivity.inMultiWindowMode());
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrientationUnspecified_noForceRotationOrRefresh() throws Exception {
|
||||
configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrientationLocked_noForceRotationOrRefresh() throws Exception {
|
||||
configureActivity(SCREEN_ORIENTATION_LOCKED);
|
||||
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
|
||||
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrientationNoSensor_noForceRotationOrRefresh() throws Exception {
|
||||
configureActivity(SCREEN_ORIENTATION_NOSENSOR);
|
||||
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
|
||||
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreOrientationRequestIsFalse_noForceRotationOrRefresh() throws Exception {
|
||||
mDisplayContent.setIgnoreOrientationRequest(false);
|
||||
|
||||
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
|
||||
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayNotInternal_noForceRotationOrRefresh() throws Exception {
|
||||
Display display = mDisplayContent.getDisplay();
|
||||
spyOn(display);
|
||||
|
||||
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
|
||||
|
||||
when(display.getType()).thenReturn(Display.TYPE_EXTERNAL);
|
||||
assertNoForceRotationOrRefresh();
|
||||
|
||||
when(display.getType()).thenReturn(Display.TYPE_WIFI);
|
||||
assertNoForceRotationOrRefresh();
|
||||
|
||||
when(display.getType()).thenReturn(Display.TYPE_OVERLAY);
|
||||
assertNoForceRotationOrRefresh();
|
||||
|
||||
when(display.getType()).thenReturn(Display.TYPE_VIRTUAL);
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCameraConnection_noForceRotationOrRefresh() throws Exception {
|
||||
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
|
||||
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
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 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
|
||||
public void testGetOrientation_cameraConnectionClosed_returnUnspecified() {
|
||||
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
|
||||
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
|
||||
|
||||
assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
|
||||
SCREEN_ORIENTATION_PORTRAIT);
|
||||
|
||||
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
|
||||
|
||||
assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
|
||||
SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCameraOpenedForDifferentPackage_noForceRotationOrRefresh() throws Exception {
|
||||
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
|
||||
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
|
||||
|
||||
assertNoForceRotationOrRefresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOrientation_portraitActivity_portraitNaturalOrientation_returnPortrait() {
|
||||
testGetOrientationForActivityAndNaturalOrientations(
|
||||
/* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
|
||||
/* naturalOrientation */ ORIENTATION_PORTRAIT,
|
||||
/* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOrientation_portraitActivity_landscapeNaturalOrientation_returnLandscape() {
|
||||
testGetOrientationForActivityAndNaturalOrientations(
|
||||
/* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
|
||||
/* naturalOrientation */ ORIENTATION_LANDSCAPE,
|
||||
/* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOrientation_landscapeActivity_portraitNaturalOrientation_returnLandscape() {
|
||||
testGetOrientationForActivityAndNaturalOrientations(
|
||||
/* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
|
||||
/* naturalOrientation */ ORIENTATION_PORTRAIT,
|
||||
/* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOrientation_landscapeActivity_landscapeNaturalOrientation_returnPortrait() {
|
||||
testGetOrientationForActivityAndNaturalOrientations(
|
||||
/* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
|
||||
/* naturalOrientation */ ORIENTATION_LANDSCAPE,
|
||||
/* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
|
||||
private void testGetOrientationForActivityAndNaturalOrientations(
|
||||
@ScreenOrientation int activityOrientation,
|
||||
@Orientation int naturalOrientation,
|
||||
@ScreenOrientation int expectedOrientation) {
|
||||
configureActivityAndDisplay(activityOrientation, naturalOrientation);
|
||||
|
||||
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
|
||||
|
||||
assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
|
||||
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);
|
||||
}
|
||||
|
||||
private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
|
||||
@Orientation int naturalOrientation) {
|
||||
|
||||
mTask = new TaskBuilder(mSupervisor)
|
||||
.setDisplay(mDisplayContent)
|
||||
.build();
|
||||
|
||||
mActivity = new ActivityBuilder(mAtm)
|
||||
.setComponent(new ComponentName(TEST_PACKAGE_1, ".TestActivity"))
|
||||
.setScreenOrientation(activityOrientation)
|
||||
.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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user