/*
* Copyright (C) 2014 AChep@xda <artemchep@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.achep.acdisplay.services.media;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
import android.media.RemoteController;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import com.achep.acdisplay.App;
import com.achep.acdisplay.services.MediaService;
import java.lang.ref.WeakReference;
/**
* Created by Artem Chepurnoy on 26.10.2014.
*/
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.KITKAT)
class MediaController2KitKat extends MediaController2 {
static WeakReference<SparseIntArray> sStateSparse = new WeakReference<>(null);
@Nullable
private MediaService mService;
private boolean mBound = false;
private final RemoteController.OnClientUpdateListener mRCClientUpdateListener =
new RemoteController.OnClientUpdateListener() {
@Override
public void onClientChange(boolean clearing) {
if (clearing) {
clearMetadata();
}
}
@Override
public void onClientPlaybackStateUpdate(int state) {
if (mPlaybackState != state) {
updatePlaybackState(state);
}
}
@Override
public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
long currentPosMs, float speed) {
onClientPlaybackStateUpdate(state);
}
@Override
public void onClientTransportControlUpdate(int transportControlFlags) {
// TODO: Bring more music control.
}
@Override
public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {
updateMetadata(metadataEditor);
}
};
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
MediaService.B binder = (MediaService.B) service;
mService = binder.getService();
mService.setRemoteControllerEnabled();
mService.setClientUpdateListener(mRCClientUpdateListener);
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};
private final SparseIntArray mStateSparse;
/**
* {@inheritDoc}
*/
protected MediaController2KitKat(@NonNull Activity activity) {
super(activity);
SparseIntArray cachedStateSparse = sStateSparse.get();
if (cachedStateSparse == null) {
mStateSparse = generatePlaybackCompatSparse();
sStateSparse = new WeakReference<>(mStateSparse);
} else {
mStateSparse = cachedStateSparse;
}
}
@NonNull
static SparseIntArray generatePlaybackCompatSparse() {
SparseIntArray sia = new SparseIntArray();
sia.put(RemoteControlClient.PLAYSTATE_BUFFERING, PlaybackStateCompat.STATE_BUFFERING);
sia.put(RemoteControlClient.PLAYSTATE_PLAYING, PlaybackStateCompat.STATE_PLAYING);
sia.put(RemoteControlClient.PLAYSTATE_PAUSED, PlaybackStateCompat.STATE_PAUSED);
sia.put(RemoteControlClient.PLAYSTATE_ERROR, PlaybackStateCompat.STATE_ERROR);
sia.put(RemoteControlClient.PLAYSTATE_REWINDING, PlaybackStateCompat.STATE_REWINDING);
sia.put(RemoteControlClient.PLAYSTATE_FAST_FORWARDING, PlaybackStateCompat.STATE_FAST_FORWARDING);
sia.put(RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS, PlaybackStateCompat.STATE_SKIPPING_TO_NEXT);
sia.put(RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS, PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS);
return sia;
}
/**
* {@inheritDoc}
*/
@Override
public void onStart(Object... objects) {
super.onStart();
Intent intent = new Intent(App.ACTION_BIND_MEDIA_CONTROL_SERVICE);
mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
/**
* {@inheritDoc}
*/
@Override
public void onStop(Object... objects) {
mMetadata.clear();
if (mBound) {
assert mService != null;
mService.setClientUpdateListener(null);
mService.setRemoteControllerDisabled();
mService = null;
mBound = false;
}
mContext.unbindService(mConnection);
super.onStop();
}
/**
* {@inheritDoc}
*/
public void sendMediaAction(int action) {
if (mService == null) {
Log.w(TAG, "Sending a media action on stopped controller.");
return;
}
int keyCode;
switch (action) {
case ACTION_PLAY_PAUSE:
keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
break;
case ACTION_STOP:
keyCode = KeyEvent.KEYCODE_MEDIA_STOP;
break;
case ACTION_SKIP_TO_NEXT:
keyCode = KeyEvent.KEYCODE_MEDIA_NEXT;
break;
case ACTION_SKIP_TO_PREVIOUS:
keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
break;
default:
throw new IllegalArgumentException();
}
// TODO We should think about sending these up/down events accurately with touch up/down
// on the buttons, but in the near term this will interfere with the long press behavior.
RemoteController rc = mService.getRemoteController();
rc.sendMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
rc.sendMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
}
/**
* {@inheritDoc}
*/
@Override
public void seekTo(long position) {
if (mService == null) {
Log.w(TAG, "Seeking a media on stopped controller.");
return;
}
RemoteController rc = mService.getRemoteController();
rc.seekTo(position);
}
/**
* {@inheritDoc}
*/
@Override
public long getPlaybackBufferedPosition() {
return -1;
}
/**
* {@inheritDoc}
*/
@Override
public long getPlaybackPosition() {
if (mService == null) {
Log.w(TAG, "Getting a playback position on stopped controller.");
return -1;
}
RemoteController rc = mService.getRemoteController();
return rc.getEstimatedMediaPosition();
}
/**
* {@inheritDoc}
*/
protected void updatePlaybackState(int playbackStateRcc) {
super.updatePlaybackState(mStateSparse.get(playbackStateRcc));
}
/**
* Clears {@link #mMetadata metadata}. Same as calling
* {@link #updateMetadata(android.media.RemoteController.MetadataEditor)}
* with {@code null} parameter.
*
* @see #updateMetadata(android.media.RemoteController.MetadataEditor)
*/
private void clearMetadata() {
updateMetadata(null);
}
/**
* Updates {@link #mMetadata metadata} from given remote metadata class.
* This also updates play state.
*
* @param data Object of metadata to update from, or {@code null} to clear local metadata.
* @see #clearMetadata()
*/
private void updateMetadata(@Nullable RemoteController.MetadataEditor data) {
if (data == null) {
if (mMetadata.isEmpty()) return;
mMetadata.clear();
} else {
mMetadata.title = data.getString(MediaMetadataRetriever.METADATA_KEY_TITLE, null);
mMetadata.artist = data.getString(MediaMetadataRetriever.METADATA_KEY_ARTIST, null);
mMetadata.album = data.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, null);
mMetadata.duration = data.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION, -1);
mMetadata.bitmap = data.getBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, null);
mMetadata.generateSubtitle();
}
notifyOnMetadataChanged();
}
}