Merge "[Media ML] Support customization for single/double/triple clicks" into rvc-dev

This commit is contained in:
Jin Seok Park
2020-04-27 00:54:30 +00:00
committed by Android (Google) Code Review
2 changed files with 184 additions and 59 deletions

View File

@@ -33,29 +33,28 @@ import java.util.Map;
/** /**
* Provides a way to customize behavior for media key events. * Provides a way to customize behavior for media key events.
* <p> * <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 * {@link #setOverriddenKeyEvents(int, int)} should be called for each key code with the
* overridden {@link KeyEventType} bit value set, and the corresponding method, * overridden {@link KeyEventType} bit value set, and the corresponding method,
* {@link #onSingleClick(KeyEvent)}, {@link #onDoubleClick(KeyEvent)}, * {@link #onSingleTap(KeyEvent)}, {@link #onDoubleTap(KeyEvent)},
* {@link #onTripleClick(KeyEvent)}, {@link #onLongPress(KeyEvent)} should be implemented. * {@link #onTripleTap(KeyEvent)}, {@link #onLongPress(KeyEvent)} should be implemented.
* <p> * <p>
* Note: When instantiating this class, {@link MediaSessionService} will only use the constructor * Note: When instantiating this class, {@link MediaSessionService} will only use the constructor
* without any parameters. * without any parameters.
*/ */
// TODO: Change API names from using "click" to "tap"
// TODO: Move this class to apex/media/ // TODO: Move this class to apex/media/
public abstract class MediaKeyDispatcher { public abstract class MediaKeyDispatcher {
@IntDef(flag = true, value = { @IntDef(flag = true, value = {
KEY_EVENT_SINGLE_CLICK, KEY_EVENT_SINGLE_TAP,
KEY_EVENT_DOUBLE_CLICK, KEY_EVENT_DOUBLE_TAP,
KEY_EVENT_TRIPLE_CLICK, KEY_EVENT_TRIPLE_TAP,
KEY_EVENT_LONG_PRESS KEY_EVENT_LONG_PRESS
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@interface KeyEventType {} @interface KeyEventType {}
static final int KEY_EVENT_SINGLE_CLICK = 1 << 0; static final int KEY_EVENT_SINGLE_TAP = 1 << 0;
static final int KEY_EVENT_DOUBLE_CLICK = 1 << 1; static final int KEY_EVENT_DOUBLE_TAP = 1 << 1;
static final int KEY_EVENT_TRIPLE_CLICK = 1 << 2; static final int KEY_EVENT_TRIPLE_TAP = 1 << 2;
static final int KEY_EVENT_LONG_PRESS = 1 << 3; static final int KEY_EVENT_LONG_PRESS = 1 << 3;
private Map<Integer, Integer> mOverriddenKeyEvents; private Map<Integer, Integer> mOverriddenKeyEvents;
@@ -110,16 +109,16 @@ public abstract class MediaKeyDispatcher {
return mOverriddenKeyEvents; return mOverriddenKeyEvents;
} }
static boolean isSingleClickOverridden(@KeyEventType int overriddenKeyEvents) { static boolean isSingleTapOverridden(@KeyEventType int overriddenKeyEvents) {
return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_SINGLE_CLICK) != 0; return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_SINGLE_TAP) != 0;
} }
static boolean isDoubleClickOverridden(@KeyEventType int overriddenKeyEvents) { static boolean isDoubleTapOverridden(@KeyEventType int overriddenKeyEvents) {
return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_DOUBLE_CLICK) != 0; return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_DOUBLE_TAP) != 0;
} }
static boolean isTripleClickOverridden(@KeyEventType int overriddenKeyEvents) { static boolean isTripleTapOverridden(@KeyEventType int overriddenKeyEvents) {
return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_TRIPLE_CLICK) != 0; return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_TRIPLE_TAP) != 0;
} }
static boolean isLongPressOverridden(@KeyEventType int overriddenKeyEvents) { static boolean isLongPressOverridden(@KeyEventType int overriddenKeyEvents) {
@@ -150,11 +149,11 @@ public abstract class MediaKeyDispatcher {
} }
/** /**
* Customized implementation for single click event. Will be run if * Customized implementation for single tap event. Will be run if
* {@link #KEY_EVENT_SINGLE_CLICK} flag is on for the corresponding key code from * {@link #KEY_EVENT_SINGLE_TAP} flag is on for the corresponding key code from
* {@link #getOverriddenKeyEvents()}. * {@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 KeyEvent#getKeyCode()} is dispatched within
* {@link ViewConfiguration#getMultiPressTimeout()} milliseconds. Change the * {@link ViewConfiguration#getMultiPressTimeout()} milliseconds. Change the
* {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval. * {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval.
@@ -163,15 +162,15 @@ public abstract class MediaKeyDispatcher {
* *
* @param keyEvent * @param keyEvent
*/ */
void onSingleClick(KeyEvent keyEvent) { void onSingleTap(KeyEvent keyEvent) {
} }
/** /**
* Customized implementation for double click event. Will be run if * Customized implementation for double tap event. Will be run if
* {@link #KEY_EVENT_DOUBLE_CLICK} flag is on for the corresponding key code from * {@link #KEY_EVENT_DOUBLE_TAP} flag is on for the corresponding key code from
* {@link #getOverriddenKeyEvents()}. * {@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 KeyEvent#getKeyCode()} are dispatched within
* {@link ViewConfiguration#getMultiPressTimeout()} milliseconds of each other. Change the * {@link ViewConfiguration#getMultiPressTimeout()} milliseconds of each other. Change the
* {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval. * {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval.
@@ -180,15 +179,15 @@ public abstract class MediaKeyDispatcher {
* *
* @param keyEvent * @param keyEvent
*/ */
void onDoubleClick(KeyEvent keyEvent) { void onDoubleTap(KeyEvent keyEvent) {
} }
/** /**
* Customized implementation for triple click event. Will be run if * Customized implementation for triple tap event. Will be run if
* {@link #KEY_EVENT_TRIPLE_CLICK} flag is on for the corresponding key code from * {@link #KEY_EVENT_TRIPLE_TAP} flag is on for the corresponding key code from
* {@link #getOverriddenKeyEvents()}. * {@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 KeyEvent#getKeyCode()} are dispatched within
* {@link ViewConfiguration#getMultiPressTimeout()} milliseconds of each other. Change the * {@link ViewConfiguration#getMultiPressTimeout()} milliseconds of each other. Change the
* {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval. * {@link android.provider.Settings.Secure#MULTI_PRESS_TIMEOUT} value to adjust the interval.
@@ -197,7 +196,7 @@ public abstract class MediaKeyDispatcher {
* *
* @param keyEvent * @param keyEvent
*/ */
void onTripleClick(KeyEvent keyEvent) { void onTripleTap(KeyEvent keyEvent) {
} }
/** /**

View File

@@ -18,6 +18,12 @@ package com.android.server.media;
import static android.os.UserHandle.USER_ALL; 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.ActivityManager;
import android.app.INotificationManager; import android.app.INotificationManager;
import android.app.KeyguardManager; 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 SESSION_CREATION_LIMIT_PER_UID = 100;
private static final int LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() private static final int LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout()
+ /* Buffer for delayed delivery of key event */ 50; + /* 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 Context mContext;
private final SessionManagerImpl mSessionManagerImpl; private final SessionManagerImpl mSessionManagerImpl;
@@ -1101,9 +1107,12 @@ public class MediaSessionService extends SystemService implements Monitor {
"android.media.AudioService.WAKELOCK_ACQUIRED"; "android.media.AudioService.WAKELOCK_ACQUIRED";
private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
private KeyEvent mPendingFirstDownKeyEvent = null; private KeyEvent mTrackingFirstDownKeyEvent = null;
private boolean mIsLongPressing = false; private boolean mIsLongPressing = false;
private Runnable mLongPressTimeoutRunnable = null; private Runnable mLongPressTimeoutRunnable = null;
private int mMultiTapCount = 0;
private int mMultiTapKeyCode = 0;
private Runnable mMultiTapTimeoutRunnable = null;
@Override @Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 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: // A long press is determined by:
// 1) A KeyEvent with KeyEvent.ACTION_DOWN and repeat count of 0, followed by // 1) A KeyEvent.ACTION_DOWN KeyEvent and repeat count of 0, followed by
// 2) A KeyEvent with KeyEvent.ACTION_DOWN and repeat count of 1 and FLAG_LONG_PRESS within // 2) A KeyEvent.ACTION_DOWN KeyEvent with the same key code, a repeat count of 1, and
// ViewConfiguration.getLongPressTimeout(). // FLAG_LONG_PRESS received within ViewConfiguration.getLongPressTimeout().
// TODO: Add description about what a click is determined by. // 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, private void handleKeyEventLocked(String packageName, int pid, int uid,
boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) { boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
if (keyEvent.isCanceled()) { if (keyEvent.isCanceled()) {
@@ -2129,61 +2140,121 @@ public class MediaSessionService extends SystemService implements Monitor {
int overriddenKeyEvents = (mCustomMediaKeyDispatcher == null) ? 0 int overriddenKeyEvents = (mCustomMediaKeyDispatcher == null) ? 0
: mCustomMediaKeyDispatcher.getOverriddenKeyEvents().get(keyEvent.getKeyCode()); : mCustomMediaKeyDispatcher.getOverriddenKeyEvents().get(keyEvent.getKeyCode());
cancelPendingIfNeeded(keyEvent); cancelTrackingIfNeeded(packageName, pid, uid, asSystemService, keyEvent, needWakeLock,
if (!needPending(keyEvent, overriddenKeyEvents)) { overriddenKeyEvents);
if (!needTracking(keyEvent, overriddenKeyEvents)) {
dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
needWakeLock); needWakeLock);
return; return;
} }
if (isFirstDownKeyEvent(keyEvent)) { if (isFirstDownKeyEvent(keyEvent)) {
mPendingFirstDownKeyEvent = keyEvent; mTrackingFirstDownKeyEvent = keyEvent;
mIsLongPressing = false; mIsLongPressing = false;
return; return;
} }
// Long press is always overridden here, otherwise the key event would have been already
// handled
if (isFirstLongPressKeyEvent(keyEvent)) { if (isFirstLongPressKeyEvent(keyEvent)) {
mIsLongPressing = true; mIsLongPressing = true;
} }
if (mIsLongPressing) { if (mIsLongPressing) {
handleLongPressLocked(keyEvent, needWakeLock, overriddenKeyEvents); handleLongPressLocked(keyEvent, needWakeLock, overriddenKeyEvents);
} else if (keyEvent.getAction() == KeyEvent.ACTION_UP) { return;
mPendingFirstDownKeyEvent = null; }
// TODO: Replace this with code to determine whether
// single/double/triple click and run custom implementations, if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
// if they exist. 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, dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService,
keyEvent, needWakeLock); keyEvent, needWakeLock);
} }
} }
}
private void cancelPendingIfNeeded(KeyEvent keyEvent) { private boolean shouldTrackForMultipleTapsLocked(int overriddenKeyEvents) {
if (mPendingFirstDownKeyEvent == null) { 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; return;
} }
if (isFirstDownKeyEvent(keyEvent)) { if (isFirstDownKeyEvent(keyEvent)) {
if (mLongPressTimeoutRunnable != null) { if (mLongPressTimeoutRunnable != null) {
mHandler.removeCallbacks(mLongPressTimeoutRunnable); mHandler.removeCallbacks(mLongPressTimeoutRunnable);
mLongPressTimeoutRunnable.run(); mLongPressTimeoutRunnable.run();
} else {
resetLongPressTracking();
} }
if (mMultiTapTimeoutRunnable != null && keyEvent.getKeyCode() != mMultiTapKeyCode) {
runExistingMultiTapRunnableLocked();
}
resetLongPressTracking();
return; return;
} }
if (mPendingFirstDownKeyEvent.getDownTime() == keyEvent.getDownTime()
&& mPendingFirstDownKeyEvent.getKeyCode() == keyEvent.getKeyCode() if (mTrackingFirstDownKeyEvent != null
&& keyEvent.getAction() == KeyEvent.ACTION_DOWN && mTrackingFirstDownKeyEvent.getDownTime() == keyEvent.getDownTime()
&& keyEvent.getRepeatCount() > 1 && !mIsLongPressing) { && 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(); resetLongPressTracking();
} }
} }
}
private boolean needPending(KeyEvent keyEvent, int overriddenKeyEvents) { private boolean needTracking(KeyEvent keyEvent, int overriddenKeyEvents) {
if (!isFirstDownKeyEvent(keyEvent)) { if (!isFirstDownKeyEvent(keyEvent)) {
if (mPendingFirstDownKeyEvent == null) { if (mTrackingFirstDownKeyEvent == null) {
return false; return false;
} else if (mPendingFirstDownKeyEvent.getDownTime() != keyEvent.getDownTime() } else if (mTrackingFirstDownKeyEvent.getDownTime() != keyEvent.getDownTime()
|| mPendingFirstDownKeyEvent.getKeyCode() != keyEvent.getKeyCode()) { || mTrackingFirstDownKeyEvent.getKeyCode() != keyEvent.getKeyCode()) {
return false; return false;
} }
} }
@@ -2193,10 +2264,21 @@ public class MediaSessionService extends SystemService implements Monitor {
return true; 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, private void handleLongPressLocked(KeyEvent keyEvent, boolean needWakeLock,
int overriddenKeyEvents) { int overriddenKeyEvents) {
if (mCustomMediaKeyDispatcher != null if (mCustomMediaKeyDispatcher != null
&& mCustomMediaKeyDispatcher.isLongPressOverridden(overriddenKeyEvents)) { && isLongPressOverridden(overriddenKeyEvents)) {
mCustomMediaKeyDispatcher.onLongPress(keyEvent); mCustomMediaKeyDispatcher.onLongPress(keyEvent);
if (mLongPressTimeoutRunnable != null) { if (mLongPressTimeoutRunnable != null) {
@@ -2230,7 +2312,7 @@ public class MediaSessionService extends SystemService implements Monitor {
} }
private void resetLongPressTracking() { private void resetLongPressTracking() {
mPendingFirstDownKeyEvent = null; mTrackingFirstDownKeyEvent = null;
mIsLongPressing = false; mIsLongPressing = false;
mLongPressTimeoutRunnable = null; mLongPressTimeoutRunnable = null;
} }
@@ -2259,6 +2341,50 @@ public class MediaSessionService extends SystemService implements Monitor {
keyEvent, needWakeLock); 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, private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) { boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
if (mCurrentFullUserRecord.getMediaButtonSessionLocked() if (mCurrentFullUserRecord.getMediaButtonSessionLocked()