[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
This commit is contained in:
Naomi Musgrave
2021-08-13 12:28:22 +01:00
parent 0bce85311c
commit f70f8ecefc
6 changed files with 174 additions and 18 deletions

View File

@@ -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<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName);
/**
* Called to show global actions.
*/

View File

@@ -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++) {

View File

@@ -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();

View File

@@ -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<WindowMetrics> getPossibleMaximumWindowMetrics(int displayId) {
throw new UnsupportedOperationException();
}
/**
* Used to asynchronously request Keyboard Shortcuts from the focused window.
*

View File

@@ -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<WindowMetrics> getPossibleMaximumWindowMetrics(int displayId) {
List<DisplayInfo> 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<WindowMetrics> 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 {

View File

@@ -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<DisplayInfo> 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<DisplayInfo> 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<DisplayInfo>(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 =