diff --git a/src/com/android/settings/connecteddevice/display/DisplayDevice.kt b/src/com/android/settings/connecteddevice/display/DisplayDevice.kt new file mode 100644 index 00000000000..e8e1110a093 --- /dev/null +++ b/src/com/android/settings/connecteddevice/display/DisplayDevice.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2025 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.settings.connecteddevice.display + +import android.view.Display.Mode + +import androidx.annotation.Keep + +enum class DisplayIsEnabled { YES, NO, UNKNOWN } + +/** + * Contains essential information from {@link android.view.Display} needed by the user to configure + * a display. + */ +@Keep +data class DisplayDevice(val id: Int, val name: String, val mode: Mode?, + val supportedModes: List, val isEnabled: DisplayIsEnabled) {} diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java index 58b2fdd24fa..a7d92930b06 100644 --- a/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java +++ b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java @@ -19,7 +19,6 @@ package com.android.settings.connecteddevice.display; import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DISPLAY_ID_ARG; import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_HELP_URL; import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE; -import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed; import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplaySizeSettingEnabled; import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isResolutionSettingEnabled; import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isRotationSettingEnabled; @@ -32,7 +31,6 @@ import android.content.Context; import android.os.Bundle; import android.os.SystemClock; import android.view.Choreographer; -import android.view.Display; import android.view.View; import android.widget.SeekBar; import android.widget.TextView; @@ -59,7 +57,6 @@ import com.android.settingslib.widget.FooterPreference; import com.android.settingslib.widget.IllustrationPreference; import com.android.settingslib.widget.MainSwitchPreference; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -398,7 +395,8 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen } private void updateScreen(final PrefRefresh screen, Context context) { - final var displaysToShow = externalDisplaysToShow(); + final var displaysToShow = mInjector == null + ? List.of() : mInjector.getConnectedDisplays(); if (displaysToShow.isEmpty()) { showTextWhenNoDisplaysToShow(screen, context, /* position= */ 0); @@ -438,19 +436,18 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen return category; } - private void showDisplaySettings(Display display, PrefRefresh refresh, + private void showDisplaySettings(DisplayDevice display, PrefRefresh refresh, Context context, boolean includeV1Helpers, int position) { - final var isEnabled = mInjector != null && mInjector.isDisplayEnabled(display); if (isUseDisplaySettingEnabled(mInjector)) { - addUseDisplayPreferenceForDisplay(context, refresh, display, isEnabled, position); + addUseDisplayPreferenceForDisplay(context, refresh, display, position); } - final var displayRotation = getDisplayRotation(display.getDisplayId()); - if (includeV1Helpers && isEnabled) { + final var displayRotation = getDisplayRotation(display.getId()); + if (includeV1Helpers && display.isEnabled() == DisplayIsEnabled.YES) { addIllustrationImage(context, refresh, displayRotation); } - addResolutionPreference(context, refresh, display, position, isEnabled); - addRotationPreference(context, refresh, display, displayRotation, position, isEnabled); + addResolutionPreference(context, refresh, display, position); + addRotationPreference(context, refresh, display, displayRotation, position); if (isResolutionSettingEnabled(mInjector)) { // Do not show the footer about changing resolution affecting apps. This is not in the // UX design for v2, and there is no good place to put it, since (a) if it is on the @@ -462,13 +459,13 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen // inconsistent with the topology pane, which shows that display. // TODO(b/352648432): probably remove footer once the pane and rest of v2 UI is in // place. - if (includeV1Helpers && isEnabled) { + if (includeV1Helpers && display.isEnabled() == DisplayIsEnabled.YES) { addFooterPreference( context, refresh, EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE); } } if (isDisplaySizeSettingEnabled(mInjector)) { - addSizePreference(context, refresh, display.getDisplayId(), position, isEnabled); + addSizePreference(context, refresh, display, position); } } @@ -485,7 +482,7 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen } } - private void showDisplaysList(@NonNull List displaysToShow, + private void showDisplaysList(@NonNull List displaysToShow, @NonNull PrefRefresh screen, @NonNull Context context) { maybeAddV2Components(context, screen); int position = 0; @@ -504,19 +501,6 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen } } - private List externalDisplaysToShow() { - if (mInjector == null) { - return List.of(); - } - var displaysToShow = new ArrayList(); - for (var display : mInjector.getAllDisplays()) { - if (display != null && isDisplayAllowed(display, mInjector)) { - displaysToShow.add(display); - } - } - return displaysToShow; - } - private void addUseDisplayPreferenceNoDisplaysFound(Context context, PrefRefresh refresh, int position) { final var pref = reuseUseDisplayPreference(context, refresh, position); @@ -526,9 +510,9 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen } private void addUseDisplayPreferenceForDisplay(final Context context, - PrefRefresh refresh, final Display display, boolean isEnabled, int position) { + PrefRefresh refresh, final DisplayDevice display, int position) { final var pref = reuseUseDisplayPreference(context, refresh, position); - pref.setChecked(isEnabled); + pref.setChecked(display.isEnabled() == DisplayIsEnabled.YES); pref.setEnabled(true); pref.setOnPreferenceChangeListener((p, newValue) -> { writePreferenceClickMetric(p); @@ -537,9 +521,9 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen return false; } if ((Boolean) newValue) { - result = mInjector.enableConnectedDisplay(display.getDisplayId()); + result = mInjector.enableConnectedDisplay(display.getId()); } else { - result = mInjector.disableConnectedDisplay(display.getDisplayId()); + result = mInjector.disableConnectedDisplay(display.getId()); } if (result) { pref.setChecked((Boolean) newValue); @@ -559,7 +543,7 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen } private void addRotationPreference(final Context context, PrefRefresh refresh, - final Display display, final int displayRotation, int position, boolean isEnabled) { + final DisplayDevice display, final int displayRotation, int position) { var pref = reuseRotationPreference(context, refresh, position); if (mRotationEntries == null || mRotationEntriesValues == null) { mRotationEntries = new String[] { @@ -576,39 +560,41 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen pref.setOnPreferenceChangeListener((p, newValue) -> { writePreferenceClickMetric(p); var rotation = Integer.parseInt((String) newValue); - var displayId = display.getDisplayId(); + var displayId = display.getId(); if (mInjector == null || !mInjector.freezeDisplayRotation(displayId, rotation)) { return false; } pref.setValueIndex(rotation); return true; }); - pref.setEnabled(isEnabled && isRotationSettingEnabled(mInjector)); + pref.setEnabled(display.isEnabled() == DisplayIsEnabled.YES + && isRotationSettingEnabled(mInjector)); } private void addResolutionPreference(final Context context, PrefRefresh refresh, - final Display display, int position, boolean isEnabled) { + final DisplayDevice display, int position) { var pref = reuseResolutionPreference(context, refresh, position); pref.setSummary(display.getMode().getPhysicalWidth() + " x " + display.getMode().getPhysicalHeight()); pref.setOnPreferenceClickListener((Preference p) -> { writePreferenceClickMetric(p); - launchResolutionSelector(context, display.getDisplayId()); + launchResolutionSelector(context, display.getId()); return true; }); - pref.setEnabled(isEnabled && isResolutionSettingEnabled(mInjector)); + pref.setEnabled(display.isEnabled() == DisplayIsEnabled.YES + && isResolutionSettingEnabled(mInjector)); } - private void addSizePreference(final Context context, PrefRefresh refresh, int displayId, - int position, boolean isEnabled) { - var pref = reuseSizePreference(context, refresh, displayId, position); + private void addSizePreference(final Context context, PrefRefresh refresh, + DisplayDevice display, int position) { + var pref = reuseSizePreference(context, refresh, display.getId(), position); pref.setSummary(EXTERNAL_DISPLAY_SIZE_SUMMARY_RESOURCE); pref.setOnPreferenceClickListener( (Preference p) -> { writePreferenceClickMetric(p); return true; }); - pref.setEnabled(isEnabled); + pref.setEnabled(display.isEnabled() == DisplayIsEnabled.YES); } private int getDisplayRotation(int displayId) { diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java b/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java index 666bf37c54a..1148aa59704 100644 --- a/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java +++ b/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java @@ -44,6 +44,10 @@ import com.android.settings.R; import com.android.settings.flags.FeatureFlags; import com.android.settings.flags.FeatureFlagsImpl; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + public class ExternalDisplaySettingsConfiguration { static final String VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY = "persist.demo.userrotation.package_name"; @@ -114,40 +118,57 @@ public class ExternalDisplaySettingsConfiguration { mHandler = handler; } + private static DisplayDevice wrapDmDisplay(Display display, DisplayIsEnabled isEnabled) { + return new DisplayDevice(display.getDisplayId(), display.getName(), + display.getMode(), List.of(display.getSupportedModes()), isEnabled); + } + /** * @return all displays including disabled. */ @NonNull - public Display[] getAllDisplays() { + public List getConnectedDisplays() { var dm = getDisplayManager(); if (dm == null) { - return new Display[0]; + return List.of(); } - return dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); - } - /** - * @return enabled displays only. - */ - @NonNull - public Display[] getEnabledDisplays() { - var dm = getDisplayManager(); - if (dm == null) { - return new Display[0]; + var enabledIds = new HashSet(); + for (Display d : dm.getDisplays()) { + enabledIds.add(d.getDisplayId()); } - return dm.getDisplays(); - } - /** - * @return true if the display is enabled - */ - public boolean isDisplayEnabled(@NonNull Display display) { - for (var enabledDisplay : getEnabledDisplays()) { - if (enabledDisplay.getDisplayId() == display.getDisplayId()) { - return true; + var displays = new ArrayList(); + for (Display d : dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) { + if (!isDisplayAllowed(d, this)) { + continue; } + var isEnabled = enabledIds.contains(d.getDisplayId()) + ? DisplayIsEnabled.YES : DisplayIsEnabled.NO; + displays.add(wrapDmDisplay(d, isEnabled)); } - return false; + return displays; + } + + /** + * @param displayId which must be returned + * @return display object for the displayId, or null if display is not a connected display, + * the ID was not found, or the ID was invalid + */ + @Nullable + public DisplayDevice getDisplay(int displayId) { + if (displayId == INVALID_DISPLAY) { + return null; + } + var dm = getDisplayManager(); + if (dm == null) { + return null; + } + var display = dm.getDisplay(displayId); + if (display == null || !isDisplayAllowed(display, this)) { + return null; + } + return wrapDmDisplay(display, DisplayIsEnabled.UNKNOWN); } /** @@ -206,22 +227,6 @@ public class ExternalDisplaySettingsConfiguration { return true; } - /** - * @param displayId which must be returned - * @return display object for the displayId - */ - @Nullable - public Display getDisplay(int displayId) { - if (displayId == INVALID_DISPLAY) { - return null; - } - var dm = getDisplayManager(); - if (dm == null) { - return null; - } - return dm.getDisplay(displayId); - } - /** * @return handler */ @@ -323,8 +328,7 @@ public class ExternalDisplaySettingsConfiguration { || flags.displayTopologyPaneInDisplayList(); } - static boolean isDisplayAllowed(@NonNull Display display, - @NonNull SystemServicesProvider props) { + private static boolean isDisplayAllowed(Display display, SystemServicesProvider props) { return display.getType() == Display.TYPE_EXTERNAL || display.getType() == Display.TYPE_OVERLAY || isVirtualDisplayAllowed(display, props); @@ -334,7 +338,7 @@ public class ExternalDisplaySettingsConfiguration { return injector != null && injector.getFlags().displayTopologyPaneInDisplayList(); } - static boolean isVirtualDisplayAllowed(@NonNull Display display, + private static boolean isVirtualDisplayAllowed(@NonNull Display display, @NonNull SystemServicesProvider properties) { var sysProp = properties.getSystemProperty(VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY); return !sysProp.isEmpty() && display.getType() == Display.TYPE_VIRTUAL diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdater.java b/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdater.java index 01dc1daf370..8a578c289bc 100644 --- a/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdater.java +++ b/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdater.java @@ -16,8 +16,6 @@ package com.android.settings.connecteddevice.display; -import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed; - import android.content.Context; import android.graphics.drawable.Drawable; import android.os.UserHandle; @@ -134,19 +132,13 @@ public class ExternalDisplayUpdater { return null; } - for (var display : mInjector.getEnabledDisplays()) { - if (display != null && isDisplayAllowed(display, mInjector)) { + var allDisplays = mInjector.getConnectedDisplays(); + for (var display : allDisplays) { + if (display.isEnabled() == DisplayIsEnabled.YES) { return context.getString(R.string.external_display_on); } } - - for (var display : mInjector.getAllDisplays()) { - if (display != null && isDisplayAllowed(display, mInjector)) { - return context.getString(R.string.external_display_off); - } - } - - return null; + return allDisplays.isEmpty() ? null : context.getString(R.string.external_display_off); } /** diff --git a/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragment.java b/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragment.java index db81be89a42..851ee2f516f 100644 --- a/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragment.java +++ b/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragment.java @@ -21,7 +21,6 @@ import static android.view.Display.INVALID_DISPLAY; import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DISPLAY_ID_ARG; import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_HELP_URL; import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE; -import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed; import android.app.settings.SettingsEnums; import android.content.Context; @@ -29,7 +28,6 @@ import android.content.res.Resources; import android.os.Bundle; import android.util.Log; import android.util.Pair; -import android.view.Display; import android.view.Display.Mode; import android.view.View; import android.widget.TextView; @@ -50,6 +48,7 @@ import com.android.settingslib.widget.SelectorWithWidgetPreference; import java.util.ArrayList; import java.util.HashSet; +import java.util.List; public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase { private static final String TAG = "ResolutionPreference"; @@ -164,7 +163,7 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase return; } var display = mInjector.getDisplay(getDisplayIdArg()); - if (display == null || !isDisplayAllowed(display, mInjector)) { + if (display == null) { screen.removeAll(); mTopOptionsPreference = null; mMoreOptionsPreference = null; @@ -210,9 +209,9 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase } private void addRemainingPreferences(@NonNull Context context, - @NonNull PreferenceCategory group, @NonNull Display display, - boolean isSelectedModeFound, @NonNull Mode[] moreModes) { - if (moreModes.length == 0) { + @NonNull PreferenceCategory group, @NonNull DisplayDevice display, + boolean isSelectedModeFound, @NonNull List moreModes) { + if (moreModes.isEmpty()) { return; } mMoreOptionsExpanded |= !isSelectedModeFound; @@ -220,12 +219,12 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase addModePreferences(context, group, moreModes, /*checkMode=*/ null, display); } - private Pair addModePreferences(@NonNull Context context, + private Pair> addModePreferences(@NonNull Context context, @NonNull PreferenceGroup group, - @NonNull Mode[] modes, + @NonNull List modes, @Nullable ToBooleanFunction checkMode, - @NonNull Display display) { - Display.Mode curMode = display.getMode(); + @NonNull DisplayDevice display) { + Mode curMode = display.getMode(); var currentResolution = curMode.getPhysicalWidth() + "x" + curMode.getPhysicalHeight(); var rotatedResolution = curMode.getPhysicalHeight() + "x" + curMode.getPhysicalWidth(); var skippedModes = new ArrayList(); @@ -260,7 +259,7 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase isAnyOfModesSelected |= isCurrentMode; group.addPreference(pref); } - return new Pair<>(isAnyOfModesSelected, skippedModes.toArray(Mode.EMPTY_ARRAY)); + return new Pair<>(isAnyOfModesSelected, skippedModes); } private boolean isTopMode(@NonNull Mode mode) { @@ -309,7 +308,7 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase } private void onDisplayModeClicked(@NonNull SelectorWithWidgetPreference preference, - @NonNull Display display) { + @NonNull DisplayDevice display) { if (mInjector == null) { return; } @@ -319,7 +318,7 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase for (var mode : display.getSupportedModes()) { if (mode.getPhysicalWidth() == width && mode.getPhysicalHeight() == height && isAllowedMode(mode)) { - mInjector.setUserPreferredDisplayMode(display.getDisplayId(), mode); + mInjector.setUserPreferredDisplayMode(display.getId(), mode); return; } } diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java similarity index 92% rename from tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java rename to tests/robotests/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java index 9532947a66b..fe1589d1ffc 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java @@ -27,7 +27,6 @@ import static com.android.settings.flags.Flags.FLAG_DISPLAY_TOPOLOGY_PANE_IN_DIS import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -38,7 +37,6 @@ import static org.mockito.Mockito.verify; import android.app.Activity; import android.content.Context; import android.os.Bundle; -import android.view.Display; import android.view.View; import android.widget.TextView; @@ -58,6 +56,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import java.util.List; + /** Unit tests for {@link ExternalDisplayPreferenceFragment}. */ @RunWith(AndroidJUnit4.class) public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBase { @@ -103,10 +103,10 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa assertDisplayListCount(0); - verify(mMockedInjector, never()).getAllDisplays(); + verify(mMockedInjector, never()).getConnectedDisplays(); mHandler.flush(); assertThat(mHandler.getPendingMessages().size()).isEqualTo(0); - verify(mMockedInjector).getAllDisplays(); + verify(mMockedInjector).getConnectedDisplays(); assertDisplayListCount(2); Preference pref = mPreferenceScreen.findPreference(PrefBasics.DISPLAY_TOPOLOGY.key); @@ -122,7 +122,7 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa mFlags.setFlag(FLAG_DISPLAY_TOPOLOGY_PANE_IN_DISPLAY_LIST, true); initFragment(); - doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays(); + doReturn(List.of(mDisplays.get(0))).when(mMockedInjector).getConnectedDisplays(); mHandler.flush(); var pref = mPreferenceScreen.findPreference(PrefBasics.DISPLAY_TOPOLOGY.key); @@ -147,7 +147,7 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa mFlags.setFlag(FLAG_DISPLAY_TOPOLOGY_PANE_IN_DISPLAY_LIST, true); initFragment(); - doReturn(new Display[0]).when(mMockedInjector).getAllDisplays(); + doReturn(List.of()).when(mMockedInjector).getConnectedDisplays(); mHandler.flush(); // When no external display is attached, interactive preferences are omitted. @@ -165,11 +165,10 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @Test @UiThreadTest public void testShowDisplayControlsDisabled() { - doReturn(new Display[] {mDisplays[0], mDisplays[2]}).when(mMockedInjector) - .getEnabledDisplays(); - doReturn(true).when(mMockedInjector).isDisplayEnabled(mDisplays[0]); - doReturn(false).when(mMockedInjector).isDisplayEnabled(mDisplays[1]); - doReturn(true).when(mMockedInjector).isDisplayEnabled(mDisplays[2]); + doReturn(List.of( + createExternalDisplay(DisplayIsEnabled.NO), + createOverlayDisplay(DisplayIsEnabled.YES))) + .when(mMockedInjector).getConnectedDisplays(); initFragment(); mHandler.flush(); @@ -194,7 +193,6 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @UiThreadTest public void testLaunchDisplaySettingFromList() { initFragment(); - doReturn(true).when(mMockedInjector).isDisplayEnabled(any()); mHandler.flush(); assertDisplayListCount(2); var display1Category = getExternalDisplayCategory(0); @@ -219,9 +217,9 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa public void testShowDisplayListForOnlyOneDisplay_PreviouslyShownList() { var fragment = initFragment(); // Only one display available - doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays(); + doReturn(List.of(mDisplays.get(0))).when(mMockedInjector).getConnectedDisplays(); mHandler.flush(); - int attachedId = mDisplays[1].getDisplayId(); + int attachedId = mDisplays.get(0).getId(); assertDisplayListCount(1); assertThat("" + getExternalDisplayCategory(0).getTitle()).isEqualTo("HDMI"); } @@ -230,9 +228,8 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @UiThreadTest public void testShowEnabledDisplay_OnlyOneDisplayAvailable_displaySizeDisabled() { mFlags.setFlag(FLAG_DISPLAY_SIZE_CONNECTED_DISPLAY_SETTING, false); - doReturn(true).when(mMockedInjector).isDisplayEnabled(any()); // Only one display available - doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays(); + doReturn(List.of(mDisplays.get(0))).when(mMockedInjector).getConnectedDisplays(); // Init initFragment(); mHandler.flush(); @@ -253,9 +250,8 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @Test @UiThreadTest public void testShowEnabledDisplay_OnlyOneDisplayAvailable() { - doReturn(true).when(mMockedInjector).isDisplayEnabled(any()); // Only one display available - doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays(); + doReturn(List.of(mDisplays.get(0))).when(mMockedInjector).getConnectedDisplays(); // Init initFragment(); mHandler.flush(); @@ -273,12 +269,11 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @Test @UiThreadTest public void testShowOneEnabledDisplay_FewAvailable() { - doReturn(true).when(mMockedInjector).isDisplayEnabled(any()); initFragment(); - verify(mMockedInjector, never()).getAllDisplays(); + verify(mMockedInjector, never()).getConnectedDisplays(); mHandler.flush(); verify(mMockedInjector, never()).getDisplay(anyInt()); - verify(mMockedInjector).getAllDisplays(); + verify(mMockedInjector).getConnectedDisplays(); var pref = mPreferenceScreen.findPreference( PrefBasics.EXTERNAL_DISPLAY_RESOLUTION.keyForNth(0)); assertThat(pref).isNotNull(); @@ -296,9 +291,13 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @UiThreadTest public void testShowDisabledDisplay() { initFragment(); + var disabledDisplays = List.of( + createExternalDisplay(DisplayIsEnabled.NO), + createOverlayDisplay(DisplayIsEnabled.NO)); + doReturn(disabledDisplays).when(mMockedInjector).getConnectedDisplays(); mHandler.flush(); verify(mMockedInjector, never()).getDisplay(anyInt()); - verify(mMockedInjector).getAllDisplays(); + verify(mMockedInjector).getConnectedDisplays(); var category = getExternalDisplayCategory(0); var mainPref = (MainSwitchPreference) category.findPreference( PrefBasics.EXTERNAL_DISPLAY_USE.keyForNth(0)); @@ -321,7 +320,7 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @Test @UiThreadTest public void testNoDisplays() { - doReturn(new Display[0]).when(mMockedInjector).getAllDisplays(); + doReturn(List.of()).when(mMockedInjector).getConnectedDisplays(); initFragment(); mHandler.flush(); var mainPref = (MainSwitchPreference) mPreferenceScreen.findPreference( @@ -342,7 +341,6 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @UiThreadTest public void testDisplayRotationPreference() { final int displayId = 1; - doReturn(true).when(mMockedInjector).isDisplayEnabled(any()); var fragment = initFragment(); mHandler.flush(); var category = getExternalDisplayCategory(0); @@ -375,7 +373,6 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @UiThreadTest public void testDisplayResolutionPreference() { final int displayId = 1; - doReturn(true).when(mMockedInjector).isDisplayEnabled(any()); var fragment = initFragment(); mHandler.flush(); var category = getExternalDisplayCategory(0); @@ -393,7 +390,6 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @Test @UiThreadTest public void testDisplaySizePreference() { - doReturn(true).when(mMockedInjector).isDisplayEnabled(any()); var fragment = initFragment(); mHandler.flush(); var category = getExternalDisplayCategory(0); @@ -412,7 +408,6 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @UiThreadTest public void testUseDisplayPreference_EnabledDisplay() { final int displayId = 1; - doReturn(true).when(mMockedInjector).isDisplayEnabled(any()); doReturn(true).when(mMockedInjector).enableConnectedDisplay(displayId); doReturn(true).when(mMockedInjector).disableConnectedDisplay(displayId); var fragment = initFragment(); diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java b/tests/robotests/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java similarity index 54% rename from tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java rename to tests/robotests/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java index fcc3daa4efc..f011f859054 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java @@ -26,21 +26,19 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.robolectric.Shadows.shadowOf; import android.content.Context; import android.content.res.Resources; -import android.hardware.display.DisplayManagerGlobal; -import android.hardware.display.IDisplayManager; +import android.os.Handler; +import android.os.Message; import android.os.RemoteException; -import android.view.Display; -import android.view.DisplayAdjustments; -import android.view.DisplayInfo; +import android.view.Display.Mode; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; -import com.android.server.testutils.TestHandler; import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener; import com.android.settings.flags.FakeFeatureFlagsImpl; @@ -48,6 +46,9 @@ import org.junit.Before; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayDeque; +import java.util.List; + public class ExternalDisplayTestBase { static final int EXTERNAL_DISPLAY_ID = 1; static final int OVERLAY_DISPLAY_ID = 2; @@ -55,16 +56,46 @@ public class ExternalDisplayTestBase { @Mock ExternalDisplaySettingsConfiguration.Injector mMockedInjector; @Mock - IDisplayManager mMockedIDisplayManager; Resources mResources; - DisplayManagerGlobal mDisplayManagerGlobal; FakeFeatureFlagsImpl mFlags = new FakeFeatureFlagsImpl(); Context mContext; DisplayListener mListener; - TestHandler mHandler = new TestHandler(null); + TestHandler mHandler; PreferenceManager mPreferenceManager; PreferenceScreen mPreferenceScreen; - Display[] mDisplays; + List mDisplays; + + static class TestHandler extends Handler { + private final ArrayDeque mPending = new ArrayDeque<>(); + private final Handler mSubhandler; + + TestHandler(Handler subhandler) { + mSubhandler = subhandler; + } + + ArrayDeque getPendingMessages() { + return mPending; + } + + /** + * Schedules to send the message upon next invocation of {@link #flush()}. This ignores the + * time argument since our code doesn't meaningfully use it, but this is the most convenient + * way to intercept both Message and Callback objects synchronously. + */ + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + mPending.add(msg); + return true; + } + + void flush() { + for (var msg : mPending) { + mSubhandler.sendMessage(msg); + } + mPending.clear(); + shadowOf(mSubhandler.getLooper()).idle(); + } + } /** * Setup. @@ -77,20 +108,19 @@ public class ExternalDisplayTestBase { doReturn(mResources).when(mContext).getResources(); mPreferenceManager = new PreferenceManager(mContext); mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext); - doReturn(0).when(mMockedIDisplayManager).getPreferredWideGamutColorSpaceId(); - mDisplayManagerGlobal = new DisplayManagerGlobal(mMockedIDisplayManager); mFlags.setFlag(FLAG_DISPLAY_TOPOLOGY_PANE_IN_DISPLAY_LIST, false); mFlags.setFlag(FLAG_ROTATION_CONNECTED_DISPLAY_SETTING, true); mFlags.setFlag(FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING, true); mFlags.setFlag(FLAG_DISPLAY_SIZE_CONNECTED_DISPLAY_SETTING, true); - mDisplays = new Display[] { - createDefaultDisplay(), createExternalDisplay(), createOverlayDisplay()}; - doReturn(mDisplays).when(mMockedInjector).getAllDisplays(); - doReturn(mDisplays).when(mMockedInjector).getEnabledDisplays(); + mDisplays = List.of( + createExternalDisplay(DisplayIsEnabled.YES), + createOverlayDisplay(DisplayIsEnabled.YES)); + doReturn(mDisplays).when(mMockedInjector).getConnectedDisplays(); for (var display : mDisplays) { - doReturn(display).when(mMockedInjector).getDisplay(display.getDisplayId()); + doReturn(display).when(mMockedInjector).getDisplay(display.getId()); } doReturn(mFlags).when(mMockedInjector).getFlags(); + mHandler = new TestHandler(mContext.getMainThreadHandler()); doReturn(mHandler).when(mMockedInjector).getHandler(); doReturn("").when(mMockedInjector).getSystemProperty( VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY); @@ -103,55 +133,26 @@ public class ExternalDisplayTestBase { doReturn(mContext).when(mMockedInjector).getContext(); } - Display createDefaultDisplay() throws RemoteException { - int displayId = 0; - var displayInfo = new DisplayInfo(); - doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId); - displayInfo.displayId = displayId; - displayInfo.name = "Built-in"; - displayInfo.type = Display.TYPE_INTERNAL; - displayInfo.supportedModes = new Display.Mode[]{ - new Display.Mode(0, 2048, 1024, 60, 60, new float[0], - new int[0])}; - displayInfo.appsSupportedModes = displayInfo.supportedModes; - return createDisplay(displayInfo); - } - - Display createExternalDisplay() throws RemoteException { + DisplayDevice createExternalDisplay(DisplayIsEnabled isEnabled) { int displayId = EXTERNAL_DISPLAY_ID; - var displayInfo = new DisplayInfo(); - doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId); - displayInfo.displayId = displayId; - displayInfo.name = "HDMI"; - displayInfo.type = Display.TYPE_EXTERNAL; - displayInfo.supportedModes = new Display.Mode[]{ - new Display.Mode(0, 1920, 1080, 60, 60, new float[0], new int[0]), - new Display.Mode(1, 800, 600, 60, 60, new float[0], new int[0]), - new Display.Mode(2, 320, 240, 70, 70, new float[0], new int[0]), - new Display.Mode(3, 640, 480, 60, 60, new float[0], new int[0]), - new Display.Mode(4, 640, 480, 50, 60, new float[0], new int[0]), - new Display.Mode(5, 2048, 1024, 60, 60, new float[0], new int[0]), - new Display.Mode(6, 720, 480, 60, 60, new float[0], new int[0])}; - displayInfo.appsSupportedModes = displayInfo.supportedModes; - return createDisplay(displayInfo); + var supportedModes = List.of( + new Mode(0, 1920, 1080, 60, 60, new float[0], new int[0]), + new Mode(1, 800, 600, 60, 60, new float[0], new int[0]), + new Mode(2, 320, 240, 70, 70, new float[0], new int[0]), + new Mode(3, 640, 480, 60, 60, new float[0], new int[0]), + new Mode(4, 640, 480, 50, 60, new float[0], new int[0]), + new Mode(5, 2048, 1024, 60, 60, new float[0], new int[0]), + new Mode(6, 720, 480, 60, 60, new float[0], new int[0])); + return new DisplayDevice( + displayId, "HDMI", supportedModes.get(0), supportedModes, isEnabled); } - Display createOverlayDisplay() throws RemoteException { + DisplayDevice createOverlayDisplay(DisplayIsEnabled isEnabled) { int displayId = OVERLAY_DISPLAY_ID; - var displayInfo = new DisplayInfo(); - doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId); - displayInfo.displayId = displayId; - displayInfo.name = "Overlay #1"; - displayInfo.type = Display.TYPE_OVERLAY; - displayInfo.supportedModes = new Display.Mode[]{ - new Display.Mode(0, 1240, 780, 60, 60, new float[0], - new int[0])}; - displayInfo.appsSupportedModes = displayInfo.supportedModes; - return createDisplay(displayInfo); - } - - Display createDisplay(DisplayInfo displayInfo) { - return new Display(mDisplayManagerGlobal, displayInfo.displayId, displayInfo, - (DisplayAdjustments) null); + var supportedModes = List.of( + new Mode(0, 1240, 780, 60, 60, new float[0], + new int[0])); + return new DisplayDevice( + displayId, "Overlay #1", supportedModes.get(0), supportedModes, isEnabled); } } diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdaterTest.java similarity index 95% rename from tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdaterTest.java rename to tests/robotests/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdaterTest.java index 698f0a42e20..f121a6c1f3a 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdaterTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.doReturn; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.RemoteException; -import android.view.Display; import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -38,6 +37,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import java.util.List; + /** Unit tests for {@link ExternalDisplayUpdater}. */ @RunWith(AndroidJUnit4.class) public class ExternalDisplayUpdaterTest extends ExternalDisplayTestBase { @@ -92,8 +93,7 @@ public class ExternalDisplayUpdaterTest extends ExternalDisplayTestBase { assertThat(mPreferenceAdded).isNotNull(); assertThat(mPreferenceRemoved).isNull(); // Remove display - doReturn(new Display[0]).when(mMockedInjector).getAllDisplays(); - doReturn(new Display[0]).when(mMockedInjector).getEnabledDisplays(); + doReturn(List.of()).when(mMockedInjector).getConnectedDisplays(); mListener.onDisplayRemoved(1); mHandler.flush(); assertThat(mPreferenceRemoved).isEqualTo(mPreferenceAdded); diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragmentTest.java similarity index 98% rename from tests/unit/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragmentTest.java rename to tests/robotests/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragmentTest.java index c8663622e74..bffdc2d63b8 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragmentTest.java @@ -134,14 +134,15 @@ public class ResolutionPreferenceFragmentTest extends ExternalDisplayTestBase { @Test @UiThreadTest public void testModeChange() { - mDisplayIdArg = 1; + DisplayDevice display = mDisplays.get(0); + mDisplayIdArg = display.getId(); initFragment(); mHandler.flush(); PreferenceCategory topPref = mPreferenceScreen.findPreference(TOP_OPTIONS_KEY); assertThat(topPref).isNotNull(); var modePref = (SelectorWithWidgetPreference) topPref.getPreference(1); modePref.onClick(); - var mode = mDisplays[mDisplayIdArg].getSupportedModes()[1]; + var mode = display.getSupportedModes().get(1); verify(mMockedInjector).setUserPreferredDisplayMode(mDisplayIdArg, mode); }