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); + } + } +}