OSDN Git Service

Handle headset hook multi click in playback service.
authorDanny Baumann <dannybaumann@web.de>
Fri, 12 Aug 2016 08:34:45 +0000 (10:34 +0200)
committerDanny Baumann <dannybaumann@web.de>
Fri, 19 Aug 2016 07:26:26 +0000 (09:26 +0200)
We usually get media button events via the session callback now, so only
handling those events in the button receiver (which is only triggered if
e.g. media buttons are pressed without us running after reboot) is not
sufficient.

Change-Id: I50ff266af826edd016804b267323effe1056f045

src/com/cyanogenmod/eleven/MediaButtonIntentReceiver.java
src/com/cyanogenmod/eleven/MusicPlaybackService.java

index d4b8bc7..b7982e7 100644 (file)
@@ -14,81 +14,14 @@ package com.cyanogenmod.eleven;
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioManager;
-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;
 
-import com.cyanogenmod.eleven.ui.activities.HomeActivity;
-
-/**
- * Used to control headset playback.
- *   Single press: pause/resume
- *   Double press: next track
- *   Triple press: previous track
- *   Long press: voice search
- */
 public class MediaButtonIntentReceiver extends WakefulBroadcastReceiver {
     private static final boolean DEBUG = false;
     private static final String TAG = "MediaButtonIntentReceiver";
 
-    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 WakeLock mWakeLock = null;
-    private static int mClickCounter = 0;
-    private static long mLastClickTime = 0;
-    private static boolean mDown = false;
-    private static boolean mLaunched = false;
-
-    private static Handler mHandler = new Handler() {
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void handleMessage(final Message msg) {
-            switch (msg.what) {
-                case MSG_LONGPRESS_TIMEOUT:
-                    if (DEBUG) Log.v(TAG, "Handling longpress timeout, launched " + mLaunched);
-                    if (!mLaunched) {
-                        final Context context = (Context)msg.obj;
-                        final Intent i = new Intent();
-                        i.setClass(context, HomeActivity.class);
-                        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                        context.startActivity(i);
-                        mLaunched = true;
-                    }
-                    break;
-
-                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 = MusicPlaybackService.CMDTOGGLEPAUSE; break;
-                        case 2: command = MusicPlaybackService.CMDNEXT; break;
-                        case 3: command = MusicPlaybackService.CMDPREVIOUS; break;
-                        default: command = null; break;
-                    }
-
-                    if (command != null) {
-                        final Context context = (Context)msg.obj;
-                        startService(context, command);
-                    }
-                    break;
-            }
-            releaseWakeLockIfHandlerIdle();
-        }
-    };
-
     /**
      * {@inheritDoc}
      */
@@ -97,23 +30,21 @@ public class MediaButtonIntentReceiver extends WakefulBroadcastReceiver {
         if (DEBUG) Log.v(TAG, "Received intent: " + intent);
         final String intentAction = intent.getAction();
         if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intentAction)) {
-            startService(context, MusicPlaybackService.CMDPAUSE);
+            startService(context, MusicPlaybackService.CMDPAUSE, System.currentTimeMillis());
         } else if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
             final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-            if (event == null) {
+            if (event == null || event.getAction() != KeyEvent.ACTION_UP) {
                 return;
             }
 
-            final int keycode = event.getKeyCode();
-            final int action = event.getAction();
-            final long eventtime = event.getEventTime();
-
             String command = null;
-            switch (keycode) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_HEADSETHOOK:
+                    command = MusicPlaybackService.CMDHEADSETHOOK;
+                    break;
                 case KeyEvent.KEYCODE_MEDIA_STOP:
                     command = MusicPlaybackService.CMDSTOP;
                     break;
-                case KeyEvent.KEYCODE_HEADSETHOOK:
                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                     command = MusicPlaybackService.CMDTOGGLEPAUSE;
                     break;
@@ -131,93 +62,20 @@ public class MediaButtonIntentReceiver extends WakefulBroadcastReceiver {
                     break;
             }
             if (command != null) {
-                if (action == KeyEvent.ACTION_DOWN) {
-                    if (mDown) {
-                        if (MusicPlaybackService.CMDTOGGLEPAUSE.equals(command)
-                                || MusicPlaybackService.CMDPLAY.equals(command)) {
-                            if (mLastClickTime != 0
-                                    && eventtime - mLastClickTime > LONG_PRESS_DELAY) {
-                                acquireWakeLockAndSendMessage(context,
-                                        mHandler.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 - 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);
-                        }
-                        mLaunched = false;
-                        mDown = true;
-                    }
-                } else {
-                    mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);
-                    mDown = false;
-                }
+                startService(context, command, event.getEventTime());
                 if (isOrderedBroadcast()) {
                     abortBroadcast();
                 }
-                releaseWakeLockIfHandlerIdle();
             }
         }
     }
 
