From dd002749678cd39a937e897d4e16481a7fa54ea9 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 27 Feb 2019 13:13:27 -0800 Subject: [PATCH] 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); + } }