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