package com.simplecity.amp_library.utils;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.AsyncPlayer;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.view.KeyEvent;
import com.simplecity.amp_library.R;
import com.simplecity.amp_library.playback.MusicService;
import com.simplecity.amp_library.ui.activities.MainActivity;
/**
* This class is used to control headset playback. Single press: pause/resume
* Double press: next track Long press: voice search
*/
public class MediaButtonIntentReceiver extends WakefulBroadcastReceiver {
private static final int MSG_LONGPRESS_TIMEOUT = 1;
private static final int MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2;
private static final int LONG_PRESS_DELAY = 1000;
private static final int DOUBLE_CLICK = 800;
private static PowerManager.WakeLock wakeLock = null;
static int clickCounter = 0;
static long lastClickTime = 0;
static boolean down = false;
static boolean launched = false;
/**
* Play a beep sound.
*/
private static void beep(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("pref_headset_beep", true)) {
AsyncPlayer beepPlayer = new AsyncPlayer("BeepPlayer");
Uri beepSoundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
context.getResources().getResourcePackageName(R.raw.beep) + '/' +
context.getResources().getResourceTypeName(R.raw.beep) + '/' +
context.getResources().getResourceEntryName(R.raw.beep));
if (ShuttleUtils.hasMarshmallow()) {
AudioAttributes audioAttributes = new AudioAttributes.Builder()
// Could use AudioAttributes.ASSISTANCE_SONIFICATION here, since this represents a button press type action..
// However, that seems to play our audio a little too quietly (and the beep track is already adjusted to be relatively quiet).
// So let's just treat it as music, which will use the user's music stream's volume anyway.
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
beepPlayer.play(context, beepSoundUri, false, audioAttributes);
} else {
beepPlayer.play(context, beepSoundUri, false, AudioManager.STREAM_MUSIC);
}
}
}
public static class MediaButtonReceiverHelper {
public static void onReceive(Context context, Intent intent) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
final String intentAction = intent.getAction();
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intentAction) && preferences.getBoolean("pref_headset_disconnect", true)) {
startService(context, MusicService.MediaButtonCommand.PAUSE);
} else if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event == null) {
return;
}
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final long eventTime = event.getEventTime();
String command = null;
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_STOP:
command = MusicService.MediaButtonCommand.STOP;
break;
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
command = MusicService.MediaButtonCommand.TOGGLE_PAUSE;
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
command = MusicService.MediaButtonCommand.NEXT;
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
command = MusicService.MediaButtonCommand.PREVIOUS;
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
command = MusicService.MediaButtonCommand.PAUSE;
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
command = MusicService.MediaButtonCommand.PLAY;
break;
}
if (command != null) {
if (action == KeyEvent.ACTION_DOWN) {
if (down) {
if ((MusicService.MediaButtonCommand.TOGGLE_PAUSE.equals(command) ||
MusicService.MediaButtonCommand.PLAY.equals(command))) {
if (lastClickTime != 0 && eventTime - lastClickTime > LONG_PRESS_DELAY) {
acquireWakeLockAndSendMessage(context,
handler.obtainMessage(MSG_LONGPRESS_TIMEOUT, context), 0);
}
}
} else if (event.getRepeatCount() == 0) {
// Only consider the first event in a sequence, not the repeat events,
// so that we don't trigger in cases where the first event went to a
// different app (e.g. when the user ends a phone call by long pressing
// the headset button)
// The service may or may not be running, but we need to send it a command
if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
if (eventTime - lastClickTime >= DOUBLE_CLICK) {
clickCounter = 0;
}
clickCounter++;
handler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT);
Message msg = handler.obtainMessage(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, clickCounter, 0, context);
long delay = clickCounter < 3 ? DOUBLE_CLICK : 0;
if (clickCounter >= 3) {
clickCounter = 0;
}
lastClickTime = eventTime;
acquireWakeLockAndSendMessage(context, msg, delay);
} else {
startService(context, command);
}
launched = false;
down = true;
}
} else {
handler.removeMessages(MSG_LONGPRESS_TIMEOUT);
down = false;
}
releaseWakeLockIfHandlerIdle();
}
}
}
}
static Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LONGPRESS_TIMEOUT:
if (!launched) {
final Context context = (Context) msg.obj;
final Intent intent = new Intent();
intent.setClass(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(intent);
launched = true;
}
break;
case MSG_HEADSET_DOUBLE_CLICK_TIMEOUT:
final int clickCount = msg.arg1;
final String command;
switch (clickCount) {
case 1:
command = MusicService.MediaButtonCommand.TOGGLE_PAUSE;
break;
case 2:
command = MusicService.MediaButtonCommand.NEXT;
break;
case 3:
command = MusicService.MediaButtonCommand.PREVIOUS;
break;
default:
command = null;
break;
}
if (command != null) {
final Context context = (Context) msg.obj;
if (MusicService.MediaButtonCommand.NEXT.equals((command))) {
beep(context);
}
startService(context, command);
}
break;
}
releaseWakeLockIfHandlerIdle();
}
};
@Override
public void onReceive(Context context, Intent intent) {
MediaButtonReceiverHelper.onReceive(context, intent);
if (isOrderedBroadcast()) {
abortBroadcast();
}
}
static void startService(final Context context, final String command) {
final Intent intent = new Intent(context, MusicService.class);
intent.setAction(MusicService.ServiceCommand.SERVICE_COMMAND);
intent.putExtra(MusicService.MediaButtonCommand.CMD_NAME, command);
intent.putExtra(MusicService.MediaButtonCommand.FROM_MEDIA_BUTTON, true);
startWakefulService(context, intent);
}
static void acquireWakeLockAndSendMessage(Context context, Message msg, long delay) {
if (wakeLock == null) {
Context appContext = context.getApplicationContext();
PowerManager pm = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Headset button");
wakeLock.setReferenceCounted(false);
}
// Make sure we don't indefinitely hold the wake lock under any circumstances
wakeLock.acquire(10000);
handler.sendMessageDelayed(msg, delay);
}
static void releaseWakeLockIfHandlerIdle() {
if (handler.hasMessages(MSG_LONGPRESS_TIMEOUT) || handler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) {
return;
}
if (wakeLock != null) {
wakeLock.release();
wakeLock = null;
}
}
}