Home | History | Annotate | Download | only in session
      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 
     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         synchronized (sLock) {
     73             if (sInstance == null) {
     74                 sInstance = new MediaSessionLegacyHelper(context.getApplicationContext());
     75             }
     76         }
     77         return sInstance;
     78     }
     79 
     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 
    166     public MediaSession getSession(PendingIntent pi) {
    167         SessionHolder holder = mSessions.get(pi);
    168         return holder == null ? null : holder.mSession;
    169     }
    170 
    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 
    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 
    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 
    243     public boolean isGlobalPriorityActive() {
    244         return mSessionManager.isGlobalPriorityActive();
    245     }
    246 
    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 
    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 
    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 
    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      */
    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 
    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 
    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 
    396         public MediaButtonListener(PendingIntent pi, Context context) {
    397             mPendingIntent = pi;
    398             mContext = context;
    399         }
    400 
    401         @Override
    402         public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
    403             MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
    404             return true;
    405         }
    406 
    407         @Override
    408         public void onPlay() {
    409             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
    410         }
    411 
    412         @Override
    413         public void onPause() {
    414             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE);
    415         }
    416 
    417         @Override
    418         public void onSkipToNext() {
    419             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT);
    420         }
    421 
    422         @Override
    423         public void onSkipToPrevious() {
    424             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
    425         }
    426 
    427         @Override
    428         public void onFastForward() {
    429             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
    430         }
    431 
    432         @Override
    433         public void onRewind() {
    434             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND);
    435         }
    436 
    437         @Override
    438         public void onStop() {
    439             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP);
    440         }
    441 
    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 
    469         public SessionHolder(MediaSession session, PendingIntent pi) {
    470             mSession = session;
    471             mPi = pi;
    472         }
    473 
    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
    490             public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
    491                 if (mMediaButtonListener != null) {
    492                     mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent);
    493                 }
    494                 return true;
    495             }
    496 
    497             @Override
    498             public void onPlay() {
    499                 if (mMediaButtonListener != null) {
    500                     mMediaButtonListener.onPlay();
    501                 }
    502             }
    503 
    504             @Override
    505             public void onPause() {
    506                 if (mMediaButtonListener != null) {
    507                     mMediaButtonListener.onPause();
    508                 }
    509             }
    510 
    511             @Override
    512             public void onSkipToNext() {
    513                 if (mMediaButtonListener != null) {
    514                     mMediaButtonListener.onSkipToNext();
    515                 }
    516             }
    517 
    518             @Override
    519             public void onSkipToPrevious() {
    520                 if (mMediaButtonListener != null) {
    521                     mMediaButtonListener.onSkipToPrevious();
    522                 }
    523             }
    524 
    525             @Override
    526             public void onFastForward() {
    527                 if (mMediaButtonListener != null) {
    528                     mMediaButtonListener.onFastForward();
    529                 }
    530             }
    531 
    532             @Override
    533             public void onRewind() {
    534                 if (mMediaButtonListener != null) {
    535                     mMediaButtonListener.onRewind();
    536                 }
    537             }
    538 
    539             @Override
    540             public void onStop() {
    541                 if (mMediaButtonListener != null) {
    542                     mMediaButtonListener.onStop();
    543                 }
    544             }
    545 
    546             @Override
    547             public void onSeekTo(long pos) {
    548                 if (mRccListener != null) {
    549                     mRccListener.onSeekTo(pos);
    550                 }
    551             }
    552 
    553             @Override
    554             public void onSetRating(Rating rating) {
    555                 if (mRccListener != null) {
    556                     mRccListener.onSetRating(rating);
    557                 }
    558             }
    559         }
    560     }
    561 }
    562