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.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 
     62     private MediaSessionLegacyHelper(Context context) {
     63         mContext = context;
     64         mSessionManager = (MediaSessionManager) context
     65                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
     66     }
     67 
     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 
     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 
    163     public MediaSession getSession(PendingIntent pi) {
    164         SessionHolder holder = mSessions.get(pi);
    165         return holder == null ? null : holder.mSession;
    166     }
    167 
    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 
    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 
    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 
    236     public boolean isGlobalPriorityActive() {
    237         return mSessionManager.isGlobalPriorityActive();
    238     }
    239 
    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 
    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 
    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 
    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      */
    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 
    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 
    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 
    389         public MediaButtonListener(PendingIntent pi, Context context) {
    390             mPendingIntent = pi;
    391             mContext = context;
    392         }
    393 
    394         @Override
    395         public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
    396             MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
    397             return true;
    398         }
    399 
    400         @Override
    401         public void onPlay() {
    402             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
    403         }
    404 
    405         @Override
    406         public void onPause() {
    407             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE);
    408         }
    409 
    410         @Override
    411         public void onSkipToNext() {
    412             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT);
    413         }
    414 
    415         @Override
    416         public void onSkipToPrevious() {
    417             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
    418         }
    419 
    420         @Override
    421         public void onFastForward() {
    422             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
    423         }
    424 
    425         @Override
    426         public void onRewind() {
    427             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND);
    428         }
    429 
    430         @Override
    431         public void onStop() {
    432             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP);
    433         }
    434 
    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 
    462         public SessionHolder(MediaSession session, PendingIntent pi) {
    463             mSession = session;
    464             mPi = pi;
    465         }
    466 
    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
    483             public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
    484                 if (mMediaButtonListener != null) {
    485                     mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent);
    486                 }
    487                 return true;
    488             }
    489 
    490             @Override
    491             public void onPlay() {
    492                 if (mMediaButtonListener != null) {
    493                     mMediaButtonListener.onPlay();
    494                 }
    495             }
    496 
    497             @Override
    498             public void onPause() {
    499                 if (mMediaButtonListener != null) {
    500                     mMediaButtonListener.onPause();
    501                 }
    502             }
    503 
    504             @Override
    505             public void onSkipToNext() {
    506                 if (mMediaButtonListener != null) {
    507                     mMediaButtonListener.onSkipToNext();
    508                 }
    509             }
    510 
    511             @Override
    512             public void onSkipToPrevious() {
    513                 if (mMediaButtonListener != null) {
    514                     mMediaButtonListener.onSkipToPrevious();
    515                 }
    516             }
    517 
    518             @Override
    519             public void onFastForward() {
    520                 if (mMediaButtonListener != null) {
    521                     mMediaButtonListener.onFastForward();
    522                 }
    523             }
    524 
    525             @Override
    526             public void onRewind() {
    527                 if (mMediaButtonListener != null) {
    528                     mMediaButtonListener.onRewind();
    529                 }
    530             }
    531 
    532             @Override
    533             public void onStop() {
    534                 if (mMediaButtonListener != null) {
    535                     mMediaButtonListener.onStop();
    536                 }
    537             }
    538 
    539             @Override
    540             public void onSeekTo(long pos) {
    541                 if (mRccListener != null) {
    542                     mRccListener.onSeekTo(pos);
    543                 }
    544             }
    545 
    546             @Override
    547             public void onSetRating(Rating rating) {
    548                 if (mRccListener != null) {
    549                     mRccListener.onSetRating(rating);
    550                 }
    551             }
    552         }
    553     }
    554 }
    555