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.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_YEAR),
    158                     metadata.getLong(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, int stream, boolean musicOnly) {
    180         if (keyEvent == null) {
    181             Log.w(TAG, "Tried to send a null key event. Ignoring.");
    182             return;
    183         }
    184         mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
    185     }
    186 
    187     public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
    188         mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);
    189         if (DEBUG) {
    190             Log.d(TAG, "dispatched volume adjustment");
    191         }
    192     }
    193 
    194     public boolean isGlobalPriorityActive() {
    195         return mSessionManager.isGlobalPriorityActive();
    196     }
    197 
    198     public void addRccListener(PendingIntent pi, MediaSession.Callback listener) {
    199         if (pi == null) {
    200             Log.w(TAG, "Pending intent was null, can't add rcc listener.");
    201             return;
    202         }
    203         SessionHolder holder = getHolder(pi, true);
    204         if (holder == null) {
    205             return;
    206         }
    207         if (holder.mRccListener != null) {
    208             if (holder.mRccListener == listener) {
    209                 if (DEBUG) {
    210                     Log.d(TAG, "addRccListener listener already added.");
    211                 }
    212                 // This is already the registered listener, ignore
    213                 return;
    214             }
    215         }
    216         holder.mRccListener = listener;
    217         holder.mFlags |= MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
    218         holder.mSession.setFlags(holder.mFlags);
    219         holder.update();
    220         if (DEBUG) {
    221             Log.d(TAG, "Added rcc listener for " + pi + ".");
    222         }
    223     }
    224 
    225     public void removeRccListener(PendingIntent pi) {
    226         if (pi == null) {
    227             return;
    228         }
    229         SessionHolder holder = getHolder(pi, false);
    230         if (holder != null && holder.mRccListener != null) {
    231             holder.mRccListener = null;
    232             holder.mFlags &= ~MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
    233             holder.mSession.setFlags(holder.mFlags);
    234             holder.update();
    235             if (DEBUG) {
    236                 Log.d(TAG, "Removed rcc listener for " + pi + ".");
    237             }
    238         }
    239     }
    240 
    241     public void addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent,
    242             Context context) {
    243         if (pi == null) {
    244             Log.w(TAG, "Pending intent was null, can't addMediaButtonListener.");
    245             return;
    246         }
    247         SessionHolder holder = getHolder(pi, true);
    248         if (holder == null) {
    249             return;
    250         }
    251         if (holder.mMediaButtonListener != null) {
    252             // Already have this listener registered
    253             if (DEBUG) {
    254                 Log.d(TAG, "addMediaButtonListener already added " + pi);
    255             }
    256         }
    257         holder.mMediaButtonListener = new MediaButtonListener(pi, context);
    258         // TODO determine if handling transport performer commands should also
    259         // set this flag
    260         holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
    261         holder.mSession.setFlags(holder.mFlags);
    262         holder.mSession.setMediaButtonReceiver(pi);
    263         holder.update();
    264         if (DEBUG) {
    265             Log.d(TAG, "addMediaButtonListener added " + pi);
    266         }
    267     }
    268 
    269     public void removeMediaButtonListener(PendingIntent pi) {
    270         if (pi == null) {
    271             return;
    272         }
    273         SessionHolder holder = getHolder(pi, false);
    274         if (holder != null && holder.mMediaButtonListener != null) {
    275             holder.mFlags &= ~MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
    276             holder.mSession.setFlags(holder.mFlags);
    277             holder.mMediaButtonListener = null;
    278 
    279             holder.update();
    280             if (DEBUG) {
    281                 Log.d(TAG, "removeMediaButtonListener removed " + pi);
    282             }
    283         }
    284     }
    285 
    286     /**
    287      * Scale a bitmap to fit the smallest dimension by uniformly scaling the
    288      * incoming bitmap. If the bitmap fits, then do nothing and return the
    289      * original.
    290      *
    291      * @param bitmap
    292      * @param maxWidth
    293      * @param maxHeight
    294      * @return
    295      */
    296     private static Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
    297         if (bitmap != null) {
    298             final int width = bitmap.getWidth();
    299             final int height = bitmap.getHeight();
    300             if (width > maxWidth || height > maxHeight) {
    301                 float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
    302                 int newWidth = Math.round(scale * width);
    303                 int newHeight = Math.round(scale * height);
    304                 Bitmap.Config newConfig = bitmap.getConfig();
    305                 if (newConfig == null) {
    306                     newConfig = Bitmap.Config.ARGB_8888;
    307                 }
    308                 Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig);
    309                 Canvas canvas = new Canvas(outBitmap);
    310                 Paint paint = new Paint();
    311                 paint.setAntiAlias(true);
    312                 paint.setFilterBitmap(true);
    313                 canvas.drawBitmap(bitmap, null,
    314                         new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
    315                 bitmap = outBitmap;
    316             }
    317         }
    318         return bitmap;
    319     }
    320 
    321     private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) {
    322         SessionHolder holder = mSessions.get(pi);
    323         if (holder == null && createIfMissing) {
    324             MediaSession session;
    325             session = new MediaSession(mContext, TAG + "-" + pi.getCreatorPackage());
    326             session.setActive(true);
    327             holder = new SessionHolder(session, pi);
    328             mSessions.put(pi, holder);
    329         }
    330         return holder;
    331     }
    332 
    333     private static void sendKeyEvent(PendingIntent pi, Context context, Intent intent) {
    334         try {
    335             pi.send(context, 0, intent);
    336         } catch (CanceledException e) {
    337             Log.e(TAG, "Error sending media key down event:", e);
    338             // Don't bother sending up if down failed
    339             return;
    340         }
    341     }
    342 
    343     private static final class MediaButtonListener extends MediaSession.Callback {
    344         private final PendingIntent mPendingIntent;
    345         private final Context mContext;
    346 
    347         public MediaButtonListener(PendingIntent pi, Context context) {
    348             mPendingIntent = pi;
    349             mContext = context;
    350         }
    351 
    352         @Override
    353         public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
    354             MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
    355             return true;
    356         }
    357 
    358         @Override
    359         public void onPlay() {
    360             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
    361         }
    362 
    363         @Override
    364         public void onPause() {
    365             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE);
    366         }
    367 
    368         @Override
    369         public void onSkipToNext() {
    370             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT);
    371         }
    372 
    373         @Override
    374         public void onSkipToPrevious() {
    375             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
    376         }
    377 
    378         @Override
    379         public void onFastForward() {
    380             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
    381         }
    382 
    383         @Override
    384         public void onRewind() {
    385             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND);
    386         }
    387 
    388         @Override
    389         public void onStop() {
    390             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP);
    391         }
    392 
    393         private void sendKeyEvent(int keyCode) {
    394             KeyEvent ke = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
    395             Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
    396             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    397 
    398             intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
    399             MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent);
    400 
    401             ke = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
    402             intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
    403             MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent);
    404 
    405             if (DEBUG) {
    406                 Log.d(TAG, "Sent " + keyCode + " to pending intent " + mPendingIntent);
    407             }
    408         }
    409     }
    410 
    411     private class SessionHolder {
    412         public final MediaSession mSession;
    413         public final PendingIntent mPi;
    414         public MediaButtonListener mMediaButtonListener;
    415         public MediaSession.Callback mRccListener;
    416         public int mFlags;
    417 
    418         public SessionCallback mCb;
    419 
    420         public SessionHolder(MediaSession session, PendingIntent pi) {
    421             mSession = session;
    422             mPi = pi;
    423         }
    424 
    425         public void update() {
    426             if (mMediaButtonListener == null && mRccListener == null) {
    427                 mSession.setCallback(null);
    428                 mSession.release();
    429                 mCb = null;
    430                 mSessions.remove(mPi);
    431             } else if (mCb == null) {
    432                 mCb = new SessionCallback();
    433                 Handler handler = new Handler(Looper.getMainLooper());
    434                 mSession.setCallback(mCb, handler);
    435             }
    436         }
    437 
    438         private class SessionCallback extends MediaSession.Callback {
    439 
    440             @Override
    441             public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
    442                 if (mMediaButtonListener != null) {
    443                     mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent);
    444                 }
    445                 return true;
    446             }
    447 
    448             @Override
    449             public void onPlay() {
    450                 if (mMediaButtonListener != null) {
    451                     mMediaButtonListener.onPlay();
    452                 }
    453             }
    454 
    455             @Override
    456             public void onPause() {
    457                 if (mMediaButtonListener != null) {
    458                     mMediaButtonListener.onPause();
    459                 }
    460             }
    461 
    462             @Override
    463             public void onSkipToNext() {
    464                 if (mMediaButtonListener != null) {
    465                     mMediaButtonListener.onSkipToNext();
    466                 }
    467             }
    468 
    469             @Override
    470             public void onSkipToPrevious() {
    471                 if (mMediaButtonListener != null) {
    472                     mMediaButtonListener.onSkipToPrevious();
    473                 }
    474             }
    475 
    476             @Override
    477             public void onFastForward() {
    478                 if (mMediaButtonListener != null) {
    479                     mMediaButtonListener.onFastForward();
    480                 }
    481             }
    482 
    483             @Override
    484             public void onRewind() {
    485                 if (mMediaButtonListener != null) {
    486                     mMediaButtonListener.onRewind();
    487                 }
    488             }
    489 
    490             @Override
    491             public void onStop() {
    492                 if (mMediaButtonListener != null) {
    493                     mMediaButtonListener.onStop();
    494                 }
    495             }
    496 
    497             @Override
    498             public void onSeekTo(long pos) {
    499                 if (mRccListener != null) {
    500                     mRccListener.onSeekTo(pos);
    501                 }
    502             }
    503 
    504             @Override
    505             public void onSetRating(Rating rating) {
    506                 if (mRccListener != null) {
    507                     mRccListener.onSetRating(rating);
    508                 }
    509             }
    510         }
    511     }
    512 }
    513