Merge "Addressing APIs council feedback"
diff --git a/api/current.txt b/api/current.txt
index 566e7a5..809831f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -25650,12 +25650,10 @@
method public final deprecated void cancelNotification(java.lang.String, java.lang.String, int);
method public final void cancelNotification(java.lang.String);
method public final void cancelNotifications(java.lang.String[]);
- method public java.lang.String[] getActiveNotificationKeys();
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
- method public android.service.notification.StatusBarNotification[] getActiveNotifications(java.lang.String[]);
method public android.service.notification.NotificationListenerService.Ranking getCurrentRanking();
method public android.os.IBinder onBind(android.content.Intent);
- method public void onListenerConnected(java.lang.String[]);
+ method public void onListenerConnected();
method public abstract void onNotificationPosted(android.service.notification.StatusBarNotification);
method public void onNotificationRankingUpdate();
method public abstract void onNotificationRemoved(android.service.notification.StatusBarNotification);
@@ -25664,8 +25662,8 @@
public static class NotificationListenerService.Ranking implements android.os.Parcelable {
method public int describeContents();
- method public int getIndexOfKey(java.lang.String);
method public java.lang.String[] getOrderedKeys();
+ method public int getRank(java.lang.String);
method public boolean isAmbient(java.lang.String);
method public boolean isInterceptedByDoNotDisturb(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b917263..a3ffc00 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -21,6 +21,7 @@
import android.app.Notification;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.net.Uri;
import android.service.notification.Condition;
import android.service.notification.IConditionListener;
@@ -43,6 +44,8 @@
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
+ // TODO: Remove this when callers have been migrated to the equivalent
+ // INotificationListener method.
StatusBarNotification[] getActiveNotifications(String callingPkg);
StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
@@ -52,8 +55,7 @@
void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id);
void cancelNotificationsFromListener(in INotificationListener token, in String[] keys);
- StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token, in String[] keys);
- String[] getActiveNotificationKeysFromListener(in INotificationListener token);
+ ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token);
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7f84877..557f5a6 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -23,6 +23,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,6 +31,8 @@
import android.os.ServiceManager;
import android.util.Log;
+import java.util.List;
+
/**
* A service that receives calls from the system when new notifications are
* posted or removed, or their ranking changed.
@@ -95,12 +98,10 @@
/**
* Implement this method to learn about when the listener is enabled and connected to
- * the notification manager. You are safe to call {@link #getActiveNotifications(String[])
+ * the notification manager. You are safe to call {@link #getActiveNotifications()}
* at this time.
- *
- * @param notificationKeys The notification keys for all currently posted notifications.
*/
- public void onListenerConnected(String[] notificationKeys) {
+ public void onListenerConnected() {
// optional
}
@@ -223,34 +224,12 @@
* @return An array of active notifications, sorted in natural order.
*/
public StatusBarNotification[] getActiveNotifications() {
- return getActiveNotifications(null /*all*/);
- }
-
- /**
- * Request the list of notification keys in their current ranking order.
- * <p>
- * You can use the notification keys for subsequent retrieval via
- * {@link #getActiveNotifications(String[]) or dismissal via
- * {@link #cancelNotifications(String[]).
- *
- * @return An array of active notification keys, in their ranking order.
- */
- public String[] getActiveNotificationKeys() {
- return mRanking.getOrderedKeys();
- }
-
- /**
- * Request the list of outstanding notifications (that is, those that are visible to the
- * current user). Useful when you don't know what's already been posted.
- *
- * @param keys A specific list of notification keys, or {@code null} for all.
- * @return An array of active notifications, sorted in natural order
- * if {@code keys} is {@code null}.
- */
- public StatusBarNotification[] getActiveNotifications(String[] keys) {
if (!isBound()) return null;
try {
- return getNotificationInterface().getActiveNotificationsFromListener(mWrapper, keys);
+ ParceledListSlice<StatusBarNotification> parceledList =
+ getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
+ List<StatusBarNotification> list = parceledList.getList();
+ return list.toArray(new StatusBarNotification[list.size()]);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
@@ -359,8 +338,7 @@
synchronized (mWrapper) {
applyUpdate(update);
try {
- NotificationListenerService.this.onListenerConnected(
- mRanking.getOrderedKeys());
+ NotificationListenerService.this.onListenerConnected();
} catch (Throwable t) {
Log.w(TAG, "Error running onListenerConnected", t);
}
@@ -418,7 +396,7 @@
* @return The rank of the notification with the given key; -1 when the
* given key is unknown.
*/
- public int getIndexOfKey(String key) {
+ public int getRank(String key) {
// TODO: Optimize.
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
for (int i = 0; i < orderedKeys.length; i++) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 99bbe39..355204e 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -171,9 +171,9 @@
$(call include-path-for, libhardware_legacy)/hardware_legacy \
$(TOP)/frameworks/av/include \
$(TOP)/system/media/camera/include \
- external/pdfrenderer/core/include/fpdfapi \
- external/pdfrenderer/core/include/fpdfdoc \
- external/pdfrenderer/fpdfsdk/include \
+ external/pdfium/core/include/fpdfapi \
+ external/pdfium/core/include/fpdfdoc \
+ external/pdfium/fpdfsdk/include \
external/skia/src/core \
external/skia/src/effects \
external/skia/src/images \
@@ -227,7 +227,7 @@
libharfbuzz_ng \
libz \
libaudioutils \
- libpdfrenderer \
+ libpdfium \
libimg_utils \
ifeq ($(USE_OPENGL_RENDERER),true)
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 7b0412e..103c3f1 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -28,7 +28,7 @@
*/
oneway interface ISessionCallback {
void onCommand(String command, in Bundle extras, in ResultReceiver cb);
- void onMediaButton(in Intent mediaButtonIntent, in ResultReceiver cb);
+ void onMediaButton(in Intent mediaButtonIntent, int sequenceNumber, in ResultReceiver cb);
void onRequestRouteChange(in RouteInfo route);
void onRouteConnected(in RouteInfo route, in RouteOptions options);
void onRouteDisconnected(in RouteInfo route, int reason);
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 5b9adaa..6a62dc2 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -119,13 +119,6 @@
*/
public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
- /**
- * Status code indicating the call was handled.
- *
- * @hide
- */
- public static final int RESULT_SUCCESS = 0;
-
private static final int MSG_MEDIA_BUTTON = 1;
private static final int MSG_COMMAND = 2;
private static final int MSG_ROUTE_CHANGE = 3;
@@ -563,14 +556,17 @@
}
@Override
- public void onMediaButton(Intent mediaButtonIntent, ResultReceiver cb)
+ public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb)
throws RemoteException {
MediaSession session = mMediaSession.get();
- if (session != null) {
- session.postMediaButton(mediaButtonIntent);
- }
- if (cb != null) {
- cb.send(RESULT_SUCCESS, null);
+ try {
+ if (session != null) {
+ session.postMediaButton(mediaButtonIntent);
+ }
+ } finally {
+ if (cb != null) {
+ cb.send(sequenceNumber, null);
+ }
}
}
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 2e02a66..249b9c4 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -137,6 +137,8 @@
return;
}
holder.mMediaButtonListener = new MediaButtonListener(pi, context);
+ // TODO determine if handling transport performer commands should also
+ // set this flag
holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
holder.mSession.setFlags(holder.mFlags);
holder.mSession.getTransportPerformer().addListener(holder.mMediaButtonListener, mHandler);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index fcbd0f4..57957a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -28,7 +28,7 @@
// Enables the filtering of tasks according to their grouping
public static final boolean EnableTaskFiltering = false;
// Enables clipping of tasks against each other
- public static final boolean EnableTaskStackClipping = false;
+ public static final boolean EnableTaskStackClipping = true;
// Enables the use of theme colors as the task bar background
public static final boolean EnableTaskBarThemeColors = true;
// Enables app-info pane on long-pressing the icon
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 3e418ca..60c442a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -592,18 +592,27 @@
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (Constants.DebugFlags.App.EnableTaskStackClipping) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
TaskView tv = (TaskView) child;
TaskView nextTv = null;
- int curIndex = indexOfChild(tv);
- if ((curIndex > -1) && (curIndex < (getChildCount() - 1))) {
+ TaskView tmpTv = null;
+ if (tv.shouldClipViewInStack()) {
+ int curIndex = indexOfChild(tv);
+
+ // Find the next view to clip against
+ while (nextTv == null && curIndex < getChildCount()) {
+ tmpTv = (TaskView) getChildAt(++curIndex);
+ if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
+ nextTv = tmpTv;
+ }
+ }
+
// Clip against the next view (if we aren't animating its alpha)
- nextTv = (TaskView) getChildAt(curIndex + 1);
- if (nextTv.getAlpha() == 1f) {
+ if (nextTv != null && nextTv.getAlpha() == 1f) {
Rect curRect = tv.getClippingRect(mTmpRect);
Rect nextRect = nextTv.getClippingRect(mTmpRect2);
- RecentsConfiguration config = RecentsConfiguration.getInstance();
- // The hit rects are relative to the task view, which needs to be offset by the
- // system bar height
+ // The hit rects are relative to the task view, which needs to be offset by
+ // the system bar height
curRect.offset(0, config.systemInsets.top);
nextRect.offset(0, config.systemInsets.top);
// Compute the clip region
@@ -1048,6 +1057,10 @@
}
}
+ // Sanity check, the task view should always be clipping against the stack at this point,
+ // but just in case, re-enable it here
+ tv.setClipViewInStack(true);
+
// Add/attach the view to the hierarchy
if (Console.Enabled) {
Console.log(Constants.Log.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]",
@@ -1500,6 +1513,9 @@
public void onBeginDrag(View v) {
// Enable HW layers
mSv.addHwLayersRefCount("swipeBegin");
+ // Disable clipping with the stack while we are swiping
+ TaskView tv = (TaskView) v;
+ tv.setClipViewInStack(false);
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
@@ -1512,13 +1528,18 @@
TaskView tv = (TaskView) v;
mSv.onTaskDismissed(tv);
+ // Re-enable clipping with the stack (we will reuse this view)
+ tv.setClipViewInStack(true);
+
// Disable HW layers
mSv.decHwLayersRefCount("swipeComplete");
}
@Override
public void onSnapBackCompleted(View v) {
- // Do Nothing
+ // Re-enable clipping with the stack
+ TaskView tv = (TaskView) v;
+ tv.setClipViewInStack(true);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 16a3f45..b423a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -57,6 +57,7 @@
Task mTask;
boolean mTaskDataLoaded;
boolean mIsFocused;
+ boolean mClipViewInStack;
Point mLastTouchDown = new Point();
Path mRoundedRectClipPath = new Path();
@@ -87,6 +88,9 @@
RecentsConfiguration config = RecentsConfiguration.getInstance();
mMaxDim = config.taskStackMaxDim;
+ // By default, all views are clipped to other views in their stack
+ mClipViewInStack = true;
+
// Bind the views
mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
@@ -250,6 +254,9 @@
/** Animates the deletion of this task view */
public void animateRemoval(final Runnable r) {
+ // Disabling clipping with the stack while the view is animating away
+ setClipViewInStack(false);
+
RecentsConfiguration config = RecentsConfiguration.getInstance();
animate().translationX(config.taskViewRemoveAnimTranslationXPx)
.alpha(0f)
@@ -261,6 +268,9 @@
@Override
public void run() {
post(r);
+
+ // Re-enable clipping with the stack (we will reuse this view)
+ setClipViewInStack(false);
}
})
.start();
@@ -285,6 +295,26 @@
mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
}
+ /**
+ * Returns whether this view should be clipped, or any views below should clip against this
+ * view.
+ */
+ boolean shouldClipViewInStack() {
+ return mClipViewInStack;
+ }
+
+ /** Sets whether this view should be clipped, or clipped against. */
+ void setClipViewInStack(boolean clip) {
+ if (clip != mClipViewInStack) {
+ mClipViewInStack = clip;
+ if (getParent() instanceof View) {
+ Rect r = new Rect();
+ getHitRect(r);
+ ((View) getParent()).invalidate(r);
+ }
+ }
+ }
+
/** Update the dim as a function of the scale of this view. */
void updateDimOverlayFromScale() {
float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
index 417527c..b2ecb61 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
@@ -24,6 +24,7 @@
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.IAudioService;
+import android.media.session.MediaSessionLegacyHelper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -39,6 +40,9 @@
private static String TAG = "PhoneFallbackEventHandler";
private static final boolean DEBUG = false;
+ // Use the new sessions APIs
+ private static final boolean USE_SESSIONS = true;
+
Context mContext;
View mView;
@@ -70,14 +74,14 @@
return onKeyUp(keyCode, event);
}
}
-
+
boolean onKeyDown(int keyCode, KeyEvent event) {
/* ****************************************************************************
* HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
* See the comment in PhoneWindow.onKeyDown
* ****************************************************************************/
final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
-
+
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -156,7 +160,7 @@
if (event.getRepeatCount() == 0) {
dispatcher.startTracking(event, this);
} else if (event.isLongPress() && dispatcher.isTracking(event)) {
- Configuration config = mContext.getResources().getConfiguration();
+ Configuration config = mContext.getResources().getConfiguration();
if (config.keyboard == Configuration.KEYBOARD_NOKEYS
|| config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
// launch the search activity
@@ -191,7 +195,7 @@
if (dispatcher != null) {
dispatcher.handleUpEvent(event);
}
-
+
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -277,29 +281,33 @@
}
return mKeyguardManager;
}
-
+
AudioManager getAudioManager() {
if (mAudioManager == null) {
mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
}
return mAudioManager;
}
-
+
void sendCloseSystemWindows() {
PhoneWindowManager.sendCloseSystemWindows(mContext, null);
}
private void handleMediaKeyEvent(KeyEvent keyEvent) {
- IAudioService audioService = IAudioService.Stub.asInterface(
- ServiceManager.checkService(Context.AUDIO_SERVICE));
- if (audioService != null) {
- try {
- audioService.dispatchMediaKeyEvent(keyEvent);
- } catch (RemoteException e) {
- Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e);
- }
+ if (USE_SESSIONS) {
+ MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, false);
} else {
- Slog.w(TAG, "Unable to find IAudioService for media key event.");
+ IAudioService audioService = IAudioService.Stub.asInterface(
+ ServiceManager.checkService(Context.AUDIO_SERVICE));
+ if (audioService != null) {
+ try {
+ audioService.dispatchMediaKeyEvent(keyEvent);
+ } catch (RemoteException e) {
+ Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e);
+ }
+ } else {
+ Slog.w(TAG, "Unable to find IAudioService for media key event.");
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7cd4ef8..ac30319 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -33,6 +33,7 @@
import android.app.IActivityContainer;
import android.app.IActivityContainerCallback;
import android.app.IAppTask;
+import android.app.admin.DevicePolicyManager;
import android.appwidget.AppWidgetManager;
import android.graphics.Rect;
import android.os.BatteryStats;
@@ -7576,12 +7577,9 @@
}
private boolean isLockTaskAuthorized(ComponentName name) {
-// enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
-// "startLockTaskMode()");
-// DevicePolicyManager dpm = (DevicePolicyManager)
-// mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-// return dpm != null && dpm.isLockTaskPermitted(name);
- return true;
+ final DevicePolicyManager dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return dpm != null && dpm.isLockTaskPermitted(name);
}
private void startLockTaskMode(TaskRecord task) {
@@ -7640,8 +7638,18 @@
@Override
public void stopLockTaskMode() {
-// enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
-// "stopLockTaskMode()");
+ // Check if the calling task is eligible to use lock task
+ final int uid = Binder.getCallingUid();
+ try {
+ final String name = AppGlobals.getPackageManager().getNameForUid(uid);
+ if (!isLockTaskAuthorized(new ComponentName(name, name))) {
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "stopLockTaskMode " + e);
+ return;
+ }
+ // Stop lock task
synchronized (this) {
mStackSupervisor.setLockTaskModeLocked(null);
}
@@ -8759,7 +8767,7 @@
if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
return true;
}
-
+
final int perm = checkComponentPermission(
android.Manifest.permission.STOP_APP_SWITCHES, callingPid,
callingUid, -1, true);
@@ -13273,7 +13281,7 @@
}
}
- // A backup agent has just come up
+ // A backup agent has just come up
public void backupAgentCreated(String agentPackageName, IBinder agent) {
if (DEBUG_BACKUP) Slog.v(TAG, "backupAgentCreated: " + agentPackageName
+ " = " + agent);
@@ -14492,7 +14500,7 @@
msg.obj = new Configuration(configCopy);
mHandler.sendMessage(msg);
}
-
+
for (int i=mLruProcesses.size()-1; i>=0; i--) {
ProcessRecord app = mLruProcesses.get(i);
try {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 00d364b..030e3ed 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -365,8 +365,8 @@
return mSessionCb.mCb;
}
- public void sendMediaButton(KeyEvent ke, ResultReceiver cb) {
- mSessionCb.sendMediaButton(ke, cb);
+ public void sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb) {
+ mSessionCb.sendMediaButton(ke, sequenceId, cb);
}
public void dump(PrintWriter pw, String prefix) {
@@ -649,11 +649,11 @@
mCb = cb;
}
- public void sendMediaButton(KeyEvent keyEvent, ResultReceiver cb) {
+ public void sendMediaButton(KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
try {
- mCb.onMediaButton(mediaButtonIntent, cb);
+ mCb.onMediaButton(mediaButtonIntent, sequenceId, cb);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
}
@@ -789,7 +789,7 @@
@Override
public void sendMediaButton(KeyEvent mediaButtonIntent) {
- mSessionCb.sendMediaButton(mediaButtonIntent, null);
+ mSessionCb.sendMediaButton(mediaButtonIntent, 0, null);
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index d9e45f5ba..9d85167 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -19,6 +19,8 @@
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -40,6 +42,7 @@
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.provider.Settings;
+import android.speech.RecognizerIntent;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -61,6 +64,8 @@
private static final String TAG = "MediaSessionService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final int WAKELOCK_TIMEOUT = 5000;
+
private final SessionManagerImpl mSessionManagerImpl;
// private final MediaRouteProviderWatcher mRouteProviderWatcher;
private final MediaSessionStack mPriorityStack;
@@ -73,6 +78,8 @@
private final Handler mHandler = new Handler();
private final PowerManager.WakeLock mMediaEventWakeLock;
+ private KeyguardManager mKeyguardManager;
+
private MediaSessionRecord mPrioritySession;
private int mCurrentUserId = -1;
@@ -96,6 +103,8 @@
publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
Watchdog.getInstance().addMonitor(this);
updateUser();
+ mKeyguardManager =
+ (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
}
/**
@@ -599,6 +608,9 @@
"android.media.AudioService.WAKELOCK_ACQUIRED";
private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
+ private boolean mVoiceButtonDown = false;
+ private boolean mVoiceButtonHandled = false;
+
@Override
public ISession createSession(String packageName, ISessionCallback cb, String tag,
int userId) throws RemoteException {
@@ -676,31 +688,13 @@
final long token = Binder.clearCallingIdentity();
try {
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- }
synchronized (mLock) {
- MediaSessionRecord mbSession = mPriorityStack
+ MediaSessionRecord session = mPriorityStack
.getDefaultMediaButtonSession(mCurrentUserId);
- if (mbSession != null) {
- if (DEBUG) {
- Log.d(TAG, "Sending media key to " + mbSession.getSessionInfo());
- }
- mbSession.sendMediaButton(keyEvent,
- needWakeLock ? mKeyEventDoneReceiver : null);
+ if (isVoiceKey(keyEvent.getKeyCode())) {
+ handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
} else {
- if (DEBUG) {
- Log.d(TAG, "Sending media key ordered broadcast");
- }
- // Fallback to legacy behavior
- Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
- keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- if (needWakeLock) {
- keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
- WAKELOCK_RELEASE_ON_FINISHED);
- }
- getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
- null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
+ dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
}
}
} finally {
@@ -743,15 +737,171 @@
}
}
- ResultReceiver mKeyEventDoneReceiver = new ResultReceiver(mHandler) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- synchronized (mLock) {
- if (mMediaEventWakeLock.isHeld()) {
- mMediaEventWakeLock.release();
+ private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
+ MediaSessionRecord session) {
+ if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
+ // If the phone app has priority just give it the event
+ dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
+ return;
+ }
+ int action = keyEvent.getAction();
+ boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
+ if (action == KeyEvent.ACTION_DOWN) {
+ if (keyEvent.getRepeatCount() == 0) {
+ mVoiceButtonDown = true;
+ mVoiceButtonHandled = false;
+ } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
+ mVoiceButtonHandled = true;
+ startVoiceInput(needWakeLock);
+ }
+ } else if (action == KeyEvent.ACTION_UP) {
+ if (mVoiceButtonDown) {
+ mVoiceButtonDown = false;
+ if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
+ // Resend the down then send this event through
+ KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
+ dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
+ dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
}
}
}
+ }
+
+ private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
+ MediaSessionRecord session) {
+ if (session != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Sending media key to " + session.getSessionInfo());
+ }
+ if (needWakeLock) {
+ mKeyEventReceiver.aquireWakeLockLocked();
+ }
+ // 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);
+ } else {
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Sending media key ordered broadcast");
+ }
+ // Fallback to legacy behavior
+ Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+ if (needWakeLock) {
+ keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
+ WAKELOCK_RELEASE_ON_FINISHED);
+ }
+ getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
+ null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
+ }
+ }
+
+ private void startVoiceInput(boolean needWakeLock) {
+ Intent voiceIntent = null;
+ // select which type of search to launch:
+ // - screen on and device unlocked: action is ACTION_WEB_SEARCH
+ // - device locked or screen off: action is
+ // ACTION_VOICE_SEARCH_HANDS_FREE
+ // with EXTRA_SECURE set to true if the device is securely locked
+ PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
+ boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+ if (!isLocked && pm.isScreenOn()) {
+ voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
+ Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
+ } else {
+ voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
+ isLocked && mKeyguardManager.isKeyguardSecure());
+ Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
+ }
+ // start the search activity
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ }
+ try {
+ if (voiceIntent != null) {
+ voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
+ }
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "No activity for search: " + e);
+ } finally {
+ if (needWakeLock) {
+ mMediaEventWakeLock.release();
+ }
+ }
+ }
+
+ private boolean isVoiceKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
+ }
+
+ private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
+
+ class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
+ private final Handler mHandler;
+ private int mRefCount = 0;
+ private int mLastTimeoutId = 0;
+
+ public KeyEventWakeLockReceiver(Handler handler) {
+ super(handler);
+ mHandler = handler;
+ }
+
+ public void onTimeout() {
+ synchronized (mLock) {
+ if (mRefCount == 0) {
+ // We've already released it, so just return
+ return;
+ }
+ mLastTimeoutId++;
+ mRefCount = 0;
+ releaseWakeLockLocked();
+ }
+ }
+
+ public void aquireWakeLockLocked() {
+ if (mRefCount == 0) {
+ mMediaEventWakeLock.acquire();
+ }
+ mRefCount++;
+ mHandler.removeCallbacks(this);
+ mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
+
+ }
+
+ @Override
+ public void run() {
+ onTimeout();
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode < mLastTimeoutId) {
+ // Ignore results from calls that were before the last
+ // timeout, just in case.
+ return;
+ } else {
+ synchronized (mLock) {
+ if (mRefCount > 0) {
+ mRefCount--;
+ if (mRefCount == 0) {
+ releaseWakeLockLocked();
+ }
+ }
+ }
+ }
+ }
+
+ private void releaseWakeLockLocked() {
+ mMediaEventWakeLock.release();
+ mHandler.removeCallbacks(this);
+ }
};
BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2945418..0b3e02a6 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -40,6 +40,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -1359,44 +1360,28 @@
* should be used.
*
* @param token The binder for the listener, to check that the caller is allowed
- * @param keys the notification keys to fetch, or null for all active notifications.
* @returns The return value will contain the notifications specified in keys, in that
* order, or if keys is null, all the notifications, in natural order.
*/
@Override
- public StatusBarNotification[] getActiveNotificationsFromListener(
- INotificationListener token, String[] keys) {
+ public ParceledListSlice<StatusBarNotification> getActiveNotificationsFromListener(
+ INotificationListener token) {
synchronized (mNotificationList) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final ArrayList<StatusBarNotification> list
= new ArrayList<StatusBarNotification>();
- if (keys == null) {
- final int N = mNotificationList.size();
- for (int i=0; i<N; i++) {
- StatusBarNotification sbn = mNotificationList.get(i).sbn;
- if (info.enabledAndUserMatches(sbn.getUserId())) {
- list.add(sbn);
- }
- }
- } else {
- final int N = keys.length;
- for (int i=0; i<N; i++) {
- NotificationRecord r = mNotificationsByKey.get(keys[i]);
- if (r != null && info.enabledAndUserMatches(r.sbn.getUserId())) {
- list.add(r.sbn);
- }
+ final int N = mNotificationList.size();
+ for (int i=0; i<N; i++) {
+ StatusBarNotification sbn = mNotificationList.get(i).sbn;
+ if (info.enabledAndUserMatches(sbn.getUserId())) {
+ list.add(sbn);
}
}
- return list.toArray(new StatusBarNotification[list.size()]);
+ return new ParceledListSlice<StatusBarNotification>(list);
}
}
@Override
- public String[] getActiveNotificationKeysFromListener(INotificationListener token) {
- return NotificationManagerService.this.getActiveNotificationKeys(token);
- }
-
- @Override
public ZenModeConfig getZenModeConfig() {
checkCallerIsSystem();
return mZenModeHelper.getConfig();