blob: 8b00eee29c6eaf80efcab5e8e81b9265779ecaaa [file] [log] [blame]
John Spurlockf88d8082015-03-25 18:09:51 -04001/*
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
17package com.android.systemui.volume;
18
19import android.app.PendingIntent;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.content.pm.ResolveInfo;
26import android.media.IRemoteVolumeController;
27import android.media.MediaMetadata;
John Spurlockf88d8082015-03-25 18:09:51 -040028import android.media.session.MediaController;
29import android.media.session.MediaController.PlaybackInfo;
Sungsoo Limaf7d46c2019-01-20 10:45:54 +090030import android.media.session.MediaSession;
John Spurlockf88d8082015-03-25 18:09:51 -040031import android.media.session.MediaSession.QueueItem;
32import android.media.session.MediaSession.Token;
33import android.media.session.MediaSessionManager;
34import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
35import android.media.session.PlaybackState;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.Looper;
39import android.os.Message;
40import android.os.RemoteException;
41import android.util.Log;
42
43import java.io.PrintWriter;
44import java.io.StringWriter;
45import java.util.HashMap;
46import java.util.HashSet;
47import java.util.List;
48import java.util.Map;
49import java.util.Objects;
50import 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 */
56public 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
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
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
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
95 protected void postUpdateSessions() {
96 if (!mInit) return;
97 mHandler.sendEmptyMessage(H.UPDATE_SESSIONS);
98 }
99
100 public void destroy() {
101 if (D.BUG) Log.d(TAG, "destroy");
102 mInit = false;
103 mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
104 }
105
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
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900116 private void onRemoteVolumeChangedH(MediaSession.Token sessionToken, int flags) {
117 final MediaController controller = new MediaController(mContext, sessionToken);
John Spurlockf88d8082015-03-25 18:09:51 -0400118 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
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900124 private void onUpdateRemoteControllerH(MediaSession.Token sessionToken) {
125 final MediaController controller =
126 sessionToken != null ? new MediaController(mContext, sessionToken) : null;
John Spurlockf88d8082015-03-25 18:09:51 -0400127 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
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
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
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900335 public void remoteVolumeChanged(MediaSession.Token sessionToken, int flags)
John Spurlockf88d8082015-03-25 18:09:51 -0400336 throws RemoteException {
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900337 mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0,
338 sessionToken).sendToTarget();
John Spurlockf88d8082015-03-25 18:09:51 -0400339 }
340
341 @Override
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900342 public void updateRemoteController(final MediaSession.Token sessionToken)
John Spurlockf88d8082015-03-25 18:09:51 -0400343 throws RemoteException {
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900344 mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, sessionToken).sendToTarget();
John Spurlockf88d8082015-03-25 18:09:51 -0400345 }
346 };
347
348 private final class H extends Handler {
349 private static final int UPDATE_SESSIONS = 1;
350 private static final int REMOTE_VOLUME_CHANGED = 2;
351 private static final int UPDATE_REMOTE_CONTROLLER = 3;
352
353 private H(Looper looper) {
354 super(looper);
355 }
356
357 @Override
358 public void handleMessage(Message msg) {
359 switch (msg.what) {
360 case UPDATE_SESSIONS:
361 onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
362 break;
363 case REMOTE_VOLUME_CHANGED:
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900364 onRemoteVolumeChangedH((MediaSession.Token) msg.obj, msg.arg1);
John Spurlockf88d8082015-03-25 18:09:51 -0400365 break;
366 case UPDATE_REMOTE_CONTROLLER:
Sungsoo Limaf7d46c2019-01-20 10:45:54 +0900367 onUpdateRemoteControllerH((MediaSession.Token) msg.obj);
John Spurlockf88d8082015-03-25 18:09:51 -0400368 break;
369 }
370 }
371 }
372
373 public interface Callbacks {
374 void onRemoteUpdate(Token token, String name, PlaybackInfo pi);
375 void onRemoteRemoved(Token t);
376 void onRemoteVolumeChanged(Token token, int flags);
377 }
378
379}