Add SliceWorker for remote volume controller
Also update some methods to fix issues in panel.
Bug: 126199571
Test: RunSettingsRoboTests
Change-Id: I9b93ae594d41cb71b984b06267161455373bf121
diff --git a/src/com/android/settings/notification/RemoteVolumePreferenceController.java b/src/com/android/settings/notification/RemoteVolumePreferenceController.java
index dece928..0ad307e 100644
--- a/src/com/android/settings/notification/RemoteVolumePreferenceController.java
+++ b/src/com/android/settings/notification/RemoteVolumePreferenceController.java
@@ -20,16 +20,20 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
return pi != null
&& pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
}
+
+ @Override
+ public Class<? extends SliceBackgroundWorker> 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<Void> 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 20c2272..62eca53 100644
--- a/src/com/android/settings/panel/VolumePanel.java
+++ b/src/com/android/settings/panel/VolumePanel.java
@@ -19,6 +19,7 @@
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 @@
@Override
public List<Uri> getSlices() {
final List<Uri> 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 ab1b248..3a1db69 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -219,6 +219,17 @@
.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 a34d4d7..1bf2fd8 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 @@
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 @@
@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<MediaController> 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 @@
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);
+ }
}