Allow privileged app to set volume key long-press listener
If the volume long-press listener is set, the listener will receive
the volume key long-presses instead of chaging the volume.
Privileged app needs permission
android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER to set the listener.
Bug: 30125811
Change-Id: I5e8fafbb950e5e11522da0f14004648d0877bf3e
diff --git a/Android.mk b/Android.mk
index 21bd76b..fac4d55 100644
--- a/Android.mk
+++ b/Android.mk
@@ -423,10 +423,11 @@
media/java/android/media/projection/IMediaProjectionManager.aidl \
media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl \
media/java/android/media/session/IActiveSessionsListener.aidl \
- media/java/android/media/session/ISessionController.aidl \
- media/java/android/media/session/ISessionControllerCallback.aidl \
+ media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl \
media/java/android/media/session/ISession.aidl \
media/java/android/media/session/ISessionCallback.aidl \
+ media/java/android/media/session/ISessionController.aidl \
+ media/java/android/media/session/ISessionControllerCallback.aidl \
media/java/android/media/session/ISessionManager.aidl \
media/java/android/media/tv/ITvInputClient.aidl \
media/java/android/media/tv/ITvInputHardware.aidl \
diff --git a/api/system-current.txt b/api/system-current.txt
index 75b0a34..07f2ce5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -222,6 +222,7 @@
field public static final java.lang.String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY";
field public static final java.lang.String SET_TIME = "android.permission.SET_TIME";
field public static final java.lang.String SET_TIME_ZONE = "android.permission.SET_TIME_ZONE";
+ field public static final java.lang.String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
field public static final java.lang.String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
field public static final java.lang.String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
field public static final java.lang.String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
@@ -24819,12 +24820,17 @@
method public void addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler);
method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
method public void removeOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
+ method public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler);
}
public static abstract interface MediaSessionManager.OnActiveSessionsChangedListener {
method public abstract void onActiveSessionsChanged(java.util.List<android.media.session.MediaController>);
}
+ public static abstract interface MediaSessionManager.OnVolumeKeyLongPressListener {
+ method public abstract void onVolumeKeyLongPress(android.view.KeyEvent);
+ }
+
public final class PlaybackState implements android.os.Parcelable {
method public int describeContents();
method public long getActions();
diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
index fb0edea..ebc2c71 100644
--- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
+++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
@@ -84,7 +84,8 @@
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
- MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
return true;
}
@@ -215,7 +216,8 @@
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (!event.isCanceled()) {
- MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
}
return true;
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index e68ebc4..84195b2 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -1856,27 +1856,25 @@
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
- int direction = 0;
- switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- direction = AudioManager.ADJUST_RAISE;
- break;
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- direction = AudioManager.ADJUST_LOWER;
- break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- direction = AudioManager.ADJUST_TOGGLE_MUTE;
- break;
- }
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
+ int direction = 0;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ direction = AudioManager.ADJUST_RAISE;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ direction = AudioManager.ADJUST_LOWER;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ direction = AudioManager.ADJUST_TOGGLE_MUTE;
+ break;
+ }
mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
} else {
- MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
- mVolumeControlStreamType, direction,
- AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE
- | AudioManager.FLAG_FROM_KEY);
+ MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
+ event, mVolumeControlStreamType, false);
}
return true;
}
@@ -1954,15 +1952,15 @@
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN: {
- final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
- | AudioManager.FLAG_FROM_KEY;
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
+ final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
+ | AudioManager.FLAG_FROM_KEY;
mMediaController.adjustVolume(0, flags);
} else {
- MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
- mVolumeControlStreamType, 0, flags);
+ MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
+ event, mVolumeControlStreamType, false);
}
return true;
}
@@ -1971,7 +1969,8 @@
// doesn't have one of these. In this case, we execute it here and
// eat the event instead, because we have mVolumeControlStreamType
// and they don't.
- getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
+ MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
return true;
}
// These are all the recognized media key codes in
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 84b03d2..f7cec81 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2625,6 +2625,13 @@
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to set the volume key long-press listener.
+ <p>When it's set, the application will receive the volume key long-press event
+ instead of changing volume.</p>
+ <p>Not for use by third-party applications</p> -->
+ <permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"
+ android:protectionLevel="signature|privileged|development" />
+
<!-- @SystemApi Required to be able to disable the device (very dangerous!).
<p>Not for use by third-party applications.
@hide
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7c60385..bcae71c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -730,61 +730,6 @@
}
/**
- * @hide
- */
- public void handleKeyDown(KeyEvent event, int stream) {
- int keyCode = event.getKeyCode();
- switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- /*
- * Adjust the volume in on key down since it is more
- * responsive to the user.
- */
- adjustSuggestedStreamVolume(
- keyCode == KeyEvent.KEYCODE_VOLUME_UP
- ? ADJUST_RAISE
- : ADJUST_LOWER,
- stream,
- FLAG_SHOW_UI | FLAG_VIBRATE);
- break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- if (event.getRepeatCount() == 0) {
- MediaSessionLegacyHelper.getHelper(getContext())
- .sendVolumeKeyEvent(event, false);
- }
- break;
- }
- }
-
- /**
- * @hide
- */
- public void handleKeyUp(KeyEvent event, int stream) {
- int keyCode = event.getKeyCode();
- switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- /*
- * Play a sound. This is done on key up since we don't want the
- * sound to play when a user holds down volume down to mute.
- */
- if (mUseVolumeKeySounds) {
- adjustSuggestedStreamVolume(
- ADJUST_SAME,
- stream,
- FLAG_PLAY_SOUND);
- }
- mVolumeKeyUpTime = SystemClock.uptimeMillis();
- break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- MediaSessionLegacyHelper.getHelper(getContext())
- .sendVolumeKeyEvent(event, false);
- break;
- }
- }
-
- /**
* Indicates if the device implements a fixed volume policy.
* <p>Some devices may not have volume control and may operate at a fixed volume,
* and may not enable muting or changing the volume of audio streams.
diff --git a/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl b/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl
new file mode 100644
index 0000000..07b8347
--- /dev/null
+++ b/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl
@@ -0,0 +1,27 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.view.KeyEvent;
+
+/**
+ * Listener to handle volume key long-press.
+ * @hide
+ */
+oneway interface IOnVolumeKeyLongPressListener {
+ void onVolumeKeyLongPress(in KeyEvent event);
+}
+
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index bb59e5b..9bd9b45 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.media.IRemoteVolumeController;
import android.media.session.IActiveSessionsListener;
+import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.os.Bundle;
@@ -31,6 +32,7 @@
ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
List<IBinder> getSessions(in ComponentName compName, int userId);
void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
+ void dispatchVolumeKeyEvent(in KeyEvent keyEvent, int stream, boolean musicOnly);
void dispatchAdjustVolume(int suggestedStream, int delta, int flags);
void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
int userId);
@@ -41,4 +43,7 @@
// For PhoneWindowManager to precheck media keys
boolean isGlobalPriorityActive();
-}
\ No newline at end of file
+
+ void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
+}
+
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 95cb8ae..7c3af31 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -176,54 +176,12 @@
}
}
- public void sendVolumeKeyEvent(KeyEvent keyEvent, boolean musicOnly) {
+ public void sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
if (keyEvent == null) {
Log.w(TAG, "Tried to send a null key event. Ignoring.");
return;
}
- boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
- boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
- int direction = 0;
- boolean isMute = false;
- switch (keyEvent.getKeyCode()) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- direction = AudioManager.ADJUST_RAISE;
- break;
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- direction = AudioManager.ADJUST_LOWER;
- break;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- isMute = true;
- break;
- }
- if (down || up) {
- int flags = AudioManager.FLAG_FROM_KEY;
- if (musicOnly) {
- // This flag is used when the screen is off to only affect
- // active media
- flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
- } else {
- // These flags are consistent with the home screen
- if (up) {
- flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
- } else {
- flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
- }
- }
- if (direction != 0) {
- // If this is action up we want to send a beep for non-music events
- if (up) {
- direction = 0;
- }
- mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
- direction, flags);
- } else if (isMute) {
- if (down && keyEvent.getRepeatCount() == 0) {
- mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
- AudioManager.ADJUST_TOGGLE_MUTE, flags);
- }
- }
- }
+ mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
}
public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 2364a13..2005573 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
import android.media.AudioManager;
@@ -57,6 +58,8 @@
private Context mContext;
+ private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
+
/**
* @hide
*/
@@ -278,6 +281,21 @@
}
/**
+ * Send a volume key event. The receiver will be selected automatically.
+ *
+ * @param keyEvent The volume KeyEvent to send.
+ * @param needWakeLock True if a wake lock should be held while sending the key.
+ * @hide
+ */
+ public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {
+ try {
+ mService.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send volume key event.", e);
+ }
+ }
+
+ /**
* Dispatch an adjust volume request to the system. It will be sent to the
* most relevant audio stream or media session. The direction must be one of
* {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
@@ -313,6 +331,39 @@
}
/**
+ * Set the volume key long-press listener. While the listener is set, the listener
+ * gets the volume key long-presses instead of changing volume.
+ *
+ * <p>System can only have a single volume key long-press listener.
+ *
+ * @param listener The volume key long-press listener. {@code null} to reset.
+ * @param handler The handler on which the listener should be invoked, or {@code null}
+ * if the listener should be invoked on the calling thread's looper.
+ * @hide
+ */
+ @SystemApi
+ public void setOnVolumeKeyLongPressListener(
+ OnVolumeKeyLongPressListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ try {
+ if (listener == null) {
+ mOnVolumeKeyLongPressListener = null;
+ mService.setOnVolumeKeyLongPressListener(null);
+ } else {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ mOnVolumeKeyLongPressListener =
+ new OnVolumeKeyLongPressListenerImpl(listener, handler);
+ mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set volume key long press listener", e);
+ }
+ }
+ }
+
+ /**
* Listens for changes to the list of active sessions. This can be added
* using {@link #addOnActiveSessionsChangedListener}.
*/
@@ -320,6 +371,19 @@
public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
}
+ /**
+ * Listens the volume key long-presses.
+ * @hide
+ */
+ @SystemApi
+ public interface OnVolumeKeyLongPressListener {
+ /**
+ * Called when the volume key is long-pressed.
+ * <p>This will be called for both down and up events.
+ */
+ void onVolumeKeyLongPress(KeyEvent event);
+ }
+
private static final class SessionsChangedWrapper {
private Context mContext;
private OnActiveSessionsChangedListener mListener;
@@ -360,4 +424,31 @@
mHandler = null;
}
}
+
+ private static final class OnVolumeKeyLongPressListenerImpl
+ extends IOnVolumeKeyLongPressListener.Stub {
+ private OnVolumeKeyLongPressListener mListener;
+ private Handler mHandler;
+
+ public OnVolumeKeyLongPressListenerImpl(
+ OnVolumeKeyLongPressListener listener, Handler handler) {
+ mListener = listener;
+ mHandler = handler;
+ }
+
+ @Override
+ public void onVolumeKeyLongPress(KeyEvent event) {
+ if (mListener == null || mHandler == null) {
+ Log.w(TAG, "Failed to call volume key long-press listener." +
+ " Either mListener or mHandler is null");
+ return;
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onVolumeKeyLongPress(event);
+ }
+ });
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 1b73a3f..f6ce70a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -29,6 +29,7 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
import android.media.session.MediaSessionLegacyHelper;
import android.net.Uri;
import android.os.Bundle;
@@ -223,7 +224,8 @@
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
if (mService.isDozing()) {
- MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, true);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
return true;
}
break;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9b37f12..04d5d9f 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -17,6 +17,7 @@
package com.android.server.media;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -36,6 +37,7 @@
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
import android.media.session.IActiveSessionsListener;
+import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.ISessionManager;
@@ -78,8 +80,8 @@
public class MediaSessionService extends SystemService implements Monitor {
private static final String TAG = "MediaSessionService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // Leave log for media key event always.
- private static final boolean DEBUG_MEDIA_KEY_EVENT = DEBUG || true;
+ // Leave log for key event always.
+ private static final boolean DEBUG_KEY_EVENT = true;
private static final int WAKELOCK_TIMEOUT = 5000;
@@ -523,6 +525,14 @@
}
}
+ private String getCallingPackageName(int uid) {
+ String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ return packages[0];
+ }
+ return "";
+ }
+
/**
* Information about a particular user. The contents of this object is
* guarded by mLock.
@@ -534,6 +544,12 @@
private PendingIntent mLastMediaButtonReceiver;
private ComponentName mRestoredMediaButtonReceiver;
+ private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
+ private int mOnVolumeKeyLongPressListenerUid;
+ private KeyEvent mInitialDownVolumeKeyEvent;
+ private int mInitialDownVolumeStream;
+ private boolean mInitialDownMusicOnly;
+
public UserRecord(Context context, int userId) {
mContext = context;
mUserId = userId;
@@ -564,6 +580,9 @@
String indent = prefix + " ";
pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
pw.println(indent + "Restored ButtonReceiver:" + mRestoredMediaButtonReceiver);
+ pw.println(indent + "Volume key long-press listener:" + mOnVolumeKeyLongPressListener);
+ pw.println(indent + "Volume key long-press listener package:" +
+ getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
int size = mSessions.size();
pw.println(indent + size + " Sessions:");
for (int i = 0; i < size; i++) {
@@ -799,13 +818,195 @@
}
@Override
+ public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
+ if (getContext().checkPermission(
+ android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
+ " permission.");
+ }
+
+ synchronized (mLock) {
+ UserRecord user = mUserRecords.get(UserHandle.getUserId(uid));
+ if (user.mOnVolumeKeyLongPressListener != null &&
+ user.mOnVolumeKeyLongPressListenerUid != uid) {
+ Log.w(TAG, "Volume key long-press listener cannot be reset by another app");
+ return;
+ }
+
+ user.mOnVolumeKeyLongPressListener = listener;
+ user.mOnVolumeKeyLongPressListenerUid = uid;
+
+ Log.d(TAG, "Volume key long-press listener "
+ + listener + " is set by " + getCallingPackageName(uid));
+
+ if (user.mOnVolumeKeyLongPressListener != null) {
+ try {
+ user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ user.mOnVolumeKeyLongPressListener = null;
+ }
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set death recipient "
+ + user.mOnVolumeKeyLongPressListener);
+ user.mOnVolumeKeyLongPressListener = null;
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Handles the dispatching of the volume button events to one of the
+ * registered listeners. If there's a volume key long-press listener and
+ * there's no active global priority session, long-pressess will be sent to the
+ * long-press listener instead of adjusting volume.
+ *
+ * @param keyEvent a non-null KeyEvent whose key code is one of the
+ * {@link KeyEvent#KEYCODE_VOLUME_UP},
+ * {@link KeyEvent#KEYCODE_VOLUME_DOWN},
+ * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
+ * @param stream stream type to adjust volume.
+ * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
+ */
+ @Override
+ public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
+ if (keyEvent == null ||
+ (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
+ && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
+ && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
+ Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
+ return;
+ }
+
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ if (DEBUG) {
+ Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
+ + keyEvent);
+ }
+
+ try {
+ synchronized (mLock) {
+ // Only consider full user.
+ UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+
+ if (mPriorityStack.isGlobalPriorityActive()
+ || user.mOnVolumeKeyLongPressListener == null) {
+ dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
+ } else {
+ // TODO: Consider the case when both volume up and down keys are pressed
+ // at the same time.
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ if (keyEvent.getRepeatCount() == 0) {
+ user.mInitialDownVolumeKeyEvent = keyEvent;
+ user.mInitialDownVolumeStream = stream;
+ user.mInitialDownMusicOnly = musicOnly;
+ }
+ if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
+ if (user.mInitialDownVolumeKeyEvent != null) {
+ dispatchVolumeKeyLongPressLocked(
+ user.mInitialDownVolumeKeyEvent);
+ // Mark that the key is already handled.
+ user.mInitialDownVolumeKeyEvent = null;
+ }
+ dispatchVolumeKeyLongPressLocked(keyEvent);
+ }
+ } else { // if up
+ if (user.mInitialDownVolumeKeyEvent != null
+ && user.mInitialDownVolumeKeyEvent.getDownTime()
+ == keyEvent.getDownTime()) {
+ // Short-press. Should change volume.
+ dispatchVolumeKeyEventLocked(
+ user.mInitialDownVolumeKeyEvent,
+ user.mInitialDownVolumeStream,
+ user.mInitialDownMusicOnly);
+ dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
+ } else {
+ dispatchVolumeKeyLongPressLocked(keyEvent);
+ }
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
+ // Only consider full user.
+ UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+ try {
+ user.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
+ }
+ }
+
+ private void dispatchVolumeKeyEventLocked(
+ KeyEvent keyEvent, int stream, boolean musicOnly) {
+ boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
+ boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
+ int direction = 0;
+ boolean isMute = false;
+ switch (keyEvent.getKeyCode()) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ direction = AudioManager.ADJUST_RAISE;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ direction = AudioManager.ADJUST_LOWER;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ isMute = true;
+ break;
+ }
+ if (down || up) {
+ int flags = AudioManager.FLAG_FROM_KEY;
+ if (musicOnly) {
+ // This flag is used when the screen is off to only affect active media.
+ flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
+ } else {
+ // These flags are consistent with the home screen
+ if (up) {
+ flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
+ } else {
+ flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
+ }
+ }
+ if (direction != 0) {
+ // If this is action up we want to send a beep for non-music events
+ if (up) {
+ direction = 0;
+ }
+ dispatchAdjustVolumeLocked(stream, direction, flags);
+ } else if (isMute) {
+ if (down && keyEvent.getRepeatCount() == 0) {
+ dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
+ }
+ }
+ }
+ }
+
+ @Override
public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- MediaSessionRecord session = mPriorityStack
- .getDefaultVolumeSession(mCurrentUserIdList);
- dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
+ dispatchAdjustVolumeLocked(suggestedStream, delta, flags);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -881,8 +1082,9 @@
return resolvedUserId;
}
- private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
- MediaSessionRecord session) {
+ private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
+ MediaSessionRecord session = mPriorityStack.getDefaultVolumeSession(mCurrentUserIdList);
+
boolean preferSuggestedStream = false;
if (isValidLocalStreamType(suggestedStream)
&& AudioSystem.isStreamActive(suggestedStream, 0)) {
@@ -958,14 +1160,13 @@
private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
MediaSessionRecord session) {
if (session != null) {
- if (DEBUG_MEDIA_KEY_EVENT) {
+ if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Sending " + keyEvent + " to " + session);
}
if (needWakeLock) {
mKeyEventReceiver.aquireWakeLockLocked();
}
- // If we don't need a wakelock use -1 as the id so we
- // won't release it later
+ // If we don't need a wakelock use -1 as the id so we won't release it later.
session.sendMediaButton(keyEvent,
needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
mKeyEventReceiver, Process.SYSTEM_UID,
@@ -986,7 +1187,7 @@
mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
try {
if (user.mLastMediaButtonReceiver != null) {
- if (DEBUG_MEDIA_KEY_EVENT) {
+ if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Sending " + keyEvent
+ " to the last known pendingIntent "
+ user.mLastMediaButtonReceiver);
@@ -995,7 +1196,7 @@
needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
mediaButtonIntent, mKeyEventReceiver, mHandler);
} else {
- if (DEBUG_MEDIA_KEY_EVENT) {
+ if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
+ user.mRestoredMediaButtonReceiver);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4b2b184..27e457c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5804,9 +5804,8 @@
&& (result & ACTION_PASS_TO_USER) == 0) {
// If we are in call but we decided not to pass the key to
// the application, just pass it to the session service.
-
- MediaSessionLegacyHelper.getHelper(mContext)
- .sendVolumeKeyEvent(event, false);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
break;
}
}
@@ -5819,8 +5818,8 @@
// If we aren't passing to the user and no one else
// handled it send it to the session manager to
// figure out.
- MediaSessionLegacyHelper.getHelper(mContext)
- .sendVolumeKeyEvent(event, true);
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+ event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
}
break;
}