From aca03a172ccbdd7f3f0f2a9a036d4fb0238b5503 Mon Sep 17 00:00:00 2001 From: Naomi Musgrave Date: Fri, 13 Aug 2021 16:45:31 +0100 Subject: [PATCH] [2/n] Display stack provides DisplayInfos For all possible states a device can assume, the DisplayManagerService provides the DisplayInfo associated with that layout of the logical display. WindowManager applies each possible rotation to these DisplayInfo. This, in turn, is used to calculate the possible max WindowMetrics on the device. Done: * Display stack builds collection of DisplayInfo, for all possible display states (folded, unfolded on jumbo) * Display stack pushing set of DisplayInfos to WindowManager * WindowManager calculates max bounds for all possible (display layouts x rotations) Not started: * WindowManager calculates insets for each rotation Bug: 181127261 Test: atest DeviceStateManagerGlobalTest Test: atest DeviceStateManagerServiceTest Test: atest LogicalDisplayMapperTest Change-Id: I3a407262e755cb57c506b7255eb5c067523381d3 --- .../DeviceStateManagerInternal.java | 28 +++ .../display/DisplayManagerInternal.java | 9 + core/java/android/view/WindowManagerImpl.java | 4 +- .../DeviceStateManagerService.java | 19 +- .../server/display/DisplayManagerService.java | 57 +++++- .../server/display/LogicalDisplayMapper.java | 57 ++++++ .../server/wm/PossibleDisplayInfoMapper.java | 131 +++++++++++++ .../server/wm/RootWindowContainer.java | 8 +- .../server/wm/WindowManagerService.java | 28 +-- .../DeviceStateManagerServiceTest.java | 20 +- .../display/LogicalDisplayMapperTest.java | 98 ++++++++-- .../wm/PossibleDisplayInfoMapperTests.java | 182 ++++++++++++++++++ 12 files changed, 585 insertions(+), 56 deletions(-) create mode 100644 core/java/android/hardware/devicestate/DeviceStateManagerInternal.java create mode 100644 services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java b/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java new file mode 100644 index 0000000000000..4c91c160f8917 --- /dev/null +++ b/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.devicestate; + +/** + * Device state manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class DeviceStateManagerInternal { + + /** Returns the list of currently supported device state identifiers. */ + public abstract int[] getSupportedStateIdentifiers(); +} diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 4f205530ef0d7..5bb51c19342da 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -35,6 +35,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Display manager local system service interface. @@ -128,6 +129,14 @@ public abstract class DisplayManagerInternal { */ public abstract DisplayInfo getDisplayInfo(int displayId); + /** + * Returns a set of DisplayInfo, for the states that may be assumed by either the given display, + * or any other display within that display's group. + * + * @param displayId The logical display id to fetch DisplayInfo for. + */ + public abstract Set getPossibleDisplayInfo(int displayId); + /** * Returns the position of the display's projection. * diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 0fc6b08ae02bb..7631269d9c1c9 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -374,8 +374,8 @@ public final class WindowManagerImpl implements WindowManager { currentDisplayInfo = possibleDisplayInfos.get(i); // Calculate max bounds for this rotation and state. - Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), - currentDisplayInfo.getNaturalHeight()); + Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth, + currentDisplayInfo.logicalHeight); // Calculate insets for the rotated max bounds. // TODO(181127261) calculate insets for each display rotation and state. diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 806a5dd65a138..792feea01e27a 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -31,6 +31,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.devicestate.DeviceStateInfo; import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateManagerInternal; import android.hardware.devicestate.IDeviceStateManager; import android.hardware.devicestate.IDeviceStateManagerCallback; import android.os.Binder; @@ -161,6 +162,7 @@ public final class DeviceStateManagerService extends SystemService { @Override public void onStart() { publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService); + publishLocalService(DeviceStateManagerInternal.class, new LocalService()); } @VisibleForTesting @@ -239,13 +241,6 @@ public final class DeviceStateManagerService extends SystemService { } } - /** Returns the list of currently supported device state identifiers. */ - private int[] getSupportedStateIdentifiers() { - synchronized (mLock) { - return getSupportedStateIdentifiersLocked(); - } - } - /** Returns the list of currently supported device state identifiers. */ private int[] getSupportedStateIdentifiersLocked() { int[] supportedStates = new int[mDeviceStates.size()]; @@ -848,4 +843,14 @@ public final class DeviceStateManagerService extends SystemService { } } } + + /** Implementation of {@link DeviceStateManagerInternal} published as a local service. */ + private final class LocalService extends DeviceStateManagerInternal { + @Override + public int[] getSupportedStateIdentifiers() { + synchronized (mLock) { + return getSupportedStateIdentifiersLocked(); + } + } + } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 827523be0d3ab..f16ed41af5caa 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -53,6 +53,7 @@ import android.graphics.Point; import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateManagerInternal; import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; @@ -131,6 +132,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -210,6 +212,7 @@ public final class DisplayManagerService extends SystemService { private WindowManagerInternal mWindowManagerInternal; private InputManagerInternal mInputManagerInternal; private IMediaProjectionManager mProjectionService; + private DeviceStateManagerInternal mDeviceStateManager; private int[] mUserDisabledHdrTypes = {}; private boolean mAreUserDisabledHdrTypesAllowed = true; @@ -557,10 +560,9 @@ public final class DisplayManagerService extends SystemService { mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); - DeviceStateManager deviceStateManager = - mContext.getSystemService(DeviceStateManager.class); - deviceStateManager.registerCallback(new HandlerExecutor(mHandler), - new DeviceStateListener()); + mDeviceStateManager = LocalServices.getService(DeviceStateManagerInternal.class); + mContext.getSystemService(DeviceStateManager.class).registerCallback( + new HandlerExecutor(mHandler), new DeviceStateListener()); scheduleTraversalLocked(false); } @@ -3273,6 +3275,53 @@ public final class DisplayManagerService extends SystemService { return getDisplayInfoInternal(displayId, Process.myUid()); } + @Override + public Set getPossibleDisplayInfo(int displayId) { + synchronized (mSyncRoot) { + // Retrieve the group associated with this display id. + final int displayGroupId = + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(displayId); + if (displayGroupId == Display.INVALID_DISPLAY_GROUP) { + Slog.w(TAG, + "Can't get possible display info since display group for " + displayId + + " does not exist"); + return new ArraySet<>(); + } + + // Assume any display in this group can be swapped out for the given display id. + Set possibleInfo = new ArraySet<>(); + final DisplayGroup group = mLogicalDisplayMapper.getDisplayGroupLocked( + displayGroupId); + for (int i = 0; i < group.getSizeLocked(); i++) { + final int id = group.getIdLocked(i); + final LogicalDisplay logical = mLogicalDisplayMapper.getDisplayLocked(id); + if (logical == null) { + Slog.w(TAG, + "Can't get possible display info since logical display for " + + "display id " + id + " does not exist, as part of group " + + displayGroupId); + } else { + possibleInfo.add(logical.getDisplayInfoLocked()); + } + } + + // For the supported device states, retrieve the DisplayInfos for the logical + // display layout. + if (mDeviceStateManager == null) { + Slog.w(TAG, "Can't get supported states since DeviceStateManager not ready"); + } else { + final int[] supportedStates = + mDeviceStateManager.getSupportedStateIdentifiers(); + for (int state : supportedStates) { + possibleInfo.addAll( + mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, displayId, + displayGroupId)); + } + } + return possibleInfo; + } + } + @Override public Point getDisplayPosition(int displayId) { synchronized (mSyncRoot) { diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index a931718353651..973dcc4c79e5a 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -24,6 +24,7 @@ import android.os.Looper; import android.os.Message; import android.os.SystemProperties; import android.text.TextUtils; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; @@ -38,6 +39,7 @@ import com.android.server.display.layout.Layout; import java.io.PrintWriter; import java.util.Arrays; +import java.util.Set; import java.util.function.Consumer; /** @@ -254,6 +256,61 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return mDisplayGroups.get(groupId); } + /** + * Returns the set of {@link DisplayInfo} for this device state, only fetching the info that is + * part of the same display group as the provided display id. The DisplayInfo represent the + * logical display layouts possible for the given device state. + * + * @param deviceState the state to query possible layouts for + * @param displayId the display id to apply to all displays within the group + * @param groupId the display group to filter display info for. Must be the same group as + * the display with the provided display id. + */ + public Set getDisplayInfoForStateLocked(int deviceState, int displayId, + int groupId) { + Set displayInfos = new ArraySet<>(); + final Layout layout = mDeviceStateToLayoutMap.get(deviceState); + final int layoutSize = layout.size(); + for (int i = 0; i < layoutSize; i++) { + Layout.Display displayLayout = layout.getAt(i); + if (displayLayout == null) { + continue; + } + + // If the underlying display-device we want to use for this display + // doesn't exist, then skip it. This can happen at startup as display-devices + // trickle in one at a time. When the new display finally shows up, the layout is + // recalculated so that the display is properly added to the current layout. + final DisplayAddress address = displayLayout.getAddress(); + final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address); + if (device == null) { + Slog.w(TAG, "The display device (" + address + "), is not available" + + " for the display state " + deviceState); + continue; + } + + // Find or create the LogicalDisplay to map the DisplayDevice to. + final int logicalDisplayId = displayLayout.getLogicalDisplayId(); + final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId); + if (logicalDisplay == null) { + Slog.w(TAG, "The logical display (" + address + "), is not available" + + " for the display state " + deviceState); + continue; + } + final DisplayInfo temp = logicalDisplay.getDisplayInfoLocked(); + DisplayInfo displayInfo = new DisplayInfo(temp); + if (displayInfo.displayGroupId != groupId) { + // Ignore any displays not in the provided group. + continue; + } + // A display in the same group can be swapped out at any point, so set the display id + // for all results to the provided display id. + displayInfo.displayId = displayId; + displayInfos.add(displayInfo); + } + return displayInfos; + } + public void dumpLocked(PrintWriter pw) { pw.println("LogicalDisplayMapper:"); IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java new file mode 100644 index 0000000000000..ef8dee401b050 --- /dev/null +++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_270; + +import android.hardware.display.DisplayManagerInternal; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; +import android.view.DisplayInfo; +import android.view.Surface; + +import java.util.Set; + +/** + * Maintains a map of possible {@link DisplayInfo} for displays and states that may be encountered + * on a device. This is not guaranteed to include all possible device states for all displays. + * + * By 'possible', this class only handles device states for displays and display groups it is + * currently aware of. It can not handle all eventual states the system may enter, for example, if + * an external display is added, or a new display is added to the group. + */ +public class PossibleDisplayInfoMapper { + private static final String TAG = "PossibleDisplayInfoMapper"; + private static final boolean DEBUG = false; + + private final DisplayManagerInternal mDisplayManagerInternal; + + /** + * Map of all logical displays, indexed by logical display id. + * Each logical display has multiple entries, one for each possible rotation and device + * state. + * + * Emptied and re-calculated when a display is added, removed, or changed. + */ + private final SparseArray> mDisplayInfos = new SparseArray<>(); + + PossibleDisplayInfoMapper(DisplayManagerInternal displayManagerInternal) { + mDisplayManagerInternal = displayManagerInternal; + } + + + /** + * Returns, for the given displayId, a set of display infos. Set contains the possible rotations + * for each supported device state. + */ + public Set getPossibleDisplayInfos(int displayId) { + // Update display infos before returning, since any cached values would have been removed + // in response to any display event. This model avoids re-computing the cache for every + // display change event (which occurs extremely frequently in the normal usage of the + // device). + updatePossibleDisplayInfos(displayId); + if (!mDisplayInfos.contains(displayId)) { + return new ArraySet<>(); + } + return Set.copyOf(mDisplayInfos.get(displayId)); + } + + /** + * Updates the possible {@link DisplayInfo}s for the given display, by calculating the + * DisplayInfo for each rotation across supported device states. + */ + public void updatePossibleDisplayInfos(int displayId) { + Set displayInfos = mDisplayManagerInternal.getPossibleDisplayInfo(displayId); + if (DEBUG) { + Slog.v(TAG, "updatePossibleDisplayInfos, calculate rotations for given DisplayInfo " + + displayInfos.size() + " on display " + displayId); + } + updateDisplayInfos(displayInfos); + } + + /** + * For the given displayId, removes all possible {@link DisplayInfo}. + */ + public void removePossibleDisplayInfos(int displayId) { + if (DEBUG && mDisplayInfos.get(displayId) != null) { + Slog.v(TAG, "onDisplayRemoved, remove all DisplayInfo (" + mDisplayInfos.get( + displayId).size() + ") with id " + displayId); + } + mDisplayInfos.remove(displayId); + } + + private void updateDisplayInfos(Set displayInfos) { + // Empty out cache before re-computing. + mDisplayInfos.clear(); + DisplayInfo[] originalDisplayInfos = new DisplayInfo[displayInfos.size()]; + displayInfos.toArray(originalDisplayInfos); + // Iterate over each logical display layout for the current state. + Set rotatedDisplayInfos; + for (DisplayInfo di : originalDisplayInfos) { + rotatedDisplayInfos = new ArraySet<>(); + // Calculate all possible rotations for each logical display. + for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) { + rotatedDisplayInfos.add(applyRotation(di, rotation)); + } + // Combine all results under the logical display id. + Set priorDisplayInfos = mDisplayInfos.get(di.displayId, new ArraySet<>()); + priorDisplayInfos.addAll(rotatedDisplayInfos); + mDisplayInfos.put(di.displayId, priorDisplayInfos); + } + } + + private static DisplayInfo applyRotation(DisplayInfo displayInfo, + @Surface.Rotation int rotation) { + DisplayInfo updatedDisplayInfo = new DisplayInfo(); + updatedDisplayInfo.copyFrom(displayInfo); + updatedDisplayInfo.rotation = rotation; + + final int naturalWidth = updatedDisplayInfo.getNaturalWidth(); + final int naturalHeight = updatedDisplayInfo.getNaturalHeight(); + updatedDisplayInfo.logicalWidth = naturalWidth; + updatedDisplayInfo.logicalHeight = naturalHeight; + return updatedDisplayInfo; + } +} diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index c48dba4078c8a..40207882d73c5 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2577,6 +2577,9 @@ class RootWindowContainer extends WindowContainer if (mService.isBooted() || mService.isBooting()) { startSystemDecorations(display.mDisplayContent); } + // Drop any cached DisplayInfos associated with this display id - the values are now + // out of date given this display added event. + mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); } } @@ -2597,8 +2600,8 @@ class RootWindowContainer extends WindowContainer if (displayContent == null) { return; } - displayContent.remove(); + mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); } } @@ -2610,6 +2613,9 @@ class RootWindowContainer extends WindowContainer if (displayContent != null) { displayContent.onDisplayChanged(); } + // Drop any cached DisplayInfos associated with this display id - the values are now + // out of date given this display changed event. + mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 907098ef0bab8..c5d7179be8763 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -46,8 +46,6 @@ 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; @@ -326,13 +324,11 @@ 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; @@ -1054,6 +1050,10 @@ public class WindowManagerService extends IWindowManager.Stub final HighRefreshRateDenylist mHighRefreshRateDenylist; + // Maintainer of a collection of all possible DisplayInfo for all configurations of the + // logical displays. + final PossibleDisplayInfoMapper mPossibleDisplayInfoMapper; + // If true, only the core apps and services are being launched because the device // is in a special boot mode, such as being encrypted or waiting for a decryption password. // For example, when this flag is true, there will be no wallpaper service. @@ -1229,6 +1229,7 @@ public class WindowManagerService extends IWindowManager.Stub mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mPossibleDisplayInfoMapper = new PossibleDisplayInfoMapper(mDisplayManagerInternal); mSurfaceControlFactory = surfaceControlFactory; mTransactionFactory = transactionFactory; @@ -8467,23 +8468,10 @@ public class WindowManagerService extends IWindowManager.Stub + " 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); + // Retrieve the DisplayInfo for all possible rotations across all possible display + // layouts. + return List.copyOf(mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId)); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index b1b6e5341f38e..2d2c6a34f4753 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -18,6 +18,9 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -36,8 +39,6 @@ import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import static org.mockito.Mockito.mock; - import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowProcessController; @@ -181,8 +182,10 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE, + OTHER_DEVICE_STATE); - mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); + mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE}); flushHandler(); // The current committed and requests states do not change because the current state remains @@ -190,9 +193,10 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE); assertArrayEquals(callback.getLastNotifiedInfo().supportedStates, - new int[] { DEFAULT_DEVICE_STATE.getIdentifier() }); + new int[]{DEFAULT_DEVICE_STATE.getIdentifier()}); } @Test @@ -207,9 +211,11 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE, + OTHER_DEVICE_STATE); - mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE, - OTHER_DEVICE_STATE }); + mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE, + OTHER_DEVICE_STATE}); flushHandler(); // The current committed and requests states do not change because the current state remains @@ -217,6 +223,8 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE, + OTHER_DEVICE_STATE); // The callback wasn't notified about a change in supported states as the states have not // changed. diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 8279624f6b970..fbc1952b0fafc 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -16,12 +16,17 @@ package com.android.server.display; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.DEFAULT_DISPLAY_GROUP; + import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED; import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED; import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.eq; @@ -55,6 +60,7 @@ import org.mockito.MockitoAnnotations; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; +import java.util.Set; @SmallTest @Presubmit @@ -123,14 +129,14 @@ public class LogicalDisplayMapperTest { // add LogicalDisplay displayAdded = add(device); assertEquals(info(displayAdded).address, info(device).address); - assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded)); + assertEquals(DEFAULT_DISPLAY, id(displayAdded)); // remove mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED); verify(mListenerMock).onLogicalDisplayEventLocked( mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED)); LogicalDisplay displayRemoved = mDisplayCaptor.getValue(); - assertEquals(Display.DEFAULT_DISPLAY, id(displayRemoved)); + assertEquals(DEFAULT_DISPLAY, id(displayRemoved)); assertEquals(displayAdded, displayRemoved); } @@ -155,11 +161,11 @@ public class LogicalDisplayMapperTest { LogicalDisplay display1 = add(device1); assertEquals(info(display1).address, info(device1).address); - assertNotEquals(Display.DEFAULT_DISPLAY, id(display1)); + assertNotEquals(DEFAULT_DISPLAY, id(display1)); LogicalDisplay display2 = add(device2); assertEquals(info(display2).address, info(device2).address); - assertEquals(Display.DEFAULT_DISPLAY, id(display2)); + assertEquals(DEFAULT_DISPLAY, id(display2)); } @Test @@ -171,12 +177,12 @@ public class LogicalDisplayMapperTest { LogicalDisplay display1 = add(device1); assertEquals(info(display1).address, info(device1).address); - assertEquals(Display.DEFAULT_DISPLAY, id(display1)); + assertEquals(DEFAULT_DISPLAY, id(display1)); LogicalDisplay display2 = add(device2); assertEquals(info(display2).address, info(device2).address); // Despite the flags, we can only have one default display - assertNotEquals(Display.DEFAULT_DISPLAY, id(display2)); + assertNotEquals(DEFAULT_DISPLAY, id(display2)); } @Test @@ -189,7 +195,67 @@ public class LogicalDisplayMapperTest { int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID); assertEquals(3, ids.length); Arrays.sort(ids); - assertEquals(Display.DEFAULT_DISPLAY, ids[0]); + assertEquals(DEFAULT_DISPLAY, ids[0]); + } + + @Test + public void testGetDisplayInfoForStateLocked_oneDisplayGroup_internalType() { + add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_INTERNAL, 700, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + + Set displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked( + DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP); + assertThat(displayInfos.size()).isEqualTo(3); + for (DisplayInfo displayInfo : displayInfos) { + assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY); + assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP); + assertThat(displayInfo.logicalWidth).isAnyOf(600, 200, 700); + assertThat(displayInfo.logicalHeight).isEqualTo(800); + } + } + + @Test + public void testGetDisplayInfoForStateLocked_oneDisplayGroup_differentTypes() { + add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_EXTERNAL, 700, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + + Set displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked( + DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP); + assertThat(displayInfos.size()).isEqualTo(2); + for (DisplayInfo displayInfo : displayInfos) { + assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY); + assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP); + assertThat(displayInfo.logicalWidth).isAnyOf(600, 200); + assertThat(displayInfo.logicalHeight).isEqualTo(800); + } + } + + @Test + public void testGetDisplayInfoForStateLocked_multipleDisplayGroups_defaultGroup() { + add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_VIRTUAL, 700, 800, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP)); + + Set displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked( + DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP); + assertThat(displayInfos.size()).isEqualTo(2); + for (DisplayInfo displayInfo : displayInfos) { + assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY); + assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP); + assertThat(displayInfo.logicalWidth).isAnyOf(600, 200); + assertThat(displayInfo.logicalHeight).isEqualTo(800); + } } @Test @@ -199,11 +265,11 @@ public class LogicalDisplayMapperTest { LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0)); LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0)); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1))); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2))); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); } @@ -218,11 +284,11 @@ public class LogicalDisplayMapperTest { DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); LogicalDisplay display3 = add(device3); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1))); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2))); - assertNotEquals(Display.DEFAULT_DISPLAY_GROUP, + assertNotEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); // Now switch it back to the default group by removing the flag and issuing an update @@ -231,7 +297,7 @@ public class LogicalDisplayMapperTest { mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); // Verify the new group is correct. - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); } @@ -287,14 +353,14 @@ public class LogicalDisplayMapperTest { // add LogicalDisplay displayAdded = add(device); assertEquals(info(displayAdded).address, info(device).address); - assertNotEquals(Display.DEFAULT_DISPLAY, id(displayAdded)); + assertNotEquals(DEFAULT_DISPLAY, id(displayAdded)); // remove mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED); verify(mListenerMock).onLogicalDisplayEventLocked( mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED)); LogicalDisplay displayRemoved = mDisplayCaptor.getValue(); - assertNotEquals(Display.DEFAULT_DISPLAY, id(displayRemoved)); + assertNotEquals(DEFAULT_DISPLAY, id(displayRemoved)); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java new file mode 100644 index 0000000000000..6e0056821aab2 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.FLAG_PRESENTATION; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; +import android.view.DisplayInfo; + +import androidx.test.filters.MediumTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Set; + + +/** + * Tests for {@link PossibleDisplayInfoMapper}. + * + * Build/Install/Run: + * atest WmTests:PossibleDisplayInfoMapperTests + */ +@MediumTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class PossibleDisplayInfoMapperTests extends WindowTestsBase { + + private PossibleDisplayInfoMapper mDisplayInfoMapper; + private final Set mPossibleDisplayInfo = new ArraySet<>(); + private DisplayInfo mDefaultDisplayInfo; + private DisplayInfo mSecondDisplayInfo; + + @Before + public void setUp() throws Exception { + mDisplayInfoMapper = mWm.mPossibleDisplayInfoMapper; + final DisplayInfo baseDisplayInfo = mWm.mRoot.getDisplayContent( + DEFAULT_DISPLAY).getDisplayInfo(); + when(mWm.mDisplayManagerInternal.getPossibleDisplayInfo(anyInt())).thenReturn( + mPossibleDisplayInfo); + + mDefaultDisplayInfo = new DisplayInfo(baseDisplayInfo); + initializeDisplayInfo(mDefaultDisplayInfo, DEFAULT_DISPLAY, new Rect(0, 0, 500, 800)); + mSecondDisplayInfo = new DisplayInfo(baseDisplayInfo); + // Use the same display id for any display in the same group, due to the assumption that + // any display in the same grouped can be swapped out for each other (while maintaining the + // display id). + initializeDisplayInfo(mSecondDisplayInfo, DEFAULT_DISPLAY, new Rect(0, 0, 600, 1600)); + mSecondDisplayInfo.flags |= FLAG_PRESENTATION; + } + + @Test + public void testInitialization_isEmpty() { + // Empty after initializing. + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY)).isEmpty(); + + // Still empty after updating. + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY)).isEmpty(); + } + + @Test + public void testUpdatePossibleDisplayInfos_singleDisplay() { + mPossibleDisplayInfo.add(mDefaultDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + + Set displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); + // An entry for each possible rotation, for a display that can be in a single state. + assertThat(displayInfos.size()).isEqualTo(4); + assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); + } + + @Test + public void testUpdatePossibleDisplayInfos_secondDisplayAdded_sameGroup() { + mPossibleDisplayInfo.add(mDefaultDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4); + + // Add another display layout to the set of supported states. + mPossibleDisplayInfo.add(mSecondDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + + Set displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); + Set defaultDisplayInfos = new ArraySet<>(); + Set secondDisplayInfos = new ArraySet<>(); + for (DisplayInfo di : displayInfos) { + if ((di.flags & FLAG_PRESENTATION) != 0) { + secondDisplayInfos.add(di); + } else { + defaultDisplayInfos.add(di); + } + } + // An entry for each possible rotation, for the default display. + assertThat(defaultDisplayInfos).hasSize(4); + assertPossibleDisplayInfoEntries(defaultDisplayInfos, mDefaultDisplayInfo); + + // An entry for each possible rotation, for the second display. + assertThat(secondDisplayInfos).hasSize(4); + assertPossibleDisplayInfoEntries(secondDisplayInfos, mSecondDisplayInfo); + } + + @Test + public void testUpdatePossibleDisplayInfos_secondDisplayAdded_differentGroup() { + mPossibleDisplayInfo.add(mDefaultDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4); + + // Add another display to a different group. + mSecondDisplayInfo.displayId = DEFAULT_DISPLAY + 1; + mSecondDisplayInfo.displayGroupId = mDefaultDisplayInfo.displayGroupId + 1; + mPossibleDisplayInfo.add(mSecondDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(mSecondDisplayInfo.displayId); + + Set displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); + // An entry for each possible rotation, for the default display. + assertThat(displayInfos).hasSize(4); + assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); + + Set secondStateEntries = + mDisplayInfoMapper.getPossibleDisplayInfos(mSecondDisplayInfo.displayId); + // An entry for each possible rotation, for the second display. + assertThat(secondStateEntries).hasSize(4); + assertPossibleDisplayInfoEntries(secondStateEntries, mSecondDisplayInfo); + } + + private static void initializeDisplayInfo(DisplayInfo outDisplayInfo, int displayId, + Rect logicalBounds) { + outDisplayInfo.displayId = displayId; + outDisplayInfo.rotation = ROTATION_0; + outDisplayInfo.logicalWidth = logicalBounds.width(); + outDisplayInfo.logicalHeight = logicalBounds.height(); + } + + private static void assertPossibleDisplayInfoEntries(Set displayInfos, + DisplayInfo expectedDisplayInfo) { + boolean[] seenEveryRotation = new boolean[4]; + for (DisplayInfo displayInfo : displayInfos) { + final int rotation = displayInfo.rotation; + seenEveryRotation[rotation] = true; + assertThat(displayInfo.displayId).isEqualTo(expectedDisplayInfo.displayId); + assertEqualsRotatedDisplayInfo(displayInfo, expectedDisplayInfo); + } + assertThat(seenEveryRotation).isEqualTo(new boolean[]{true, true, true, true}); + } + + private static void assertEqualsRotatedDisplayInfo(DisplayInfo actual, DisplayInfo expected) { + if (actual.rotation == ROTATION_0 || actual.rotation == ROTATION_180) { + assertThat(actual.logicalWidth).isEqualTo(expected.logicalWidth); + assertThat(actual.logicalHeight).isEqualTo(expected.logicalHeight); + } else { + assertThat(actual.logicalWidth).isEqualTo(expected.logicalHeight); + assertThat(actual.logicalHeight).isEqualTo(expected.logicalWidth); + } + } +}