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
This commit is contained in:
Matthew DeVore
2025-01-17 17:40:25 +00:00
parent 878f860fd3
commit 8f051ce5d4
5 changed files with 79 additions and 6 deletions

View File

@@ -2120,6 +2120,8 @@
<string name="external_display_more_options_title">More options</string> <string name="external_display_more_options_title">More options</string>
<!-- External Display settings. --> <!-- External Display settings. -->
<string name="external_display_topology_hint">Hold and drag to rearrange displays.</string> <string name="external_display_topology_hint">Hold and drag to rearrange displays.</string>
<!-- External Display settings. -->
<string name="external_display_mirroring_title">Mirror built-in display</string>
<!-- Wifi Display settings. The title of the screen. [CHAR LIMIT=40] --> <!-- Wifi Display settings. The title of the screen. [CHAR LIMIT=40] -->
<string name="wifi_display_settings_title">Cast</string> <string name="wifi_display_settings_title">Cast</string>

View File

@@ -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. */ /** Padding in pane coordinate pixels on each side of a display block. */
const val BLOCK_PADDING = 2f const val BLOCK_PADDING = 2f
@@ -220,7 +220,8 @@ class DisplayTopologyPreference(context : Context)
// Prevent highlight when hovering with mouse. // Prevent highlight when hovering with mouse.
isSelectable = false isSelectable = false
key = PREFERENCE_KEY key = TOPOLOGY_PREFERENCE_KEY
isPersistent = false
injector = Injector(context) injector = Injector(context)
} }

View File

@@ -102,6 +102,8 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
@Nullable @Nullable
private Preference mDisplayTopologyPreference; private Preference mDisplayTopologyPreference;
@Nullable @Nullable
private Preference mMirrorPreference;
@Nullable
private PreferenceCategory mDisplaysPreference; private PreferenceCategory mDisplaysPreference;
@Nullable @Nullable
private PreferenceCategory mBuiltinDisplayPreference; private PreferenceCategory mBuiltinDisplayPreference;
@@ -292,6 +294,7 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
if (mDisplaysPreference == null) { if (mDisplaysPreference == null) {
mDisplaysPreference = new PreferenceCategory(context); mDisplaysPreference = new PreferenceCategory(context);
mDisplaysPreference.setPersistent(false); mDisplaysPreference.setPersistent(false);
mDisplaysPreference.setOrder(40);
} }
return mDisplaysPreference; return mDisplaysPreference;
} }
@@ -301,6 +304,7 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
if (mBuiltinDisplayPreference == null) { if (mBuiltinDisplayPreference == null) {
mBuiltinDisplayPreference = new PreferenceCategory(context); mBuiltinDisplayPreference = new PreferenceCategory(context);
mBuiltinDisplayPreference.setPersistent(false); mBuiltinDisplayPreference.setPersistent(false);
mBuiltinDisplayPreference.setOrder(30);
} }
return mBuiltinDisplayPreference; return mBuiltinDisplayPreference;
} }
@@ -308,11 +312,19 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
@NonNull Preference getDisplayTopologyPreference(@NonNull Context context) { @NonNull Preference getDisplayTopologyPreference(@NonNull Context context) {
if (mDisplayTopologyPreference == null) { if (mDisplayTopologyPreference == null) {
mDisplayTopologyPreference = new DisplayTopologyPreference(context); mDisplayTopologyPreference = new DisplayTopologyPreference(context);
mDisplayTopologyPreference.setPersistent(false); mDisplayTopologyPreference.setOrder(10);
} }
return mDisplayTopologyPreference; 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) { private void restoreState(@Nullable Bundle savedInstanceState) {
if (savedInstanceState == null) { if (savedInstanceState == null) {
return; return;
@@ -399,6 +411,9 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
@NonNull PreferenceScreen screen, @NonNull Context context) { @NonNull PreferenceScreen screen, @NonNull Context context) {
if (mInjector != null && mInjector.getFlags().displayTopologyPaneInDisplayList()) { if (mInjector != null && mInjector.getFlags().displayTopologyPaneInDisplayList()) {
screen.addPreference(getDisplayTopologyPreference(context)); 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 // If topology is shown, we also show a preference for the built-in display for
// consistency with the topology. // consistency with the topology.

View File

@@ -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)
}
}

View File

@@ -105,7 +105,7 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa
fragment.onSaveInstanceStateCallback(outState); fragment.onSaveInstanceStateCallback(outState);
assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isTrue(); assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isTrue();
pref = mPreferenceScreen.findPreference(DisplayTopologyKt.PREFERENCE_KEY); pref = mPreferenceScreen.findPreference(DisplayTopologyKt.TOPOLOGY_PREFERENCE_KEY);
assertThat(pref).isNull(); assertThat(pref).isNull();
pref = mPreferenceScreen.findPreference( pref = mPreferenceScreen.findPreference(
@@ -122,7 +122,9 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa
doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays(); doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays();
mHandler.flush(); 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(); assertThat(pref).isNotNull();
PreferenceCategory listPref = PreferenceCategory listPref =
@@ -145,8 +147,10 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa
doReturn(new Display[0]).when(mMockedInjector).getAllDisplays(); doReturn(new Display[0]).when(mMockedInjector).getAllDisplays();
mHandler.flush(); mHandler.flush();
var pref = mPreferenceScreen.findPreference(DisplayTopologyKt.PREFERENCE_KEY); var pref = mPreferenceScreen.findPreference(DisplayTopologyKt.TOPOLOGY_PREFERENCE_KEY);
assertThat(pref).isNotNull(); assertThat(pref).isNotNull();
pref = mPreferenceScreen.findPreference(MirroringKt.MIRROR_PREFERENCE_KEY);
assertThat(pref).isNull();
PreferenceCategory listPref = PreferenceCategory listPref =
mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY); mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);