diff --git a/res/drawable/display_topology_background.xml b/res/drawable/display_topology_background.xml new file mode 100644 index 00000000000..bd0b94f6977 --- /dev/null +++ b/res/drawable/display_topology_background.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/res/layout/display_topology_preference.xml b/res/layout/display_topology_preference.xml new file mode 100644 index 00000000000..d2e430027a5 --- /dev/null +++ b/res/layout/display_topology_preference.xml @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml index 6421e3e42dc..c175ee7e591 100644 --- a/res/values-land/dimens.xml +++ b/res/values-land/dimens.xml @@ -30,4 +30,7 @@ 106dp + + + 80dp diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml index 57453ce5a2c..69f17f64b88 100644 --- a/res/values-night/colors.xml +++ b/res/values-night/colors.xml @@ -79,5 +79,8 @@ #FFFFFF + + + @color/settingslib_color_charcoal diff --git a/res/values/colors.xml b/res/values/colors.xml index 95b38cb5250..91598fe6309 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -221,4 +221,7 @@ #000000 + + + @color/settingslib_color_grey100 diff --git a/res/values/dimens.xml b/res/values/dimens.xml index e69502a4713..d202f850a11 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -539,4 +539,7 @@ 4dp 8dp 18dp + + + 24dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 170e4b63afa..4d51512d29f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2027,6 +2027,8 @@ Your device must be connected to an external display to mirror your screen More options + + Hold and drag to rearrange displays. Cast diff --git a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt new file mode 100644 index 00000000000..81559027837 --- /dev/null +++ b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 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 com.android.settings.R + +import android.content.Context + +import androidx.preference.Preference + +const val PREFERENCE_KEY = "display_topology_preference" + +/** + * DisplayTopologyPreference allows the user to change the display topology + * when there is one or more extended display attached. + */ +class DisplayTopologyPreference(context : Context) : Preference(context) { + init { + layoutResource = R.layout.display_topology_preference + + // Prevent highlight when hovering with mouse. + isSelectable = false + + key = PREFERENCE_KEY + } +} diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java index c53be1aa932..047ffd41da2 100644 --- a/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java +++ b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java @@ -97,6 +97,8 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen @Nullable private FooterPreference mFooterPreference; @Nullable + private Preference mDisplayTopologyPreference; + @Nullable private PreferenceCategory mDisplaysPreference; @Nullable private Injector mInjector; @@ -279,6 +281,14 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen return mDisplaysPreference; } + @NonNull Preference getDisplayTopologyPreference(@NonNull Context context) { + if (mDisplayTopologyPreference == null) { + mDisplayTopologyPreference = new DisplayTopologyPreference(context); + mDisplayTopologyPreference.setPersistent(false); + } + return mDisplayTopologyPreference; + } + private void restoreState(@Nullable Bundle savedInstanceState) { if (savedInstanceState == null) { return; @@ -296,20 +306,16 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen updateScreenForDisplayId(getDisplayIdArg(), screen, mInjector.getContext()); } - private boolean okayToBypassDisplayListSelection() { - if (mInjector != null && forceShowDisplayList(mInjector.getFlags())) { - return false; - } - return !mPreviouslyShownListOfDisplays; - } - private void updateScreenForDisplayId(final int displayId, @NonNull final PreferenceScreen screen, @NonNull Context context) { + final boolean forceShowList = displayId == INVALID_DISPLAY + && mInjector != null && forceShowDisplayList(mInjector.getFlags()); final var displaysToShow = getDisplaysToShow(displayId); - if (displaysToShow.isEmpty() && displayId == INVALID_DISPLAY) { + + if (!forceShowList && displaysToShow.isEmpty() && displayId == INVALID_DISPLAY) { showTextWhenNoDisplaysToShow(screen, context); - } else if (displaysToShow.size() == 1 - && ((displayId == INVALID_DISPLAY && okayToBypassDisplayListSelection()) + } else if (!forceShowList && displaysToShow.size() == 1 + && ((displayId == INVALID_DISPLAY && !mPreviouslyShownListOfDisplays) || displaysToShow.get(0).getDisplayId() == displayId)) { showDisplaySettings(displaysToShow.get(0), screen, context); } else if (displayId == INVALID_DISPLAY) { @@ -367,6 +373,11 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen private void showDisplaysList(@NonNull List displaysToShow, @NonNull PreferenceScreen screen, @NonNull Context context) { + if (mInjector != null + && mInjector.getFlags().displayTopologyPaneInDisplayList()) { + screen.addPreference(getDisplayTopologyPreference(context)); + } + var pref = getDisplaysListPreference(context); pref.setKey(DISPLAYS_LIST_PREFERENCE_KEY); pref.removeAll(); diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java index 019ade7ae58..63652260296 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java +++ b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java @@ -15,7 +15,6 @@ */ package com.android.settings.connecteddevice.display; - import static android.view.Display.INVALID_DISPLAY; import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.PREVIOUSLY_SHOWN_LIST_KEY; @@ -29,6 +28,7 @@ import static com.android.settings.connecteddevice.display.ExternalDisplayPrefer import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_SETTINGS_RESOURCE; import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_USE_PREFERENCE_KEY; import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_USE_TITLE_RESOURCE; +import static com.android.settings.flags.Flags.FLAG_DISPLAY_TOPOLOGY_PANE_IN_DISPLAY_LIST; import static com.android.settingslib.widget.FooterPreference.KEY_FOOTER; import static com.google.common.truth.Truth.assertThat; @@ -85,6 +85,8 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa @Test @UiThreadTest public void testShowDisplayList() { + mFlags.setFlag(FLAG_DISPLAY_TOPOLOGY_PANE_IN_DISPLAY_LIST, false); + var fragment = initFragment(); var outState = new Bundle(); fragment.onSaveInstanceStateCallback(outState); @@ -101,6 +103,46 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa assertThat(pref.getPreferenceCount()).isEqualTo(2); fragment.onSaveInstanceStateCallback(outState); assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isTrue(); + + pref = mPreferenceScreen.findPreference(DisplayTopologyKt.PREFERENCE_KEY); + assertThat(pref).isNull(); + } + + @Test + @UiThreadTest + public void testShowDisplayListWithPane_OneExternalDisplay() { + mFlags.setFlag(FLAG_DISPLAY_TOPOLOGY_PANE_IN_DISPLAY_LIST, true); + + initFragment(); + doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays(); + mHandler.flush(); + + var pref = mPreferenceScreen.findPreference(DisplayTopologyKt.PREFERENCE_KEY); + assertThat(pref).isNotNull(); + + PreferenceCategory listPref = + mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY); + assertThat(listPref).isNotNull(); + assertThat(listPref.getPreferenceCount()).isEqualTo(1); + } + + @Test + @UiThreadTest + public void testShowDisplayListWithPane_NoExternalDisplays() { + mFlags.setFlag(FLAG_DISPLAY_TOPOLOGY_PANE_IN_DISPLAY_LIST, true); + + initFragment(); + doReturn(new Display[0]).when(mMockedInjector).getAllDisplays(); + mHandler.flush(); + + var pref = mPreferenceScreen.findPreference(DisplayTopologyKt.PREFERENCE_KEY); + assertThat(pref).isNotNull(); + + // TODO: add the built-in display to the list, which will cause this preference to not be + // null. + PreferenceCategory listPref = + mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY); + assertThat(listPref).isNull(); } @Test