diff --git a/services/core/java/com/android/server/media/MediaKeyDispatcher.java b/services/core/java/com/android/server/media/MediaKeyDispatcher.java index 0b9697840a1fb..3a29622556cc0 100644 --- a/services/core/java/com/android/server/media/MediaKeyDispatcher.java +++ b/services/core/java/com/android/server/media/MediaKeyDispatcher.java @@ -33,29 +33,28 @@ import java.util.Map; /** * Provides a way to customize behavior for media key events. *

- * In order to override the implementation of the single/double/triple click or long press, + * In order to override the implementation of the single/double/triple tap or long press, * {@link #setOverriddenKeyEvents(int, int)} should be called for each key code with the * overridden {@link KeyEventType} bit value set, and the corresponding method, - * {@link #onSingleClick(KeyEvent)}, {@link #onDoubleClick(KeyEvent)}, - * {@link #onTripleClick(KeyEvent)}, {@link #onLongPress(KeyEvent)} should be implemented. + * {@link #onSingleTap(KeyEvent)}, {@link #onDoubleTap(KeyEvent)}, + * {@link #onTripleTap(KeyEvent)}, {@link #onLongPress(KeyEvent)} should be implemented. *

* Note: When instantiating this class, {@link MediaSessionService} will only use the constructor * without any parameters. */ -// TODO: Change API names from using "click" to "tap" // TODO: Move this class to apex/media/ public abstract class MediaKeyDispatcher { @IntDef(flag = true, value = { - KEY_EVENT_SINGLE_CLICK, - KEY_EVENT_DOUBLE_CLICK, - KEY_EVENT_TRIPLE_CLICK, + KEY_EVENT_SINGLE_TAP, + KEY_EVENT_DOUBLE_TAP, + KEY_EVENT_TRIPLE_TAP, KEY_EVENT_LONG_PRESS }) @Retention(RetentionPolicy.SOURCE) @interface KeyEventType {} - static final int KEY_EVENT_SINGLE_CLICK = 1 << 0; - static final int KEY_EVENT_DOUBLE_CLICK = 1 << 1; - static final int KEY_EVENT_TRIPLE_CLICK = 1 << 2; + static final int KEY_EVENT_SINGLE_TAP = 1 << 0; + static final int KEY_EVENT_DOUBLE_TAP = 1 << 1; + static final int KEY_EVENT_TRIPLE_TAP = 1 << 2; static final int KEY_EVENT_LONG_PRESS = 1 << 3; private Map mOverriddenKeyEvents; @@ -110,16 +109,16 @@ public abstract class MediaKeyDispatcher { return mOverriddenKeyEvents; } - static boolean isSingleClickOverridden(@KeyEventType int overriddenKeyEvents) { - return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_SINGLE_CLICK) != 0; + static boolean isSingleTapOverridden(@KeyEventType int overriddenKeyEvents) { + return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_SINGLE_TAP) != 0; } - static boolean isDoubleClickOverridden(@KeyEventType int overriddenKeyEvents) { - return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_DOUBLE_CLICK) != 0; + static boolean isDoubleTapOverridden(@KeyEventType int overriddenKeyEvents) { + return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_DOUBLE_TAP) != 0; } - static boolean isTripleClickOverridden(@KeyEventType int overriddenKeyEvents) { - return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_TRIPLE_CLICK) != 0; + static boolean isTripleTapOverridden(@KeyEventType int overriddenKeyEvents) { + return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_TRIPLE_TAP) != 0; } static boolean isLongPressOverridden(@KeyEventType int overriddenKeyEvents) { @@ -150,11 +149,11 @@ public abstract class MediaKeyDispatcher { } /** - * Customized implementation for single click event. Will be run if - * {@link #KEY_EVENT_SINGLE_CLICK} flag is on for the corresponding key code from + * Customized implementation for single tap event. Will be run if + * {@link #KEY_EVENT_SINGLE_TAP} flag is on for the corresponding key code from * {@link #getOverriddenKeyEvents()}. * - * It is considered a single click if only one {@link KeyEvent} with the same + * It is considered a single tap if only one {@link KeyEvent} with the same * {@link KeyEvent#getKeyCode()} is dispatched within * {@link ViewConfiguration#getMultiPressTimeout()} milliseconds. Change the * {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval. @@ -163,15 +162,15 @@ public abstract class MediaKeyDispatcher { * * @param keyEvent */ - void onSingleClick(KeyEvent keyEvent) { + void onSingleTap(KeyEvent keyEvent) { } /** - * Customized implementation for double click event. Will be run if - * {@link #KEY_EVENT_DOUBLE_CLICK} flag is on for the corresponding key code from + * Customized implementation for double tap event. Will be run if + * {@link #KEY_EVENT_DOUBLE_TAP} flag is on for the corresponding key code from * {@link #getOverriddenKeyEvents()}. * - * It is considered a double click if two {@link KeyEvent}s with the same + * It is considered a double tap if two {@link KeyEvent}s with the same * {@link KeyEvent#getKeyCode()} are dispatched within * {@link ViewConfiguration#getMultiPressTimeout()} milliseconds of each other. Change the * {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval. @@ -180,15 +179,15 @@ public abstract class MediaKeyDispatcher { * * @param keyEvent */ - void onDoubleClick(KeyEvent keyEvent) { + void onDoubleTap(KeyEvent keyEvent) { } /** - * Customized implementation for triple click event. Will be run if - * {@link #KEY_EVENT_TRIPLE_CLICK} flag is on for the corresponding key code from + * Customized implementation for triple tap event. Will be run if + * {@link #KEY_EVENT_TRIPLE_TAP} flag is on for the corresponding key code from * {@link #getOverriddenKeyEvents()}. * - * It is considered a triple click if three {@link KeyEvent}s with the same + * It is considered a triple tap if three {@link KeyEvent}s with the same * {@link KeyEvent#getKeyCode()} are dispatched within * {@link ViewConfiguration#getMultiPressTimeout()} milliseconds of each other. Change the * {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval. @@ -197,7 +196,7 @@ public abstract class MediaKeyDispatcher { * * @param keyEvent */ - void onTripleClick(KeyEvent keyEvent) { + void onTripleTap(KeyEvent keyEvent) { } /** diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 476c9f879ca21..bc0e8166fb428 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -18,6 +18,12 @@ package com.android.server.media; import static android.os.UserHandle.USER_ALL; +import static com.android.server.media.MediaKeyDispatcher.KEY_EVENT_LONG_PRESS; +import static com.android.server.media.MediaKeyDispatcher.isDoubleTapOverridden; +import static com.android.server.media.MediaKeyDispatcher.isLongPressOverridden; +import static com.android.server.media.MediaKeyDispatcher.isSingleTapOverridden; +import static com.android.server.media.MediaKeyDispatcher.isTripleTapOverridden; + import android.app.ActivityManager; import android.app.INotificationManager; import android.app.KeyguardManager; @@ -105,7 +111,7 @@ public class MediaSessionService extends SystemService implements Monitor { private static final int SESSION_CREATION_LIMIT_PER_UID = 100; private static final int LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() + /* Buffer for delayed delivery of key event */ 50; - private static final int MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout(); + private static final int MULTI_TAP_TIMEOUT = ViewConfiguration.getMultiPressTimeout(); private final Context mContext; private final SessionManagerImpl mSessionManagerImpl; @@ -1101,9 +1107,12 @@ public class MediaSessionService extends SystemService implements Monitor { "android.media.AudioService.WAKELOCK_ACQUIRED"; private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number - private KeyEvent mPendingFirstDownKeyEvent = null; + private KeyEvent mTrackingFirstDownKeyEvent = null; private boolean mIsLongPressing = false; private Runnable mLongPressTimeoutRunnable = null; + private int mMultiTapCount = 0; + private int mMultiTapKeyCode = 0; + private Runnable mMultiTapTimeoutRunnable = null; @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, @@ -2117,10 +2126,12 @@ public class MediaSessionService extends SystemService implements Monitor { } // A long press is determined by: - // 1) A KeyEvent with KeyEvent.ACTION_DOWN and repeat count of 0, followed by - // 2) A KeyEvent with KeyEvent.ACTION_DOWN and repeat count of 1 and FLAG_LONG_PRESS within - // ViewConfiguration.getLongPressTimeout(). - // TODO: Add description about what a click is determined by. + // 1) A KeyEvent.ACTION_DOWN KeyEvent and repeat count of 0, followed by + // 2) A KeyEvent.ACTION_DOWN KeyEvent with the same key code, a repeat count of 1, and + // FLAG_LONG_PRESS received within ViewConfiguration.getLongPressTimeout(). + // A tap is determined by: + // 1) A KeyEvent.ACTION_DOWN KeyEvent followed by + // 2) A KeyEvent.ACTION_UP KeyEvent with the same key code. private void handleKeyEventLocked(String packageName, int pid, int uid, boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) { if (keyEvent.isCanceled()) { @@ -2129,61 +2140,121 @@ public class MediaSessionService extends SystemService implements Monitor { int overriddenKeyEvents = (mCustomMediaKeyDispatcher == null) ? 0 : mCustomMediaKeyDispatcher.getOverriddenKeyEvents().get(keyEvent.getKeyCode()); - cancelPendingIfNeeded(keyEvent); - if (!needPending(keyEvent, overriddenKeyEvents)) { + cancelTrackingIfNeeded(packageName, pid, uid, asSystemService, keyEvent, needWakeLock, + overriddenKeyEvents); + if (!needTracking(keyEvent, overriddenKeyEvents)) { dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, needWakeLock); return; } if (isFirstDownKeyEvent(keyEvent)) { - mPendingFirstDownKeyEvent = keyEvent; + mTrackingFirstDownKeyEvent = keyEvent; mIsLongPressing = false; return; } + // Long press is always overridden here, otherwise the key event would have been already + // handled if (isFirstLongPressKeyEvent(keyEvent)) { mIsLongPressing = true; } if (mIsLongPressing) { handleLongPressLocked(keyEvent, needWakeLock, overriddenKeyEvents); - } else if (keyEvent.getAction() == KeyEvent.ACTION_UP) { - mPendingFirstDownKeyEvent = null; - // TODO: Replace this with code to determine whether - // single/double/triple click and run custom implementations, - // if they exist. - dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, - keyEvent, needWakeLock); + return; + } + + if (keyEvent.getAction() == KeyEvent.ACTION_UP) { + mTrackingFirstDownKeyEvent = null; + if (shouldTrackForMultipleTapsLocked(overriddenKeyEvents)) { + if (mMultiTapCount == 0) { + mMultiTapTimeoutRunnable = createSingleTapRunnable(packageName, pid, uid, + asSystemService, keyEvent, needWakeLock, + isSingleTapOverridden(overriddenKeyEvents)); + if (isSingleTapOverridden(overriddenKeyEvents) + && !isDoubleTapOverridden(overriddenKeyEvents) + && !isTripleTapOverridden(overriddenKeyEvents)) { + mMultiTapTimeoutRunnable.run(); + } else { + mHandler.postDelayed(mMultiTapTimeoutRunnable, + MULTI_TAP_TIMEOUT); + mMultiTapCount = 1; + mMultiTapKeyCode = keyEvent.getKeyCode(); + } + } else if (mMultiTapCount == 1) { + mHandler.removeCallbacks(mMultiTapTimeoutRunnable); + mMultiTapTimeoutRunnable = createDoubleTapRunnable(packageName, pid, uid, + asSystemService, keyEvent, needWakeLock, + isSingleTapOverridden(overriddenKeyEvents), + isDoubleTapOverridden(overriddenKeyEvents)); + if (isTripleTapOverridden(overriddenKeyEvents)) { + mHandler.postDelayed(mMultiTapTimeoutRunnable, MULTI_TAP_TIMEOUT); + mMultiTapCount = 2; + } else { + mMultiTapTimeoutRunnable.run(); + } + } else if (mMultiTapCount == 2) { + mHandler.removeCallbacks(mMultiTapTimeoutRunnable); + onTripleTap(keyEvent); + } + } else { + dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock); + } } } - private void cancelPendingIfNeeded(KeyEvent keyEvent) { - if (mPendingFirstDownKeyEvent == null) { + private boolean shouldTrackForMultipleTapsLocked(int overriddenKeyEvents) { + return isSingleTapOverridden(overriddenKeyEvents) + || isDoubleTapOverridden(overriddenKeyEvents) + || isTripleTapOverridden(overriddenKeyEvents); + } + + private void cancelTrackingIfNeeded(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, + int overriddenKeyEvents) { + if (mTrackingFirstDownKeyEvent == null && mMultiTapTimeoutRunnable == null) { return; } + if (isFirstDownKeyEvent(keyEvent)) { if (mLongPressTimeoutRunnable != null) { mHandler.removeCallbacks(mLongPressTimeoutRunnable); mLongPressTimeoutRunnable.run(); - } else { - resetLongPressTracking(); } + if (mMultiTapTimeoutRunnable != null && keyEvent.getKeyCode() != mMultiTapKeyCode) { + runExistingMultiTapRunnableLocked(); + } + resetLongPressTracking(); return; } - if (mPendingFirstDownKeyEvent.getDownTime() == keyEvent.getDownTime() - && mPendingFirstDownKeyEvent.getKeyCode() == keyEvent.getKeyCode() - && keyEvent.getAction() == KeyEvent.ACTION_DOWN - && keyEvent.getRepeatCount() > 1 && !mIsLongPressing) { - resetLongPressTracking(); + + if (mTrackingFirstDownKeyEvent != null + && mTrackingFirstDownKeyEvent.getDownTime() == keyEvent.getDownTime() + && mTrackingFirstDownKeyEvent.getKeyCode() == keyEvent.getKeyCode() + && keyEvent.getAction() == KeyEvent.ACTION_DOWN) { + if (isFirstLongPressKeyEvent(keyEvent)) { + if (mMultiTapTimeoutRunnable != null) { + runExistingMultiTapRunnableLocked(); + } + if ((overriddenKeyEvents & KEY_EVENT_LONG_PRESS) == 0 + && !isVoiceKey(keyEvent.getKeyCode())) { + dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, + mTrackingFirstDownKeyEvent, needWakeLock); + mTrackingFirstDownKeyEvent = null; + } + } else if (keyEvent.getRepeatCount() > 1 && !mIsLongPressing) { + resetLongPressTracking(); + } } } - private boolean needPending(KeyEvent keyEvent, int overriddenKeyEvents) { + private boolean needTracking(KeyEvent keyEvent, int overriddenKeyEvents) { if (!isFirstDownKeyEvent(keyEvent)) { - if (mPendingFirstDownKeyEvent == null) { + if (mTrackingFirstDownKeyEvent == null) { return false; - } else if (mPendingFirstDownKeyEvent.getDownTime() != keyEvent.getDownTime() - || mPendingFirstDownKeyEvent.getKeyCode() != keyEvent.getKeyCode()) { + } else if (mTrackingFirstDownKeyEvent.getDownTime() != keyEvent.getDownTime() + || mTrackingFirstDownKeyEvent.getKeyCode() != keyEvent.getKeyCode()) { return false; } } @@ -2193,10 +2264,21 @@ public class MediaSessionService extends SystemService implements Monitor { return true; } + private void runExistingMultiTapRunnableLocked() { + mHandler.removeCallbacks(mMultiTapTimeoutRunnable); + mMultiTapTimeoutRunnable.run(); + } + + private void resetMultiTapTrackingLocked() { + mMultiTapCount = 0; + mMultiTapTimeoutRunnable = null; + mMultiTapKeyCode = 0; + } + private void handleLongPressLocked(KeyEvent keyEvent, boolean needWakeLock, int overriddenKeyEvents) { if (mCustomMediaKeyDispatcher != null - && mCustomMediaKeyDispatcher.isLongPressOverridden(overriddenKeyEvents)) { + && isLongPressOverridden(overriddenKeyEvents)) { mCustomMediaKeyDispatcher.onLongPress(keyEvent); if (mLongPressTimeoutRunnable != null) { @@ -2230,7 +2312,7 @@ public class MediaSessionService extends SystemService implements Monitor { } private void resetLongPressTracking() { - mPendingFirstDownKeyEvent = null; + mTrackingFirstDownKeyEvent = null; mIsLongPressing = false; mLongPressTimeoutRunnable = null; } @@ -2259,6 +2341,50 @@ public class MediaSessionService extends SystemService implements Monitor { keyEvent, needWakeLock); } + Runnable createSingleTapRunnable(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, + boolean overridden) { + return new Runnable() { + @Override + public void run() { + resetMultiTapTrackingLocked(); + if (overridden) { + mCustomMediaKeyDispatcher.onSingleTap(keyEvent); + } else { + dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock); + } + } + }; + }; + + Runnable createDoubleTapRunnable(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, + boolean singleTapOverridden, boolean doubleTapOverridden) { + return new Runnable() { + @Override + public void run() { + resetMultiTapTrackingLocked(); + if (doubleTapOverridden) { + mCustomMediaKeyDispatcher.onDoubleTap(keyEvent); + } else if (singleTapOverridden) { + mCustomMediaKeyDispatcher.onSingleTap(keyEvent); + mCustomMediaKeyDispatcher.onSingleTap(keyEvent); + } else { + dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock); + dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock); + } + } + }; + }; + + private void onTripleTap(KeyEvent keyEvent) { + resetMultiTapTrackingLocked(); + mCustomMediaKeyDispatcher.onTripleTap(keyEvent); + } + private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid, boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) { if (mCurrentFullUserRecord.getMediaButtonSessionLocked()