diff --git a/res/values/strings.xml b/res/values/strings.xml index 3cf0bdc3f95..e023c700d77 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13153,18 +13153,18 @@ Media - - Media player in Quick Settings + + Pin media player - Show media player for an extended period to easily resume playback + To quickly resume playback, media player stays open in Quick Settings + + Show media recommendations + + Based on your activity Hide player Show player - - No players available - - Allowed apps media diff --git a/res/xml/media_controls_settings.xml b/res/xml/media_controls_settings.xml index 14a687548a5..0318097ef3a 100644 --- a/res/xml/media_controls_settings.xml +++ b/res/xml/media_controls_settings.xml @@ -20,20 +20,17 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:title="@string/media_controls_title"> - + app:controller="com.android.settings.sound.MediaControlsPreferenceController" /> - - - + diff --git a/src/com/android/settings/sound/MediaControlsPreferenceController.java b/src/com/android/settings/sound/MediaControlsPreferenceController.java index 06392abbc9e..ad09e2a6bee 100644 --- a/src/com/android/settings/sound/MediaControlsPreferenceController.java +++ b/src/com/android/settings/sound/MediaControlsPreferenceController.java @@ -20,47 +20,34 @@ import static android.provider.Settings.Secure.MEDIA_CONTROLS_RESUME; import android.content.Context; import android.provider.Settings; -import android.widget.Switch; import androidx.annotation.VisibleForTesting; -import androidx.preference.PreferenceScreen; -import com.android.settings.core.BasePreferenceController; -import com.android.settings.widget.SettingsMainSwitchPreference; -import com.android.settingslib.widget.OnMainSwitchChangeListener; +import com.android.settings.core.TogglePreferenceController; /** - * Toggle for media controls settings + * Toggle for media controls resumption setting */ -public class MediaControlsPreferenceController extends BasePreferenceController - implements OnMainSwitchChangeListener { +public class MediaControlsPreferenceController extends TogglePreferenceController { public MediaControlsPreferenceController(Context context, String key) { super(context, key); } - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - SettingsMainSwitchPreference mainSwitch = screen.findPreference(mPreferenceKey); - mainSwitch.addOnSwitchChangeListener(this); - mainSwitch.setChecked(isChecked()); - } - @VisibleForTesting - protected boolean isChecked() { + public boolean isChecked() { int val = Settings.Secure.getInt(mContext.getContentResolver(), MEDIA_CONTROLS_RESUME, 1); return val == 1; } + @Override + public boolean setChecked(boolean isChecked) { + int val = isChecked ? 1 : 0; + return Settings.Secure.putInt(mContext.getContentResolver(), MEDIA_CONTROLS_RESUME, val); + } + @Override public int getAvailabilityStatus() { return AVAILABLE; } - - @Override - public void onSwitchChanged(Switch switchView, boolean isChecked) { - int val = isChecked ? 1 : 0; - Settings.Secure.putInt(mContext.getContentResolver(), MEDIA_CONTROLS_RESUME, val); - } } diff --git a/src/com/android/settings/sound/MediaControlsRecommendationController.java b/src/com/android/settings/sound/MediaControlsRecommendationController.java new file mode 100644 index 00000000000..682cb5bc1e6 --- /dev/null +++ b/src/com/android/settings/sound/MediaControlsRecommendationController.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 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 static android.provider.Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.core.TogglePreferenceController; + +/** + * Toggle for media controls recommendation setting + */ +public class MediaControlsRecommendationController extends TogglePreferenceController { + + public MediaControlsRecommendationController(Context context, String key) { + super(context, key); + } + + @Override + public boolean isChecked() { + int val = Settings.Secure.getInt(mContext.getContentResolver(), + MEDIA_CONTROLS_RECOMMENDATION, 1); + return val == 1; + } + + @Override + public boolean setChecked(boolean isChecked) { + int val = isChecked ? 1 : 0; + return Settings.Secure.putInt(mContext.getContentResolver(), + MEDIA_CONTROLS_RECOMMENDATION, val); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } +} diff --git a/src/com/android/settings/sound/ResumableMediaAppsController.java b/src/com/android/settings/sound/ResumableMediaAppsController.java deleted file mode 100644 index 3c6f8ddbdc6..00000000000 --- a/src/com/android/settings/sound/ResumableMediaAppsController.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2020 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.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.provider.Settings; -import android.service.media.MediaBrowserService; -import android.text.TextUtils; -import android.util.ArraySet; -import android.util.Log; - -import androidx.preference.PreferenceGroup; -import androidx.preference.PreferenceScreen; - -import com.android.settings.core.BasePreferenceController; -import com.android.settingslib.widget.AppSwitchPreference; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * Section of media controls settings that contains a list of potentially resumable apps - */ -public class ResumableMediaAppsController extends BasePreferenceController { - private static final String TAG = "ResumableMediaAppsCtrl"; - - private PreferenceGroup mPreferenceGroup; - private PackageManager mPackageManager; - private List mResumeInfo; - - public ResumableMediaAppsController(Context context, String key) { - super(context, key); - mPackageManager = mContext.getPackageManager(); - Intent serviceIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE); - mResumeInfo = mPackageManager.queryIntentServices(serviceIntent, 0); - } - - @Override - public int getAvailabilityStatus() { - // Update list, since this will be called when the app goes to onStart / onPause - Intent serviceIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE); - mResumeInfo = mPackageManager.queryIntentServices(serviceIntent, 0); - return (mResumeInfo.size() > 0) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreferenceGroup = screen.findPreference(getPreferenceKey()); - Set blockedApps = getBlockedMediaApps(); - for (ResolveInfo inf : mResumeInfo) { - String packageName = inf.getComponentInfo().packageName; - MediaSwitchPreference pref = new MediaSwitchPreference(mContext, packageName); - CharSequence appTitle = packageName; - try { - appTitle = mPackageManager.getApplicationLabel( - mPackageManager.getApplicationInfo(packageName, 0)); - Drawable appIcon = mPackageManager.getApplicationIcon(packageName); - pref.setIcon(appIcon); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Couldn't get app title", e); - } - pref.setTitle(appTitle); - - pref.setOnPreferenceChangeListener((preference, status) -> { - MediaSwitchPreference mediaPreference = (MediaSwitchPreference) preference; - boolean isEnabled = (boolean) status; - Log.d(TAG, "preference " + mediaPreference + " changed " + isEnabled); - - if (isEnabled) { - blockedApps.remove(mediaPreference.getPackageName()); - } else { - blockedApps.add(mediaPreference.getPackageName()); - } - setBlockedMediaApps(blockedApps); - return true; - }); - - pref.setChecked(!blockedApps.contains(packageName)); - mPreferenceGroup.addPreference(pref); - } - } - - class MediaSwitchPreference extends AppSwitchPreference { - private String mPackageName; - - MediaSwitchPreference(Context context, String packageName) { - super(context); - mPackageName = packageName; - } - - public String getPackageName() { - return mPackageName; - } - } - - private Set getBlockedMediaApps() { - String list = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED); - if (TextUtils.isEmpty(list)) { - return new ArraySet<>(); - } - String[] names = list.split(":"); - Set set = new ArraySet<>(names.length); - Collections.addAll(set, names); - return set; - } - - private void setBlockedMediaApps(Set apps) { - if (apps == null || apps.size() == 0) { - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED, ""); - return; - } - String list = String.join(":", apps); - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED, list); - } -} diff --git a/tests/robotests/src/com/android/settings/sound/ResumableMediaAppsControllerTest.java b/tests/robotests/src/com/android/settings/sound/ResumableMediaAppsControllerTest.java deleted file mode 100644 index 797560a5841..00000000000 --- a/tests/robotests/src/com/android/settings/sound/ResumableMediaAppsControllerTest.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2020 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 static com.android.settings.core.BasePreferenceController.AVAILABLE; -import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.provider.Settings; - -import androidx.preference.PreferenceGroup; -import androidx.preference.PreferenceScreen; - -import com.android.settings.testutils.ResolveInfoBuilder; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -public class ResumableMediaAppsControllerTest { - - private static final String KEY = "media_controls_resumable_apps"; - - private static final String FAKE_APP = "com.test.fakeapp1"; - - private Context mContext; - private int mOriginalQs; - private int mOriginalResume; - private String mOriginalBlocked; - private ContentResolver mContentResolver; - private ResumableMediaAppsController mController; - @Mock - private PackageManager mPackageManager; - @Mock - private PreferenceScreen mPreferenceScreen; - @Mock - private PreferenceGroup mPreferenceGroup; - - @Before - public void setUp() { - mContext = spy(RuntimeEnvironment.application); - mContentResolver = mContext.getContentResolver(); - mOriginalQs = Settings.Global.getInt(mContentResolver, - Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); - mOriginalResume = Settings.Secure.getInt(mContentResolver, - Settings.Secure.MEDIA_CONTROLS_RESUME, 1); - mOriginalBlocked = Settings.Secure.getString(mContentResolver, - Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED); - - // Start all tests with feature enabled, nothing blocked - Settings.Global.putInt(mContentResolver, Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); - Settings.Secure.putInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1); - Settings.Secure.putString(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED, - mOriginalBlocked); - - mPreferenceScreen = mock(PreferenceScreen.class); - mPreferenceGroup = mock(PreferenceGroup.class); - mPackageManager = mock(PackageManager.class); - - List fakeInfo = new ArrayList<>(); - fakeInfo.add(createResolveInfo(FAKE_APP)); - when(mPackageManager.queryIntentServices(any(), anyInt())).thenReturn(fakeInfo); - - when(mContext.getPackageManager()).thenReturn(mPackageManager); - when(mPreferenceScreen.findPreference(KEY)).thenReturn(mPreferenceGroup); - - mController = new ResumableMediaAppsController(mContext, KEY); - } - - @After - public void tearDown() { - Settings.Global.putInt(mContentResolver, Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, - mOriginalQs); - Settings.Secure.putInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, - mOriginalResume); - Settings.Secure.putString(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED, - mOriginalBlocked); - } - - @Test - public void getAvailability_hasEligibleApps_isAvailable() { - // The package manager already has an eligible app from setUp() - assertEquals(AVAILABLE, mController.getAvailabilityStatus()); - } - - @Test - public void getAvailability_noEligibleApps_isConditionallyUnavailable() { - Context context = mock(Context.class); - PackageManager packageManager = mock(PackageManager.class); - List fakeInfo = new ArrayList<>(); - when(packageManager.queryIntentServices(any(), anyInt())).thenReturn(fakeInfo); - when(context.getPackageManager()).thenReturn(packageManager); - ResumableMediaAppsController controller = new ResumableMediaAppsController(context, KEY); - - assertEquals(CONDITIONALLY_UNAVAILABLE, controller.getAvailabilityStatus()); - } - - @Test - public void displayPreference_addsApps() { - mController.displayPreference(mPreferenceScreen); - verify(mPreferenceGroup, times(1)).addPreference(any()); - } - - @Test - public void unblockedApp_isChecked() { - ArgumentCaptor argument = - ArgumentCaptor.forClass(ResumableMediaAppsController.MediaSwitchPreference.class); - mController.displayPreference(mPreferenceScreen); - verify(mPreferenceGroup).addPreference(argument.capture()); - assertTrue(argument.getValue().isChecked()); - } - - @Test - public void blockedApp_isNotChecked() { - Settings.Secure.putString(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED, - FAKE_APP); - - ArgumentCaptor argument = - ArgumentCaptor.forClass(ResumableMediaAppsController.MediaSwitchPreference.class); - mController.displayPreference(mPreferenceScreen); - verify(mPreferenceGroup).addPreference(argument.capture()); - - assertFalse(argument.getValue().isChecked()); - } - - private ResolveInfo createResolveInfo(String name) { - ResolveInfoBuilder builder = new ResolveInfoBuilder(name); - builder.setActivity(name, name); - return builder.build(); - } -} diff --git a/tests/unit/src/com/android/settings/sound/MediaControlsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/sound/MediaControlsPreferenceControllerTest.java index 71bb5ee28c6..57307320260 100644 --- a/tests/unit/src/com/android/settings/sound/MediaControlsPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/sound/MediaControlsPreferenceControllerTest.java @@ -40,7 +40,6 @@ public class MediaControlsPreferenceControllerTest { private static final String KEY = "media_controls_resume_switch"; private Context mContext; - private int mOriginalQs; private int mOriginalResume; private ContentResolver mContentResolver; private MediaControlsPreferenceController mController; @@ -49,8 +48,6 @@ public class MediaControlsPreferenceControllerTest { public void setUp() { mContext = spy(ApplicationProvider.getApplicationContext()); mContentResolver = mContext.getContentResolver(); - mOriginalQs = Settings.Global.getInt(mContentResolver, - Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); mOriginalResume = Settings.Secure.getInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1); mController = new MediaControlsPreferenceController(mContext, KEY); @@ -58,8 +55,6 @@ public class MediaControlsPreferenceControllerTest { @After public void tearDown() { - Settings.Global.putInt(mContentResolver, Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, - mOriginalQs); Settings.Secure.putInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, mOriginalResume); } @@ -71,12 +66,11 @@ public class MediaControlsPreferenceControllerTest { @Test public void setChecked_enable_shouldTurnOn() { - Settings.Global.putInt(mContentResolver, Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); Settings.Secure.putInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1); assertThat(mController.isChecked()).isTrue(); - mController.onSwitchChanged(null /* switchView */, false); + mController.setChecked(false); assertThat(Settings.Secure.getInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, -1)).isEqualTo(0); @@ -84,12 +78,11 @@ public class MediaControlsPreferenceControllerTest { @Test public void setChecked_disable_shouldTurnOff() { - Settings.Global.putInt(mContentResolver, Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); Settings.Secure.putInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0); assertThat(mController.isChecked()).isFalse(); - mController.onSwitchChanged(null /* switchView */, true); + mController.setChecked(true); assertThat(Settings.Secure.getInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, -1)).isEqualTo(1); diff --git a/tests/unit/src/com/android/settings/sound/MediaControlsRecommendationControllerTest.java b/tests/unit/src/com/android/settings/sound/MediaControlsRecommendationControllerTest.java new file mode 100644 index 00000000000..68170b24af8 --- /dev/null +++ b/tests/unit/src/com/android/settings/sound/MediaControlsRecommendationControllerTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020 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 static com.android.settings.core.BasePreferenceController.AVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class MediaControlsRecommendationControllerTest { + + private static final String KEY = "media_controls_recommendations"; + + private Context mContext; + private int mOriginalRecommendation; + private ContentResolver mContentResolver; + private MediaControlsRecommendationController mController; + + @Before + public void setUp() { + mContext = spy(ApplicationProvider.getApplicationContext()); + mContentResolver = mContext.getContentResolver(); + mOriginalRecommendation = Settings.Secure.getInt(mContentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1); + mController = new MediaControlsRecommendationController(mContext, KEY); + } + + @After + public void tearDown() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, + mOriginalRecommendation); + } + + @Test + public void getAvailability_returnAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void setChecked_enable_shouldTurnOn() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1); + + assertThat(mController.isChecked()).isTrue(); + + mController.setChecked(false); + + assertThat(Settings.Secure.getInt(mContentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, -1)).isEqualTo(0); + } + + @Test + public void setChecked_disable_shouldTurnOff() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0); + + assertThat(mController.isChecked()).isFalse(); + + mController.setChecked(true); + + assertThat(Settings.Secure.getInt(mContentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, -1)).isEqualTo(1); + } +}