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 MediaSessionLegacyHelper(Context context)65 private MediaSessionLegacyHelper(Context context) { 66 mContext = context; 67 mSessionManager = (MediaSessionManager) context 68 .getSystemService(Context.MEDIA_SESSION_SERVICE); 69 } 70 getHelper(Context context)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 getOldMetadata(MediaMetadata metadata, int artworkWidth, int artworkHeight)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 getSession(PendingIntent pi)166 public MediaSession getSession(PendingIntent pi) { 167 SessionHolder holder = mSessions.get(pi); 168 return holder == null ? null : holder.mSession; 169 } 170 sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock)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 sendVolumeKeyEvent(KeyEvent keyEvent, boolean musicOnly)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 sendAdjustVolumeBy(int suggestedStream, int delta, int flags)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 isGlobalPriorityActive()243 public boolean isGlobalPriorityActive() { 244 return mSessionManager.isGlobalPriorityActive(); 245 } 246 addRccListener(PendingIntent pi, MediaSession.Callback listener)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 removeRccListener(PendingIntent pi)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 addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent, Context context)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 removeMediaButtonListener(PendingIntent pi)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 */ scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight)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 getHolder(PendingIntent pi, boolean createIfMissing)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 sendKeyEvent(PendingIntent pi, Context context, Intent intent)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 MediaButtonListener(PendingIntent pi, Context context)396 public MediaButtonListener(PendingIntent pi, Context context) { 397 mPendingIntent = pi; 398 mContext = context; 399 } 400 401 @Override onMediaButtonEvent(Intent mediaButtonIntent)402 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 403 MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent); 404 return true; 405 } 406 407 @Override onPlay()408 public void onPlay() { 409 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY); 410 } 411 412 @Override onPause()413 public void onPause() { 414 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE); 415 } 416 417 @Override onSkipToNext()418 public void onSkipToNext() { 419 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT); 420 } 421 422 @Override onSkipToPrevious()423 public void onSkipToPrevious() { 424 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS); 425 } 426 427 @Override onFastForward()428 public void onFastForward() { 429 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD); 430 } 431 432 @Override onRewind()433 public void onRewind() { 434 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND); 435 } 436 437 @Override onStop()438 public void onStop() { 439 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP); 440 } 441 sendKeyEvent(int keyCode)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 SessionHolder(MediaSession session, PendingIntent pi)469 public SessionHolder(MediaSession session, PendingIntent pi) { 470 mSession = session; 471 mPi = pi; 472 } 473 update()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 onMediaButtonEvent(Intent mediaButtonIntent)490 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 491 if (mMediaButtonListener != null) { 492 mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent); 493 } 494 return true; 495 } 496 497 @Override onPlay()498 public void onPlay() { 499 if (mMediaButtonListener != null) { 500 mMediaButtonListener.onPlay(); 501 } 502 } 503 504 @Override onPause()505 public void onPause() { 506 if (mMediaButtonListener != null) { 507 mMediaButtonListener.onPause(); 508 } 509 } 510 511 @Override onSkipToNext()512 public void onSkipToNext() { 513 if (mMediaButtonListener != null) { 514 mMediaButtonListener.onSkipToNext(); 515 } 516 } 517 518 @Override onSkipToPrevious()519 public void onSkipToPrevious() { 520 if (mMediaButtonListener != null) { 521 mMediaButtonListener.onSkipToPrevious(); 522 } 523 } 524 525 @Override onFastForward()526 public void onFastForward() { 527 if (mMediaButtonListener != null) { 528 mMediaButtonListener.onFastForward(); 529 } 530 } 531 532 @Override onRewind()533 public void onRewind() { 534 if (mMediaButtonListener != null) { 535 mMediaButtonListener.onRewind(); 536 } 537 } 538 539 @Override onStop()540 public void onStop() { 541 if (mMediaButtonListener != null) { 542 mMediaButtonListener.onStop(); 543 } 544 } 545 546 @Override onSeekTo(long pos)547 public void onSeekTo(long pos) { 548 if (mRccListener != null) { 549 mRccListener.onSeekTo(pos); 550 } 551 } 552 553 @Override onSetRating(Rating rating)554 public void onSetRating(Rating rating) { 555 if (mRccListener != null) { 556 mRccListener.onSetRating(rating); 557 } 558 } 559 } 560 } 561 } 562