blob: c61d7ad0a0b6be98ecf439d6edf7f34902c531dc [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.session;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
import android.media.Rating;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
/**
* Helper for connecting existing APIs up to the new session APIs. This can be
* used by RCC, AudioFocus, etc. to create a single session that translates to
* all those components.
*
* @hide
*/
public class MediaSessionLegacyHelper {
private static final String TAG = "MediaSessionHelper";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final Object sLock = new Object();
private static MediaSessionLegacyHelper sInstance;
private Context mContext;
private MediaSessionManager mSessionManager;
private Handler mHandler = new Handler(Looper.getMainLooper());
// The legacy APIs use PendingIntents to register/unregister media button
// receivers and these are associated with RCC.
private ArrayMap<PendingIntent, SessionHolder> mSessions
= new ArrayMap<PendingIntent, SessionHolder>();
private MediaSessionLegacyHelper(Context context) {
mContext = context;
mSessionManager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
}
public static MediaSessionLegacyHelper getHelper(Context context) {
synchronized (sLock) {
if (sInstance == null) {
sInstance = new MediaSessionLegacyHelper(context.getApplicationContext());
}
}
return sInstance;
}
public static Bundle getOldMetadata(MediaMetadata metadata, int artworkWidth,
int artworkHeight) {
boolean includeArtwork = artworkWidth != -1 && artworkHeight != -1;
Bundle oldMetadata = new Bundle();
if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUM),
metadata.getString(MediaMetadata.METADATA_KEY_ALBUM));
}
if (includeArtwork && metadata.containsKey(MediaMetadata.METADATA_KEY_ART)) {
Bitmap art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
scaleBitmapIfTooBig(art, artworkWidth, artworkHeight));
} else if (includeArtwork && metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART)) {
// Fall back to album art if the track art wasn't available
Bitmap art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
scaleBitmapIfTooBig(art, artworkWidth, artworkHeight));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ARTIST)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST),
metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST),
metadata.getString(MediaMetadata.METADATA_KEY_ARTIST));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_AUTHOR)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_AUTHOR),
metadata.getString(MediaMetadata.METADATA_KEY_AUTHOR));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPILATION)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPILATION),
metadata.getString(MediaMetadata.METADATA_KEY_COMPILATION));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPOSER)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPOSER),
metadata.getString(MediaMetadata.METADATA_KEY_COMPOSER));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_DATE)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DATE),
metadata.getString(MediaMetadata.METADATA_KEY_DATE));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_DISC_NUMBER)) {
oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER),
metadata.getLong(MediaMetadata.METADATA_KEY_DISC_NUMBER));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION),
metadata.getLong(MediaMetadata.METADATA_KEY_DURATION));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_GENRE),
metadata.getString(MediaMetadata.METADATA_KEY_GENRE));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS),
metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_RATING)) {
oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_OTHERS),
metadata.getRating(MediaMetadata.METADATA_KEY_RATING));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_USER_RATING)) {
oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER),
metadata.getRating(MediaMetadata.METADATA_KEY_USER_RATING));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE),
metadata.getString(MediaMetadata.METADATA_KEY_TITLE));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
oldMetadata.putLong(
String.valueOf(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER),
metadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_WRITER)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_WRITER),
metadata.getString(MediaMetadata.METADATA_KEY_WRITER));
}
if (metadata.containsKey(MediaMetadata.METADATA_KEY_YEAR)) {
oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_YEAR),
metadata.getString(MediaMetadata.METADATA_KEY_YEAR));
}
return oldMetadata;
}
public MediaSession getSession(PendingIntent pi) {
SessionHolder holder = mSessions.get(pi);
return holder == null ? null : holder.mSession;
}
public void sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock) {
if (keyEvent == null) {
Log.w(TAG, "Tried to send a null key event. Ignoring.");
return;
}
mSessionManager.dispatchMediaKeyEvent(keyEvent, needWakeLock);
if (DEBUG) {
Log.d(TAG, "dispatched media key " + keyEvent);
}
}
public void sendVolumeKeyEvent(KeyEvent keyEvent, boolean musicOnly) {
if (keyEvent == null) {
Log.w(TAG, "Tried to send a null key event. Ignoring.");
return;
}
boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
int direction = 0;
boolean isMute = false;
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_VOLUME_UP:
direction = AudioManager.ADJUST_RAISE;
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
direction = AudioManager.ADJUST_LOWER;
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
isMute = true;
break;
}
if (down || up) {
int flags = AudioManager.FLAG_FROM_KEY;
if (musicOnly) {
// This flag is used when the screen is off to only affect
// active media
flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
} else {
// These flags are consistent with the home screen
if (up) {
flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
} else {
flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
}
}
if (direction != 0) {
// If this is action up we want to send a beep for non-music events
if (up) {
direction = 0;
}
mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
direction, flags);
} else if (isMute) {
if (down && keyEvent.getRepeatCount() == 0) {
mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
AudioManager.ADJUST_TOGGLE_MUTE, flags);
}
}
}
}
public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);
if (DEBUG) {
Log.d(TAG, "dispatched volume adjustment");
}
}
public boolean isGlobalPriorityActive() {
return mSessionManager.isGlobalPriorityActive();
}
public void addRccListener(PendingIntent pi, MediaSession.Callback listener) {
if (pi == null) {
Log.w(TAG, "Pending intent was null, can't add rcc listener.");
return;
}
SessionHolder holder = getHolder(pi, true);
if (holder == null) {
return;
}
if (holder.mRccListener != null) {
if (holder.mRccListener == listener) {
if (DEBUG) {
Log.d(TAG, "addRccListener listener already added.");
}
// This is already the registered listener, ignore
return;
}
}
holder.mRccListener = listener;
holder.mFlags |= MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
holder.mSession.setFlags(holder.mFlags);
holder.update();
if (DEBUG) {
Log.d(TAG, "Added rcc listener for " + pi + ".");
}
}
public void removeRccListener(PendingIntent pi) {
if (pi == null) {
return;
}
SessionHolder holder = getHolder(pi, false);
if (holder != null && holder.mRccListener != null) {
holder.mRccListener = null;
holder.mFlags &= ~MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
holder.mSession.setFlags(holder.mFlags);
holder.update();
if (DEBUG) {
Log.d(TAG, "Removed rcc listener for " + pi + ".");
}
}
}
public void addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent,
Context context) {
if (pi == null) {
Log.w(TAG, "Pending intent was null, can't addMediaButtonListener.");
return;
}
SessionHolder holder = getHolder(pi, true);
if (holder == null) {
return;
}
if (holder.mMediaButtonListener != null) {
// Already have this listener registered
if (DEBUG) {
Log.d(TAG, "addMediaButtonListener already added " + pi);
}
}
holder.mMediaButtonListener = new MediaButtonListener(pi, context);
// TODO determine if handling transport performer commands should also
// set this flag
holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
holder.mSession.setFlags(holder.mFlags);
holder.mSession.setMediaButtonReceiver(pi);
holder.update();
if (DEBUG) {
Log.d(TAG, "addMediaButtonListener added " + pi);
}
}
public void removeMediaButtonListener(PendingIntent pi) {
if (pi == null) {
return;
}
SessionHolder holder = getHolder(pi, false);
if (holder != null && holder.mMediaButtonListener != null) {
holder.mFlags &= ~MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
holder.mSession.setFlags(holder.mFlags);
holder.mMediaButtonListener = null;
holder.update();
if (DEBUG) {
Log.d(TAG, "removeMediaButtonListener removed " + pi);
}
}
}
/**
* Scale a bitmap to fit the smallest dimension by uniformly scaling the
* incoming bitmap. If the bitmap fits, then do nothing and return the
* original.
*
* @param bitmap
* @param maxWidth
* @param maxHeight
* @return
*/
private static Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
if (bitmap != null) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
if (width > maxWidth || height > maxHeight) {
float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
int newWidth = Math.round(scale * width);
int newHeight = Math.round(scale * height);
Bitmap.Config newConfig = bitmap.getConfig();
if (newConfig == null) {
newConfig = Bitmap.Config.ARGB_8888;
}
Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig);
Canvas canvas = new Canvas(outBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
canvas.drawBitmap(bitmap, null,
new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
bitmap = outBitmap;
}
}
return bitmap;
}
private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) {
SessionHolder holder = mSessions.get(pi);
if (holder == null && createIfMissing) {
MediaSession session;
session = new MediaSession(mContext, TAG + "-" + pi.getCreatorPackage());
session.setActive(true);
holder = new SessionHolder(session, pi);
mSessions.put(pi, holder);
}
return holder;
}
private static void sendKeyEvent(PendingIntent pi, Context context, Intent intent) {
try {
pi.send(context, 0, intent);
} catch (CanceledException e) {
Log.e(TAG, "Error sending media key down event:", e);
// Don't bother sending up if down failed
return;
}
}
private static final class MediaButtonListener extends MediaSession.Callback {
private final PendingIntent mPendingIntent;
private final Context mContext;
public MediaButtonListener(PendingIntent pi, Context context) {
mPendingIntent = pi;
mContext = context;
}
@Override
public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
return true;
}
@Override
public void onPlay() {
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
}
@Override
public void onPause() {
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE);
}
@Override
public void onSkipToNext() {
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT);
}
@Override
public void onSkipToPrevious() {
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
}
@Override
public void onFastForward() {
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
}
@Override
public void onRewind() {
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND);
}
@Override
public void onStop() {
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP);
}
private void sendKeyEvent(int keyCode) {
KeyEvent ke = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent);
ke = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, intent);
if (DEBUG) {
Log.d(TAG, "Sent " + keyCode + " to pending intent " + mPendingIntent);
}
}
}
private class SessionHolder {
public final MediaSession mSession;
public final PendingIntent mPi;
public MediaButtonListener mMediaButtonListener;
public MediaSession.Callback mRccListener;
public int mFlags;
public SessionCallback mCb;
public SessionHolder(MediaSession session, PendingIntent pi) {
mSession = session;
mPi = pi;
}
public void update() {
if (mMediaButtonListener == null && mRccListener == null) {
mSession.setCallback(null);
mSession.release();
mCb = null;
mSessions.remove(mPi);
} else if (mCb == null) {
mCb = new SessionCallback();
Handler handler = new Handler(Looper.getMainLooper());
mSession.setCallback(mCb, handler);
}
}
private class SessionCallback extends MediaSession.Callback {
@Override
public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
if (mMediaButtonListener != null) {
mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent);
}
return true;
}
@Override
public void onPlay() {
if (mMediaButtonListener != null) {
mMediaButtonListener.onPlay();
}
}
@Override
public void onPause() {
if (mMediaButtonListener != null) {
mMediaButtonListener.onPause();
}
}
@Override
public void onSkipToNext() {
if (mMediaButtonListener != null) {
mMediaButtonListener.onSkipToNext();
}
}
@Override
public void onSkipToPrevious() {
if (mMediaButtonListener != null) {
mMediaButtonListener.onSkipToPrevious();
}
}
@Override
public void onFastForward() {
if (mMediaButtonListener != null) {
mMediaButtonListener.onFastForward();
}
}
@Override
public void onRewind() {
if (mMediaButtonListener != null) {
mMediaButtonListener.onRewind();
}
}
@Override
public void onStop() {
if (mMediaButtonListener != null) {
mMediaButtonListener.onStop();
}
}
@Override
public void onSeekTo(long pos) {
if (mRccListener != null) {
mRccListener.onSeekTo(pos);
}
}
@Override
public void onSetRating(Rating rating) {
if (mRccListener != null) {
mRccListener.onSetRating(rating);
}
}
}
}
}