diff options
| author | 2019-07-17 10:17:11 -0700 | |
|---|---|---|
| committer | 2019-10-22 14:10:36 -0700 | |
| commit | 780b5bb91e67a53b48c508e5e77d9b076c0200c3 (patch) | |
| tree | a86b9a61b1f45c42033c81d90fdb589824f1cee6 | |
| parent | 9c8649e513c9787c66001f160f1c2ddc0df44d77 (diff) | |
Launch SizeCompat Activities in a Window on Freeform Desktop Mode
Currently, when a display is in freeform mode, an activity that
is not resizable launches in fullscreen. However, for large
screen devices, it is preferable to launch activities in windows
to create the true desktop experience. This CL changes launch
configurations and resize actions to make non-resizable activities
launch in a window and to prevent resizing of these windows. When
the display enters fullscreen mode, the activity keeps its original
bounds and launches in a letterbox.
To make this all work, ActivityRecord was updated to use the
hierarchy instead of trying to recalculate everything independently
with a fullscreen assumption. The result is that the ActivityRecord
configuration now (mostly) has the actual bounds of the app so
that positioning works properly.
Please note, the following tests were removed because the
functionality they were trying to test has changed. Previously,
when a task was nonresizeable, even if the display windowing mode
was freeform, it was launched in fullscreen mode. However this CL
allows nonresizeable tasks in freeform mode to be launched in
windows. These tests were removed since they are redundant
with non-resizable (which is handled by packagemanager):
- TaskLaunchParamsModifierTests#testForceMaximizesPreDApp
- TaskLaunchParamsModifierTests#testForceMaximizesAppWithoutMultipleDensitySupport
The following tests were added:
- TaskPositioningControllerTests#testHandleTapOutsideNonResizableTask
- TaskLaunchParamsModifierTests#testLaunchesAppInWindowOnFreeformDisplay
- ActivityRecordTests#testSizeCompatMode_KeepBoundsWhenChangingFromFreeformToFullscreen
This also adds a developer option to enable size-compat
apps to start in freeform mode.
Test: go/wm-smoke
Change-Id: I3aa3fcdcd2b1e0b875d61dfaed3d5e85313edc29
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; |