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