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