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