• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.notification;
18 
19 import android.content.Context;
20 import android.media.session.MediaController;
21 import android.media.session.MediaSession;
22 import android.media.session.MediaSessionManager;
23 import android.net.Uri;
24 import android.os.Looper;
25 import android.text.TextUtils;
26 
27 import androidx.annotation.VisibleForTesting;
28 import androidx.lifecycle.OnLifecycleEvent;
29 import androidx.preference.PreferenceScreen;
30 
31 import com.android.settings.R;
32 import com.android.settings.slices.SliceBackgroundWorker;
33 import com.android.settingslib.core.lifecycle.Lifecycle;
34 import com.android.settingslib.volume.MediaSessions;
35 
36 import java.io.IOException;
37 import java.util.List;
38 import java.util.Objects;
39 
40 public class RemoteVolumePreferenceController extends VolumeSeekBarPreferenceController {
41 
42     private static final String KEY_REMOTE_VOLUME = "remote_volume";
43     @VisibleForTesting
44     static final int REMOTE_VOLUME = 100;
45 
46     private MediaSessions mMediaSessions;
47     @VisibleForTesting
48     MediaSession.Token mActiveToken;
49     @VisibleForTesting
50     MediaController mMediaController;
51 
52     @VisibleForTesting
53     MediaSessions.Callbacks mCallbacks = new MediaSessions.Callbacks() {
54         @Override
55         public void onRemoteUpdate(MediaSession.Token token, String name,
56                 MediaController.PlaybackInfo pi) {
57             if (mActiveToken == null) {
58                 updateToken(token);
59             }
60             if (Objects.equals(mActiveToken, token)) {
61                 updatePreference(mPreference, mActiveToken, pi);
62             }
63         }
64 
65         @Override
66         public void onRemoteRemoved(MediaSession.Token t) {
67             if (Objects.equals(mActiveToken, t)) {
68                 updateToken(null);
69                 if (mPreference != null) {
70                     mPreference.setVisible(false);
71                 }
72             }
73         }
74 
75         @Override
76         public void onRemoteVolumeChanged(MediaSession.Token token, int flags) {
77             if (Objects.equals(mActiveToken, token)) {
78                 final MediaController.PlaybackInfo pi = mMediaController.getPlaybackInfo();
79                 if (pi != null) {
80                     setSliderPosition(pi.getCurrentVolume());
81                 }
82             }
83         }
84     };
85 
RemoteVolumePreferenceController(Context context)86     public RemoteVolumePreferenceController(Context context) {
87         super(context, KEY_REMOTE_VOLUME);
88         mMediaSessions = new MediaSessions(context, Looper.getMainLooper(), mCallbacks);
89         updateToken(getActiveRemoteToken(mContext));
90     }
91 
92     @Override
getAvailabilityStatus()93     public int getAvailabilityStatus() {
94         // Always return true to make it indexed in database
95         return AVAILABLE_UNSEARCHABLE;
96     }
97 
98     /**
99      * Return {@link android.media.session.MediaSession.Token} for active remote token, or
100      * {@code null} if there is no active remote token.
101      */
getActiveRemoteToken(Context context)102     public static MediaSession.Token getActiveRemoteToken(Context context) {
103         final MediaSessionManager sessionManager = context.getSystemService(
104                 MediaSessionManager.class);
105         final List<MediaController> controllers = sessionManager.getActiveSessions(null);
106         for (MediaController mediaController : controllers) {
107             final MediaController.PlaybackInfo pi = mediaController.getPlaybackInfo();
108             if (isRemote(pi)) {
109                 return mediaController.getSessionToken();
110             }
111         }
112 
113         // No active remote media at this point
114         return null;
115     }
116 
117     @Override
displayPreference(PreferenceScreen screen)118     public void displayPreference(PreferenceScreen screen) {
119         super.displayPreference(screen);
120         mPreference.setVisible(mActiveToken != null);
121         if (mMediaController != null) {
122             updatePreference(mPreference, mActiveToken, mMediaController.getPlaybackInfo());
123         }
124     }
125 
126     @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
onResume()127     public void onResume() {
128         super.onResume();
129         mMediaSessions.init();
130     }
131 
132     @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
onPause()133     public void onPause() {
134         super.onPause();
135         mMediaSessions.destroy();
136     }
137 
138     @Override
getSliderPosition()139     public int getSliderPosition() {
140         if (mPreference != null) {
141             return mPreference.getProgress();
142         }
143         if (mMediaController == null) {
144             return 0;
145         }
146         final MediaController.PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo();
147         return playbackInfo != null ? playbackInfo.getCurrentVolume() : 0;
148     }
149 
150     @Override
setSliderPosition(int position)151     public boolean setSliderPosition(int position) {
152         if (mPreference != null) {
153             mPreference.setProgress(position);
154         }
155         if (mMediaController == null) {
156             return false;
157         }
158         mMediaController.setVolumeTo(position, 0);
159         return true;
160     }
161 
162     @Override
getMax()163     public int getMax() {
164         if (mPreference != null) {
165             return mPreference.getMax();
166         }
167         if (mMediaController == null) {
168             return 0;
169         }
170         final MediaController.PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo();
171         return playbackInfo != null ? playbackInfo.getMaxVolume() : 0;
172     }
173 
174     @Override
getMin()175     public int getMin() {
176         if (mPreference != null) {
177             return mPreference.getMin();
178         }
179         return 0;
180     }
181 
182     @Override
isSliceable()183     public boolean isSliceable() {
184         return TextUtils.equals(getPreferenceKey(), KEY_REMOTE_VOLUME);
185     }
186 
187     @Override
useDynamicSliceSummary()188     public boolean useDynamicSliceSummary() {
189         return true;
190     }
191 
192     @Override
getPreferenceKey()193     public String getPreferenceKey() {
194         return KEY_REMOTE_VOLUME;
195     }
196 
197     @Override
getAudioStream()198     public int getAudioStream() {
199         // This can be anything because remote volume controller doesn't rely on it.
200         return REMOTE_VOLUME;
201     }
202 
203     @Override
getMuteIcon()204     public int getMuteIcon() {
205         return R.drawable.ic_volume_remote_mute;
206     }
207 
isRemote(MediaController.PlaybackInfo pi)208     public static boolean isRemote(MediaController.PlaybackInfo pi) {
209         return pi != null
210                 && pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
211     }
212 
213     @Override
getBackgroundWorkerClass()214     public Class<? extends SliceBackgroundWorker> getBackgroundWorkerClass() {
215         return RemoteVolumeSliceWorker.class;
216     }
217 
updatePreference(VolumeSeekBarPreference seekBarPreference, MediaSession.Token token, MediaController.PlaybackInfo playbackInfo)218     private void updatePreference(VolumeSeekBarPreference seekBarPreference,
219             MediaSession.Token token, MediaController.PlaybackInfo playbackInfo) {
220         if (seekBarPreference == null || token == null || playbackInfo == null) {
221             return;
222         }
223 
224         seekBarPreference.setMax(playbackInfo.getMaxVolume());
225         seekBarPreference.setVisible(true);
226         setSliderPosition(playbackInfo.getCurrentVolume());
227     }
228 
updateToken(MediaSession.Token token)229     private void updateToken(MediaSession.Token token) {
230         mActiveToken = token;
231         if (token != null) {
232             mMediaController = new MediaController(mContext, mActiveToken);
233         } else {
234             mMediaController = null;
235         }
236     }
237 
238     /**
239      * Listener for background change to remote volume, which listens callback
240      * from {@code MediaSessions}
241      */
242     public static class RemoteVolumeSliceWorker extends SliceBackgroundWorker<Void> implements
243             MediaSessions.Callbacks {
244 
245         private MediaSessions mMediaSessions;
246 
RemoteVolumeSliceWorker(Context context, Uri uri)247         public RemoteVolumeSliceWorker(Context context, Uri uri) {
248             super(context, uri);
249             mMediaSessions = new MediaSessions(context, Looper.getMainLooper(), this);
250         }
251 
252         @Override
onSlicePinned()253         protected void onSlicePinned() {
254             mMediaSessions.init();
255         }
256 
257         @Override
onSliceUnpinned()258         protected void onSliceUnpinned() {
259             mMediaSessions.destroy();
260         }
261 
262         @Override
close()263         public void close() throws IOException {
264             mMediaSessions = null;
265         }
266 
267         @Override
onRemoteUpdate(MediaSession.Token token, String name, MediaController.PlaybackInfo pi)268         public void onRemoteUpdate(MediaSession.Token token, String name,
269                 MediaController.PlaybackInfo pi) {
270             notifySliceChange();
271         }
272 
273         @Override
onRemoteRemoved(MediaSession.Token t)274         public void onRemoteRemoved(MediaSession.Token t) {
275             notifySliceChange();
276         }
277 
278         @Override
onRemoteVolumeChanged(MediaSession.Token token, int flags)279         public void onRemoteVolumeChanged(MediaSession.Token token, int flags) {
280             notifySliceChange();
281         }
282     }
283 }
284