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.annotation.UnsupportedAppUsage; 20 import android.app.PendingIntent; 21 import android.app.PendingIntent.CanceledException; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.Bitmap; 26 import android.graphics.Canvas; 27 import android.graphics.Paint; 28 import android.graphics.RectF; 29 import android.media.AudioManager; 30 import android.media.MediaMetadata; 31 import android.media.MediaMetadataEditor; 32 import android.media.MediaMetadataRetriever; 33 import android.media.Rating; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.Looper; 37 import android.util.ArrayMap; 38 import android.util.Log; 39 import android.view.KeyEvent; 40 41 /** 42 * Helper for connecting existing APIs up to the new session APIs. This can be 43 * used by RCC, AudioFocus, etc. to create a single session that translates to 44 * all those components. 45 * 46 * @hide 47 */ 48 public class MediaSessionLegacyHelper { 49 private static final String TAG = "MediaSessionHelper"; 50 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 51 52 private static final Object sLock = new Object(); 53 private static MediaSessionLegacyHelper sInstance; 54 55 private Context mContext; 56 private MediaSessionManager mSessionManager; 57 private Handler mHandler = new Handler(Looper.getMainLooper()); 58 // The legacy APIs use PendingIntents to register/unregister media button 59 // receivers and these are associated with RCC. 60 private ArrayMap<PendingIntent, SessionHolder> mSessions 61 = new ArrayMap<PendingIntent, SessionHolder>(); 62 MediaSessionLegacyHelper(Context context)63 private MediaSessionLegacyHelper(Context context) { 64 mContext = context; 65 mSessionManager = (MediaSessionManager) context 66 .getSystemService(Context.MEDIA_SESSION_SERVICE); 67 } 68 69 @UnsupportedAppUsage getHelper(Context context)70 public static MediaSessionLegacyHelper getHelper(Context context) { 71 synchronized (sLock) { 72 if (sInstance == null) { 73 sInstance = new MediaSessionLegacyHelper(context.getApplicationContext()); 74 } 75 } 76 return sInstance; 77 } 78 getOldMetadata(MediaMetadata metadata, int artworkWidth, int artworkHeight)79 public static Bundle getOldMetadata(MediaMetadata metadata, int artworkWidth, 80 int artworkHeight) { 81 boolean includeArtwork = artworkWidth != -1 && artworkHeight != -1; 82 Bundle oldMetadata = new Bundle(); 83 if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) { 84 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUM), 85 metadata.getString(MediaMetadata.METADATA_KEY_ALBUM)); 86 } 87 if (includeArtwork && metadata.containsKey(MediaMetadata.METADATA_KEY_ART)) { 88 Bitmap art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART); 89 oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), 90 scaleBitmapIfTooBig(art, artworkWidth, artworkHeight)); 91 } else if (includeArtwork && metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART)) { 92 // Fall back to album art if the track art wasn't available 93 Bitmap art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); 94 oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), 95 scaleBitmapIfTooBig(art, artworkWidth, artworkHeight)); 96 } 97 if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ARTIST)) { 98 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST), 99 metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST)); 100 } 101 if (metadata.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) { 102 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST), 103 metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)); 104 } 105 if (metadata.containsKey(MediaMetadata.METADATA_KEY_AUTHOR)) { 106 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_AUTHOR), 107 metadata.getString(MediaMetadata.METADATA_KEY_AUTHOR)); 108 } 109 if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPILATION)) { 110 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPILATION), 111 metadata.getString(MediaMetadata.METADATA_KEY_COMPILATION)); 112 } 113 if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPOSER)) { 114 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPOSER), 115 metadata.getString(MediaMetadata.METADATA_KEY_COMPOSER)); 116 } 117 if (metadata.containsKey(MediaMetadata.METADATA_KEY_DATE)) { 118 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DATE), 119 metadata.getString(MediaMetadata.METADATA_KEY_DATE)); 120 } 121 if (metadata.containsKey(MediaMetadata.METADATA_KEY_DISC_NUMBER)) { 122 oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER), 123 metadata.getLong(MediaMetadata.METADATA_KEY_DISC_NUMBER)); 124 } 125 if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { 126 oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), 127 metadata.getLong(MediaMetadata.METADATA_KEY_DURATION)); 128 } 129 if (metadata.containsKey(MediaMetadata.METADATA_KEY_GENRE)) { 130 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_GENRE), 131 metadata.getString(MediaMetadata.METADATA_KEY_GENRE)); 132 } 133 if (metadata.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) { 134 oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS), 135 metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)); 136 } 137 if (metadata.containsKey(MediaMetadata.METADATA_KEY_RATING)) { 138 oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_OTHERS), 139 metadata.getRating(MediaMetadata.METADATA_KEY_RATING)); 140 } 141 if (metadata.containsKey(MediaMetadata.METADATA_KEY_USER_RATING)) { 142 oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER), 143 metadata.getRating(MediaMetadata.METADATA_KEY_USER_RATING)); 144 } 145 if (metadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) { 146 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE), 147 metadata.getString(MediaMetadata.METADATA_KEY_TITLE)); 148 } 149 if (metadata.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) { 150 oldMetadata.putLong( 151 String.valueOf(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER), 152 metadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)); 153 } 154 if (metadata.containsKey(MediaMetadata.METADATA_KEY_WRITER)) { 155 oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_WRITER), 156 metadata.getString(MediaMetadata.METADATA_KEY_WRITER)); 157 } 158 if (metadata.containsKey(MediaMetadata.METADATA_KEY_YEAR)) { 159 oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_YEAR), 160 metadata.getLong(MediaMetadata.METADATA_KEY_YEAR)); 161 } 162 return oldMetadata; 163 } 164 getSession(PendingIntent pi)165 public MediaSession getSession(PendingIntent pi) { 166 SessionHolder holder = mSessions.get(pi); 167 return holder == null ? null : holder.mSession; 168 } 169 sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock)170 public void sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock) { 171 if (keyEvent == null) { 172 Log.w(TAG, "Tried to send a null key event. Ignoring."); 173 return; 174 } 175 mSessionManager.dispatchMediaKeyEvent(keyEvent, needWakeLock); 176 if (DEBUG) { 177 Log.d(TAG, "dispatched media key " + keyEvent); 178 } 179 } 180 sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly)181 public void sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) { 182 if (keyEvent == null) { 183 Log.w(TAG, "Tried to send a null key event. Ignoring."); 184 return; 185 } 186 mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly); 187 } 188 sendAdjustVolumeBy(int suggestedStream, int delta, int flags)189 public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) { 190 mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags); 191 if (DEBUG) { 192 Log.d(TAG, "dispatched volume adjustment"); 193 } 194 } 195 isGlobalPriorityActive()196 public boolean isGlobalPriorityActive() { 197 return mSessionManager.isGlobalPriorityActive(); 198 } 199 addRccListener(PendingIntent pi, MediaSession.Callback listener)200 public void addRccListener(PendingIntent pi, MediaSession.Callback listener) { 201 if (pi == null) { 202 Log.w(TAG, "Pending intent was null, can't add rcc listener."); 203 return; 204 } 205 SessionHolder holder = getHolder(pi, true); 206 if (holder == null) { 207 return; 208 } 209 if (holder.mRccListener != null) { 210 if (holder.mRccListener == listener) { 211 if (DEBUG) { 212 Log.d(TAG, "addRccListener listener already added."); 213 } 214 // This is already the registered listener, ignore 215 return; 216 } 217 } 218 holder.mRccListener = listener; 219 holder.mFlags |= MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS; 220 holder.mSession.setFlags(holder.mFlags); 221 holder.update(); 222 if (DEBUG) { 223 Log.d(TAG, "Added rcc listener for " + pi + "."); 224 } 225 } 226 removeRccListener(PendingIntent pi)227 public void removeRccListener(PendingIntent pi) { 228 if (pi == null) { 229 return; 230 } 231 SessionHolder holder = getHolder(pi, false); 232 if (holder != null && holder.mRccListener != null) { 233 holder.mRccListener = null; 234 holder.mFlags &= ~MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS; 235 holder.mSession.setFlags(holder.mFlags); 236 holder.update(); 237 if (DEBUG) { 238 Log.d(TAG, "Removed rcc listener for " + pi + "."); 239 } 240 } 241 } 242 addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent, Context context)243 public void addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent, 244 Context context) { 245 if (pi == null) { 246 Log.w(TAG, "Pending intent was null, can't addMediaButtonListener."); 247 return; 248 } 249 SessionHolder holder = getHolder(pi, true); 250 if (holder == null) { 251 return; 252 } 253 if (holder.mMediaButtonListener != null) { 254 // Already have this listener registered 255 if (DEBUG) { 256 Log.d(TAG, "addMediaButtonListener already added " + pi); 257 } 258 } 259 holder.mMediaButtonListener = new MediaButtonListener(pi, context); 260 // TODO determine if handling transport performer commands should also 261 // set this flag 262 holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS; 263 holder.mSession.setFlags(holder.mFlags); 264 holder.mSession.setMediaButtonReceiver(pi); 265 holder.update(); 266 if (DEBUG) { 267 Log.d(TAG, "addMediaButtonListener added " + pi); 268 } 269 } 270 removeMediaButtonListener(PendingIntent pi)271 public void removeMediaButtonListener(PendingIntent pi) { 272 if (pi == null) { 273 return; 274 } 275 SessionHolder holder = getHolder(pi, false); 276 if (holder != null && holder.mMediaButtonListener != null) { 277 holder.mFlags &= ~MediaSession.FLAG_HANDLES_MEDIA_BUTTONS; 278 holder.mSession.setFlags(holder.mFlags); 279 holder.mMediaButtonListener = null; 280 281 holder.update(); 282 if (DEBUG) { 283 Log.d(TAG, "removeMediaButtonListener removed " + pi); 284 } 285 } 286 } 287 288 /** 289 * Scale a bitmap to fit the smallest dimension by uniformly scaling the 290 * incoming bitmap. If the bitmap fits, then do nothing and return the 291 * original. 292 * 293 * @param bitmap 294 * @param maxWidth 295 * @param maxHeight 296 * @return 297 */ scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight)298 private static Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) { 299 if (bitmap != null) { 300 final int width = bitmap.getWidth(); 301 final int height = bitmap.getHeight(); 302 if (width > maxWidth || height > maxHeight) { 303 float scale = Math.min((float) maxWidth / width, (float) maxHeight / height); 304 int newWidth = Math.round(scale * width); 305 int newHeight = Math.round(scale * height); 306 Bitmap.Config newConfig = bitmap.getConfig(); 307 if (newConfig == null) { 308 newConfig = Bitmap.Config.ARGB_8888; 309 } 310 Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig); 311 Canvas canvas = new Canvas(outBitmap); 312 Paint paint = new Paint(); 313 paint.setAntiAlias(true); 314 paint.setFilterBitmap(true); 315 canvas.drawBitmap(bitmap, null, 316 new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint); 317 bitmap = outBitmap; 318 } 319 } 320 return bitmap; 321 } 322 getHolder(PendingIntent pi, boolean createIfMissing)323 private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) { 324 SessionHolder holder = mSessions.get(pi); 325 if (holder == null && createIfMissing) { 326 MediaSession session; 327 session = new MediaSession(mContext, TAG + "-" + pi.getCreatorPackage()); 328 session.setActive(true); 329 holder = new SessionHolder(session, pi); 330 mSessions.put(pi, holder); 331 } 332 return holder; 333 } 334 sendKeyEvent(PendingIntent pi, Context context, Intent intent)335 private static void sendKeyEvent(PendingIntent pi, Context context, Intent intent) { 336 try { 337 pi.send(context, 0, intent); 338 } catch (CanceledException e) { 339 Log.e(TAG, "Error sending media key down event:", e); 340 // Don't bother sending up if down failed 341 return; 342 } 343 } 344 345 private static final class MediaButtonListener extends MediaSession.Callback { 346 private final PendingIntent mPendingIntent; 347 private final Context mContext; 348 MediaButtonListener(PendingIntent pi, Context context)349 public MediaButtonListener(PendingIntent pi, Context context) { 350 mPendingIntent = pi; 351 mContext = context; 352 } 353 354 @Override onMediaButtonEvent(Intent mediaButtonIntent)355 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 356 MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent); 357 return true; 358 } 359 360 @Override onPlay()361 public void onPlay() { 362 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY); 363 } 364 365 @Override onPause()366 public void onPause() { 367 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE); 368 } 369 370 @Override onSkipToNext()371 public void onSkipToNext() { 372 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT); 373 } 374 375 @Override onSkipToPrevious()376 public void onSkipToPrevious() { 377 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS); 378 } 379 380 @Override onFastForward()381 public void onFastForward() { 382 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD); 383 } 384 385 @Override onRewind()386 public void onRewind() { 387 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND); 388 } 389 390 @Override onStop()391 public void onStop() { 392 sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP); 393 } 394 sendKeyEvent(int keyCode)395 private void sendKeyEvent(int keyCode) { 396 KeyEvent ke = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode); 397 Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); 398 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 399 400 intent.putExtra(Intent.EXTRA_KEY_EVENT, ke); 401 MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent); 402 403 ke = new KeyEvent(KeyEvent.ACTION_UP, keyCode); 404 intent.putExtra(Intent.EXTRA_KEY_EVENT, ke); 405 MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent); 406 407 if (DEBUG) { 408 Log.d(TAG, "Sent " + keyCode + " to pending intent " + mPendingIntent); 409 } 410 } 411 } 412 413 private class SessionHolder { 414 public final MediaSession mSession; 415 public final PendingIntent mPi; 416 public MediaButtonListener mMediaButtonListener; 417 public MediaSession.Callback mRccListener; 418 public int mFlags; 419 420 public SessionCallback mCb; 421 SessionHolder(MediaSession session, PendingIntent pi)422 public SessionHolder(MediaSession session, PendingIntent pi) { 423 mSession = session; 424 mPi = pi; 425 } 426 update()427 public void update() { 428 if (mMediaButtonListener == null && mRccListener == null) { 429 mSession.setCallback(null); 430 mSession.release(); 431 mCb = null; 432 mSessions.remove(mPi); 433 } else if (mCb == null) { 434 mCb = new SessionCallback(); 435 Handler handler = new Handler(Looper.getMainLooper()); 436 mSession.setCallback(mCb, handler); 437 } 438 } 439 440 private class SessionCallback extends MediaSession.Callback { 441 442 @Override onMediaButtonEvent(Intent mediaButtonIntent)443 public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 444 if (mMediaButtonListener != null) { 445 mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent); 446 } 447 return true; 448 } 449 450 @Override onPlay()451 public void onPlay() { 452 if (mMediaButtonListener != null) { 453 mMediaButtonListener.onPlay(); 454 } 455 } 456 457 @Override onPause()458 public void onPause() { 459 if (mMediaButtonListener != null) { 460 mMediaButtonListener.onPause(); 461 } 462 } 463 464 @Override onSkipToNext()465 public void onSkipToNext() { 466 if (mMediaButtonListener != null) { 467 mMediaButtonListener.onSkipToNext(); 468 } 469 } 470 471 @Override onSkipToPrevious()472 public void onSkipToPrevious() { 473 if (mMediaButtonListener != null) { 474 mMediaButtonListener.onSkipToPrevious(); 475 } 476 } 477 478 @Override onFastForward()479 public void onFastForward() { 480 if (mMediaButtonListener != null) { 481 mMediaButtonListener.onFastForward(); 482 } 483 } 484 485 @Override onRewind()486 public void onRewind() { 487 if (mMediaButtonListener != null) { 488 mMediaButtonListener.onRewind(); 489 } 490 } 491 492 @Override onStop()493 public void onStop() { 494 if (mMediaButtonListener != null) { 495 mMediaButtonListener.onStop(); 496 } 497 } 498 499 @Override onSeekTo(long pos)500 public void onSeekTo(long pos) { 501 if (mRccListener != null) { 502 mRccListener.onSeekTo(pos); 503 } 504 } 505 506 @Override onSetRating(Rating rating)507 public void onSetRating(Rating rating) { 508 if (mRccListener != null) { 509 mRccListener.onSetRating(rating); 510 } 511 } 512 } 513 } 514 } 515