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