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