From f25c9b0efccf023a245cd481949197832d73cfdb Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 27 Feb 2019 10:05:56 -0800 Subject: [PATCH 1/2] Add remote volume slider to sound settings 1. Add RemoteVolumePreferenceController to control volume through MediaSessions. 2. Add related preference to skip calling AudioManager. Bug: 126199571 Test: RunSettingsRoboTests Change-Id: I9cb85ccf5f49be6d127cb61caf445b2ee7dfb579 --- AndroidManifest.xml | 1 + res/values/strings.xml | 3 + res/xml/sound_settings.xml | 9 + .../RemoteVolumePreferenceController.java | 159 ++++++++++++++++++ .../RemoteVolumeSeekBarPreference.java | 56 ++++++ .../settings/notification/SoundSettings.java | 1 + .../notification/VolumeSeekBarPreference.java | 8 +- .../RemoteVolumePreferenceControllerTest.java | 91 ++++++++++ 8 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 src/com/android/settings/notification/RemoteVolumePreferenceController.java create mode 100644 src/com/android/settings/notification/RemoteVolumeSeekBarPreference.java create mode 100644 tests/robotests/src/com/android/settings/notification/RemoteVolumePreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6724ff5355d..083b34916a6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -100,6 +100,7 @@ + Media volume + + Cast volume + Call volume diff --git a/res/xml/sound_settings.xml b/res/xml/sound_settings.xml index ee8613d5fca..81a04538430 100644 --- a/res/xml/sound_settings.xml +++ b/res/xml/sound_settings.xml @@ -22,6 +22,15 @@ settings:keywords="@string/keywords_sounds" settings:initialExpandedChildrenCount="9"> + + + controllers = mMediaSessionManager.getActiveSessions(null); + for (MediaController mediaController : controllers) { + final MediaController.PlaybackInfo pi = mediaController.getPlaybackInfo(); + if (isRemote(pi)) { + mActiveToken = mediaController.getSessionToken(); + return AVAILABLE; + } + } + + // No active remote media at this point + return CONDITIONALLY_UNAVAILABLE; + } + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + public void onResume() { + super.onResume(); + mMediaSessions.init(); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + public void onPause() { + super.onPause(); + mMediaSessions.destroy(); + } + + @Override + public int getSliderPosition() { + if (mPreference != null) { + return mPreference.getProgress(); + } + //TODO(b/126199571): get it from media controller + return 0; + } + + @Override + public boolean setSliderPosition(int position) { + if (mPreference != null) { + mPreference.setProgress(position); + } + //TODO(b/126199571): set it through media controller + return false; + } + + @Override + public int getMaxSteps() { + if (mPreference != null) { + return mPreference.getMax(); + } + //TODO(b/126199571): get it from media controller + return 0; + } + + @Override + public boolean isSliceable() { + return TextUtils.equals(getPreferenceKey(), KEY_REMOTE_VOLUME); + } + + @Override + public String getPreferenceKey() { + return KEY_REMOTE_VOLUME; + } + + @Override + public int getAudioStream() { + // This can be anything because remote volume controller doesn't rely on it. + return REMOTE_VOLUME; + } + + @Override + public int getMuteIcon() { + return R.drawable.ic_volume_remote_mute; + } + + public static boolean isRemote(MediaController.PlaybackInfo pi) { + return pi != null + && pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE; + } +} diff --git a/src/com/android/settings/notification/RemoteVolumeSeekBarPreference.java b/src/com/android/settings/notification/RemoteVolumeSeekBarPreference.java new file mode 100644 index 00000000000..e99af6a42cb --- /dev/null +++ b/src/com/android/settings/notification/RemoteVolumeSeekBarPreference.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 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.notification; + +import android.content.Context; +import android.util.AttributeSet; + +/** + * A slider preference that controls remote volume, which doesn't go through + * {@link android.media.AudioManager} + **/ +public class RemoteVolumeSeekBarPreference extends VolumeSeekBarPreference { + + public RemoteVolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public RemoteVolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public RemoteVolumeSeekBarPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public RemoteVolumeSeekBarPreference(Context context) { + super(context); + } + + @Override + public void setStream(int stream) { + // Do nothing here, volume is not controlled by AudioManager + } + + @Override + protected void init() { + if (mSeekBar == null) return; + updateIconView(); + updateSuppressionText(); + } +} diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java index e8b7ee0d782..b26b9219cd4 100644 --- a/src/com/android/settings/notification/SoundSettings.java +++ b/src/com/android/settings/notification/SoundSettings.java @@ -184,6 +184,7 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult volumeControllers.add(use(RingVolumePreferenceController.class)); volumeControllers.add(use(NotificationVolumePreferenceController.class)); volumeControllers.add(use(CallVolumePreferenceController.class)); + volumeControllers.add(use(RemoteVolumePreferenceController.class)); use(MediaOutputPreferenceController.class).setCallback(listPreference -> onPreferenceDataChanged(listPreference)); diff --git a/src/com/android/settings/notification/VolumeSeekBarPreference.java b/src/com/android/settings/notification/VolumeSeekBarPreference.java index 13f630004d1..7f36791e069 100644 --- a/src/com/android/settings/notification/VolumeSeekBarPreference.java +++ b/src/com/android/settings/notification/VolumeSeekBarPreference.java @@ -40,8 +40,8 @@ import java.util.Objects; public class VolumeSeekBarPreference extends SeekBarPreference { private static final String TAG = "VolumeSeekBarPreference"; + protected SeekBar mSeekBar; private int mStream; - private SeekBar mSeekBar; private SeekBarVolumizer mVolumizer; private Callback mCallback; private ImageView mIconView; @@ -121,7 +121,7 @@ public class VolumeSeekBarPreference extends SeekBarPreference { init(); } - private void init() { + protected void init() { if (mSeekBar == null) return; final SeekBarVolumizer.Callback sbvc = new SeekBarVolumizer.Callback() { @Override @@ -158,7 +158,7 @@ public class VolumeSeekBarPreference extends SeekBarPreference { } } - private void updateIconView() { + protected void updateIconView() { if (mIconView == null) return; if (mIconResId != 0) { mIconView.setImageResource(mIconResId); @@ -195,7 +195,7 @@ public class VolumeSeekBarPreference extends SeekBarPreference { updateSuppressionText(); } - private void updateSuppressionText() { + protected void updateSuppressionText() { if (mSuppressionTextView != null && mSeekBar != null) { mSuppressionTextView.setText(mSuppressionText); final boolean showSuppression = !TextUtils.isEmpty(mSuppressionText); diff --git a/tests/robotests/src/com/android/settings/notification/RemoteVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/RemoteVolumePreferenceControllerTest.java new file mode 100644 index 00000000000..a34d4d7a37c --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/RemoteVolumePreferenceControllerTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2019 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.notification; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.media.session.MediaController; +import android.media.session.MediaSessionManager; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class RemoteVolumePreferenceControllerTest { + + @Mock + private MediaSessionManager mMediaSessionManager; + @Mock + private MediaController mMediaController; + private RemoteVolumePreferenceController mController; + private Context mContext; + private List mActiveSessions; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mMediaSessionManager); + mActiveSessions = new ArrayList<>(); + mActiveSessions.add(mMediaController); + when(mMediaSessionManager.getActiveSessions(null)).thenReturn( + mActiveSessions); + + mController = new RemoteVolumePreferenceController(mContext); + } + + @Test + public void isAvailable_containRemoteMedia_returnTrue() { + when(mMediaController.getPlaybackInfo()).thenReturn( + new MediaController.PlaybackInfo(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE, + 0, 0, 0, null)); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_noRemoteMedia_returnFalse() { + when(mMediaController.getPlaybackInfo()).thenReturn( + new MediaController.PlaybackInfo(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, + 0, 0, 0, null)); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void getMuteIcon_returnMuteIcon() { + assertThat(mController.getMuteIcon()).isEqualTo(R.drawable.ic_volume_remote_mute); + } + + @Test + public void getAudioStream_returnRemoteVolume() { + assertThat(mController.getAudioStream()).isEqualTo( + RemoteVolumePreferenceController.REMOTE_VOLUME); + } +} From dd002749678cd39a937e897d4e16481a7fa54ea9 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 27 Feb 2019 13:13:27 -0800 Subject: [PATCH 2/2] Add SliceWorker for remote volume controller Also update some methods to fix issues in panel. Bug: 126199571 Test: RunSettingsRoboTests Change-Id: I9b93ae594d41cb71b984b06267161455373bf121 --- .../RemoteVolumePreferenceController.java | 142 +++++++++++++++--- .../android/settings/panel/VolumePanel.java | 2 + .../settings/slices/CustomSliceRegistry.java | 11 ++ .../RemoteVolumePreferenceControllerTest.java | 106 +++++++++++++ 4 files changed, 241 insertions(+), 20 deletions(-) diff --git a/src/com/android/settings/notification/RemoteVolumePreferenceController.java b/src/com/android/settings/notification/RemoteVolumePreferenceController.java index dece92841e2..0ad307e4c40 100644 --- a/src/com/android/settings/notification/RemoteVolumePreferenceController.java +++ b/src/com/android/settings/notification/RemoteVolumePreferenceController.java @@ -20,16 +20,20 @@ import android.content.Context; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.MediaSessionManager; +import android.net.Uri; import android.os.Looper; import android.text.TextUtils; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.OnLifecycleEvent; +import androidx.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.slices.SliceBackgroundWorker; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.volume.MediaSessions; +import java.io.IOException; import java.util.List; public class RemoteVolumePreferenceController extends @@ -41,32 +45,41 @@ public class RemoteVolumePreferenceController extends private MediaSessionManager mMediaSessionManager; private MediaSessions mMediaSessions; - private MediaSession.Token mActiveToken; + @VisibleForTesting + MediaSession.Token mActiveToken; + @VisibleForTesting + MediaController mMediaController; - private MediaSessions.Callbacks mCallbacks = new MediaSessions.Callbacks() { + @VisibleForTesting + MediaSessions.Callbacks mCallbacks = new MediaSessions.Callbacks() { @Override public void onRemoteUpdate(MediaSession.Token token, String name, MediaController.PlaybackInfo pi) { - mActiveToken = token; - mPreference.setMax(pi.getMaxVolume()); - mPreference.setVisible(true); - setSliderPosition(pi.getCurrentVolume()); + if (mActiveToken == null) { + updateToken(token); + } + if (mActiveToken == token) { + updatePreference(mPreference, mActiveToken, pi); + } } @Override public void onRemoteRemoved(MediaSession.Token t) { if (mActiveToken == t) { - mActiveToken = null; - mPreference.setVisible(false); + updateToken(null); + if (mPreference != null) { + mPreference.setVisible(false); + } } } @Override public void onRemoteVolumeChanged(MediaSession.Token token, int flags) { if (mActiveToken == token) { - final MediaController mediaController = new MediaController(mContext, token); - final MediaController.PlaybackInfo pi = mediaController.getPlaybackInfo(); - setSliderPosition(pi.getCurrentVolume()); + final MediaController.PlaybackInfo pi = mMediaController.getPlaybackInfo(); + if (pi != null) { + setSliderPosition(pi.getCurrentVolume()); + } } } }; @@ -83,7 +96,7 @@ public class RemoteVolumePreferenceController extends for (MediaController mediaController : controllers) { final MediaController.PlaybackInfo pi = mediaController.getPlaybackInfo(); if (isRemote(pi)) { - mActiveToken = mediaController.getSessionToken(); + updateToken(mediaController.getSessionToken()); return AVAILABLE; } } @@ -92,16 +105,24 @@ public class RemoteVolumePreferenceController extends return CONDITIONALLY_UNAVAILABLE; } + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (mMediaController != null) { + updatePreference(mPreference, mActiveToken, mMediaController.getPlaybackInfo()); + } + } + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume() { super.onResume(); - mMediaSessions.init(); + //TODO(b/126199571): register callback once b/126890783 is fixed } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void onPause() { super.onPause(); - mMediaSessions.destroy(); + //TODO(b/126199571): unregister callback once b/126890783 is fixed } @Override @@ -109,8 +130,11 @@ public class RemoteVolumePreferenceController extends if (mPreference != null) { return mPreference.getProgress(); } - //TODO(b/126199571): get it from media controller - return 0; + if (mMediaController == null) { + return 0; + } + final MediaController.PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo(); + return playbackInfo != null ? playbackInfo.getCurrentVolume() : 0; } @Override @@ -118,8 +142,11 @@ public class RemoteVolumePreferenceController extends if (mPreference != null) { mPreference.setProgress(position); } - //TODO(b/126199571): set it through media controller - return false; + if (mMediaController == null) { + return false; + } + mMediaController.setVolumeTo(position, 0); + return true; } @Override @@ -127,8 +154,11 @@ public class RemoteVolumePreferenceController extends if (mPreference != null) { return mPreference.getMax(); } - //TODO(b/126199571): get it from media controller - return 0; + if (mMediaController == null) { + return 0; + } + final MediaController.PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo(); + return playbackInfo != null ? playbackInfo.getMaxVolume() : 0; } @Override @@ -156,4 +186,76 @@ public class RemoteVolumePreferenceController extends return pi != null && pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE; } + + @Override + public Class getBackgroundWorkerClass() { + //TODO(b/126199571): return RemoteVolumeSliceWorker once b/126890783 is fixed + return null; + } + + private void updatePreference(VolumeSeekBarPreference seekBarPreference, + MediaSession.Token token, MediaController.PlaybackInfo playbackInfo) { + if (seekBarPreference == null || token == null || playbackInfo == null) { + return; + } + + seekBarPreference.setMax(playbackInfo.getMaxVolume()); + seekBarPreference.setVisible(true); + setSliderPosition(playbackInfo.getCurrentVolume()); + } + + private void updateToken(MediaSession.Token token) { + mActiveToken = token; + if (token != null) { + mMediaController = new MediaController(mContext, mActiveToken); + } else { + mMediaController = null; + } + } + + /** + * Listener for background change to remote volume, which listens callback + * from {@code MediaSessions} + */ + public static class RemoteVolumeSliceWorker extends SliceBackgroundWorker implements + MediaSessions.Callbacks { + + private MediaSessions mMediaSessions; + + public RemoteVolumeSliceWorker(Context context, Uri uri) { + super(context, uri); + mMediaSessions = new MediaSessions(context, Looper.getMainLooper(), this); + } + + @Override + protected void onSlicePinned() { + mMediaSessions.init(); + } + + @Override + protected void onSliceUnpinned() { + mMediaSessions.destroy(); + } + + @Override + public void close() throws IOException { + mMediaSessions = null; + } + + @Override + public void onRemoteUpdate(MediaSession.Token token, String name, + MediaController.PlaybackInfo pi) { + notifySliceChange(); + } + + @Override + public void onRemoteRemoved(MediaSession.Token t) { + notifySliceChange(); + } + + @Override + public void onRemoteVolumeChanged(MediaSession.Token token, int flags) { + notifySliceChange(); + } + } } diff --git a/src/com/android/settings/panel/VolumePanel.java b/src/com/android/settings/panel/VolumePanel.java index 20c2272a2a6..62eca53898b 100644 --- a/src/com/android/settings/panel/VolumePanel.java +++ b/src/com/android/settings/panel/VolumePanel.java @@ -19,6 +19,7 @@ package com.android.settings.panel; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_CALL_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI; +import static com.android.settings.slices.CustomSliceRegistry.VOLUME_REMOTE_MEDIA_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI; import android.app.settings.SettingsEnums; @@ -52,6 +53,7 @@ public class VolumePanel implements PanelContent { @Override public List getSlices() { final List uris = new ArrayList<>(); + uris.add(VOLUME_REMOTE_MEDIA_URI); uris.add(VOLUME_MEDIA_URI); uris.add(VOLUME_CALL_URI); uris.add(VOLUME_RINGER_URI); diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java index ab1b2484828..3a1db69c00a 100644 --- a/src/com/android/settings/slices/CustomSliceRegistry.java +++ b/src/com/android/settings/slices/CustomSliceRegistry.java @@ -219,6 +219,17 @@ public class CustomSliceRegistry { .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) .appendPath("media_volume") .build(); + + /** + * Full {@link Uri} for the Remote Media Volume Slice. + */ + public static final Uri VOLUME_REMOTE_MEDIA_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath("remote_volume") + .build(); + /** * Full {@link Uri} for the Ringer volume Slice. */ diff --git a/tests/robotests/src/com/android/settings/notification/RemoteVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/RemoteVolumePreferenceControllerTest.java index a34d4d7a37c..1bf2fd81bb6 100644 --- a/tests/robotests/src/com/android/settings/notification/RemoteVolumePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/RemoteVolumePreferenceControllerTest.java @@ -19,10 +19,13 @@ package com.android.settings.notification; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.media.session.ControllerLink; import android.media.session.MediaController; +import android.media.session.MediaSession; import android.media.session.MediaSessionManager; import com.android.settings.R; @@ -40,26 +43,41 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public class RemoteVolumePreferenceControllerTest { + private static final int CURRENT_POS = 5; + private static final int MAX_POS = 10; @Mock private MediaSessionManager mMediaSessionManager; @Mock private MediaController mMediaController; + @Mock + private ControllerLink.ControllerStub mStub; + @Mock + private ControllerLink.ControllerStub mStub2; + private MediaSession.Token mToken; + private MediaSession.Token mToken2; private RemoteVolumePreferenceController mController; private Context mContext; private List mActiveSessions; + private MediaController.PlaybackInfo mPlaybackInfo; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mMediaSessionManager); mActiveSessions = new ArrayList<>(); mActiveSessions.add(mMediaController); when(mMediaSessionManager.getActiveSessions(null)).thenReturn( mActiveSessions); + mToken = new MediaSession.Token(new ControllerLink(mStub)); + mToken2 = new MediaSession.Token(new ControllerLink(mStub2)); mController = new RemoteVolumePreferenceController(mContext); + mPlaybackInfo = new MediaController.PlaybackInfo( + MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE, 0, MAX_POS, CURRENT_POS, null); + when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo); } @Test @@ -88,4 +106,92 @@ public class RemoteVolumePreferenceControllerTest { assertThat(mController.getAudioStream()).isEqualTo( RemoteVolumePreferenceController.REMOTE_VOLUME); } + + @Test + public void getSliderPosition_controllerNull_returnZero() { + mController.mMediaController = null; + + assertThat(mController.getSliderPosition()).isEqualTo(0); + } + + @Test + public void getSliderPosition_controllerExists_returnValue() { + mController.mMediaController = mMediaController; + + assertThat(mController.getSliderPosition()).isEqualTo(CURRENT_POS); + } + + @Test + public void getMaxSteps_controllerNull_returnZero() { + mController.mMediaController = null; + + assertThat(mController.getMaxSteps()).isEqualTo(0); + } + + @Test + public void getMaxSteps_controllerExists_returnValue() { + mController.mMediaController = mMediaController; + + assertThat(mController.getMaxSteps()).isEqualTo(MAX_POS); + } + + @Test + public void setSliderPosition_controllerNull_returnFalse() { + mController.mMediaController = null; + + assertThat(mController.setSliderPosition(CURRENT_POS)).isFalse(); + } + + @Test + public void setSliderPosition_controllerExists_returnTrue() { + mController.mMediaController = mMediaController; + + assertThat(mController.setSliderPosition(CURRENT_POS)).isTrue(); + verify(mMediaController).setVolumeTo(CURRENT_POS, 0 /* flags */); + } + + @Test + public void onRemoteUpdate_firstToken_updateTokenAndPreference() { + mController.mPreference = new VolumeSeekBarPreference(mContext); + mController.mActiveToken = null; + + mController.mCallbacks.onRemoteUpdate(mToken, "token", mPlaybackInfo); + + assertThat(mController.mActiveToken).isEqualTo(mToken); + assertThat(mController.mPreference.isVisible()).isTrue(); + assertThat(mController.mPreference.getMax()).isEqualTo(MAX_POS); + assertThat(mController.mPreference.getProgress()).isEqualTo(CURRENT_POS); + } + + @Test + public void onRemoteUpdate_differentToken_doNothing() { + mController.mActiveToken = mToken; + + mController.mCallbacks.onRemoteUpdate(mToken2, "token2", mPlaybackInfo); + + assertThat(mController.mActiveToken).isEqualTo(mToken); + } + + @Test + public void onRemoteRemoved_tokenRemoved_setInvisible() { + mController.mPreference = new VolumeSeekBarPreference(mContext); + mController.mActiveToken = mToken; + + mController.mCallbacks.onRemoteRemoved(mToken); + + assertThat(mController.mActiveToken).isNull(); + assertThat(mController.mPreference.isVisible()).isFalse(); + } + + @Test + public void onRemoteVolumeChanged_volumeChanged_updateIt() { + mController.mPreference = new VolumeSeekBarPreference(mContext); + mController.mPreference.setMax(MAX_POS); + mController.mActiveToken = mToken; + mController.mMediaController = mMediaController; + + mController.mCallbacks.onRemoteVolumeChanged(mToken, 0 /* flags */); + + assertThat(mController.mPreference.getProgress()).isEqualTo(CURRENT_POS); + } }