diff options
15 files changed, 229 insertions, 166 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bdc78342d42a..49710e5575a1 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8803,6 +8803,13 @@ public final class Settings { public static final String DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS = "force_desktop_mode_on_external_displays"; + /** + * Whether to allow non-resizable apps to be freeform. + * @hide + */ + public static final String DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM = + "enable_sizecompat_freeform"; + /** * Whether user has enabled development settings. */ diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index a568c13d7dde..8d1fbe5def2e 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -281,6 +281,7 @@ message GlobalSettingsProto { optional SettingProto force_rtl = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto emulate_display_cutout = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto force_desktop_mode_on_external_displays = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto enable_sizecompat_freeform = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Development development = 39; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 6bd26bf03a55..dbb25abd537d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -548,6 +548,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, GlobalSettingsProto.Development.FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS); + dumpSetting(s, p, + Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM, + GlobalSettingsProto.Development.ENABLE_SIZECOMPAT_FREEFORM); p.end(developmentToken); final long deviceToken = p.start(GlobalSettingsProto.DEVICE); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 62827bc3a98e..7ea555de0177 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -222,6 +222,7 @@ public class SettingsBackupTest { Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, Settings.Global.DEVELOPMENT_FORCE_RTL, + Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, Settings.Global.DEVICE_DEMO_MODE, Settings.Global.DEVICE_IDLE_CONSTANTS, diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3853841b63ff..273a28303bfd 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -299,10 +299,10 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.animation.Animation; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity; import com.android.internal.content.ReferrerIntent; -import com.android.internal.R; import com.android.internal.util.ToBooleanFunction; import com.android.internal.util.XmlUtils; import com.android.server.AttributeCache; @@ -6599,14 +6599,25 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The role of CompatDisplayInsets is like the override bounds. final ActivityDisplay display = getDisplay(); - if (display != null && display.mDisplayContent != null) { - mCompatDisplayInsets = new CompatDisplayInsets(display.mDisplayContent); + if (display != null) { + if (display.inFreeformWindowingMode()) { + // in freeform, apps in compat mode still launch in windows so don't want to + // use display bounds, but rather use TaskRecord bounds. + if (getTaskRecord() != null) { + mCompatDisplayInsets = new CompatDisplayInsets(getTaskRecord()); + } + } else if (display.mDisplayContent != null) { + mCompatDisplayInsets = new CompatDisplayInsets(display.mDisplayContent); + } } } else { // We must base this on the parent configuration, because we set our override // configuration's appBounds based on the result of this method. If we used our own // configuration, it would be influenced by past invocations. - computeBounds(mTmpBounds, getParent().getWindowConfiguration().getAppBounds()); + + // inherits from parent by default + mTmpBounds.set(getRequestedOverrideBounds()); + applyAspectRatio(mTmpBounds, getParent().getWindowConfiguration().getAppBounds()); if (mTmpBounds.equals(getRequestedOverrideBounds())) { // The bounds is not changed or the activity is resizable (both the 2 bounds are @@ -6673,12 +6684,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // fixed orientation. orientation = parentOrientation; } else { - if (!resolvedBounds.isEmpty() - // The decor insets may be different according to the rotation. - && getWindowConfiguration().getRotation() == parentRotation) { - // Keep the computed resolved override configuration. - return; - } final int requestedOrientation = getRequestedConfigurationOrientation(); if (requestedOrientation != ORIENTATION_UNDEFINED) { orientation = requestedOrientation; @@ -6687,57 +6692,53 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A super.resolveOverrideConfiguration(newParentConfiguration); - boolean useParentOverrideBounds = false; - final Rect displayBounds = mTmpBounds; - final Rect containingAppBounds = new Rect(); - if (task.handlesOrientationChangeFromDescendant()) { - // Prefer to use the orientation which is determined by this activity to calculate - // bounds because the parent will follow the requested orientation. - mCompatDisplayInsets.getDisplayBoundsByOrientation(displayBounds, orientation); - } else { - // The parent hierarchy doesn't handle the orientation changes. This is usually because - // the aspect ratio of display is close to square or the display rotation is fixed. - // In this case, task will compute override bounds to fit the app with respect to the - // requested orientation. So here we perform similar calculation to have consistent - // bounds even the original parent hierarchies were changed. - final int baseOrientation = task.getParent().getConfiguration().orientation; - mCompatDisplayInsets.getDisplayBoundsByOrientation(displayBounds, baseOrientation); - task.computeFullscreenBounds(containingAppBounds, this, displayBounds, baseOrientation); - useParentOverrideBounds = !containingAppBounds.isEmpty(); - } - - // The offsets will be non-zero if the parent has override bounds. - final int containingOffsetX = containingAppBounds.left; - final int containingOffsetY = containingAppBounds.top; - if (!useParentOverrideBounds) { - containingAppBounds.set(displayBounds); - } + Rect containingAppBounds = new Rect(); + Rect parentBounds = new Rect(newParentConfiguration.windowConfiguration.getBounds()); + + // Use compat insets to lock width and height. We should not use the parent width and height + // because apps in compat mode should have a constant width and height. The compat insets + // are locked when the app is first launched and are never changed after that, so we can + // rely on them to contain the original and unchanging width and height of the app. + final Rect compatDisplayBounds = mTmpBounds; + mCompatDisplayInsets.getDisplayBoundsByOrientation(compatDisplayBounds, orientation); + containingAppBounds.set(0, 0, compatDisplayBounds.width(), compatDisplayBounds.height()); + + resolvedBounds.set(containingAppBounds); + if (parentRotation != ROTATION_UNDEFINED) { - // Ensure the container bounds won't overlap with the decors. - TaskRecord.intersectWithInsetsIfFits(containingAppBounds, displayBounds, + // Ensure the parent and container bounds won't overlap with insets. + TaskRecord.intersectWithInsetsIfFits(containingAppBounds, compatDisplayBounds, + mCompatDisplayInsets.mNonDecorInsets[parentRotation]); + TaskRecord.intersectWithInsetsIfFits(parentBounds, compatDisplayBounds, mCompatDisplayInsets.mNonDecorInsets[parentRotation]); } - computeBounds(resolvedBounds, containingAppBounds); - if (resolvedBounds.isEmpty()) { - // Use the entire available bounds because there is no restriction. - resolvedBounds.set(useParentOverrideBounds ? containingAppBounds : displayBounds); - } else { - // The offsets are included in width and height by {@link #computeBounds}, so we have to - // restore it. - resolvedBounds.left += containingOffsetX; - resolvedBounds.top += containingOffsetY; - } + applyAspectRatio(resolvedBounds, containingAppBounds); + + // Center horizontally in parent and align to top of parent - this is a UX choice + final int left = parentBounds.left + parentBounds.width() / 2 - resolvedBounds.width() / 2; + resolvedBounds.set(left, parentBounds.top, left + resolvedBounds.width(), + parentBounds.top + resolvedBounds.height()); + + // We want to get as much of the app on the screen even if insets cover it. This is because + // insets change but an app's bounds are more permanent after launch. After computing insets + // and horizontally centering resolvedBounds, the resolvedBounds may end up outside parent + // bounds. This is okay only if the resolvedBounds exceed their parent on the bottom and + // right, because that is clipped when the final bounds are computed. To reach this state, + // we first try and push the app as much inside the parent towards the top and left (the + // min). The app may then end up outside the parent by going too far left and top, so we + // push it back into the parent by taking the max with parent left and top. + Rect fullParentBounds = newParentConfiguration.windowConfiguration.getBounds(); + resolvedBounds.offsetTo(Math.max(fullParentBounds.left, + Math.min(fullParentBounds.right - resolvedBounds.width(), resolvedBounds.left)), + Math.max(fullParentBounds.top, + Math.min(fullParentBounds.bottom - resolvedBounds.height(), + resolvedBounds.top))); + + // Use resolvedBounds to compute other override configurations such as appBounds task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, mCompatDisplayInsets); - // The horizontal inset included in width is not needed if the activity cannot fill the - // parent, because the offset will be applied by {@link ActivityRecord#mSizeCompatBounds}. - final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); - final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds(); - if (resolvedBounds.width() < parentAppBounds.width()) { - resolvedBounds.right -= resolvedAppBounds.left; - } // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside // the parent bounds appropriately. if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) { @@ -6918,11 +6919,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** - * Computes the bounds to fit the Activity within the bounds of the {@link Configuration}. + * Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is + * made to outBounds. */ // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. - private void computeBounds(Rect outBounds, Rect containingAppBounds) { - outBounds.setEmpty(); + private void applyAspectRatio(Rect outBounds, Rect containingAppBounds) { final float maxAspectRatio = info.maxAspectRatio; final ActivityStack stack = getActivityStack(); final float minAspectRatio = info.minAspectRatio; @@ -6991,12 +6992,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (containingAppWidth <= activityWidth && containingAppHeight <= activityHeight) { // The display matches or is less than the activity aspect ratio, so nothing else to do. - // Return the existing bounds. If this method is running for the first time, - // {@link #getRequestedOverrideBounds()} will be empty (representing no override). If - // the method has run before, then effect of {@link #getRequestedOverrideBounds()} will - // already have been applied to the value returned from {@link getConfiguration}. Refer - // to {@link TaskRecord#computeConfigResourceOverrides()}. - outBounds.set(getRequestedOverrideBounds()); return; } @@ -7776,6 +7771,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + /** + * Sets bounds to {@link TaskRecord} bounds. For apps in freeform, the task bounds are the + * parent bounds from the app's perspective. No insets because within a window. + */ + CompatDisplayInsets(TaskRecord taskRecord) { + Rect taskBounds = taskRecord.getConfiguration().windowConfiguration.getBounds(); + mDisplayWidth = taskBounds.width(); + mDisplayHeight = taskBounds.height(); + for (int rotation = 0; rotation < 4; rotation++) { + mNonDecorInsets[rotation] = new Rect(); + mStableInsets[rotation] = new Rect(); + } + } + void getDisplayBoundsByRotation(Rect outBounds, int rotation) { final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final int dw = rotated ? mDisplayHeight : mDisplayWidth; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 14df505e8986..014b682ff8d6 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -54,6 +54,7 @@ import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; +import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL; import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS; @@ -569,6 +570,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { boolean mSupportsPictureInPicture; boolean mSupportsMultiDisplay; boolean mForceResizableActivities; + boolean mSizeCompatFreeform; final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = new ArrayList<>(); @@ -744,6 +746,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0; final boolean forceResizable = Settings.Global.getInt( resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0; + final boolean sizeCompatFreeform = Settings.Global.getInt( + resolver, DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM, 0) != 0; // Transfer any global setting for forcing RTL layout, into a System Property DisplayProperties.debug_force_rtl(forceRtl); @@ -757,6 +761,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { mForceResizableActivities = forceResizable; + mSizeCompatFreeform = sizeCompatFreeform; final boolean multiWindowFormEnabled = freeformWindowManagement || supportsSplitScreenMultiWindow || supportsPictureInPicture @@ -3393,6 +3398,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (stack.inFreeformWindowingMode()) { stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + } else if (!mSizeCompatFreeform) { + throw new IllegalStateException("Size-compat windows are currently not" + + "freeform-enabled"); } else if (stack.getParent().inFreeformWindowingMode()) { // If the window is on a freeform display, set it to undefined. It will be // resolved to freeform and it can adjust windowing mode when the display mode diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index 9712277495df..31145deb449e 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -43,10 +43,8 @@ import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Rect; -import android.os.Build; import android.util.Slog; import android.view.Gravity; import android.view.View; @@ -65,15 +63,6 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskLaunchParamsModifier" : TAG_ATM; private static final boolean DEBUG = false; - // A mask for SUPPORTS_SCREEN that indicates the activity supports resize. - private static final int SUPPORTS_SCREEN_RESIZEABLE_MASK = - ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES - | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS - | ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS - | ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS - | ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES - | ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS; - // Screen size of Nexus 5x private static final int DEFAULT_PORTRAIT_PHONE_WIDTH_DP = 412; private static final int DEFAULT_PORTRAIT_PHONE_HEIGHT_DP = 732; @@ -253,10 +242,9 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { if (display.inFreeformWindowingMode()) { if (launchMode == WINDOWING_MODE_PINNED) { if (DEBUG) appendLog("picture-in-picture"); - } else if (isTaskForcedMaximized(root)) { - // We're launching an activity that probably can't handle resizing nicely, so force - // it to be maximized even someone suggests launching it in freeform using launch - // options. + } else if (!mSupervisor.mService.mSizeCompatFreeform && !root.isResizeable()) { + // We're launching an activity in size-compat mode and they aren't allowed in + // freeform, so force it to be maximized. launchMode = WINDOWING_MODE_FULLSCREEN; outParams.mBounds.setEmpty(); if (DEBUG) appendLog("forced-maximize"); @@ -460,28 +448,6 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } /** - * Returns if task is forced to maximize. - * - * There are several cases where we force a task to maximize: - * 1) Root activity is targeting pre-Donut, which by default can't handle multiple screen - * densities, so resizing will likely cause issues; - * 2) Root activity doesn't declare any flag that it supports any screen density, so resizing - * may also cause issues; - * 3) Root activity is not resizeable, for which we shouldn't allow user resize it. - * - * @param root the root activity to check against. - * @return {@code true} if it should be forced to maximize; {@code false} otherwise. - */ - private boolean isTaskForcedMaximized(@NonNull ActivityRecord root) { - if (root.info.applicationInfo.targetSdkVersion < Build.VERSION_CODES.DONUT - || (root.info.applicationInfo.flags & SUPPORTS_SCREEN_RESIZEABLE_MASK) == 0) { - return true; - } - - return !root.isResizeable(); - } - - /** * Resolves activity requested orientation to 4 categories: * 1) {@link ActivityInfo#SCREEN_ORIENTATION_LOCKED} indicating app wants to lock down * orientation; diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java index ed07f30fc9dc..e1123fa05b7d 100644 --- a/services/core/java/com/android/server/wm/TaskPositioningController.java +++ b/services/core/java/com/android/server/wm/TaskPositioningController.java @@ -126,6 +126,11 @@ class TaskPositioningController { synchronized (mService.mGlobalLock) { final Task task = displayContent.findTaskForResizePoint(x, y); if (task != null) { + if (!task.isResizeable()) { + // The task is not resizable, so don't do anything when the user drags the + // the resize handles. + return; + } if (!startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/, task.preserveOrientationOnResize(), x, y)) { return; diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 299b32cce039..09c25f061c5e 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -519,18 +519,7 @@ class TaskRecord extends ConfigurationContainer { mAtmService.deferWindowLayout(); try { - if (!isResizeable()) { - Slog.w(TAG, "resizeTask: task " + this + " not resizeable."); - return true; - } - - // If this is a forced resize, let it go through even if the bounds is not changing, - // as we might need a relayout due to surface size change (to/from fullscreen). final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0; - if (equivalentRequestedOverrideBounds(bounds) && !forced) { - // Nothing to do here... - return true; - } if (mTask == null) { // Task doesn't exist in window manager yet (e.g. was restored from recents). diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e90f3da96409..415606b23478 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -39,6 +39,7 @@ import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURES_EX import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP; import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS; import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; +import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES; import static android.view.Display.DEFAULT_DISPLAY; @@ -717,6 +718,8 @@ public class WindowManagerService extends IWindowManager.Stub Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT); private final Uri mForceResizableUri = Settings.Global.getUriFor( DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES); + private final Uri mSizeCompatFreeformUri = Settings.Global.getUriFor( + DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM); public SettingsObserver() { super(new Handler()); @@ -737,6 +740,8 @@ public class WindowManagerService extends IWindowManager.Stub UserHandle.USER_ALL); resolver.registerContentObserver(mFreeformWindowUri, false, this, UserHandle.USER_ALL); resolver.registerContentObserver(mForceResizableUri, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(mSizeCompatFreeformUri, false, this, + UserHandle.USER_ALL); } @Override @@ -775,6 +780,11 @@ public class WindowManagerService extends IWindowManager.Stub return; } + if (mSizeCompatFreeformUri.equals(uri)) { + updateSizeCompatFreeform(); + return; + } + @UpdateAnimationScaleMode final int mode; if (mWindowAnimationScaleUri.equals(uri)) { @@ -844,6 +854,14 @@ public class WindowManagerService extends IWindowManager.Stub mAtmService.mForceResizableActivities = forceResizable; } + + void updateSizeCompatFreeform() { + ContentResolver resolver = mContext.getContentResolver(); + final boolean sizeCompatFreeform = Settings.Global.getInt(resolver, + DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM, 0) != 0; + + mAtmService.mSizeCompatFreeform = sizeCompatFreeform; + } } PowerManager mPowerManager; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b9cf29ad7790..991241dd645f 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2319,29 +2319,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final Region region = inputWindowHandle.touchableRegion; setTouchableRegionCropIfNeeded(inputWindowHandle); - final Rect appOverrideBounds = mActivityRecord != null - ? mActivityRecord.getResolvedOverrideBounds() : null; - if (appOverrideBounds != null && !appOverrideBounds.isEmpty()) { - // There may have touchable letterboxes around the activity, so in order to let the - // letterboxes are able to receive touch event and slip to activity, the activity with - // compatibility bounds cannot occupy full screen touchable region. - if (modal) { - // A modal window uses the whole compatibility bounds. - flags |= FLAG_NOT_TOUCH_MODAL; - mTmpRect.set(0, 0, appOverrideBounds.width(), appOverrideBounds.height()); - } else { - // Non-modal uses the application based frame. - mTmpRect.set(mWindowFrames.mCompatFrame); - } - // The offset of compatibility bounds is applied to surface of {@link #ActivityRecord} - // and frame, so it is unnecessary to translate twice in surface based coordinates. - final int surfaceOffsetX = mActivityRecord.hasSizeCompatBounds() - ? mActivityRecord.getBounds().left : 0; - mTmpRect.offset(surfaceOffsetX - mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top); - region.set(mTmpRect); - return flags; - } - if (modal && mActivityRecord != null) { // Limit the outer touch to the activity stack region. flags |= FLAG_NOT_TOUCH_MODAL; @@ -2398,6 +2375,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Translate to surface based coordinates. region.translate(-mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top); + // TODO(b/139804591): sizecompat layout needs to be reworked. Currently mFrame is post- + // scaling but the existing logic doesn't expect that. The result is that the already- + // scaled region ends up getting sent to surfaceflinger which then applies the scale + // (again). Until this is resolved, apply an inverse-scale here. + if (mActivityRecord != null && mActivityRecord.hasSizeCompatBounds() + && mGlobalScale != 1.f) { + region.scale(mInvGlobalScale); + } + return flags; } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index bf1508a34872..e43a364b45d9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -40,7 +40,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED; @@ -70,10 +69,12 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; +import android.app.WindowConfiguration; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.PauseActivityItem; @@ -485,6 +486,42 @@ public class ActivityRecordTests extends ActivityTestsBase { } @Test + public void testSizeCompatMode_KeepBoundsWhenChangingFromFreeformToFullscreen() { + setupDisplayContentForCompatDisplayInsets(); + + // put display in freeform mode + ActivityDisplay display = mActivity.getDisplay(); + final Configuration c = new Configuration(display.getRequestedOverrideConfiguration()); + c.windowConfiguration.setBounds(new Rect(0, 0, 2000, 1000)); + c.densityDpi = 300; + c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + display.onRequestedOverrideConfigurationChanged(c); + + // launch compat activity in freeform and store bounds + mActivity.mAppWindowToken.mOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + mTask.getRequestedOverrideConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT; + mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + mActivity.visible = true; + ensureActivityConfiguration(); + + final Rect bounds = new Rect(mActivity.getBounds()); + final int density = mActivity.getConfiguration().densityDpi; + final int windowingMode = mActivity.getWindowingMode(); + + // change display configuration to fullscreen + c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + display.onRequestedOverrideConfigurationChanged(c); + + // check if dimensions stay the same + assertTrue(mActivity.inSizeCompatMode()); + assertEquals(bounds.width(), mActivity.getBounds().width()); + assertEquals(bounds.height(), mActivity.getBounds().height()); + assertEquals(density, mActivity.getConfiguration().densityDpi); + assertEquals(windowingMode, mActivity.getWindowingMode()); + } + + @Test public void testSizeCompatMode_FixedAspectRatioBoundsWithDecor() { setupDisplayContentForCompatDisplayInsets(); final int decorHeight = 200; // e.g. The device has cutout. @@ -506,6 +543,7 @@ public class ActivityRecordTests extends ActivityTestsBase { .when(mActivity).getRequestedOrientation(); mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1; + mActivity.visible = true; ensureActivityConfiguration(); // The parent configuration doesn't change since the first resolved configuration, so the // activity shouldn't be in the size compatibility mode. @@ -555,7 +593,10 @@ public class ActivityRecordTests extends ActivityTestsBase { // Move the non-resizable activity to the new display. mStack.reparent(newDisplay, true /* onTop */, false /* displayRemoved */); - assertEquals(originalBounds, mActivity.getWindowConfiguration().getBounds()); + assertEquals(originalBounds.width(), + mActivity.getWindowConfiguration().getBounds().width()); + assertEquals(originalBounds.height(), + mActivity.getWindowConfiguration().getBounds().height()); assertEquals(originalDpi, mActivity.getConfiguration().densityDpi); assertTrue(mActivity.inSizeCompatMode()); } @@ -566,9 +607,12 @@ public class ActivityRecordTests extends ActivityTestsBase { when(mActivity.getRequestedOrientation()).thenReturn( ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds()); - mTask.getConfiguration().orientation = ORIENTATION_PORTRAIT; + mTask.getRequestedOverrideConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT; + mActivity.mAppWindowToken.mOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + mActivity.visible = true; + ensureActivityConfiguration(); final Rect originalBounds = new Rect(mActivity.getBounds()); @@ -576,7 +620,10 @@ public class ActivityRecordTests extends ActivityTestsBase { setupDisplayAndParentSize(1000, 2000); ensureActivityConfiguration(); - assertEquals(originalBounds, mActivity.getWindowConfiguration().getBounds()); + assertEquals(originalBounds.width(), + mActivity.getWindowConfiguration().getBounds().width()); + assertEquals(originalBounds.height(), + mActivity.getWindowConfiguration().getBounds().height()); assertTrue(mActivity.inSizeCompatMode()); } @@ -1242,6 +1289,8 @@ public class ActivityRecordTests extends ActivityTestsBase { c.windowConfiguration.setBounds(new Rect(0, 0, width, height)); c.windowConfiguration.setAppBounds(0, 0, width, height); c.windowConfiguration.setRotation(ROTATION_0); + c.orientation = width > height + ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT; mStack.getDisplay().onRequestedOverrideConfigurationChanged(c); return displayContent; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index dd85f69b7160..5a141ae983f2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -410,7 +410,8 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { } @Test - public void testForceMaximizesPreDApp() { + public void testForceMaximizesUnresizeableApp() { + mService.mSizeCompatFreeform = false; final TestActivityDisplay freeformDisplay = createNewActivityDisplay( WINDOWING_MODE_FREEFORM); @@ -422,7 +423,7 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM; mCurrent.mBounds.set(0, 0, 200, 100); - mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUPCAKE; + mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null, mActivity, /* source */ null, options, mCurrent, mResult)); @@ -432,46 +433,29 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { } @Test - public void testForceMaximizesAppWithoutMultipleDensitySupport() { + public void testLaunchesAppInWindowOnFreeformDisplay() { + mService.mSizeCompatFreeform = true; final TestActivityDisplay freeformDisplay = createNewActivityDisplay( WINDOWING_MODE_FREEFORM); - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); - options.setLaunchBounds(new Rect(0, 0, 200, 100)); - - mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId; - mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM; - mCurrent.mBounds.set(0, 0, 200, 100); - - mActivity.info.applicationInfo.flags = 0; - - assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null, - mActivity, /* source */ null, options, mCurrent, mResult)); - - assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode, - WINDOWING_MODE_FREEFORM); - } - - @Test - public void testForceMaximizesUnresizeableApp() { - final TestActivityDisplay freeformDisplay = createNewActivityDisplay( - WINDOWING_MODE_FREEFORM); + Rect expectedLaunchBounds = new Rect(0, 0, 200, 100); final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); - options.setLaunchBounds(new Rect(0, 0, 200, 100)); + options.setLaunchBounds(expectedLaunchBounds); mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId; mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM; - mCurrent.mBounds.set(0, 0, 200, 100); + mCurrent.mBounds.set(expectedLaunchBounds); mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null, mActivity, /* source */ null, options, mCurrent, mResult)); - assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode, + assertEquals(expectedLaunchBounds, mResult.mBounds); + + assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode, WINDOWING_MODE_FREEFORM); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java index dc89f5080c4b..f8d49ad18664 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; @@ -64,6 +66,7 @@ public class TaskPositioningControllerTests extends WindowTestsBase { any(InputChannel.class))).thenReturn(true); mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); + mWindow.getTask().setResizeable(RESIZE_MODE_RESIZEABLE); mWindow.mInputChannel = new InputChannel(); mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor(); @@ -129,4 +132,23 @@ public class TaskPositioningControllerTests extends WindowTestsBase { assertFalse(mTarget.isPositioningLocked()); assertNull(mTarget.getDragWindowHandleLocked()); } + + @Test + public void testHandleTapOutsideNonResizableTask() { + assertFalse(mTarget.isPositioningLocked()); + assertNull(mTarget.getDragWindowHandleLocked()); + + final DisplayContent content = mock(DisplayContent.class); + doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt()); + assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow()); + + mWindow.getTask().setResizeable(RESIZE_MODE_UNRESIZEABLE); + + mTarget.handleTapOutsideTask(content, 0, 0); + // Wait until the looper processes finishTaskPositioning. + assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS)); + + assertFalse(mTarget.isPositioningLocked()); + } + } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java index 86f0f8bc1bb9..1c9eed2e5622 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; +import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES; @@ -77,6 +78,20 @@ public class WindowManagerSettingsTests extends WindowTestsBase { } } + @Test + public void testEnableSizeCompatFreeform() { + try (SettingsSession enableSizeCompatFreeformSession = new + SettingsSession(DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM)) { + final boolean enableSizeCompatFreeform = + !enableSizeCompatFreeformSession.getSetting(); + final Uri enableSizeCompatFreeformUri = + enableSizeCompatFreeformSession.setSetting(enableSizeCompatFreeform); + mWm.mSettingsObserver.onChange(false, enableSizeCompatFreeformUri); + + assertEquals(mWm.mAtmService.mSizeCompatFreeform, enableSizeCompatFreeform); + } + } + private class SettingsSession implements AutoCloseable { private static final int SETTING_VALUE_OFF = 0; |