-    private static void startService(Context context, String command) {
+    private static void startService(Context context, String command, long timestamp) {
         final Intent i = new Intent(context, MusicPlaybackService.class);
         i.setAction(MusicPlaybackService.SERVICECMD);
         i.putExtra(MusicPlaybackService.CMDNAME, command);
         i.putExtra(MusicPlaybackService.FROM_MEDIA_BUTTON, true);
+        i.putExtra(MusicPlaybackService.TIMESTAMP, timestamp);
         startWakefulService(context, i);
     }
-
-    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, "Eleven 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_LONGPRESS_TIMEOUT)
-                || 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;
-        }
-    }
 }
index d8163a8..bed0afa 100644 (file)
@@ -13,6 +13,7 @@
 
 package com.cyanogenmod.eleven;
 
+import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.app.AlarmManager;
 import android.app.Notification;
@@ -53,6 +54,7 @@ import android.provider.MediaStore.Audio.AlbumColumns;
 import android.provider.MediaStore.Audio.AudioColumns;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.KeyEvent;
 
 import com.cyanogenmod.eleven.Config.IdType;
 import com.cyanogenmod.eleven.appwidgets.AppWidgetLarge;
@@ -183,6 +185,8 @@ public class MusicPlaybackService extends Service {
 
     public static final String FROM_MEDIA_BUTTON = "frommediabutton";
 
+    public static final String TIMESTAMP = "timestamp";
+
     /**
      * Used to easily notify a list that it should refresh. i.e. A playlist
      * changes
@@ -218,7 +222,7 @@ public class MusicPlaybackService extends Service {
 
     public static final String CMDNEXT = "next";
 
-    public static final String CMDNOTIF = "buttonId";
+    public static final String CMDHEADSETHOOK = "headsethook";
 
     private static final int IDCOLIDX = 0;
 
@@ -298,6 +302,16 @@ public class MusicPlaybackService extends Service {
     private static final int LYRICS = 7;
 
     /**
+     * Indicates a headset hook key event
+     */
+    private static final int HEADSET_HOOK_EVENT = 8;
+
+    /**
+     * Indicates waiting for another headset hook event has timed out
+     */
+    private static final int HEADSET_HOOK_MULTI_CLICK_TIMEOUT = 9;
+
+    /**
      * Idle time before stopping the foreground notfication (5 minutes)
      */
     private static final int IDLE_DELAY = 5 * 60 * 1000;
@@ -515,6 +529,8 @@ public class MusicPlaybackService extends Service {
      */
     private boolean mShowAlbumArtOnLockscreen;
 
+    private PowerManager.WakeLock mHeadsetHookWakeLock;
+
     private ShakeDetector.Listener mShakeDetectorListener=new ShakeDetector.Listener() {
 
         @Override
@@ -697,6 +713,19 @@ public class MusicPlaybackService extends Service {
                 seek(0);
                 releaseServiceUiAndStop();
             }
+            @Override
+            public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+                if (Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
+                    KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+                    if (ke != null && ke.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK) {
+                        if (ke.getAction() == KeyEvent.ACTION_UP) {
+                            handleHeadsetHookClick(ke.getEventTime());
+                        }
+                        return true;
+                    }
+                }
+                return super.onMediaButtonEvent(mediaButtonIntent);
+            }
         });
 
         PendingIntent pi = PendingIntent.getBroadcast(this, 0,
@@ -816,12 +845,7 @@ public class MusicPlaybackService extends Service {
                 || PREVIOUS_FORCE_ACTION.equals(action)) {
             prev(PREVIOUS_FORCE_ACTION.equals(action));
         } else if (CMDTOGGLEPAUSE.equals(command) || TOGGLEPAUSE_ACTION.equals(action)) {
-            if (isPlaying()) {
-                pause();
-                mPausedByTransientLossOfFocus = false;
-            } else {
-                play();
-            }
+            togglePlayPause();
         } else if (CMDPAUSE.equals(command) || PAUSE_ACTION.equals(action)) {
             pause();
             mPausedByTransientLossOfFocus = false;
@@ -836,7 +860,24 @@ public class MusicPlaybackService extends Service {
             cycleRepeat();
         } else if (SHUFFLE_ACTION.equals(action)) {
             cycleShuffle();
+        } else if (CMDHEADSETHOOK.equals(command)) {
+            long timestamp = intent.getLongExtra(TIMESTAMP, 0);
+            handleHeadsetHookClick(timestamp);
+        }
+    }
+
+    private void handleHeadsetHookClick(long timestamp) {
+        if (mHeadsetHookWakeLock == null) {
+            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+            mHeadsetHookWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    "Eleven headset button");
+            mHeadsetHookWakeLock.setReferenceCounted(false);
         }
+        // Make sure we don't indefinitely hold the wake lock under any circumstances
+        mHeadsetHookWakeLock.acquire(10000);
+
+        Message msg = mPlayerHandler.obtainMessage(HEADSET_HOOK_EVENT, Long.valueOf(timestamp));
+        msg.sendToTarget();
     }
 
     /**
@@ -2421,6 +2462,15 @@ public class MusicPlaybackService extends Service {
         }
     }
 
+    private void togglePlayPause() {
+        if (isPlaying()) {
+            pause();
+            mPausedByTransientLossOfFocus = false;
+        } else {
+            play();
+        }
+    }
+
     /**
      * Temporarily pauses playback.
      */
