Merge "[Media ML] Support customization for long press implementation" into rvc-dev am: 7101c11243
Change-Id: I641a8036239df9a9895ccfd6befe70e6128645f4
This commit is contained in:
@@ -16,24 +16,64 @@
|
|||||||
|
|
||||||
package com.android.server.media;
|
package com.android.server.media;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.media.session.ISessionManager;
|
import android.media.session.ISessionManager;
|
||||||
import android.media.session.MediaSession;
|
import android.media.session.MediaSession;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.HashMap;
|
||||||
|
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>
|
||||||
|
* In order to override the implementation of the single/double/triple click 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.
|
||||||
|
* <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/
|
||||||
public abstract class MediaKeyDispatcher {
|
public abstract class MediaKeyDispatcher {
|
||||||
|
@IntDef(flag = true, value = {
|
||||||
|
KEY_EVENT_SINGLE_CLICK,
|
||||||
|
KEY_EVENT_DOUBLE_CLICK,
|
||||||
|
KEY_EVENT_TRIPLE_CLICK,
|
||||||
|
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_LONG_PRESS = 1 << 3;
|
||||||
|
|
||||||
|
private Map<Integer, Integer> mOverriddenKeyEvents;
|
||||||
|
|
||||||
public MediaKeyDispatcher() {
|
public MediaKeyDispatcher() {
|
||||||
// Constructor used for reflection
|
// Constructor used for reflection
|
||||||
|
mOverriddenKeyEvents = new HashMap<>();
|
||||||
|
mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_PLAY, 0);
|
||||||
|
mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_PAUSE, 0);
|
||||||
|
mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0);
|
||||||
|
mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MUTE, 0);
|
||||||
|
mOverriddenKeyEvents.put(KeyEvent.KEYCODE_HEADSETHOOK, 0);
|
||||||
|
mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_STOP, 0);
|
||||||
|
mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_NEXT, 0);
|
||||||
|
mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move this method into SessionPolicyProvider.java for better readability.
|
||||||
/**
|
/**
|
||||||
* Implement this to customize the logic for which MediaSession should consume which key event.
|
* Implement this to customize the logic for which MediaSession should consume which key event.
|
||||||
*
|
*
|
||||||
@@ -49,4 +89,137 @@ public abstract class MediaKeyDispatcher {
|
|||||||
boolean asSystemService) {
|
boolean asSystemService) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the map of key code -> {@link KeyEventType} that have been overridden.
|
||||||
|
* <p>
|
||||||
|
* The list of valid key codes are the following:
|
||||||
|
* <ul>
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_PLAY}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_PAUSE}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MUTE}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_HEADSETHOOK}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_STOP}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_NEXT}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}
|
||||||
|
* </ul>
|
||||||
|
* @see {@link KeyEvent#isMediaSessionKey(int)}
|
||||||
|
*/
|
||||||
|
@KeyEventType Map<Integer, Integer> getOverriddenKeyEvents() {
|
||||||
|
return mOverriddenKeyEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isSingleClickOverridden(@KeyEventType int overriddenKeyEvents) {
|
||||||
|
return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_SINGLE_CLICK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isDoubleClickOverridden(@KeyEventType int overriddenKeyEvents) {
|
||||||
|
return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_DOUBLE_CLICK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isTripleClickOverridden(@KeyEventType int overriddenKeyEvents) {
|
||||||
|
return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_TRIPLE_CLICK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isLongPressOverridden(@KeyEventType int overriddenKeyEvents) {
|
||||||
|
return (overriddenKeyEvents & MediaKeyDispatcher.KEY_EVENT_LONG_PRESS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the given key event type flagged with overridden {@link KeyEventType} to
|
||||||
|
* the given key code. If called multiple times for the same key code, will be overwritten to
|
||||||
|
* the most recently called {@link KeyEventType} value.
|
||||||
|
* <p>
|
||||||
|
* The list of valid key codes are the following:
|
||||||
|
* <ul>
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_PLAY}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_PAUSE}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MUTE}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_HEADSETHOOK}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_STOP}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_NEXT}
|
||||||
|
* <li> {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}
|
||||||
|
* </ul>
|
||||||
|
* @see {@link KeyEvent#isMediaSessionKey(int)}
|
||||||
|
* @param keyCode
|
||||||
|
*/
|
||||||
|
void setOverriddenKeyEvents(int keyCode, @KeyEventType int keyEventType) {
|
||||||
|
mOverriddenKeyEvents.put(keyCode, keyEventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customized implementation for single click event. Will be run if
|
||||||
|
* {@link #KEY_EVENT_SINGLE_CLICK} 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
|
||||||
|
* {@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.
|
||||||
|
*
|
||||||
|
* Note: This will only be called once with the {@link KeyEvent#ACTION_UP} KeyEvent.
|
||||||
|
*
|
||||||
|
* @param keyEvent
|
||||||
|
*/
|
||||||
|
void onSingleClick(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
|
||||||
|
* {@link #getOverriddenKeyEvents()}.
|
||||||
|
*
|
||||||
|
* It is considered a double click 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.
|
||||||
|
*
|
||||||
|
* Note: This will only be called once with the {@link KeyEvent#ACTION_UP} KeyEvent.
|
||||||
|
*
|
||||||
|
* @param keyEvent
|
||||||
|
*/
|
||||||
|
void onDoubleClick(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
|
||||||
|
* {@link #getOverriddenKeyEvents()}.
|
||||||
|
*
|
||||||
|
* It is considered a triple click 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.
|
||||||
|
*
|
||||||
|
* Note: This will only be called once with the {@link KeyEvent#ACTION_UP} KeyEvent.
|
||||||
|
*
|
||||||
|
* @param keyEvent
|
||||||
|
*/
|
||||||
|
void onTripleClick(KeyEvent keyEvent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customized implementation for long press event. Will be run if
|
||||||
|
* {@link #KEY_EVENT_LONG_PRESS} flag is on for the corresponding key code from
|
||||||
|
* {@link #getOverriddenKeyEvents()}.
|
||||||
|
*
|
||||||
|
* It is considered a long press if an {@link KeyEvent#ACTION_DOWN} key event is followed by
|
||||||
|
* another {@link KeyEvent#ACTION_DOWN} key event with {@link KeyEvent#FLAG_LONG_PRESS}
|
||||||
|
* enabled, and an {@link KeyEvent#getRepeatCount()} that is equal to 1.
|
||||||
|
*
|
||||||
|
* Note: This will be called for the following key events:
|
||||||
|
* <ul>
|
||||||
|
* <li>A {@link KeyEvent#ACTION_DOWN} KeyEvent with {@link KeyEvent#FLAG_LONG_PRESS} and
|
||||||
|
* {@link KeyEvent#getRepeatCount()} equal to 1</li>
|
||||||
|
* <li>Multiple {@link KeyEvent#ACTION_DOWN} KeyEvents with increasing
|
||||||
|
* {@link KeyEvent#getRepeatCount()}</li>
|
||||||
|
* <li>A {@link KeyEvent#ACTION_UP} KeyEvent</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param keyEvent
|
||||||
|
*/
|
||||||
|
void onLongPress(KeyEvent keyEvent) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,10 +86,10 @@ import java.io.FileDescriptor;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System implementation of MediaSessionManager
|
* System implementation of MediaSessionManager
|
||||||
@@ -103,12 +103,14 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
private static final int WAKELOCK_TIMEOUT = 5000;
|
private static final int WAKELOCK_TIMEOUT = 5000;
|
||||||
private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
|
private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
|
||||||
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()
|
||||||
|
+ /* Buffer for delayed delivery of key event */ 50;
|
||||||
|
private static final int MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout();
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final SessionManagerImpl mSessionManagerImpl;
|
private final SessionManagerImpl mSessionManagerImpl;
|
||||||
private final MessageHandler mHandler = new MessageHandler();
|
private final MessageHandler mHandler = new MessageHandler();
|
||||||
private final PowerManager.WakeLock mMediaEventWakeLock;
|
private final PowerManager.WakeLock mMediaEventWakeLock;
|
||||||
private final int mLongPressTimeout;
|
|
||||||
private final INotificationManager mNotificationManager;
|
private final INotificationManager mNotificationManager;
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
// Keeps the full user id for each user.
|
// Keeps the full user id for each user.
|
||||||
@@ -142,8 +144,7 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
|
|
||||||
private SessionPolicyProvider mCustomSessionPolicyProvider;
|
private SessionPolicyProvider mCustomSessionPolicyProvider;
|
||||||
private MediaKeyDispatcher mCustomMediaKeyDispatcher;
|
private MediaKeyDispatcher mCustomMediaKeyDispatcher;
|
||||||
private Method mGetSessionForKeyEventMethod;
|
private Map<Integer, Integer> mOverriddenKeyEventsMap;
|
||||||
private Method mGetSessionPoliciesMethod;
|
|
||||||
|
|
||||||
public MediaSessionService(Context context) {
|
public MediaSessionService(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -151,7 +152,6 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
mSessionManagerImpl = new SessionManagerImpl();
|
mSessionManagerImpl = new SessionManagerImpl();
|
||||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
|
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
|
||||||
mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
|
|
||||||
mNotificationManager = INotificationManager.Stub.asInterface(
|
mNotificationManager = INotificationManager.Stub.asInterface(
|
||||||
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
|
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
|
||||||
}
|
}
|
||||||
@@ -184,9 +184,10 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(
|
mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(
|
||||||
PackageManager.FEATURE_LEANBACK);
|
PackageManager.FEATURE_LEANBACK);
|
||||||
|
|
||||||
|
updateUser();
|
||||||
|
|
||||||
instantiateCustomProvider(null);
|
instantiateCustomProvider(null);
|
||||||
instantiateCustomDispatcher(null);
|
instantiateCustomDispatcher(null);
|
||||||
updateUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isGlobalPriorityActiveLocked() {
|
private boolean isGlobalPriorityActiveLocked() {
|
||||||
@@ -570,13 +571,9 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
|
String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
int policies = 0;
|
int policies = 0;
|
||||||
if (mCustomSessionPolicyProvider != null && mGetSessionPoliciesMethod != null) {
|
if (mCustomSessionPolicyProvider != null) {
|
||||||
try {
|
policies = mCustomSessionPolicyProvider.getSessionPoliciesForApplication(
|
||||||
policies = (int) mGetSessionPoliciesMethod.invoke(
|
callerUid, callerPackageName);
|
||||||
mCustomSessionPolicyProvider, callerUid, callerPackageName);
|
|
||||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
|
||||||
Log.w(TAG, "Encountered problem while using reflection", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FullUserRecord user = getFullUserRecordLocked(userId);
|
FullUserRecord user = getFullUserRecordLocked(userId);
|
||||||
@@ -762,44 +759,46 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void instantiateCustomDispatcher(String nameFromTesting) {
|
private void instantiateCustomDispatcher(String nameFromTesting) {
|
||||||
mCustomMediaKeyDispatcher = null;
|
synchronized (mLock) {
|
||||||
mGetSessionForKeyEventMethod = null;
|
mCustomMediaKeyDispatcher = null;
|
||||||
|
mOverriddenKeyEventsMap = null;
|
||||||
|
|
||||||
String customDispatcherClassName = (nameFromTesting == null)
|
String customDispatcherClassName = (nameFromTesting == null)
|
||||||
? mContext.getResources().getString(R.string.config_customMediaKeyDispatcher)
|
? mContext.getResources().getString(R.string.config_customMediaKeyDispatcher)
|
||||||
: nameFromTesting;
|
: nameFromTesting;
|
||||||
try {
|
try {
|
||||||
if (!TextUtils.isEmpty(customDispatcherClassName)) {
|
if (!TextUtils.isEmpty(customDispatcherClassName)) {
|
||||||
Class customDispatcherClass = Class.forName(customDispatcherClassName);
|
Class customDispatcherClass = Class.forName(customDispatcherClassName);
|
||||||
Constructor constructor = customDispatcherClass.getDeclaredConstructor();
|
Constructor constructor = customDispatcherClass.getDeclaredConstructor();
|
||||||
mCustomMediaKeyDispatcher = (MediaKeyDispatcher) constructor.newInstance();
|
mCustomMediaKeyDispatcher = (MediaKeyDispatcher) constructor.newInstance();
|
||||||
mGetSessionForKeyEventMethod = customDispatcherClass.getDeclaredMethod(
|
mOverriddenKeyEventsMap = mCustomMediaKeyDispatcher.getOverriddenKeyEvents();
|
||||||
"getSessionForKeyEvent", KeyEvent.class, int.class, boolean.class);
|
}
|
||||||
|
} catch (ClassNotFoundException | InstantiationException | InvocationTargetException
|
||||||
|
| IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
mCustomMediaKeyDispatcher = null;
|
||||||
|
Log.w(TAG, "Encountered problem while using reflection", e);
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException | InstantiationException | InvocationTargetException
|
|
||||||
| IllegalAccessException | NoSuchMethodException e) {
|
|
||||||
Log.w(TAG, "Encountered problem while using reflection", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void instantiateCustomProvider(String nameFromTesting) {
|
private void instantiateCustomProvider(String nameFromTesting) {
|
||||||
mCustomSessionPolicyProvider = null;
|
synchronized (mLock) {
|
||||||
mGetSessionPoliciesMethod = null;
|
mCustomSessionPolicyProvider = null;
|
||||||
|
|
||||||
String customProviderClassName = (nameFromTesting == null)
|
String customProviderClassName = (nameFromTesting == null)
|
||||||
? mContext.getResources().getString(R.string.config_customSessionPolicyProvider)
|
? mContext.getResources().getString(R.string.config_customSessionPolicyProvider)
|
||||||
: nameFromTesting;
|
: nameFromTesting;
|
||||||
try {
|
try {
|
||||||
if (!TextUtils.isEmpty(customProviderClassName)) {
|
if (!TextUtils.isEmpty(customProviderClassName)) {
|
||||||
Class customProviderClass = Class.forName(customProviderClassName);
|
Class customProviderClass = Class.forName(customProviderClassName);
|
||||||
Constructor constructor = customProviderClass.getDeclaredConstructor();
|
Constructor constructor = customProviderClass.getDeclaredConstructor();
|
||||||
mCustomSessionPolicyProvider = (SessionPolicyProvider) constructor.newInstance();
|
mCustomSessionPolicyProvider =
|
||||||
mGetSessionPoliciesMethod = customProviderClass.getDeclaredMethod(
|
(SessionPolicyProvider) constructor.newInstance();
|
||||||
"getSessionPoliciesForApplication", int.class, String.class);
|
}
|
||||||
|
} catch (ClassNotFoundException | InstantiationException | InvocationTargetException
|
||||||
|
| IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Log.w(TAG, "Encountered problem while using reflection", e);
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException | InstantiationException | InvocationTargetException
|
|
||||||
| IllegalAccessException | NoSuchMethodException e) {
|
|
||||||
Log.w(TAG, "Encountered problem while using reflection", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1098,8 +1097,9 @@ 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 boolean mVoiceButtonDown = false;
|
private KeyEvent mPendingFirstDownKeyEvent = null;
|
||||||
private boolean mVoiceButtonHandled = false;
|
private boolean mIsLongPressing = false;
|
||||||
|
private Runnable mLongPressTimeoutRunnable = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
|
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
|
||||||
@@ -1362,12 +1362,12 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
|
if (isGlobalPriorityActive) {
|
||||||
handleVoiceKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
|
|
||||||
needWakeLock);
|
|
||||||
} else {
|
|
||||||
dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
|
dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
|
||||||
keyEvent, needWakeLock);
|
keyEvent, needWakeLock);
|
||||||
|
} else {
|
||||||
|
handleKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
|
||||||
|
needWakeLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1641,7 +1641,7 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispaches volume key events. This is called when the foreground activity didn't handled
|
* Dispatches volume key events. This is called when the foreground activity didn't handle
|
||||||
* the incoming volume key event.
|
* the incoming volume key event.
|
||||||
* <p>
|
* <p>
|
||||||
* Handles the dispatching of the volume button events to one of the
|
* Handles the dispatching of the volume button events to one of the
|
||||||
@@ -1662,7 +1662,7 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
* {@link KeyEvent#KEYCODE_VOLUME_DOWN},
|
* {@link KeyEvent#KEYCODE_VOLUME_DOWN},
|
||||||
* or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
|
* or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
|
||||||
* @param stream stream type to adjust volume.
|
* @param stream stream type to adjust volume.
|
||||||
* @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
|
* @param musicOnly true if both UI and haptic feedback aren't needed when adjusting volume.
|
||||||
* @see #dispatchVolumeKeyEventToSessionAsSystemService
|
* @see #dispatchVolumeKeyEventToSessionAsSystemService
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -1707,7 +1707,7 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
mHandler.obtainMessage(
|
mHandler.obtainMessage(
|
||||||
MessageHandler.MSG_VOLUME_INITIAL_DOWN,
|
MessageHandler.MSG_VOLUME_INITIAL_DOWN,
|
||||||
mCurrentFullUserRecord.mFullUserId, 0),
|
mCurrentFullUserRecord.mFullUserId, 0),
|
||||||
mLongPressTimeout);
|
LONG_PRESS_TIMEOUT);
|
||||||
}
|
}
|
||||||
if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
|
if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
|
||||||
mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
|
mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
|
||||||
@@ -2112,31 +2112,147 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleVoiceKeyEventLocked(String packageName, int pid, int uid,
|
// 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.
|
||||||
|
private void handleKeyEventLocked(String packageName, int pid, int uid,
|
||||||
boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
|
boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
|
||||||
int action = keyEvent.getAction();
|
if (keyEvent.isCanceled()) {
|
||||||
boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
|
return;
|
||||||
if (action == KeyEvent.ACTION_DOWN) {
|
}
|
||||||
if (keyEvent.getRepeatCount() == 0) {
|
|
||||||
mVoiceButtonDown = true;
|
int overriddenKeyEvents = (mCustomMediaKeyDispatcher == null) ? 0
|
||||||
mVoiceButtonHandled = false;
|
: mCustomMediaKeyDispatcher.getOverriddenKeyEvents().get(keyEvent.getKeyCode());
|
||||||
} else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
|
cancelPendingIfNeeded(keyEvent);
|
||||||
mVoiceButtonHandled = true;
|
if (!needPending(keyEvent, overriddenKeyEvents)) {
|
||||||
startVoiceInput(needWakeLock);
|
dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
|
||||||
|
needWakeLock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFirstDownKeyEvent(keyEvent)) {
|
||||||
|
mPendingFirstDownKeyEvent = keyEvent;
|
||||||
|
mIsLongPressing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelPendingIfNeeded(KeyEvent keyEvent) {
|
||||||
|
if (mPendingFirstDownKeyEvent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isFirstDownKeyEvent(keyEvent)) {
|
||||||
|
if (mLongPressTimeoutRunnable != null) {
|
||||||
|
mHandler.removeCallbacks(mLongPressTimeoutRunnable);
|
||||||
|
mLongPressTimeoutRunnable.run();
|
||||||
|
} else {
|
||||||
|
resetLongPressTracking();
|
||||||
}
|
}
|
||||||
} else if (action == KeyEvent.ACTION_UP) {
|
return;
|
||||||
if (mVoiceButtonDown) {
|
}
|
||||||
mVoiceButtonDown = false;
|
if (mPendingFirstDownKeyEvent.getDownTime() == keyEvent.getDownTime()
|
||||||
if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
|
&& mPendingFirstDownKeyEvent.getKeyCode() == keyEvent.getKeyCode()
|
||||||
// Resend the down then send this event through
|
&& keyEvent.getAction() == KeyEvent.ACTION_DOWN
|
||||||
KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
|
&& keyEvent.getRepeatCount() > 1 && !mIsLongPressing) {
|
||||||
dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
|
resetLongPressTracking();
|
||||||
downEvent, needWakeLock);
|
}
|
||||||
dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
|
}
|
||||||
keyEvent, needWakeLock);
|
|
||||||
}
|
private boolean needPending(KeyEvent keyEvent, int overriddenKeyEvents) {
|
||||||
|
if (!isFirstDownKeyEvent(keyEvent)) {
|
||||||
|
if (mPendingFirstDownKeyEvent == null) {
|
||||||
|
return false;
|
||||||
|
} else if (mPendingFirstDownKeyEvent.getDownTime() != keyEvent.getDownTime()
|
||||||
|
|| mPendingFirstDownKeyEvent.getKeyCode() != keyEvent.getKeyCode()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (overriddenKeyEvents == 0 && !isVoiceKey(keyEvent.getKeyCode())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLongPressLocked(KeyEvent keyEvent, boolean needWakeLock,
|
||||||
|
int overriddenKeyEvents) {
|
||||||
|
if (mCustomMediaKeyDispatcher != null
|
||||||
|
&& mCustomMediaKeyDispatcher.isLongPressOverridden(overriddenKeyEvents)) {
|
||||||
|
mCustomMediaKeyDispatcher.onLongPress(keyEvent);
|
||||||
|
|
||||||
|
if (mLongPressTimeoutRunnable != null) {
|
||||||
|
mHandler.removeCallbacks(mLongPressTimeoutRunnable);
|
||||||
|
}
|
||||||
|
if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
if (mLongPressTimeoutRunnable == null) {
|
||||||
|
mLongPressTimeoutRunnable = createLongPressTimeoutRunnable(keyEvent);
|
||||||
|
}
|
||||||
|
mHandler.postDelayed(mLongPressTimeoutRunnable, LONG_PRESS_TIMEOUT);
|
||||||
|
} else {
|
||||||
|
resetLongPressTracking();
|
||||||
|
}
|
||||||
|
} else if (isFirstLongPressKeyEvent(keyEvent) && isVoiceKey(keyEvent.getKeyCode())) {
|
||||||
|
// Default implementation
|
||||||
|
startVoiceInput(needWakeLock);
|
||||||
|
resetLongPressTracking();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Runnable createLongPressTimeoutRunnable(KeyEvent keyEvent) {
|
||||||
|
return new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (mCustomMediaKeyDispatcher != null) {
|
||||||
|
mCustomMediaKeyDispatcher.onLongPress(createCanceledKeyEvent(keyEvent));
|
||||||
|
}
|
||||||
|
resetLongPressTracking();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetLongPressTracking() {
|
||||||
|
mPendingFirstDownKeyEvent = null;
|
||||||
|
mIsLongPressing = false;
|
||||||
|
mLongPressTimeoutRunnable = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyEvent createCanceledKeyEvent(KeyEvent keyEvent) {
|
||||||
|
KeyEvent upEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_UP);
|
||||||
|
return KeyEvent.changeTimeRepeat(upEvent, System.currentTimeMillis(), 0,
|
||||||
|
KeyEvent.FLAG_CANCELED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFirstLongPressKeyEvent(KeyEvent keyEvent) {
|
||||||
|
return ((keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0)
|
||||||
|
&& keyEvent.getRepeatCount() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFirstDownKeyEvent(KeyEvent keyEvent) {
|
||||||
|
return keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.getRepeatCount() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dispatchDownAndUpKeyEventsLocked(String packageName, int pid, int uid,
|
||||||
|
boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
|
||||||
|
KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
|
||||||
|
dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
|
||||||
|
downEvent, needWakeLock);
|
||||||
|
dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
|
||||||
|
keyEvent, needWakeLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
|
private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
|
||||||
@@ -2149,15 +2265,11 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
MediaSessionRecord session = null;
|
MediaSessionRecord session = null;
|
||||||
|
|
||||||
// Retrieve custom session for key event if it exists.
|
// Retrieve custom session for key event if it exists.
|
||||||
if (mCustomMediaKeyDispatcher != null && mGetSessionForKeyEventMethod != null) {
|
if (mCustomMediaKeyDispatcher != null) {
|
||||||
try {
|
MediaSession.Token token = mCustomMediaKeyDispatcher.getSessionForKeyEvent(
|
||||||
Object tokenObject = mGetSessionForKeyEventMethod.invoke(
|
keyEvent, uid, asSystemService);
|
||||||
mCustomMediaKeyDispatcher, keyEvent, uid, asSystemService);
|
if (token != null) {
|
||||||
if (tokenObject != null) {
|
session = getMediaSessionRecordLocked(token);
|
||||||
session = getMediaSessionRecordLocked((MediaSession.Token) tokenObject);
|
|
||||||
}
|
|
||||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
|
||||||
Log.w(TAG, "Encountered problem while using reflection", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2312,12 +2424,11 @@ public class MediaSessionService extends SystemService implements Monitor {
|
|||||||
mHandled = true;
|
mHandled = true;
|
||||||
mHandler.removeCallbacks(this);
|
mHandler.removeCallbacks(this);
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (!isGlobalPriorityActiveLocked()
|
if (isGlobalPriorityActiveLocked()) {
|
||||||
&& isVoiceKey(mKeyEvent.getKeyCode())) {
|
dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
|
||||||
handleVoiceKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
|
|
||||||
mKeyEvent, mNeedWakeLock);
|
mKeyEvent, mNeedWakeLock);
|
||||||
} else {
|
} else {
|
||||||
dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
|
handleKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
|
||||||
mKeyEvent, mNeedWakeLock);
|
mKeyEvent, mNeedWakeLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
* 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: Move this class to apex/media/
|
||||||
public abstract class SessionPolicyProvider {
|
public abstract class SessionPolicyProvider {
|
||||||
@IntDef(value = {
|
@IntDef(value = {
|
||||||
SESSION_POLICY_IGNORE_BUTTON_RECEIVER,
|
SESSION_POLICY_IGNORE_BUTTON_RECEIVER,
|
||||||
|
|||||||
Reference in New Issue
Block a user