From f70f8ecefccdf5aac844a6615f671575a17ded0d Mon Sep 17 00:00:00 2001 From: Naomi Musgrave Date: Fri, 13 Aug 2021 12:28:22 +0100 Subject: [PATCH] [1/n] Introduce API for all available max WindowMetrics New API provides all maximum WindowMetrics, for all possible rotations in each DeviceState. This information allows launcher apps to determine different device layouts ahead of time, without having to reload the grid model on every device state change (fold/unfold). Done: * WindowManager calculates bounds for the given display, in possible rotations * Defining API surface Not started: * WindowManager calculates insets for each rotation * Display stack builds collection of DisplayInfo, for all possible display states (folded, unfolded) * Display stack pushing set of DisplayInfos to WindowManager Bug: 181127261 Test: atest FrameworksCoreTests:WindowMetricsTest Test: atest FrameworksCoreTests:WindowInsetsTest Change-Id: Ic4580f9c1ee919e5e93cd96b8f11c743fa42f9f1 --- core/java/android/view/IWindowManager.aidl | 12 +++ core/java/android/view/InsetsState.java | 2 +- core/java/android/view/WindowInsets.java | 10 +++ core/java/android/view/WindowManager.java | 15 ++++ core/java/android/view/WindowManagerImpl.java | 88 +++++++++++++++---- .../server/wm/WindowManagerService.java | 65 ++++++++++++++ 6 files changed, 174 insertions(+), 18 deletions(-) diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 52d57cf0a7f64..8e149a98af0eb 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -33,6 +33,7 @@ import android.os.Bundle; import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; import android.view.DisplayCutout; +import android.view.DisplayInfo; import android.view.IApplicationToken; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.ICrossWindowBlurEnabledListener; @@ -735,6 +736,17 @@ interface IWindowManager boolean getWindowInsets(in WindowManager.LayoutParams attrs, int displayId, out InsetsState outInsetsState); + /** + * Returns a list of {@link android.view.DisplayInfo} for the logical display. This is not + * guaranteed to include all possible device states. The list items are unique. + * + * If invoked through a package other than a launcher app, returns an empty list. + * + * @param displayId the id of the logical display + * @param packageName the name of the calling package + */ + List getPossibleDisplayInfo(int displayId, String packageName); + /** * Called to show global actions. */ diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 75b69cb12d32e..39172794f6028 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -202,7 +202,7 @@ public class InsetsState implements Parcelable { @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { Insets[] typeInsetsMap = new Insets[Type.SIZE]; Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; - boolean[] typeVisibilityMap = new boolean[SIZE]; + boolean[] typeVisibilityMap = new boolean[Type.SIZE]; final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 0f1a9d9c0a986..cde1cc704f929 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -903,6 +903,16 @@ public final class WindowInsets { result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds=" + mPrivacyIndicatorBounds : ""); result.append("\n "); + result.append("compatInsetsTypes=" + mCompatInsetsTypes); + result.append("\n "); + result.append("compatIgnoreVisibility=" + mCompatIgnoreVisibility); + result.append("\n "); + result.append("systemWindowInsetsConsumed=" + mSystemWindowInsetsConsumed); + result.append("\n "); + result.append("stableInsetsConsumed=" + mStableInsetsConsumed); + result.append("\n "); + result.append("displayCutoutConsumed=" + mDisplayCutoutConsumed); + result.append("\n "); result.append(isRound() ? "round" : ""); result.append("}"); return result.toString(); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 3ce3fabbd6fea..51cd95e427428 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -123,6 +123,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -713,6 +714,20 @@ public interface WindowManager extends ViewManager { throw new UnsupportedOperationException(); } + /** + * Returns a set of {@link WindowMetrics} for the given display. Each WindowMetrics instance + * is the maximum WindowMetrics for a device state, including rotations. This is not guaranteed + * to include all possible device states. + * + * This API can only be used by Launcher. + * + * @param displayId the id of the logical display + * @hide + */ + default @NonNull Set getPossibleMaximumWindowMetrics(int displayId) { + throw new UnsupportedOperationException(); + } + /** * Used to asynchronously request Keyboard Shortcuts from the focused window. * diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index a2d3e3447354a..0fc6b08ae02bb 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -16,12 +16,9 @@ package android.view; -import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; -import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.window.WindowProviderService.isWindowProviderService; @@ -46,7 +43,9 @@ import android.window.WindowProvider; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -304,33 +303,88 @@ public final class WindowManagerImpl implements WindowManager { private WindowInsets computeWindowInsets(Rect bounds) { // Initialize params which used for obtaining all system insets. final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); - params.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; final Context context = (mParentWindow != null) ? mParentWindow.getContext() : mContext; params.token = Context.getToken(context); - params.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - params.setFitInsetsTypes(0); - params.setFitInsetsSides(0); - - return getWindowInsetsFromServer(params, bounds); + return getWindowInsetsFromServerForCurrentDisplay(params, bounds); } - private WindowInsets getWindowInsetsFromServer(WindowManager.LayoutParams attrs, Rect bounds) { + private WindowInsets getWindowInsetsFromServerForCurrentDisplay( + WindowManager.LayoutParams attrs, Rect bounds) { + final Configuration config = mContext.getResources().getConfiguration(); + return getWindowInsetsFromServerForDisplay(mContext.getDisplayId(), attrs, bounds, + config.isScreenRound(), config.windowConfiguration.getWindowingMode()); + } + + /** + * Retrieves WindowInsets for the given context and display, given the window bounds. + * + * @param displayId the ID of the logical display to calculate insets for + * @param attrs the LayoutParams for the calling app + * @param bounds the window bounds to calculate insets for + * @param isScreenRound if the display identified by displayId is round + * @param windowingMode the windowing mode of the window to calculate insets for + * @return WindowInsets calculated for the given window bounds, on the given display + */ + private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId, + WindowManager.LayoutParams attrs, Rect bounds, boolean isScreenRound, + int windowingMode) { try { final InsetsState insetsState = new InsetsState(); final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService() - .getWindowInsets(attrs, mContext.getDisplayId(), insetsState); - final Configuration config = mContext.getResources().getConfiguration(); - final boolean isScreenRound = config.isScreenRound(); - final int windowingMode = config.windowConfiguration.getWindowingMode(); + .getWindowInsets(attrs, displayId, insetsState); return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState*/, isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING, attrs.flags, - SYSTEM_UI_FLAG_VISIBLE, attrs.type, windowingMode, null /* typeSideMap */); + SYSTEM_UI_FLAG_VISIBLE, attrs.type, windowingMode, + null /* typeSideMap */); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + @Override + @NonNull + public Set getPossibleMaximumWindowMetrics(int displayId) { + List possibleDisplayInfos; + try { + possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService() + .getPossibleDisplayInfo(displayId, mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + int size = possibleDisplayInfos.size(); + DisplayInfo currentDisplayInfo; + WindowInsets windowInsets = null; + if (size > 0) { + currentDisplayInfo = possibleDisplayInfos.get(0); + + final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0; + // TODO(181127261) not computing insets correctly - need to have underlying + // frame reflect the faked orientation. + windowInsets = getWindowInsetsFromServerForDisplay( + currentDisplayInfo.displayId, params, + new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), + currentDisplayInfo.getNaturalHeight()), isScreenRound, + WINDOWING_MODE_FULLSCREEN); + } + + Set maxMetrics = new HashSet<>(); + for (int i = 0; i < size; i++) { + currentDisplayInfo = possibleDisplayInfos.get(i); + + // Calculate max bounds for this rotation and state. + Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), + currentDisplayInfo.getNaturalHeight()); + + // Calculate insets for the rotated max bounds. + // TODO(181127261) calculate insets for each display rotation and state. + + maxMetrics.add(new WindowMetrics(maxBounds, windowInsets)); + } + return maxMetrics; + } + @Override public void holdLock(IBinder token, int durationMs) { try { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4340a365775a5..232c283699551 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -46,6 +46,8 @@ import static android.provider.Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COM import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_270; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; @@ -152,6 +154,7 @@ import android.app.IActivityTaskManager; import android.app.IAssistDataReceiver; import android.app.WindowConfiguration; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -161,6 +164,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.TestUtilityService; import android.content.res.Configuration; +import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -322,11 +326,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; @@ -8450,6 +8456,65 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public List getPossibleDisplayInfo(int displayId, String packageName) { + final int callingUid = Binder.getCallingUid(); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + if (packageName == null || !isRecentsComponent(packageName, callingUid)) { + Slog.e(TAG, "Unable to verify uid for package " + packageName + + " for getPossibleMaximumWindowMetrics"); + return new ArrayList<>(); + } + // TODO(181127261) DisplayInfo should be pushed from DisplayManager. + final DisplayContent dc = mRoot.getDisplayContent(displayId); + if (dc == null) { + Slog.e(TAG, "Invalid displayId " + displayId + + " for getPossibleMaximumWindowMetrics"); + return new ArrayList<>(); + } + + // TODO(181127261) DisplayManager should provide a DisplayInfo for each rotation + DisplayInfo currentDisplayInfo = dc.getDisplayInfo(); + Set displayInfoSet = new HashSet<>(); + for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) { + currentDisplayInfo.rotation = rotation; + // TODO(181127261) Retrieve the device state from display stack. + displayInfoSet.add(new DisplayInfo(currentDisplayInfo)); + } + return new ArrayList(displayInfoSet); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + /** + * Returns {@code true} when the calling package is the recents component. + */ + boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) { + String recentsPackage; + try { + String recentsComponent = mContext.getResources().getString( + R.string.config_recentsComponentName); + if (recentsComponent == null) { + return false; + } + recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName(); + } catch (Resources.NotFoundException e) { + Slog.e(TAG, "Unable to verify if recents component", e); + return false; + } + try { + return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0) + && callingPackageName.equals(recentsPackage); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Unable to verify if recents component", e); + return false; + } + } + void grantEmbeddedWindowFocus(Session session, IBinder inputToken, boolean grantFocus) { synchronized (mGlobalLock) { final EmbeddedWindowController.EmbeddedWindow embeddedWindow =