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