@@ -2874,6 +2924,9 @@ public class MusicPlaybackService extends Service {
         private final WeakReference<MusicPlaybackService> mService;
         private float mCurrentVolume = 1.0f;
 
+        private static final int DOUBLE_CLICK_TIMEOUT = 800;
+        private int mHeadsetHookClickCounter = 0;
+
         /**
          * Constructor of <code>MusicPlayerHandler</code>
          *
@@ -2980,6 +3033,31 @@ public class MusicPlaybackService extends Service {
                             default:
                         }
                         break;
+                    case HEADSET_HOOK_EVENT: {
+                        long eventTime = (Long) msg.obj;
+
+                        mHeadsetHookClickCounter = Math.min(mHeadsetHookClickCounter + 1, 3);
+                        if (D) Log.d(TAG, "Got headset click, count = " + mHeadsetHookClickCounter);
+                        removeMessages(HEADSET_HOOK_MULTI_CLICK_TIMEOUT);
+
+                        if (mHeadsetHookClickCounter == 3) {
+                            sendEmptyMessage(HEADSET_HOOK_MULTI_CLICK_TIMEOUT);
+                        } else {
+                            sendEmptyMessageAtTime(HEADSET_HOOK_MULTI_CLICK_TIMEOUT,
+                                    eventTime + DOUBLE_CLICK_TIMEOUT);
+                        }
+                        break;
+                    }
+                    case HEADSET_HOOK_MULTI_CLICK_TIMEOUT:
+                        if (D) Log.d(TAG, "Handling headset click");
+                        switch (mHeadsetHookClickCounter) {
+                            case 1: service.togglePlayPause(); break;
+                            case 2: service.gotoNext(true); break;
+                            case 3: service.prev(false); break;
+                        }
+                        mHeadsetHookClickCounter = 0;
+                        service.mHeadsetHookWakeLock.release();
+                        break;
                     default:
                         break;
                 }