Loading services/core/java/com/android/server/wm/ActivityRecord.java +6 −1 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.CameraCompatTaskInfo.cameraCompatControlStateToString; import static android.app.WaitResult.INVALID_DELAY; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; Loading @@ -46,6 +47,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; Loading Loading @@ -855,7 +857,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @CameraCompatControlState private int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN; // The callback that allows to ask the calling View to apply the treatment for stretched // issues affecting camera viewfinders when the user clicks on the camera compat control. @Nullable Loading Loading @@ -8556,11 +8557,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // and back which can cause visible issues (see b/184078928). final int parentWindowingMode = newParentConfiguration.windowConfiguration.getWindowingMode(); final boolean isInCameraCompatFreeform = parentWindowingMode == WINDOWING_MODE_FREEFORM && mLetterboxUiController.getFreeformCameraCompatMode() != CAMERA_COMPAT_FREEFORM_NONE; // Bubble activities should always fill their parent and should not be letterboxed. final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble() && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW || parentWindowingMode == WINDOWING_MODE_FULLSCREEN || isInCameraCompatFreeform // When starting to switch between PiP and fullscreen, the task is pinned // and the activity is fullscreen. But only allow to apply letterbox if the // activity is exiting PiP because an entered PiP should fill the task. Loading services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java 0 → 100644 +196 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.app.CameraCompatTaskInfo; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.window.flags.Flags; /** * Policy for camera compatibility freeform treatment. * * <p>The treatment is applied to a fixed-orientation camera activity in freeform windowing mode. * The treatment letterboxes or pillarboxes the activity to the expected orientation and provides * changes to the camera and display orientation signals to match those expected on a portrait * device in that orientation (for example, on a standard phone). */ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompatStateListener, ActivityRefresher.Evaluator { private static final String TAG = TAG_WITH_CLASS_NAME ? "CameraCompatFreeformPolicy" : TAG_WM; @NonNull private final DisplayContent mDisplayContent; @NonNull private final ActivityRefresher mActivityRefresher; @NonNull private final CameraStateMonitor mCameraStateMonitor; private boolean mIsCameraCompatTreatmentPending = false; CameraCompatFreeformPolicy(@NonNull DisplayContent displayContent, @NonNull CameraStateMonitor cameraStateMonitor, @NonNull ActivityRefresher activityRefresher) { mDisplayContent = displayContent; mCameraStateMonitor = cameraStateMonitor; mActivityRefresher = activityRefresher; } void start() { mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher.addEvaluator(this); } /** Releases camera callback listener. */ void dispose() { mCameraStateMonitor.removeCameraStateListener(this); mActivityRefresher.removeEvaluator(this); } // Refreshing only when configuration changes after rotation or camera split screen aspect ratio // treatment is enabled. @Override public boolean shouldRefreshActivity(@NonNull ActivityRecord activity, @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { return isTreatmentEnabledForActivity(activity) && mIsCameraCompatTreatmentPending; } /** * Whether activity is eligible for camera compatibility free-form treatment. * * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and * provides changes to the camera and display orientation signals to match those expected on a * portrait device in that orientation (for example, on a standard phone). * * <p>The treatment is enabled when the following conditions are met: * <ul> * <li>Property gating the camera compatibility free-form treatment is enabled. * <li>Activity isn't opted out by the device manufacturer with override. * </ul> */ @VisibleForTesting boolean shouldApplyFreeformTreatmentForCameraCompat(@NonNull ActivityRecord activity) { return Flags.cameraCompatForFreeform() && !activity.info.isChangeEnabled( ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT); } @Override public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { if (!isTreatmentEnabledForActivity(cameraActivity)) { return false; } final int existingCameraCompatMode = cameraActivity.mLetterboxUiController.getFreeformCameraCompatMode(); final int newCameraCompatMode = getCameraCompatMode(cameraActivity); if (newCameraCompatMode != existingCameraCompatMode) { mIsCameraCompatTreatmentPending = true; cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode(newCameraCompatMode); forceUpdateActivityAndTask(cameraActivity); return true; } else { mIsCameraCompatTreatmentPending = false; } return false; } @Override public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { if (isActivityForCameraIdRefreshing(cameraId)) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES, "Display id=%d is notified that Camera %s is closed but activity is" + " still refreshing. Rescheduling an update.", mDisplayContent.mDisplayId, cameraId); return false; } cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode( CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE); forceUpdateActivityAndTask(cameraActivity); mIsCameraCompatTreatmentPending = false; return true; } private void forceUpdateActivityAndTask(ActivityRecord cameraActivity) { cameraActivity.recomputeConfiguration(); cameraActivity.updateReportedConfigurationAndSend(); Task cameraTask = cameraActivity.getTask(); if (cameraTask != null) { cameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true); } } private static int getCameraCompatMode(@NonNull ActivityRecord topActivity) { return switch (topActivity.getRequestedConfigurationOrientation()) { case ORIENTATION_PORTRAIT -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT; case ORIENTATION_LANDSCAPE -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE; default -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; }; } /** * Whether camera compat treatment is applicable for the given activity, ignoring its windowing * mode. * * <p>Conditions that need to be met: * <ul> * <li>Treatment is enabled. * <li>Camera is active for the package. * <li>The app has a fixed orientation. * <li>The app is in freeform windowing mode. * </ul> */ private boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) { int orientation = activity.getRequestedConfigurationOrientation(); return shouldApplyFreeformTreatmentForCameraCompat(activity) && mCameraStateMonitor.isCameraRunningForActivity(activity) && orientation != ORIENTATION_UNDEFINED && activity.inFreeformWindowingMode() // "locked" and "nosensor" values are often used by camera apps that can't // handle dynamic changes so we shouldn't force-letterbox them. && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED // TODO(b/332665280): investigate whether we can support activity embedding. && !activity.isEmbedded(); } private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) { final ActivityRecord topActivity = mDisplayContent.topRunningActivity( /* considerKeyguardState= */ true); if (topActivity == null || !isTreatmentEnabledForActivity(topActivity) || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) { return false; } return topActivity.mLetterboxUiController.isRefreshRequested(); } } services/core/java/com/android/server/wm/DisplayContent.java +27 −5 Original line number Diff line number Diff line Loading @@ -263,6 +263,7 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.RegionUtils; import com.android.server.wm.utils.RotationCache; import com.android.server.wm.utils.WmDisplayCutout; import com.android.window.flags.Flags; import java.io.PrintWriter; import java.lang.annotation.Retention; Loading Loading @@ -477,6 +478,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; @Nullable final CameraCompatFreeformPolicy mCameraCompatFreeformPolicy; @Nullable final CameraStateMonitor mCameraStateMonitor; @Nullable final ActivityRefresher mActivityRefresher; Loading Loading @@ -683,7 +686,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private InputTarget mLastImeInputTarget; /** * Tracks the windowToken of the input method input target and the corresponding * {@link WindowContainerListener} for monitoring changes (e.g. the requested visibility Loading Loading @@ -1233,11 +1235,26 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // without the need to restart the device. final boolean shouldCreateDisplayRotationCompatPolicy = mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime(); if (shouldCreateDisplayRotationCompatPolicy) { final boolean shouldCreateCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform() && DesktopModeLaunchParamsModifier.canEnterDesktopMode(mWmService.mContext); if (shouldCreateDisplayRotationCompatPolicy || shouldCreateCameraCompatFreeformPolicy) { mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH); mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH); mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy( this, mCameraStateMonitor, mActivityRefresher); if (shouldCreateDisplayRotationCompatPolicy) { mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(this, mCameraStateMonitor, mActivityRefresher); mDisplayRotationCompatPolicy.start(); } else { mDisplayRotationCompatPolicy = null; } if (shouldCreateCameraCompatFreeformPolicy) { mCameraCompatFreeformPolicy = new CameraCompatFreeformPolicy(this, mCameraStateMonitor, mActivityRefresher); mCameraCompatFreeformPolicy.start(); } else { mCameraCompatFreeformPolicy = null; } mCameraStateMonitor.startListeningToCameraState(); } else { Loading @@ -1245,9 +1262,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mCameraStateMonitor = null; mActivityRefresher = null; mDisplayRotationCompatPolicy = null; mCameraCompatFreeformPolicy = null; } mRotationReversionController = new DisplayRotationReversionController(this); mInputMonitor = new InputMonitor(mWmService, this); Loading Loading @@ -3350,6 +3367,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (mDisplayRotationCompatPolicy != null) { mDisplayRotationCompatPolicy.dispose(); } if (mCameraCompatFreeformPolicy != null) { mCameraCompatFreeformPolicy.dispose(); } if (mCameraStateMonitor != null) { mCameraStateMonitor.dispose(); } Loading services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +5 −2 Original line number Diff line number Diff line Loading @@ -80,8 +80,11 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp mDisplayContent = displayContent; mWmService = displayContent.mWmService; mCameraStateMonitor = cameraStateMonitor; mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher = activityRefresher; } void start() { mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher.addEvaluator(this); } Loading Loading @@ -365,7 +368,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp } // TODO(b/336474959): Do we need cameraId here? private boolean isActivityForCameraIdRefreshing(String cameraId) { private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) { final ActivityRecord topActivity = mDisplayContent.topRunningActivity( /* considerKeyguardState= */ true); if (!isTreatmentEnabledForActivity(topActivity) Loading services/core/java/com/android/server/wm/LetterboxUiController.java +14 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.wm; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; Loading Loading @@ -103,6 +104,7 @@ import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTy import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.pm.PackageManager; import android.content.res.Configuration; Loading Loading @@ -231,6 +233,9 @@ final class LetterboxUiController { private boolean mDoubleTapEvent; @FreeformCameraCompatMode private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE; LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { mLetterboxConfiguration = wmService.mLetterboxConfiguration; // Given activityRecord may not be fully constructed since LetterboxUiController Loading Loading @@ -711,6 +716,15 @@ final class LetterboxUiController { .isTreatmentEnabledForActivity(mActivityRecord); } @FreeformCameraCompatMode int getFreeformCameraCompatMode() { return mFreeformCameraCompatMode; } void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) { mFreeformCameraCompatMode = freeformCameraCompatMode; } private boolean isCompatChangeEnabled(long overrideChangeId) { return mActivityRecord.info.isChangeEnabled(overrideChangeId); } Loading Loading
services/core/java/com/android/server/wm/ActivityRecord.java +6 −1 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.CameraCompatTaskInfo.cameraCompatControlStateToString; import static android.app.WaitResult.INVALID_DELAY; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; Loading @@ -46,6 +47,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; Loading Loading @@ -855,7 +857,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @CameraCompatControlState private int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN; // The callback that allows to ask the calling View to apply the treatment for stretched // issues affecting camera viewfinders when the user clicks on the camera compat control. @Nullable Loading Loading @@ -8556,11 +8557,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // and back which can cause visible issues (see b/184078928). final int parentWindowingMode = newParentConfiguration.windowConfiguration.getWindowingMode(); final boolean isInCameraCompatFreeform = parentWindowingMode == WINDOWING_MODE_FREEFORM && mLetterboxUiController.getFreeformCameraCompatMode() != CAMERA_COMPAT_FREEFORM_NONE; // Bubble activities should always fill their parent and should not be letterboxed. final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble() && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW || parentWindowingMode == WINDOWING_MODE_FULLSCREEN || isInCameraCompatFreeform // When starting to switch between PiP and fullscreen, the task is pinned // and the activity is fullscreen. But only allow to apply letterbox if the // activity is exiting PiP because an entered PiP should fill the task. Loading
services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java 0 → 100644 +196 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.app.CameraCompatTaskInfo; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.window.flags.Flags; /** * Policy for camera compatibility freeform treatment. * * <p>The treatment is applied to a fixed-orientation camera activity in freeform windowing mode. * The treatment letterboxes or pillarboxes the activity to the expected orientation and provides * changes to the camera and display orientation signals to match those expected on a portrait * device in that orientation (for example, on a standard phone). */ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompatStateListener, ActivityRefresher.Evaluator { private static final String TAG = TAG_WITH_CLASS_NAME ? "CameraCompatFreeformPolicy" : TAG_WM; @NonNull private final DisplayContent mDisplayContent; @NonNull private final ActivityRefresher mActivityRefresher; @NonNull private final CameraStateMonitor mCameraStateMonitor; private boolean mIsCameraCompatTreatmentPending = false; CameraCompatFreeformPolicy(@NonNull DisplayContent displayContent, @NonNull CameraStateMonitor cameraStateMonitor, @NonNull ActivityRefresher activityRefresher) { mDisplayContent = displayContent; mCameraStateMonitor = cameraStateMonitor; mActivityRefresher = activityRefresher; } void start() { mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher.addEvaluator(this); } /** Releases camera callback listener. */ void dispose() { mCameraStateMonitor.removeCameraStateListener(this); mActivityRefresher.removeEvaluator(this); } // Refreshing only when configuration changes after rotation or camera split screen aspect ratio // treatment is enabled. @Override public boolean shouldRefreshActivity(@NonNull ActivityRecord activity, @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { return isTreatmentEnabledForActivity(activity) && mIsCameraCompatTreatmentPending; } /** * Whether activity is eligible for camera compatibility free-form treatment. * * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and * provides changes to the camera and display orientation signals to match those expected on a * portrait device in that orientation (for example, on a standard phone). * * <p>The treatment is enabled when the following conditions are met: * <ul> * <li>Property gating the camera compatibility free-form treatment is enabled. * <li>Activity isn't opted out by the device manufacturer with override. * </ul> */ @VisibleForTesting boolean shouldApplyFreeformTreatmentForCameraCompat(@NonNull ActivityRecord activity) { return Flags.cameraCompatForFreeform() && !activity.info.isChangeEnabled( ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT); } @Override public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { if (!isTreatmentEnabledForActivity(cameraActivity)) { return false; } final int existingCameraCompatMode = cameraActivity.mLetterboxUiController.getFreeformCameraCompatMode(); final int newCameraCompatMode = getCameraCompatMode(cameraActivity); if (newCameraCompatMode != existingCameraCompatMode) { mIsCameraCompatTreatmentPending = true; cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode(newCameraCompatMode); forceUpdateActivityAndTask(cameraActivity); return true; } else { mIsCameraCompatTreatmentPending = false; } return false; } @Override public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { if (isActivityForCameraIdRefreshing(cameraId)) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES, "Display id=%d is notified that Camera %s is closed but activity is" + " still refreshing. Rescheduling an update.", mDisplayContent.mDisplayId, cameraId); return false; } cameraActivity.mLetterboxUiController.setFreeformCameraCompatMode( CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE); forceUpdateActivityAndTask(cameraActivity); mIsCameraCompatTreatmentPending = false; return true; } private void forceUpdateActivityAndTask(ActivityRecord cameraActivity) { cameraActivity.recomputeConfiguration(); cameraActivity.updateReportedConfigurationAndSend(); Task cameraTask = cameraActivity.getTask(); if (cameraTask != null) { cameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true); } } private static int getCameraCompatMode(@NonNull ActivityRecord topActivity) { return switch (topActivity.getRequestedConfigurationOrientation()) { case ORIENTATION_PORTRAIT -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT; case ORIENTATION_LANDSCAPE -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE; default -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; }; } /** * Whether camera compat treatment is applicable for the given activity, ignoring its windowing * mode. * * <p>Conditions that need to be met: * <ul> * <li>Treatment is enabled. * <li>Camera is active for the package. * <li>The app has a fixed orientation. * <li>The app is in freeform windowing mode. * </ul> */ private boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) { int orientation = activity.getRequestedConfigurationOrientation(); return shouldApplyFreeformTreatmentForCameraCompat(activity) && mCameraStateMonitor.isCameraRunningForActivity(activity) && orientation != ORIENTATION_UNDEFINED && activity.inFreeformWindowingMode() // "locked" and "nosensor" values are often used by camera apps that can't // handle dynamic changes so we shouldn't force-letterbox them. && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED // TODO(b/332665280): investigate whether we can support activity embedding. && !activity.isEmbedded(); } private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) { final ActivityRecord topActivity = mDisplayContent.topRunningActivity( /* considerKeyguardState= */ true); if (topActivity == null || !isTreatmentEnabledForActivity(topActivity) || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) { return false; } return topActivity.mLetterboxUiController.isRefreshRequested(); } }
services/core/java/com/android/server/wm/DisplayContent.java +27 −5 Original line number Diff line number Diff line Loading @@ -263,6 +263,7 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.RegionUtils; import com.android.server.wm.utils.RotationCache; import com.android.server.wm.utils.WmDisplayCutout; import com.android.window.flags.Flags; import java.io.PrintWriter; import java.lang.annotation.Retention; Loading Loading @@ -477,6 +478,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; @Nullable final CameraCompatFreeformPolicy mCameraCompatFreeformPolicy; @Nullable final CameraStateMonitor mCameraStateMonitor; @Nullable final ActivityRefresher mActivityRefresher; Loading Loading @@ -683,7 +686,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private InputTarget mLastImeInputTarget; /** * Tracks the windowToken of the input method input target and the corresponding * {@link WindowContainerListener} for monitoring changes (e.g. the requested visibility Loading Loading @@ -1233,11 +1235,26 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // without the need to restart the device. final boolean shouldCreateDisplayRotationCompatPolicy = mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime(); if (shouldCreateDisplayRotationCompatPolicy) { final boolean shouldCreateCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform() && DesktopModeLaunchParamsModifier.canEnterDesktopMode(mWmService.mContext); if (shouldCreateDisplayRotationCompatPolicy || shouldCreateCameraCompatFreeformPolicy) { mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH); mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH); mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy( this, mCameraStateMonitor, mActivityRefresher); if (shouldCreateDisplayRotationCompatPolicy) { mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(this, mCameraStateMonitor, mActivityRefresher); mDisplayRotationCompatPolicy.start(); } else { mDisplayRotationCompatPolicy = null; } if (shouldCreateCameraCompatFreeformPolicy) { mCameraCompatFreeformPolicy = new CameraCompatFreeformPolicy(this, mCameraStateMonitor, mActivityRefresher); mCameraCompatFreeformPolicy.start(); } else { mCameraCompatFreeformPolicy = null; } mCameraStateMonitor.startListeningToCameraState(); } else { Loading @@ -1245,9 +1262,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mCameraStateMonitor = null; mActivityRefresher = null; mDisplayRotationCompatPolicy = null; mCameraCompatFreeformPolicy = null; } mRotationReversionController = new DisplayRotationReversionController(this); mInputMonitor = new InputMonitor(mWmService, this); Loading Loading @@ -3350,6 +3367,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (mDisplayRotationCompatPolicy != null) { mDisplayRotationCompatPolicy.dispose(); } if (mCameraCompatFreeformPolicy != null) { mCameraCompatFreeformPolicy.dispose(); } if (mCameraStateMonitor != null) { mCameraStateMonitor.dispose(); } Loading
services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +5 −2 Original line number Diff line number Diff line Loading @@ -80,8 +80,11 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp mDisplayContent = displayContent; mWmService = displayContent.mWmService; mCameraStateMonitor = cameraStateMonitor; mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher = activityRefresher; } void start() { mCameraStateMonitor.addCameraStateListener(this); mActivityRefresher.addEvaluator(this); } Loading Loading @@ -365,7 +368,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp } // TODO(b/336474959): Do we need cameraId here? private boolean isActivityForCameraIdRefreshing(String cameraId) { private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) { final ActivityRecord topActivity = mDisplayContent.topRunningActivity( /* considerKeyguardState= */ true); if (!isTreatmentEnabledForActivity(topActivity) Loading
services/core/java/com/android/server/wm/LetterboxUiController.java +14 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.wm; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; Loading Loading @@ -103,6 +104,7 @@ import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTy import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.pm.PackageManager; import android.content.res.Configuration; Loading Loading @@ -231,6 +233,9 @@ final class LetterboxUiController { private boolean mDoubleTapEvent; @FreeformCameraCompatMode private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE; LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { mLetterboxConfiguration = wmService.mLetterboxConfiguration; // Given activityRecord may not be fully constructed since LetterboxUiController Loading Loading @@ -711,6 +716,15 @@ final class LetterboxUiController { .isTreatmentEnabledForActivity(mActivityRecord); } @FreeformCameraCompatMode int getFreeformCameraCompatMode() { return mFreeformCameraCompatMode; } void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) { mFreeformCameraCompatMode = freeformCameraCompatMode; } private boolean isCompatChangeEnabled(long overrideChangeId) { return mActivityRecord.info.isChangeEnabled(overrideChangeId); } Loading