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