Loading services/core/java/com/android/server/media/MediaKeyDispatcher.java +27 −28 Original line number Diff line number Diff line Loading @@ -33,29 +33,28 @@ import java.util.Map; /** * Provides a way to customize behavior for media key events. * <p> * 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. * <p> * 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<Integer, Integer> mOverriddenKeyEvents; Loading Loading @@ -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) { Loading Loading @@ -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. Loading @@ -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. Loading @@ -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. Loading @@ -197,7 +196,7 @@ public abstract class MediaKeyDispatcher { * * @param keyEvent */ void onTripleClick(KeyEvent keyEvent) { void onTripleTap(KeyEvent keyEvent) { } /** Loading services/core/java/com/android/server/media/MediaSessionService.java +157 −31 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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()) { Loading @@ -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. 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) { 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; } } Loading @@ -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) { Loading Loading @@ -2230,7 +2312,7 @@ public class MediaSessionService extends SystemService implements Monitor { } private void resetLongPressTracking() { mPendingFirstDownKeyEvent = null; mTrackingFirstDownKeyEvent = null; mIsLongPressing = false; mLongPressTimeoutRunnable = null; } Loading Loading @@ -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() Loading Loading
services/core/java/com/android/server/media/MediaKeyDispatcher.java +27 −28 Original line number Diff line number Diff line Loading @@ -33,29 +33,28 @@ import java.util.Map; /** * Provides a way to customize behavior for media key events. * <p> * 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. * <p> * 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<Integer, Integer> mOverriddenKeyEvents; Loading Loading @@ -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) { Loading Loading @@ -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. Loading @@ -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. Loading @@ -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. Loading @@ -197,7 +196,7 @@ public abstract class MediaKeyDispatcher { * * @param keyEvent */ void onTripleClick(KeyEvent keyEvent) { void onTripleTap(KeyEvent keyEvent) { } /** Loading
services/core/java/com/android/server/media/MediaSessionService.java +157 −31 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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()) { Loading @@ -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. 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) { 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; } } Loading @@ -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) { Loading Loading @@ -2230,7 +2312,7 @@ public class MediaSessionService extends SystemService implements Monitor { } private void resetLongPressTracking() { mPendingFirstDownKeyEvent = null; mTrackingFirstDownKeyEvent = null; mIsLongPressing = false; mLongPressTimeoutRunnable = null; } Loading Loading @@ -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() Loading