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