• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 android.media.session;
18 
19 import android.app.PendingIntent;
20 import android.app.PendingIntent.CanceledException;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.graphics.Bitmap;
26 import android.graphics.Canvas;
27 import android.graphics.Paint;
28 import android.graphics.RectF;
29 import android.media.MediaCommunicationManager;
30 import android.media.MediaMetadata;
31 import android.media.MediaMetadataEditor;
32 import android.media.MediaMetadataRetriever;
33 import android.media.Rating;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.Looper;
37 import android.util.ArrayMap;
38 import android.util.Log;
39 import android.view.KeyEvent;
40 
41 /**
42  * Helper for connecting existing APIs up to the new session APIs. This can be
43  * used by RCC, AudioFocus, etc. to create a single session that translates to
44  * all those components.
45  *
46  * @hide
47  */
48 public class MediaSessionLegacyHelper {
49     private static final String TAG = "MediaSessionHelper";
50     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
51 
52     private static final Object sLock = new Object();
53     private static MediaSessionLegacyHelper sInstance;
54 
55     private Context mContext;
56     private MediaSessionManager mSessionManager;
57     private MediaCommunicationManager mCommunicationManager;
58     private Handler mHandler = new Handler(Looper.getMainLooper());
59     // The legacy APIs use PendingIntents to register/unregister media button
60     // receivers and these are associated with RCC.
61     private ArrayMap<PendingIntent, SessionHolder> mSessions
62             = new ArrayMap<PendingIntent, SessionHolder>();
63 
MediaSessionLegacyHelper(Context context)64     private MediaSessionLegacyHelper(Context context) {
65         mContext = context;
66         mSessionManager = (MediaSessionManager) context
67                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
68         mCommunicationManager = context.getSystemService(MediaCommunicationManager.class);
69     }
70 
71     @UnsupportedAppUsage
getHelper(Context context)72     public static MediaSessionLegacyHelper getHelper(Context context) {
73         synchronized (sLock) {
74             if (sInstance == null) {
75                 sInstance = new MediaSessionLegacyHelper(context.getApplicationContext());
76             }
77         }
78         return sInstance;
79     }
80 
getOldMetadata(MediaMetadata metadata, int artworkWidth, int artworkHeight)81     public static Bundle getOldMetadata(MediaMetadata metadata, int artworkWidth,
82             int artworkHeight) {
83         boolean includeArtwork = artworkWidth != -1 && artworkHeight != -1;
84         Bundle oldMetadata = new Bundle();
85         if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
86             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUM),
87                     metadata.getString(MediaMetadata.METADATA_KEY_ALBUM));
88         }
89         if (includeArtwork && metadata.containsKey(MediaMetadata.METADATA_KEY_ART)) {
90             Bitmap art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
91             oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
92                     scaleBitmapIfTooBig(art, artworkWidth, artworkHeight));
93         } else if (includeArtwork && metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART)) {
94             // Fall back to album art if the track art wasn't available
95             Bitmap art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
96             oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
97                     scaleBitmapIfTooBig(art, artworkWidth, artworkHeight));
98         }
99         if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ARTIST)) {
100             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST),
101                     metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST));
102         }
103         if (metadata.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
104             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST),
105                     metadata.getString(MediaMetadata.METADATA_KEY_ARTIST));
106         }
107         if (metadata.containsKey(MediaMetadata.METADATA_KEY_AUTHOR)) {
108             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_AUTHOR),
109                     metadata.getString(MediaMetadata.METADATA_KEY_AUTHOR));
110         }
111         if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPILATION)) {
112             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPILATION),
113                     metadata.getString(MediaMetadata.METADATA_KEY_COMPILATION));
114         }
115         if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPOSER)) {
116             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPOSER),
117                     metadata.getString(MediaMetadata.METADATA_KEY_COMPOSER));
118         }
119         if (metadata.containsKey(MediaMetadata.METADATA_KEY_DATE)) {
120             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DATE),
121                     metadata.getString(MediaMetadata.METADATA_KEY_DATE));
122         }
123         if (metadata.containsKey(MediaMetadata.METADATA_KEY_DISC_NUMBER)) {
124             oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER),
125                     metadata.getLong(MediaMetadata.METADATA_KEY_DISC_NUMBER));
126         }
127         if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
128             oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION),
129                     metadata.getLong(MediaMetadata.METADATA_KEY_DURATION));
130         }
131         if (metadata.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
132             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_GENRE),
133                     metadata.getString(MediaMetadata.METADATA_KEY_GENRE));
134         }
135         if (metadata.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
136             oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS),
137                     metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
138         }
139         if (metadata.containsKey(MediaMetadata.METADATA_KEY_RATING)) {
140             oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_OTHERS),
141                     metadata.getRating(MediaMetadata.METADATA_KEY_RATING));
142         }
143         if (metadata.containsKey(MediaMetadata.METADATA_KEY_USER_RATING)) {
144             oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER),
145                     metadata.getRating(MediaMetadata.METADATA_KEY_USER_RATING));
146         }
147         if (metadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
148             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE),
149                     metadata.getString(MediaMetadata.METADATA_KEY_TITLE));
150         }
151         if (metadata.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
152             oldMetadata.putLong(
153                     String.valueOf(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER),
154                     metadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
155         }
156         if (metadata.containsKey(MediaMetadata.METADATA_KEY_WRITER)) {
157             oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_WRITER),
158                     metadata.getString(MediaMetadata.METADATA_KEY_WRITER));
159         }
160         if (metadata.containsKey(MediaMetadata.METADATA_KEY_YEAR)) {
161             oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_YEAR),
162                     metadata.getLong(MediaMetadata.METADATA_KEY_YEAR));
163         }
164         return oldMetadata;
165     }
166 
getSession(PendingIntent pi)167     public MediaSession getSession(PendingIntent pi) {
168         SessionHolder holder = mSessions.get(pi);
169         return holder == null ? null : holder.mSession;
170     }
171 
sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock)172     public void sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock) {
173         if (keyEvent == null) {
174             Log.w(TAG, "Tried to send a null key event. Ignoring.");
175             return;
176         }
177         mCommunicationManager.dispatchMediaKeyEvent(keyEvent, needWakeLock);
178         if (DEBUG) {
179             Log.d(TAG, "dispatched media key " + keyEvent);
180         }
181     }
182 
sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly)183     public void sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
184         if (keyEvent == null) {
185             Log.w(TAG, "Tried to send a null key event. Ignoring.");
186             return;
187         }
188         mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
189     }
190 
sendAdjustVolumeBy(int suggestedStream, int delta, int flags)191     public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
192         mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);
193         if (DEBUG) {
194             Log.d(TAG, "dispatched volume adjustment");
195         }
196     }
197 
isGlobalPriorityActive()198     public boolean isGlobalPriorityActive() {
199         return mSessionManager.isGlobalPriorityActive();
200     }
201 
addRccListener(PendingIntent pi, MediaSession.Callback listener)202     public void addRccListener(PendingIntent pi, MediaSession.Callback listener) {
203         if (pi == null) {
204             Log.w(TAG, "Pending intent was null, can't add rcc listener.");
205             return;
206         }
207         SessionHolder holder = getHolder(pi, true);
208         if (holder == null) {
209             return;
210         }
211         if (holder.mRccListener != null) {
212             if (holder.mRccListener == listener) {
213                 if (DEBUG) {
214                     Log.d(TAG, "addRccListener listener already added.");
215                 }
216                 // This is already the registered listener, ignore
217                 return;
218             }
219         }
220         holder.mRccListener = listener;
221         holder.mFlags |= MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
222         holder.mSession.setFlags(holder.mFlags);
223         holder.update();
224         if (DEBUG) {
225             Log.d(TAG, "Added rcc listener for " + pi + ".");
226         }
227     }
228 
removeRccListener(PendingIntent pi)229     public void removeRccListener(PendingIntent pi) {
230         if (pi == null) {
231             return;
232         }
233         SessionHolder holder = getHolder(pi, false);
234         if (holder != null && holder.mRccListener != null) {
235             holder.mRccListener = null;
236             holder.mFlags &= ~MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
237             holder.mSession.setFlags(holder.mFlags);
238             holder.update();
239             if (DEBUG) {
240                 Log.d(TAG, "Removed rcc listener for " + pi + ".");
241             }
242         }
243     }
244 
addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent, Context context)245     public void addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent,
246             Context context) {
247         if (pi == null) {
248             Log.w(TAG, "Pending intent was null, can't addMediaButtonListener.");
249             return;
250         }
251         SessionHolder holder = getHolder(pi, true);
252         if (holder == null) {
253             return;
254         }
255         if (holder.mMediaButtonListener != null) {
256             // Already have this listener registered
257             if (DEBUG) {
258                 Log.d(TAG, "addMediaButtonListener already added " + pi);
259             }
260         }
261         holder.mMediaButtonListener = new MediaButtonListener(pi, context);
262         // TODO determine if handling transport performer commands should also
263         // set this flag
264         holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
265         holder.mSession.setFlags(holder.mFlags);
266         holder.mSession.setMediaButtonReceiver(pi);
267         holder.update();
268         if (DEBUG) {
269             Log.d(TAG, "addMediaButtonListener added " + pi);
270         }
271     }
272 
removeMediaButtonListener(PendingIntent pi)273     public void removeMediaButtonListener(PendingIntent pi) {
274         if (pi == null) {
275             return;
276         }
277         SessionHolder holder = getHolder(pi, false);
278         if (holder != null && holder.mMediaButtonListener != null) {
279             holder.mFlags &= ~MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
280             holder.mSession.setFlags(holder.mFlags);
281             holder.mMediaButtonListener = null;
282 
283             holder.update();
284             if (DEBUG) {
285                 Log.d(TAG, "removeMediaButtonListener removed " + pi);
286             }
287         }
288     }
289 
290     /**
291      * Scale a bitmap to fit the smallest dimension by uniformly scaling the
292      * incoming bitmap. If the bitmap fits, then do nothing and return the
293      * original.
294      *
295      * @param bitmap
296      * @param maxWidth
297      * @param maxHeight
298      * @return
299      */
scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight)300     private static Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
301         if (bitmap != null) {
302             final int width = bitmap.getWidth();
303             final int height = bitmap.getHeight();
304             if (width > maxWidth || height > maxHeight) {
305                 float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
306                 int newWidth = Math.round(scale * width);
307                 int newHeight = Math.round(scale * height);
308                 Bitmap.Config newConfig = bitmap.getConfig();
309                 if (newConfig == null) {
310                     newConfig = Bitmap.Config.ARGB_8888;
311                 }
312                 Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig);
313                 Canvas canvas = new Canvas(outBitmap);
314                 Paint paint = new Paint();
315                 paint.setAntiAlias(true);
316                 paint.setFilterBitmap(true);
317                 canvas.drawBitmap(bitmap, null,
318                         new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
319                 bitmap = outBitmap;
320             }
321         }
322         return bitmap;
323     }
324 
getHolder(PendingIntent pi, boolean createIfMissing)325     private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) {
326         SessionHolder holder = mSessions.get(pi);
327         if (holder == null && createIfMissing) {
328             MediaSession session;
329             session = new MediaSession(mContext, TAG + "-" + pi.getCreatorPackage());
330             session.setActive(true);
331             holder = new SessionHolder(session, pi);
332             mSessions.put(pi, holder);
333         }
334         return holder;
335     }
336 
sendKeyEvent(PendingIntent pi, Context context, Intent intent)337     private static void sendKeyEvent(PendingIntent pi, Context context, Intent intent) {
338         try {
339             pi.send(context, 0, intent);
340         } catch (CanceledException e) {
341             Log.e(TAG, "Error sending media key down event:", e);
342             // Don't bother sending up if down failed
343             return;
344         }
345     }
346 
347     private static final class MediaButtonListener extends MediaSession.Callback {
348         private final PendingIntent mPendingIntent;
349         private final Context mContext;
350 
MediaButtonListener(PendingIntent pi, Context context)351         public MediaButtonListener(PendingIntent pi, Context context) {
352             mPendingIntent = pi;
353             mContext = context;
354         }
355 
356         @Override
onMediaButtonEvent(Intent mediaButtonIntent)357         public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
358             MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
359             return true;
360         }
361 
362         @Override
onPlay()363         public void onPlay() {
364             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
365         }
366 
367         @Override
onPause()368         public void onPause() {
369             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE);
370         }
371 
372         @Override
onSkipToNext()373         public void onSkipToNext() {
374             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT);
375         }
376 
377         @Override
onSkipToPrevious()378         public void onSkipToPrevious() {
379             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
380         }
381 
382         @Override
onFastForward()383         public void onFastForward() {
384             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
385         }
386 
387         @Override
onRewind()388         public void onRewind() {
389             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND);
390         }
391 
392         @Override
onStop()393         public void onStop() {
394             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP);
395         }
396 
sendKeyEvent(int keyCode)397         private void sendKeyEvent(int keyCode) {
398             KeyEvent ke = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
399             Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
400             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
401 
402             intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
403             MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent);
404 
405             ke = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
406             intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
407             MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent);
408 
409             if (DEBUG) {
410                 Log.d(TAG, "Sent " + keyCode + " to pending intent " + mPendingIntent);
411             }
412         }
413     }
414 
415     private class SessionHolder {
416         public final MediaSession mSession;
417         public final PendingIntent mPi;
418         public MediaButtonListener mMediaButtonListener;
419         public MediaSession.Callback mRccListener;
420         public int mFlags;
421 
422         public SessionCallback mCb;
423 
SessionHolder(MediaSession session, PendingIntent pi)424         public SessionHolder(MediaSession session, PendingIntent pi) {
425             mSession = session;
426             mPi = pi;
427         }
428 
update()429         public void update() {
430             if (mMediaButtonListener == null && mRccListener == null) {
431                 mSession.setCallback(null);
432                 mSession.release();
433                 mCb = null;
434                 mSessions.remove(mPi);
435             } else if (mCb == null) {
436                 mCb = new SessionCallback();
437                 Handler handler = new Handler(Looper.getMainLooper());
438                 mSession.setCallback(mCb, handler);
439             }
440         }
441 
442         private class SessionCallback extends MediaSession.Callback {
443 
444             @Override
onMediaButtonEvent(Intent mediaButtonIntent)445             public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
446                 if (mMediaButtonListener != null) {
447                     mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent);
448                 }
449                 return true;
450             }
451 
452             @Override
onPlay()453             public void onPlay() {
454                 if (mMediaButtonListener != null) {
455                     mMediaButtonListener.onPlay();
456                 }
457             }
458 
459             @Override
onPause()460             public void onPause() {
461                 if (mMediaButtonListener != null) {
462                     mMediaButtonListener.onPause();
463                 }
464             }
465 
466             @Override
onSkipToNext()467             public void onSkipToNext() {
468                 if (mMediaButtonListener != null) {
469                     mMediaButtonListener.onSkipToNext();
470                 }
471             }
472 
473             @Override
onSkipToPrevious()474             public void onSkipToPrevious() {
475                 if (mMediaButtonListener != null) {
476                     mMediaButtonListener.onSkipToPrevious();
477                 }
478             }
479 
480             @Override
onFastForward()481             public void onFastForward() {
482                 if (mMediaButtonListener != null) {
483                     mMediaButtonListener.onFastForward();
484                 }
485             }
486 
487             @Override
onRewind()488             public void onRewind() {
489                 if (mMediaButtonListener != null) {
490                     mMediaButtonListener.onRewind();
491                 }
492             }
493 
494             @Override
onStop()495             public void onStop() {
496                 if (mMediaButtonListener != null) {
497                     mMediaButtonListener.onStop();
498                 }
499             }
500 
501             @Override
onSeekTo(long pos)502             public void onSeekTo(long pos) {
503                 if (mRccListener != null) {
504                     mRccListener.onSeekTo(pos);
505                 }
506             }
507 
508             @Override
onSetRating(Rating rating)509             public void onSetRating(Rating rating) {
510                 if (mRccListener != null) {
511                     mRccListener.onSetRating(rating);
512                 }
513             }
514         }
515     }
516 }
517