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