content: /*
   * 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.annotation.UnsupportedAppUsage;
  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);
      }
  
      @UnsupportedAppUsage
      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.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_YEAR),
                      metadata.getLong(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, int stream, boolean musicOnly) {
          if (keyEvent == null) {
              Log.w(TAG, "Tried to send a null key event. Ignoring.");
              return;
          }
          mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
      }
  
      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);
                  }
              }
          }
      }
  }
  