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