From 79eee79ab74c45f3f224b2df9790d026e8696a70 Mon Sep 17 00:00:00 2001 From: Karim Abou Zeid Date: Sat, 30 Jan 2016 15:48:19 +0100 Subject: [PATCH] Tripple click headset button to rewind. Close #63 Also fixed the multiple instance bug when opening the player from the notification or widget. --- .../gramophone/appwidget/WidgetMedium.java | 2 +- .../helper/PlayingNotificationHelper.java | 2 +- .../service/MediaButtonIntentReceiver.java | 169 +++++++++++++++--- .../gramophone/service/MusicService.java | 10 +- 4 files changed, 150 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/kabouzeid/gramophone/appwidget/WidgetMedium.java b/app/src/main/java/com/kabouzeid/gramophone/appwidget/WidgetMedium.java index 6881dab7..fda46630 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/appwidget/WidgetMedium.java +++ b/app/src/main/java/com/kabouzeid/gramophone/appwidget/WidgetMedium.java @@ -138,7 +138,7 @@ public class WidgetMedium extends AppWidgetProvider { switch (which) { case 0: intent = new Intent(context, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); case 1: intent = new Intent(MusicService.ACTION_TOGGLE_PAUSE); diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java index 525cf7be..68580d75 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java @@ -114,7 +114,7 @@ public class PlayingNotificationHelper { private PendingIntent getOpenMusicControllerPendingIntent() { Intent intent = new Intent(service, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); return PendingIntent.getActivity(service, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MediaButtonIntentReceiver.java b/app/src/main/java/com/kabouzeid/gramophone/service/MediaButtonIntentReceiver.java index 5e801404..7b6b4b12 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MediaButtonIntentReceiver.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MediaButtonIntentReceiver.java @@ -1,23 +1,95 @@ +/* + * Copyright (C) 2007 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. + */ + +// Modified for Phonograph by Karim Abou Zeid (kabouzeid). + package com.kabouzeid.gramophone.service; -import android.content.BroadcastReceiver; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; -import android.support.annotation.NonNull; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.support.v4.content.WakefulBroadcastReceiver; +import android.util.Log; import android.view.KeyEvent; -public class MediaButtonIntentReceiver extends BroadcastReceiver { +import com.kabouzeid.gramophone.BuildConfig; + +/** + * Used to control headset playback. + * Single press: pause/resume + * Double press: next track + * Triple press: previous track + */ +public class MediaButtonIntentReceiver extends WakefulBroadcastReceiver { + private static final boolean DEBUG = BuildConfig.DEBUG; public static final String TAG = MediaButtonIntentReceiver.class.getSimpleName(); - private static final int DOUBLE_CLICK = 500; + private static final int MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2; + + private static final int DOUBLE_CLICK = 400; + + private static WakeLock mWakeLock = null; + private static int mClickCounter = 0; private static long mLastClickTime = 0; + @SuppressLint("HandlerLeak") // false alarm, handler is already static + private static Handler mHandler = new Handler() { + + @Override + public void handleMessage(final Message msg) { + switch (msg.what) { + case MSG_HEADSET_DOUBLE_CLICK_TIMEOUT: + final int clickCount = msg.arg1; + final String command; + + if (DEBUG) Log.v(TAG, "Handling headset click, count = " + clickCount); + switch (clickCount) { + case 1: + command = MusicService.ACTION_TOGGLE_PAUSE; + break; + case 2: + command = MusicService.ACTION_SKIP; + break; + case 3: + command = MusicService.ACTION_REWIND; + break; + default: + command = null; + break; + } + + if (command != null) { + final Context context = (Context) msg.obj; + startService(context, command); + } + break; + } + releaseWakeLockIfHandlerIdle(); + } + }; + @Override - public void onReceive(@NonNull Context context, @NonNull Intent intent) { - if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) { + public void onReceive(final Context context, final Intent intent) { + if (DEBUG) Log.v(TAG, "Received intent: " + intent); + final String intentAction = intent.getAction(); + if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) { final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); - if (event == null) + if (event == null) { return; + } + final int keycode = event.getKeyCode(); final int action = event.getAction(); final long eventTime = event.getEventTime(); @@ -31,9 +103,6 @@ public class MediaButtonIntentReceiver extends BroadcastReceiver { case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: command = MusicService.ACTION_TOGGLE_PAUSE; break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - command = MusicService.ACTION_PLAY; - break; case KeyEvent.KEYCODE_MEDIA_NEXT: command = MusicService.ACTION_SKIP; break; @@ -43,29 +112,81 @@ public class MediaButtonIntentReceiver extends BroadcastReceiver { case KeyEvent.KEYCODE_MEDIA_PAUSE: command = MusicService.ACTION_PAUSE; break; + case KeyEvent.KEYCODE_MEDIA_PLAY: + command = MusicService.ACTION_PLAY; + break; } if (command != null) { if (action == KeyEvent.ACTION_DOWN) { if (event.getRepeatCount() == 0) { - /** - * If another app received the broadcast first, this if statement will skip. - */ - //TODO triple click to rewind - final Intent i = new Intent(context, MusicService.class); - if (keycode == KeyEvent.KEYCODE_HEADSETHOOK - && eventTime - mLastClickTime < DOUBLE_CLICK) { - i.setAction(MusicService.ACTION_SKIP); - mLastClickTime = 0; - } else { - i.setAction(command); + // 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 - mLastClickTime >= DOUBLE_CLICK) { + mClickCounter = 0; + } + + mClickCounter++; + if (DEBUG) Log.v(TAG, "Got headset click, count = " + mClickCounter); + mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT); + + Message msg = mHandler.obtainMessage( + MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context); + + long delay = mClickCounter < 3 ? DOUBLE_CLICK : 0; + if (mClickCounter >= 3) { + mClickCounter = 0; + } mLastClickTime = eventTime; + acquireWakeLockAndSendMessage(context, msg, delay); + } else { + startService(context, command); } - context.startService(i); } } - if (isOrderedBroadcast()) + if (isOrderedBroadcast()) { abortBroadcast(); + } + releaseWakeLockIfHandlerIdle(); } } } -} + + private static void startService(Context context, String command) { + final Intent intent = new Intent(context, MusicService.class); + intent.setAction(command); + startWakefulService(context, intent); + } + + private static void acquireWakeLockAndSendMessage(Context context, Message msg, long delay) { + if (mWakeLock == null) { + Context appContext = context.getApplicationContext(); + PowerManager pm = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Phonograph headset button"); + mWakeLock.setReferenceCounted(false); + } + if (DEBUG) Log.v(TAG, "Acquiring wake lock and sending " + msg.what); + // Make sure we don't indefinitely hold the wake lock under any circumstances + mWakeLock.acquire(10000); + + mHandler.sendMessageDelayed(msg, delay); + } + + private static void releaseWakeLockIfHandlerIdle() { + if (mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) { + if (DEBUG) Log.v(TAG, "Handler still has messages pending, not releasing wake lock"); + return; + } + + if (mWakeLock != null) { + if (DEBUG) Log.v(TAG, "Releasing wake lock"); + mWakeLock.release(); + mWakeLock = null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java index cdbe4a9f..2d4636fb 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -212,7 +212,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP @SuppressWarnings("deprecation") private void initRemoteControlClient() { - remoteControlClient = new RemoteControlClient(getMediaButtonIntent()); + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); + remoteControlClient = new RemoteControlClient(PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0)); remoteControlClient.setTransportControlFlags( RemoteControlClient.FLAG_KEY_MEDIA_PLAY | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | @@ -221,12 +223,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP getAudioManager().registerRemoteControlClient(remoteControlClient); } - private PendingIntent getMediaButtonIntent() { - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); - return PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); - } - @Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) { if (intent != null) {