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 MediaSessionLegacyHelper(Context context)62 private MediaSessionLegacyHelper(Context context) { 63 mContext = context; 64 mSessionManager = (MediaSessionManager) context 65 .getSystemService(Context.MEDIA_SESSION_SERVICE); 66 } 67 getHelper(Context context)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 getOldMetadata(MediaMetadata metadata, int artworkWidth, int artworkHeight)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 getSession(PendingIntent pi)163 public MediaSession getSession(PendingIntent pi) { 164 SessionHolder holder = mSessions.get(pi); 165 return holder == null ? null : holder.mSession; 166 } 167 sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock)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 sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly)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 sendAdjustVolumeBy(int suggestedStream, int delta, int flags)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 isGlobalPriorityActive()194 public boolean isGlobalPriorityActive() { 195 return mSessionManager.isGlobalPriorityActive(); 196 } 197 addRccListener(PendingIntent pi, MediaSession.Callback listener)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 removeRccListener(PendingIntent pi)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 addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent, Context context)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 removeMediaButtonListener(PendingIntent pi)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 */ scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight)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 getHolder(PendingIntent pi, boolean createIfMissing)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 sendKeyEvent(PendingIntent pi, Context context, Intent intent)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 MediaButtonListener(PendingIntent pi, Context context)347 public MediaButtonListener(PendingIntent pi, Context context) { 348 mPendingIntent = pi; 349 mContext = context; 350 } 351 352 @Override onMediaButtonEvent(Intent mediaButtonIntent)353 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 354 MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent); 355 return true; 356 } 357 358 @Override onPlay()359 public void onPlay() { 360 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY); 361 } 362 363 @Override onPause()364 public void onPause() { 365 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE); 366 } 367 368 @Override onSkipToNext()369 public void onSkipToNext() { 370 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT); 371 } 372 373 @Override onSkipToPrevious()374 public void onSkipToPrevious() { 375 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS); 376 } 377 378 @Override onFastForward()379 public void onFastForward() { 380 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD); 381 } 382 383 @Override onRewind()384 public void onRewind() { 385 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND); 386 } 387 388 @Override onStop()389 public void onStop() { 390 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP); 391 } 392 sendKeyEvent(int keyCode)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 SessionHolder(MediaSession session, PendingIntent pi)420 public SessionHolder(MediaSession session, PendingIntent pi) { 421 mSession = session; 422 mPi = pi; 423 } 424 update()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 onMediaButtonEvent(Intent mediaButtonIntent)441 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 442 if (mMediaButtonListener != null) { 443 mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent); 444 } 445 return true; 446 } 447 448 @Override onPlay()449 public void onPlay() { 450 if (mMediaButtonListener != null) { 451 mMediaButtonListener.onPlay(); 452 } 453 } 454 455 @Override onPause()456 public void onPause() { 457 if (mMediaButtonListener != null) { 458 mMediaButtonListener.onPause(); 459 } 460 } 461 462 @Override onSkipToNext()463 public void onSkipToNext() { 464 if (mMediaButtonListener != null) { 465 mMediaButtonListener.onSkipToNext(); 466 } 467 } 468 469 @Override onSkipToPrevious()470 public void onSkipToPrevious() { 471 if (mMediaButtonListener != null) { 472 mMediaButtonListener.onSkipToPrevious(); 473 } 474 } 475 476 @Override onFastForward()477 public void onFastForward() { 478 if (mMediaButtonListener != null) { 479 mMediaButtonListener.onFastForward(); 480 } 481 } 482 483 @Override onRewind()484 public void onRewind() { 485 if (mMediaButtonListener != null) { 486 mMediaButtonListener.onRewind(); 487 } 488 } 489 490 @Override onStop()491 public void onStop() { 492 if (mMediaButtonListener != null) { 493 mMediaButtonListener.onStop(); 494 } 495 } 496 497 @Override onSeekTo(long pos)498 public void onSeekTo(long pos) { 499 if (mRccListener != null) { 500 mRccListener.onSeekTo(pos); 501 } 502 } 503 504 @Override onSetRating(Rating rating)505 public void onSetRating(Rating rating) { 506 if (mRccListener != null) { 507 mRccListener.onSetRating(rating); 508 } 509 } 510 } 511 } 512 } 513