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

Change-Id: I6ecbb2650a46db69d61a076af2ffea6d82006f8c
This commit is contained in:
Jin Seok Park
2020-04-27 01:07:37 +00:00
committed by Automerger Merge Worker
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.
* <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;
@@ -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) {
}
/**

View File

@@ -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()