• 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.settingslib.volume;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.PendingIntent;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.PackageManager.NameNotFoundException;
27 import android.content.pm.ResolveInfo;
28 import android.media.MediaMetadata;
29 import android.media.session.MediaController;
30 import android.media.session.MediaController.PlaybackInfo;
31 import android.media.session.MediaSession;
32 import android.media.session.MediaSession.QueueItem;
33 import android.media.session.MediaSession.Token;
34 import android.media.session.MediaSessionManager;
35 import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
36 import android.media.session.MediaSessionManager.RemoteSessionCallback;
37 import android.media.session.PlaybackState;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.HandlerExecutor;
41 import android.os.Looper;
42 import android.os.Message;
43 import android.util.Log;
44 
45 import java.io.PrintWriter;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Objects;
51 import java.util.Set;
52 
53 /**
54  * Convenience client for all media session updates.  Provides a callback interface for events
55  * related to remote media sessions.
56  */
57 public class MediaSessions {
58     private static final String TAG = Util.logTag(MediaSessions.class);
59 
60     private static final boolean USE_SERVICE_LABEL = false;
61 
62     private final Context mContext;
63     private final H mHandler;
64     private final HandlerExecutor mHandlerExecutor;
65     private final MediaSessionManager mMgr;
66     private final Map<Token, MediaControllerRecord> mRecords = new HashMap<>();
67     private final Callbacks mCallbacks;
68 
69     private boolean mInit;
70 
MediaSessions(Context context, Looper looper, Callbacks callbacks)71     public MediaSessions(Context context, Looper looper, Callbacks callbacks) {
72         mContext = context;
73         mHandler = new H(looper);
74         mHandlerExecutor = new HandlerExecutor(mHandler);
75         mMgr = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
76         mCallbacks = callbacks;
77     }
78 
79     /**
80      * Dump to {@code writer}
81      */
dump(PrintWriter writer)82     public void dump(PrintWriter writer) {
83         writer.println(getClass().getSimpleName() + " state:");
84         writer.print("  mInit: ");
85         writer.println(mInit);
86         writer.print("  mRecords.size: ");
87         writer.println(mRecords.size());
88         int i = 0;
89         for (MediaControllerRecord r : mRecords.values()) {
90             dump(++i, writer, r.controller);
91         }
92     }
93 
94     /**
95      * init MediaSessions
96      */
init()97     public void init() {
98         if (D.BUG) Log.d(TAG, "init");
99         // will throw if no permission
100         mMgr.addOnActiveSessionsChangedListener(mSessionsListener, null, mHandler);
101         mInit = true;
102         postUpdateSessions();
103         mMgr.registerRemoteSessionCallback(mHandlerExecutor,
104                 mRemoteSessionCallback);
105     }
106 
postUpdateSessions()107     protected void postUpdateSessions() {
108         if (!mInit) return;
109         mHandler.sendEmptyMessage(H.UPDATE_SESSIONS);
110     }
111 
112     /**
113      * Destroy MediaSessions
114      */
destroy()115     public void destroy() {
116         if (D.BUG) Log.d(TAG, "destroy");
117         mInit = false;
118         mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
119         mMgr.unregisterRemoteSessionCallback(mRemoteSessionCallback);
120     }
121 
122     /**
123      * Set volume {@code level} to remote media {@code token}
124      */
setVolume(Token token, int level)125     public void setVolume(Token token, int level) {
126         final MediaControllerRecord r = mRecords.get(token);
127         if (r == null) {
128             Log.w(TAG, "setVolume: No record found for token " + token);
129             return;
130         }
131         if (D.BUG) Log.d(TAG, "Setting level to " + level);
132         r.controller.setVolumeTo(level, 0);
133     }
134 
onRemoteVolumeChangedH(Token sessionToken, int flags)135     private void onRemoteVolumeChangedH(Token sessionToken, int flags) {
136         final MediaController controller = new MediaController(mContext, sessionToken);
137         if (D.BUG) {
138             Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
139                     + Util.audioManagerFlagsToString(flags));
140         }
141         final Token token = controller.getSessionToken();
142         mCallbacks.onRemoteVolumeChanged(token, flags);
143     }
144 
onUpdateRemoteSessionListH(Token sessionToken)145     private void onUpdateRemoteSessionListH(Token sessionToken) {
146         final MediaController controller =
147                 sessionToken != null ? new MediaController(mContext, sessionToken) : null;
148         final String pkg = controller != null ? controller.getPackageName() : null;
149         if (D.BUG) Log.d(TAG, "onUpdateRemoteSessionListH " + pkg);
150         // this may be our only indication that a remote session is changed, refresh
151         postUpdateSessions();
152     }
153 
onActiveSessionsUpdatedH(List<MediaController> controllers)154     protected void onActiveSessionsUpdatedH(List<MediaController> controllers) {
155         if (D.BUG) Log.d(TAG, "onActiveSessionsUpdatedH n=" + controllers.size());
156         final Set<Token> toRemove = new HashSet<Token>(mRecords.keySet());
157         for (MediaController controller : controllers) {
158             final Token token = controller.getSessionToken();
159             final PlaybackInfo pi = controller.getPlaybackInfo();
160             toRemove.remove(token);
161             if (!mRecords.containsKey(token)) {
162                 final MediaControllerRecord r = new MediaControllerRecord(controller);
163                 r.name = getControllerName(controller);
164                 mRecords.put(token, r);
165                 controller.registerCallback(r, mHandler);
166             }
167             final MediaControllerRecord r = mRecords.get(token);
168             final boolean remote = isRemote(pi);
169             if (remote) {
170                 updateRemoteH(token, r.name, pi);
171                 r.sentRemote = true;
172             }
173         }
174         for (Token t : toRemove) {
175             final MediaControllerRecord r = mRecords.get(t);
176             r.controller.unregisterCallback(r);
177             mRecords.remove(t);
178             if (D.BUG) Log.d(TAG, "Removing " + r.name + " sentRemote=" + r.sentRemote);
179             if (r.sentRemote) {
180                 mCallbacks.onRemoteRemoved(t);
181                 r.sentRemote = false;
182             }
183         }
184     }
185 
isRemote(PlaybackInfo pi)186     private static boolean isRemote(PlaybackInfo pi) {
187         return pi != null && pi.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
188     }
189 
getControllerName(MediaController controller)190     protected String getControllerName(MediaController controller) {
191         final PackageManager pm = mContext.getPackageManager();
192         final String pkg = controller.getPackageName();
193         try {
194             if (USE_SERVICE_LABEL) {
195                 final List<ResolveInfo> ris = pm.queryIntentServices(
196                         new Intent("android.media.MediaRouteProviderService").setPackage(pkg), 0);
197                 if (ris != null) {
198                     for (ResolveInfo ri : ris) {
199                         if (ri.serviceInfo == null) continue;
200                         if (pkg.equals(ri.serviceInfo.packageName)) {
201                             final String serviceLabel =
202                                     Objects.toString(ri.serviceInfo.loadLabel(pm), "").trim();
203                             if (serviceLabel.length() > 0) {
204                                 return serviceLabel;
205                             }
206                         }
207                     }
208                 }
209             }
210             final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
211             final String appLabel = Objects.toString(ai.loadLabel(pm), "").trim();
212             if (appLabel.length() > 0) {
213                 return appLabel;
214             }
215         } catch (NameNotFoundException e) {
216         }
217         return pkg;
218     }
219 
updateRemoteH(Token token, String name, PlaybackInfo pi)220     private void updateRemoteH(Token token, String name, PlaybackInfo pi) {
221         if (mCallbacks != null) {
222             mCallbacks.onRemoteUpdate(token, name, pi);
223         }
224     }
225 
dump(int n, PrintWriter writer, MediaController c)226     private static void dump(int n, PrintWriter writer, MediaController c) {
227         writer.println("  Controller " + n + ": " + c.getPackageName());
228         final Bundle extras = c.getExtras();
229         final long flags = c.getFlags();
230         final MediaMetadata mm = c.getMetadata();
231         final PlaybackInfo pi = c.getPlaybackInfo();
232         final PlaybackState playbackState = c.getPlaybackState();
233         final List<QueueItem> queue = c.getQueue();
234         final CharSequence queueTitle = c.getQueueTitle();
235         final int ratingType = c.getRatingType();
236         final PendingIntent sessionActivity = c.getSessionActivity();
237 
238         writer.println("    PlaybackState: " + Util.playbackStateToString(playbackState));
239         writer.println("    PlaybackInfo: " + Util.playbackInfoToString(pi));
240         if (mm != null) {
241             writer.println("  MediaMetadata.desc=" + mm.getDescription());
242         }
243         writer.println("    RatingType: " + ratingType);
244         writer.println("    Flags: " + flags);
245         if (extras != null) {
246             writer.println("    Extras:");
247             for (String key : extras.keySet()) {
248                 writer.println("      " + key + "=" + extras.get(key));
249             }
250         }
251         if (queueTitle != null) {
252             writer.println("    QueueTitle: " + queueTitle);
253         }
254         if (queue != null && !queue.isEmpty()) {
255             writer.println("    Queue:");
256             for (QueueItem qi : queue) {
257                 writer.println("      " + qi);
258             }
259         }
260         if (pi != null) {
261             writer.println("    sessionActivity: " + sessionActivity);
262         }
263     }
264 
265     private final class MediaControllerRecord extends MediaController.Callback {
266         public final MediaController controller;
267 
268         public boolean sentRemote;
269         public String name;
270 
MediaControllerRecord(MediaController controller)271         private MediaControllerRecord(MediaController controller) {
272             this.controller = controller;
273         }
274 
cb(String method)275         private String cb(String method) {
276             return method + " " + controller.getPackageName() + " ";
277         }
278 
279         @Override
onAudioInfoChanged(PlaybackInfo info)280         public void onAudioInfoChanged(PlaybackInfo info) {
281             if (D.BUG) {
282                 Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
283                         + " sentRemote=" + sentRemote);
284             }
285             final boolean remote = isRemote(info);
286             if (!remote && sentRemote) {
287                 mCallbacks.onRemoteRemoved(controller.getSessionToken());
288                 sentRemote = false;
289             } else if (remote) {
290                 updateRemoteH(controller.getSessionToken(), name, info);
291                 sentRemote = true;
292             }
293         }
294 
295         @Override
onExtrasChanged(Bundle extras)296         public void onExtrasChanged(Bundle extras) {
297             if (D.BUG) Log.d(TAG, cb("onExtrasChanged") + extras);
298         }
299 
300         @Override
onMetadataChanged(MediaMetadata metadata)301         public void onMetadataChanged(MediaMetadata metadata) {
302             if (D.BUG) Log.d(TAG, cb("onMetadataChanged") + Util.mediaMetadataToString(metadata));
303         }
304 
305         @Override
onPlaybackStateChanged(PlaybackState state)306         public void onPlaybackStateChanged(PlaybackState state) {
307             if (D.BUG) Log.d(TAG, cb("onPlaybackStateChanged") + Util.playbackStateToString(state));
308         }
309 
310         @Override
onQueueChanged(List<QueueItem> queue)311         public void onQueueChanged(List<QueueItem> queue) {
312             if (D.BUG) Log.d(TAG, cb("onQueueChanged") + queue);
313         }
314 
315         @Override
onQueueTitleChanged(CharSequence title)316         public void onQueueTitleChanged(CharSequence title) {
317             if (D.BUG) Log.d(TAG, cb("onQueueTitleChanged") + title);
318         }
319 
320         @Override
onSessionDestroyed()321         public void onSessionDestroyed() {
322             if (D.BUG) Log.d(TAG, cb("onSessionDestroyed"));
323         }
324 
325         @Override
onSessionEvent(String event, Bundle extras)326         public void onSessionEvent(String event, Bundle extras) {
327             if (D.BUG) Log.d(TAG, cb("onSessionEvent") + "event=" + event + " extras=" + extras);
328         }
329     }
330 
331     private final OnActiveSessionsChangedListener mSessionsListener =
332             new OnActiveSessionsChangedListener() {
333                 @Override
334                 public void onActiveSessionsChanged(List<MediaController> controllers) {
335                     onActiveSessionsUpdatedH(controllers);
336                 }
337             };
338 
339     private final RemoteSessionCallback mRemoteSessionCallback =
340             new RemoteSessionCallback() {
341                 @Override
342                 public void onVolumeChanged(@NonNull MediaSession.Token sessionToken,
343                         int flags) {
344                     mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0,
345                             sessionToken).sendToTarget();
346                 }
347 
348                 @Override
349                 public void onDefaultRemoteSessionChanged(
350                         @Nullable MediaSession.Token sessionToken) {
351                     mHandler.obtainMessage(H.UPDATE_REMOTE_SESSION_LIST,
352                             sessionToken).sendToTarget();
353                 }
354     };
355 
356     private final class H extends Handler {
357         private static final int UPDATE_SESSIONS = 1;
358         private static final int REMOTE_VOLUME_CHANGED = 2;
359         private static final int UPDATE_REMOTE_SESSION_LIST = 3;
360 
H(Looper looper)361         private H(Looper looper) {
362             super(looper);
363         }
364 
365         @Override
handleMessage(Message msg)366         public void handleMessage(Message msg) {
367             switch (msg.what) {
368                 case UPDATE_SESSIONS:
369                     onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
370                     break;
371                 case REMOTE_VOLUME_CHANGED:
372                     onRemoteVolumeChangedH((Token) msg.obj, msg.arg1);
373                     break;
374                 case UPDATE_REMOTE_SESSION_LIST:
375                     onUpdateRemoteSessionListH((Token) msg.obj);
376                     break;
377             }
378         }
379     }
380 
381     /**
382      * Callback for remote media sessions
383      */
384     public interface Callbacks {
385         /**
386          * Invoked when remote media session is updated
387          */
onRemoteUpdate(Token token, String name, PlaybackInfo pi)388         void onRemoteUpdate(Token token, String name, PlaybackInfo pi);
389 
390         /**
391          * Invoked when remote media session is removed
392          */
onRemoteRemoved(Token t)393         void onRemoteRemoved(Token t);
394 
395         /**
396          * Invoked when remote volume is changed
397          */
onRemoteVolumeChanged(Token token, int flags)398         void onRemoteVolumeChanged(Token token, int flags);
399     }
400 
401 }
402