186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines/* 286a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * Copyright (C) 2015 The Android Open Source Project 386a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * 486a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * Licensed under the Apache License, Version 2.0 (the "License"); 586a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * you may not use this file except in compliance with the License. 686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * You may obtain a copy of the License at 786a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * 886a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * http://www.apache.org/licenses/LICENSE-2.0 986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * 1086a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * Unless required by applicable law or agreed to in writing, software 1186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * distributed under the License is distributed on an "AS IS" BASIS, 1286a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1386a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * See the License for the specific language governing permissions and 1486a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * limitations under the License. 1586a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines */ 1686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines 1786a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinespackage com.android.systemui.volume; 1886a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines 1986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.app.PendingIntent; 2086a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.content.Context; 210116d8b4247829adfb64b9cb7992eb783a54abd5Stephen Hinesimport android.content.Intent; 2286a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.content.pm.ApplicationInfo; 23b730e239619a546d93e5926ea92d698ab77ec7f6Stephen Hinesimport android.content.pm.PackageManager; 24b730e239619a546d93e5926ea92d698ab77ec7f6Stephen Hinesimport android.content.pm.PackageManager.NameNotFoundException; 25b730e239619a546d93e5926ea92d698ab77ec7f6Stephen Hinesimport android.content.pm.ResolveInfo; 26b730e239619a546d93e5926ea92d698ab77ec7f6Stephen Hinesimport android.media.IRemoteVolumeController; 27b730e239619a546d93e5926ea92d698ab77ec7f6Stephen Hinesimport android.media.MediaMetadata; 2886a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.media.session.ISessionController; 2986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.media.session.MediaController; 30b730e239619a546d93e5926ea92d698ab77ec7f6Stephen Hinesimport android.media.session.MediaController.PlaybackInfo; 3186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.media.session.MediaSession.QueueItem; 3286a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.media.session.MediaSession.Token; 3386a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.media.session.MediaSessionManager; 341253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hinesimport android.media.session.MediaSessionManager.OnActiveSessionsChangedListener; 3586a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.media.session.PlaybackState; 3686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.os.Bundle; 3786a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.os.Handler; 3886a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.os.Looper; 3986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport android.os.Message; 402d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hinesimport android.os.RemoteException; 412d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hinesimport android.util.Log; 422d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hines 432d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hinesimport java.io.PrintWriter; 442d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hinesimport java.io.StringWriter; 452d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hinesimport java.util.HashMap; 4686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport java.util.HashSet; 4786a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport java.util.List; 4886a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport java.util.Map; 4986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport java.util.Objects; 5086a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinesimport java.util.Set; 5186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines 5286a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines/** 5386a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * Convenience client for all media session updates. Provides a callback interface for events 5486a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines * related to remote media sessions. 551253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines */ 5686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hinespublic class MediaSessions { 57900c6c1f08f7c572125d7d39abe0f0f9eafbfa14Chris Wailes private static final String TAG = Util.logTag(MediaSessions.class); 5886a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines 5986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines private static final boolean USE_SERVICE_LABEL = false; 60c754d49ee856be620e041348a9f2b3d5610a5a26Stephen Hines 61c754d49ee856be620e041348a9f2b3d5610a5a26Stephen Hines private final Context mContext; 62c754d49ee856be620e041348a9f2b3d5610a5a26Stephen Hines private final H mHandler; 63c754d49ee856be620e041348a9f2b3d5610a5a26Stephen Hines private final MediaSessionManager mMgr; 641253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines private final Map<Token, MediaControllerRecord> mRecords = new HashMap<>(); 6586a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines private final Callbacks mCallbacks; 6686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines 671253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines private boolean mInit; 681253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines 691253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines public MediaSessions(Context context, Looper looper, Callbacks callbacks) { 701253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines mContext = context; 711253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines mHandler = new H(looper); 721253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines mMgr = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE); 731253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines mCallbacks = callbacks; 741253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines } 751253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines 761253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines public void dump(PrintWriter writer) { 771253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines writer.println(getClass().getSimpleName() + " state:"); 781253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines writer.print(" mInit: "); writer.println(mInit); 791253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines writer.print(" mRecords.size: "); writer.println(mRecords.size()); 801253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines int i = 0; 811253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines for (MediaControllerRecord r : mRecords.values()) { 821253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines dump(++i, writer, r.controller); 831253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines } 8486a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines } 8586a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines 8686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines public void init() { 8786a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines if (D.BUG) Log.d(TAG, "init"); 8886a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines // will throw if no permission 8986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines mMgr.addOnActiveSessionsChangedListener(mSessionsListener, null, mHandler); 9086a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines mInit = true; 9186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines postUpdateSessions(); 921253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines mMgr.setRemoteVolumeController(mRvc); 931253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines } 941253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines 9586a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines protected void postUpdateSessions() { 9686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines if (!mInit) return; 971253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines mHandler.sendEmptyMessage(H.UPDATE_SESSIONS); 981253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines } 991253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines 10086a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines public void destroy() { 10186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines if (D.BUG) Log.d(TAG, "destroy"); 1021253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines mInit = false; 1031253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines mMgr.removeOnActiveSessionsChangedListener(mSessionsListener); 1041253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines } 1051253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines 10686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines public void setVolume(Token token, int level) { 10786a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines final MediaControllerRecord r = mRecords.get(token); 1081253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines if (r == null) { 1091253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines Log.w(TAG, "setVolume: No record found for token " + token); 1101253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines return; 11186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines } 11286a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines if (D.BUG) Log.d(TAG, "Setting level to " + level); 11386a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines r.controller.setVolumeTo(level, 0); 1142d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hines } 1152d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hines 1162d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hines private void onRemoteVolumeChangedH(ISessionController session, int flags) { 1172d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hines final MediaController controller = new MediaController(mContext, session); 1182d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hines if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " " 1192d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hines + Util.audioManagerFlagsToString(flags)); 12086a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines final Token token = controller.getSessionToken(); 12186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines mCallbacks.onRemoteVolumeChanged(token, flags); 12286a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines } 1232d201e547f1d32140ff8ead1818c169f441cf5fbStephen Hines 1241253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines private void onUpdateRemoteControllerH(ISessionController session) { 12586a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines final MediaController controller = session != null ? new MediaController(mContext, session) 12686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines : null; 12786a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines final String pkg = controller != null ? controller.getPackageName() : null; 12886a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg); 12986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines // this may be our only indication that a remote session is changed, refresh 13086a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines postUpdateSessions(); 13186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines } 13286a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines 13386a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines protected void onActiveSessionsUpdatedH(List<MediaController> controllers) { 13486a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines if (D.BUG) Log.d(TAG, "onActiveSessionsUpdatedH n=" + controllers.size()); 13586a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines final Set<Token> toRemove = new HashSet<Token>(mRecords.keySet()); 13686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines for (MediaController controller : controllers) { 13786a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines final Token token = controller.getSessionToken(); 13886a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines final PlaybackInfo pi = controller.getPlaybackInfo(); 13986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines toRemove.remove(token); 14086a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines if (!mRecords.containsKey(token)) { 14186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines final MediaControllerRecord r = new MediaControllerRecord(controller); 14286a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines r.name = getControllerName(controller); 14386a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines mRecords.put(token, r); 14486a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines controller.registerCallback(r, mHandler); 14586a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines } 14686a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines final MediaControllerRecord r = mRecords.get(token); 1471253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines final boolean remote = isRemote(pi); 1481253c195dd7911ad91bd66790f03e4c2f8888ad2Stephen Hines if (remote) { 14986a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines updateRemoteH(token, r.name, pi); 15086a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines r.sentRemote = true; 15186a0b797c221d4c3373dc10c8229b75b6747f6e7Stephen Hines } 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 165 private static boolean isRemote(PlaybackInfo pi) { 166 return pi != null && pi.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE; 167 } 168 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 198 private void updateRemoteH(Token token, String name, PlaybackInfo pi) { 199 if (mCallbacks != null) { 200 mCallbacks.onRemoteUpdate(token, name, pi); 201 } 202 } 203 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 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 267 private MediaControllerRecord(MediaController controller) { 268 this.controller = controller; 269 } 270 271 private String cb(String method) { 272 return method + " " + controller.getPackageName() + " "; 273 } 274 275 @Override 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 290 public void onExtrasChanged(Bundle extras) { 291 if (D.BUG) Log.d(TAG, cb("onExtrasChanged") + extras); 292 } 293 294 @Override 295 public void onMetadataChanged(MediaMetadata metadata) { 296 if (D.BUG) Log.d(TAG, cb("onMetadataChanged") + Util.mediaMetadataToString(metadata)); 297 } 298 299 @Override 300 public void onPlaybackStateChanged(PlaybackState state) { 301 if (D.BUG) Log.d(TAG, cb("onPlaybackStateChanged") + Util.playbackStateToString(state)); 302 } 303 304 @Override 305 public void onQueueChanged(List<QueueItem> queue) { 306 if (D.BUG) Log.d(TAG, cb("onQueueChanged") + queue); 307 } 308 309 @Override 310 public void onQueueTitleChanged(CharSequence title) { 311 if (D.BUG) Log.d(TAG, cb("onQueueTitleChanged") + title); 312 } 313 314 @Override 315 public void onSessionDestroyed() { 316 if (D.BUG) Log.d(TAG, cb("onSessionDestroyed")); 317 } 318 319 @Override 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 352 private H(Looper looper) { 353 super(looper); 354 } 355 356 @Override 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 { 373 void onRemoteUpdate(Token token, String name, PlaybackInfo pi); 374 void onRemoteRemoved(Token t); 375 void onRemoteVolumeChanged(Token token, int flags); 376 } 377 378} 379