Merge "[catalyst] migrate media controls settings" into main

This commit is contained in:
Michael Mikhail
2025-03-07 07:27:51 -08:00
committed by Android (Google) Code Review
15 changed files with 477 additions and 4 deletions

View File

@@ -8,6 +8,13 @@ flag {
bug: "323791114" bug: "323791114"
} }
flag {
name: "catalyst_media_controls"
namespace: "android_settings"
description: "Flag for media page"
bug: "337243570"
}
flag { flag {
name: "catalyst_vibration_intensity_screen" name: "catalyst_vibration_intensity_screen"
namespace: "android_settings" namespace: "android_settings"

View File

@@ -21,14 +21,14 @@
android:title="@string/media_controls_title"> android:title="@string/media_controls_title">
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="media_controls_resume_switch" android:key="qs_media_resumption"
android:title="@string/media_controls_resume_title" android:title="@string/media_controls_resume_title"
android:summary="@string/media_controls_resume_description" android:summary="@string/media_controls_resume_description"
app:keywords="@string/keywords_media_controls" app:keywords="@string/keywords_media_controls"
app:controller="com.android.settings.sound.MediaControlsPreferenceController" /> app:controller="com.android.settings.sound.MediaControlsPreferenceController" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="media_controls_lockscreen" android:key="media_controls_lock_screen"
android:title="@string/media_controls_lockscreen_title" android:title="@string/media_controls_lockscreen_title"
android:summary="@string/media_controls_lockscreen_description" android:summary="@string/media_controls_lockscreen_description"
app:controller="com.android.settings.sound.MediaControlsLockScreenPreferenceController" /> app:controller="com.android.settings.sound.MediaControlsLockScreenPreferenceController" />

View File

@@ -126,7 +126,7 @@
settings:controller="com.android.settings.notification.SpatialAudioParentPreferenceController"/> settings:controller="com.android.settings.notification.SpatialAudioParentPreferenceController"/>
<Preference <Preference
android:key="media_controls_summary" android:key="media_controls"
android:title="@string/media_controls_title" android:title="@string/media_controls_title"
android:fragment="com.android.settings.sound.MediaControlsSettings" android:fragment="com.android.settings.sound.MediaControlsSettings"
android:order="-100" android:order="-100"

View File

@@ -20,6 +20,7 @@ import androidx.fragment.app.Fragment
import com.android.settings.R import com.android.settings.R
import com.android.settings.Settings.SoundSettingsActivity import com.android.settings.Settings.SoundSettingsActivity
import com.android.settings.flags.Flags import com.android.settings.flags.Flags
import com.android.settings.sound.MediaControlsScreen
import com.android.settings.utils.makeLaunchIntent import com.android.settings.utils.makeLaunchIntent
import com.android.settingslib.metadata.PreferenceIconProvider import com.android.settingslib.metadata.PreferenceIconProvider
import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceMetadata
@@ -57,6 +58,7 @@ class SoundScreen : PreferenceScreenCreator, PreferenceIconProvider {
+MediaVolumePreference() order -180 +MediaVolumePreference() order -180
+CallVolumePreference() order -170 +CallVolumePreference() order -170
+SeparateRingVolumePreference() order -155 +SeparateRingVolumePreference() order -155
+MediaControlsScreen.KEY order -100
+DialPadTonePreference() order -50 +DialPadTonePreference() order -50
} }

View File

@@ -27,6 +27,7 @@ import com.android.settings.core.TogglePreferenceController;
/** /**
* Toggle for media control resumption on lock screen. * Toggle for media control resumption on lock screen.
*/ */
// LINT.IfChange
public class MediaControlsLockScreenPreferenceController extends TogglePreferenceController { public class MediaControlsLockScreenPreferenceController extends TogglePreferenceController {
public MediaControlsLockScreenPreferenceController(Context context, String key) { public MediaControlsLockScreenPreferenceController(Context context, String key) {
super(context, key); super(context, key);
@@ -56,3 +57,4 @@ public class MediaControlsLockScreenPreferenceController extends TogglePreferenc
return R.string.menu_key_sound; return R.string.menu_key_sound;
} }
} }
// LINT.ThenChange(MediaControlsLockScreenSwitchPreference.kt)

View File

@@ -0,0 +1,72 @@
/*
* 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.sound
import android.content.Context
import android.provider.Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyedObservableDelegate
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.datastore.SettingsStore
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference
import com.android.settings.R
// LINT.IfChange
class MediaControlsLockscreenSwitchPreference : SwitchPreference(
KEY,
R.string.media_controls_lockscreen_title,
R.string.media_controls_lockscreen_description,
) {
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions()
override fun getWritePermissions(context: Context) = SettingsSecureStore.getWritePermissions()
override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun getWritePermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun storage(context: Context): KeyValueStore =
MediaControlsLockscreenStore(SettingsSecureStore.get(context))
@Suppress("UNCHECKED_CAST")
private class MediaControlsLockscreenStore(private val settingsStore: SettingsStore) :
KeyedObservableDelegate<String>(settingsStore), KeyValueStore {
override fun contains(key: String) = settingsStore.contains(key)
override fun <T : Any> getDefaultValue(key: String, valueType: Class<T>) = true as T
override fun <T : Any> getValue(key: String, valueType: Class<T>) =
settingsStore.getValue(key, valueType) ?: getDefaultValue(key, valueType)
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) =
settingsStore.setValue(key, valueType, value)
}
companion object {
const val KEY = MEDIA_CONTROLS_LOCK_SCREEN
}
}
// LINT.ThenChange(MediaControlsLockScreenPreferenceController.java)

View File

@@ -29,6 +29,7 @@ import com.android.settings.core.TogglePreferenceController;
/** /**
* Toggle for media controls resumption setting * Toggle for media controls resumption setting
*/ */
// LINT.IfChange
public class MediaControlsPreferenceController extends TogglePreferenceController { public class MediaControlsPreferenceController extends TogglePreferenceController {
public MediaControlsPreferenceController(Context context, String key) { public MediaControlsPreferenceController(Context context, String key) {
@@ -56,4 +57,6 @@ public class MediaControlsPreferenceController extends TogglePreferenceControlle
public int getSliceHighlightMenuRes() { public int getSliceHighlightMenuRes() {
return R.string.menu_key_sound; return R.string.menu_key_sound;
} }
} }
// LINT.ThenChange(MediaControlsSwitchPreference.kt)

View File

@@ -0,0 +1,103 @@
/*
* 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.sound
import android.content.Context
import com.android.settings.R
import com.android.settings.flags.Flags
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.HandlerExecutor
import com.android.settingslib.datastore.KeyedObserver
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyedObservableDelegate
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.datastore.SettingsStore
import com.android.settingslib.metadata.PreferenceChangeReason
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceScreenCreator
// LINT.IfChange
@ProvidePreferenceScreen(MediaControlsScreen.KEY)
class MediaControlsScreen(context: Context) :
AbstractKeyedDataObservable<String>(), PreferenceScreenCreator, PreferenceSummaryProvider {
private val observer =
KeyedObserver<String> { _, _ -> notifyChange(KEY, PreferenceChangeReason.STATE) }
private val mediaControlsStore = MediaControlsStore(SettingsSecureStore.get(context))
override val key: String
get() = KEY
override val title: Int
get() = R.string.media_controls_title
override val keywords: Int
get() = R.string.keywords_media_controls
override fun onFirstObserverAdded() {
mediaControlsStore.addObserver(
MediaControlsSwitchPreference.KEY,
observer,
HandlerExecutor.main,
)
}
override fun onLastObserverRemoved() {
mediaControlsStore.removeObserver(MediaControlsSwitchPreference.KEY, observer)
}
override fun isFlagEnabled(context: Context) = Flags.catalystMediaControls()
override fun fragmentClass() = MediaControlsSettings::class.java
override fun getPreferenceHierarchy(context: Context) =
preferenceHierarchy(context, this) {
+MediaControlsSwitchPreference(mediaControlsStore)
+MediaControlsLockscreenSwitchPreference()
}
override fun getSummary(context: Context): CharSequence? =
if (mediaControlsStore.getBoolean(MediaControlsSwitchPreference.KEY) == false) {
context.getString(R.string.media_controls_hide_player)
} else {
context.getString(R.string.media_controls_show_player)
}
@Suppress("UNCHECKED_CAST")
class MediaControlsStore(private val settingsStore: SettingsStore) :
KeyedObservableDelegate<String>(settingsStore), KeyValueStore {
override fun contains(key: String) = settingsStore.contains(key)
override fun <T : Any> getDefaultValue(key: String, valueType: Class<T>) = true as T
override fun <T : Any> getValue(key: String, valueType: Class<T>) =
settingsStore.getValue(key, valueType) ?: getDefaultValue(key, valueType)
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) =
settingsStore.setValue(key, valueType, value)
}
companion object {
const val KEY = "media_controls"
}
}
// LINT.ThenChange(MediaControlsSettings.java)

View File

@@ -17,6 +17,10 @@
package com.android.settings.sound; package com.android.settings.sound;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
@@ -26,6 +30,7 @@ import com.android.settingslib.search.SearchIndexable;
/** /**
* Media control settings located in the sound menu * Media control settings located in the sound menu
*/ */
// LINT.IfChange
@SearchIndexable @SearchIndexable
public class MediaControlsSettings extends DashboardFragment { public class MediaControlsSettings extends DashboardFragment {
@@ -48,4 +53,10 @@ public class MediaControlsSettings extends DashboardFragment {
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.media_controls_settings); new BaseSearchIndexProvider(R.xml.media_controls_settings);
@Override
public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
return MediaControlsScreen.KEY;
}
} }
// LINT.ThenChange(MediaControlsScreen.kt)

View File

@@ -0,0 +1,59 @@
/*
* 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.sound
import android.content.Context
import android.provider.Settings.Secure.MEDIA_CONTROLS_RESUME
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference
import com.android.settings.R
// LINT.IfChange
class MediaControlsSwitchPreference(
private val mediaControlsStore: MediaControlsScreen.MediaControlsStore,
) : SwitchPreference(
KEY,
R.string.media_controls_resume_title,
R.string.media_controls_resume_description,
) {
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
override val keywords: Int
get() = R.string.keywords_media_controls
override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions()
override fun getWritePermissions(context: Context) = SettingsSecureStore.getWritePermissions()
override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun getWritePermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun storage(context: Context): KeyValueStore = mediaControlsStore
companion object {
const val KEY = MEDIA_CONTROLS_RESUME
}
}
// LINT.ThenChange(MediaControlsPreferenceController.java)

View File

@@ -38,7 +38,7 @@ import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class MediaControlsParentPreferenceControllerTest { public class MediaControlsParentPreferenceControllerTest {
private static final String KEY = "media_controls_summary"; private static final String KEY = "media_controls";
private Context mContext; private Context mContext;
private int mOriginalQs; private int mOriginalQs;

View File

@@ -32,6 +32,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
// LINT.IfChange
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class MediaControlsLockScreenPreferenceControllerTest { public class MediaControlsLockScreenPreferenceControllerTest {
@@ -87,3 +88,4 @@ public class MediaControlsLockScreenPreferenceControllerTest {
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, -1)).isEqualTo(1); Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, -1)).isEqualTo(1);
} }
} }
// LINT.ThenChange(MediaControlsLockScreenSwitchPreferenceTest.kt)

View File

@@ -0,0 +1,104 @@
/*
* 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.sound
import android.content.Context
import androidx.preference.SwitchPreferenceCompat
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.preference.createAndBindWidget
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
// LINT.IfChange
@RunWith(AndroidJUnit4::class)
class MediaControlsLockScreenSwitchPreferenceTest {
private val appContext: Context = ApplicationProvider.getApplicationContext()
private var originalValue: Boolean? = null
private val key = MediaControlsLockscreenSwitchPreference.KEY
private val preference = MediaControlsLockscreenSwitchPreference()
@Before
fun setUp() {
originalValue = preference.storage(appContext).getBoolean(key)
}
@After
fun tearDown() {
preference.storage(appContext).setBoolean(key, originalValue)
}
@Test
fun mediaControlsLockScreenDefaultValue_isChecked() {
preference.storage(appContext).setBoolean(key, null)
val switchPreference = getSwitchPreferenceCompat()
assertThat(switchPreference.isChecked).isTrue()
}
@Test
fun mediaControlsLockScreenEnabled_switchPreferenceIsChecked() {
setMediaControlsLockScreenEnabled(true)
val switchPreference = getSwitchPreferenceCompat()
assertThat(switchPreference.isChecked).isTrue()
}
@Test
fun mediaControlsLockScreenDisabled_switchPreferenceIsNotChecked() {
setMediaControlsLockScreenEnabled(false)
val switchPreference = getSwitchPreferenceCompat()
assertThat(switchPreference.isChecked).isFalse()
}
@Test
fun click_defaultMediaControlsLockScreenEnabled_turnOff() {
setMediaControlsLockScreenEnabled(true)
val switchPreference = getSwitchPreferenceCompat().apply { performClick() }
assertThat(switchPreference.isChecked).isFalse()
}
@Test
fun click_defaultMediaControlsLockScreenDisabled_turnOn() {
setMediaControlsLockScreenEnabled(false)
val switchPreference = getSwitchPreferenceCompat().apply { performClick() }
assertThat(switchPreference.isChecked).isTrue()
}
private fun getSwitchPreferenceCompat(): SwitchPreferenceCompat =
preference.createAndBindWidget(appContext)
private fun setMediaControlsLockScreenEnabled(value: Boolean) =
preference.storage(appContext).setBoolean(key, value)
}
// LINT.ThenChange(MediaControlsLockScreenPreferenceControllerTest.java)

View File

@@ -34,6 +34,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
// LINT.IfChange
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class MediaControlsPreferenceControllerTest { public class MediaControlsPreferenceControllerTest {
@@ -88,3 +89,4 @@ public class MediaControlsPreferenceControllerTest {
Settings.Secure.MEDIA_CONTROLS_RESUME, -1)).isEqualTo(1); Settings.Secure.MEDIA_CONTROLS_RESUME, -1)).isEqualTo(1);
} }
} }
// LINT.ThenChange(MediaControlsSwitchPreferenceTest.kt)

View File

@@ -0,0 +1,106 @@
/*
* 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.sound
import android.content.Context
import androidx.preference.SwitchPreferenceCompat
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.preference.createAndBindWidget
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
// LINT.IfChange
@RunWith(AndroidJUnit4::class)
class MediaControlsSwitchPreferenceTest {
private val appContext: Context = ApplicationProvider.getApplicationContext()
private var originalValue: Boolean? = null
private val store = MediaControlsScreen.MediaControlsStore(SettingsSecureStore.get(appContext))
private val key = MediaControlsSwitchPreference.KEY
private val preference = MediaControlsSwitchPreference(store)
@Before
fun setUp() {
originalValue = preference.storage(appContext).getBoolean(key)
}
@After
fun tearDown() {
preference.storage(appContext).setBoolean(key, originalValue)
}
@Test
fun mediaControlsLockScreenDefaultValue_isChecked() {
preference.storage(appContext).setBoolean(key, null)
val switchPreference = getSwitchPreferenceCompat()
assertThat(switchPreference.isChecked).isTrue()
}
@Test
fun mediaControlsLockScreenEnabled_switchPreferenceIsChecked() {
setMediaControlsResumeEnabled(true)
val switchPreference = getSwitchPreferenceCompat()
assertThat(switchPreference.isChecked).isTrue()
}
@Test
fun mediaControlsLockScreenDisabled_switchPreferenceIsNotChecked() {
setMediaControlsResumeEnabled(false)
val switchPreference = getSwitchPreferenceCompat()
assertThat(switchPreference.isChecked).isFalse()
}
@Test
fun click_defaultMediaControlsLockScreenEnabled_turnOff() {
setMediaControlsResumeEnabled(true)
val switchPreference = getSwitchPreferenceCompat().apply { performClick() }
assertThat(switchPreference.isChecked).isFalse()
}
@Test
fun click_defaultMediaControlsLockScreenDisabled_turnOn() {
setMediaControlsResumeEnabled(false)
val switchPreference = getSwitchPreferenceCompat().apply { performClick() }
assertThat(switchPreference.isChecked).isTrue()
}
private fun getSwitchPreferenceCompat(): SwitchPreferenceCompat =
preference.createAndBindWidget(appContext)
private fun setMediaControlsResumeEnabled(value: Boolean) =
preference.storage(appContext).setBoolean(key, value)
}
// LINT.ThenChange(MediaControlsPreferenceControllerTest.java)