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