From 8f051ce5d49b5e320637a3299634ba3822aa10c0 Mon Sep 17 00:00:00 2001 From: Matthew DeVore Date: Fri, 17 Jan 2025 17:40:25 +0000 Subject: [PATCH] external displays: mirror/extend switch Add a mirror/extend built-in display switch. Make minor changes to DisplayTopology.kt for consistency and correctness. Kotlin requires the two preference key names are different since they are in the same namespace, so fix the name in the existing DisplayTopology.kt module. Make DisplayTopologyPreference responsible, rather than the caller, for setting its persistence property, since a wrong value may cause unusual behavior. The setOrder calls are necessary to prevent the new switch from appearing below the Built-in display category when a display is hot-plugged in after showing the UI. We set them on all top-level preferences (not just the two we are fixing) for consistency. Flag: com.android.settings.flags.display_topology_pane_in_display_list Test: atest ExternalDisplayPreferenceFragmentTest.java Bug: b/352648432 Bug: b/366056921 Change-Id: Ib0072dd75066758903cc48c2d1e7142e1d921f67 --- res/values/strings.xml | 2 + .../display/DisplayTopology.kt | 5 +- .../ExternalDisplayPreferenceFragment.java | 17 ++++++- .../connecteddevice/display/Mirroring.kt | 51 +++++++++++++++++++ ...ExternalDisplayPreferenceFragmentTest.java | 10 ++-- 5 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/com/android/settings/connecteddevice/display/Mirroring.kt diff --git a/res/values/strings.xml b/res/values/strings.xml index 9930c3685c0..21a60e1385c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2120,6 +2120,8 @@ More options Hold and drag to rearrange displays. + + Mirror built-in display Cast diff --git a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt index c4f0b29ac00..a5086b108b7 100644 --- a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt +++ b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt @@ -157,7 +157,7 @@ class TopologyScale( } } -const val PREFERENCE_KEY = "display_topology_preference" +const val TOPOLOGY_PREFERENCE_KEY = "display_topology_preference" /** Padding in pane coordinate pixels on each side of a display block. */ const val BLOCK_PADDING = 2f @@ -220,7 +220,8 @@ class DisplayTopologyPreference(context : Context) // Prevent highlight when hovering with mouse. isSelectable = false - key = PREFERENCE_KEY + key = TOPOLOGY_PREFERENCE_KEY + isPersistent = false injector = Injector(context) } diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java index de1363d24cd..1314737839d 100644 --- a/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java +++ b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java @@ -102,6 +102,8 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen @Nullable private Preference mDisplayTopologyPreference; @Nullable + private Preference mMirrorPreference; + @Nullable private PreferenceCategory mDisplaysPreference; @Nullable private PreferenceCategory mBuiltinDisplayPreference; @@ -292,6 +294,7 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen if (mDisplaysPreference == null) { mDisplaysPreference = new PreferenceCategory(context); mDisplaysPreference.setPersistent(false); + mDisplaysPreference.setOrder(40); } return mDisplaysPreference; } @@ -301,6 +304,7 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen if (mBuiltinDisplayPreference == null) { mBuiltinDisplayPreference = new PreferenceCategory(context); mBuiltinDisplayPreference.setPersistent(false); + mBuiltinDisplayPreference.setOrder(30); } return mBuiltinDisplayPreference; } @@ -308,11 +312,19 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen @NonNull Preference getDisplayTopologyPreference(@NonNull Context context) { if (mDisplayTopologyPreference == null) { mDisplayTopologyPreference = new DisplayTopologyPreference(context); - mDisplayTopologyPreference.setPersistent(false); + mDisplayTopologyPreference.setOrder(10); } return mDisplayTopologyPreference; } + @NonNull Preference getMirrorPreference(@NonNull Context context) { + if (mMirrorPreference == null) { + mMirrorPreference = new MirrorPreference(context); + mMirrorPreference.setOrder(20); + } + return mMirrorPreference; + } + private void restoreState(@Nullable Bundle savedInstanceState) { if (savedInstanceState == null) { return; @@ -399,6 +411,9 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen @NonNull PreferenceScreen screen, @NonNull Context context) { if (mInjector != null && mInjector.getFlags().displayTopologyPaneInDisplayList()) { screen.addPreference(getDisplayTopologyPreference(context)); + if (!displaysToShow.isEmpty()) { + screen.addPreference(getMirrorPreference(context)); + } // If topology is shown, we also show a preference for the built-in display for // consistency with the topology. diff --git a/src/com/android/settings/connecteddevice/display/Mirroring.kt b/src/com/android/settings/connecteddevice/display/Mirroring.kt new file mode 100644 index 00000000000..ff35294604c --- /dev/null +++ b/src/com/android/settings/connecteddevice/display/Mirroring.kt @@ -0,0 +1,51 @@ +/* + * 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.content.Context +import android.provider.Settings + +import androidx.preference.SwitchPreferenceCompat + +import com.android.settings.R + +const val MIRROR_PREFERENCE_KEY = "mirror_builtin_display" + +/** + * A switch preference which is backed by the MIRROR_BUILT_IN_DISPLAY global setting. + */ +class MirrorPreference(context: Context): SwitchPreferenceCompat(context) { + init { + setTitle(R.string.external_display_mirroring_title) + + key = MIRROR_PREFERENCE_KEY + isPersistent = false + } + + override fun onAttached() { + super.onAttached() + setChecked(0 != Settings.Global.getInt( + context.contentResolver, Settings.Secure.MIRROR_BUILT_IN_DISPLAY, 0)) + } + + override fun onClick() { + super.onClick() + Settings.Global.putInt( + context.contentResolver, Settings.Secure.MIRROR_BUILT_IN_DISPLAY, + if (isChecked()) 1 else 0) + } +} 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 93ba97b27f5..87af6b09708 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java +++ b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java @@ -105,7 +105,7 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa fragment.onSaveInstanceStateCallback(outState); assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isTrue(); - pref = mPreferenceScreen.findPreference(DisplayTopologyKt.PREFERENCE_KEY); + pref = mPreferenceScreen.findPreference(DisplayTopologyKt.TOPOLOGY_PREFERENCE_KEY); assertThat(pref).isNull(); pref = mPreferenceScreen.findPreference( @@ -122,7 +122,9 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays(); mHandler.flush(); - var pref = mPreferenceScreen.findPreference(DisplayTopologyKt.PREFERENCE_KEY); + var pref = mPreferenceScreen.findPreference(DisplayTopologyKt.TOPOLOGY_PREFERENCE_KEY); + assertThat(pref).isNotNull(); + pref = mPreferenceScreen.findPreference(MirroringKt.MIRROR_PREFERENCE_KEY); assertThat(pref).isNotNull(); PreferenceCategory listPref = @@ -145,8 +147,10 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa doReturn(new Display[0]).when(mMockedInjector).getAllDisplays(); mHandler.flush(); - var pref = mPreferenceScreen.findPreference(DisplayTopologyKt.PREFERENCE_KEY); + var pref = mPreferenceScreen.findPreference(DisplayTopologyKt.TOPOLOGY_PREFERENCE_KEY); assertThat(pref).isNotNull(); + pref = mPreferenceScreen.findPreference(MirroringKt.MIRROR_PREFERENCE_KEY); + assertThat(pref).isNull(); PreferenceCategory listPref = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);