Merge "1/ Refactor out logic to fetch assist data asynchronously"
diff --git a/Android.mk b/Android.mk
index 9890bb4..62f750c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -380,7 +380,7 @@
 	core/java/android/speech/tts/ITextToSpeechService.aidl \
 	core/java/com/android/internal/app/IAppOpsCallback.aidl \
 	core/java/com/android/internal/app/IAppOpsService.aidl \
-	core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl \
+	core/java/com/android/internal/app/IAssistDataReceiver.aidl \
 	core/java/com/android/internal/app/IBatteryStats.aidl \
 	core/java/com/android/internal/app/ISoundTriggerService.aidl \
 	core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 49513d1..322450e 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -63,6 +63,7 @@
 import android.os.PersistableBundle;
 import android.os.StrictMode;
 import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -453,7 +454,7 @@
     void notifyCleartextNetwork(int uid, in byte[] firstPacket);
     int createStackOnDisplay(int displayId);
     void setTaskResizeable(int taskId, int resizeableMode);
-    boolean requestAssistContextExtras(int requestType, in IResultReceiver receiver,
+    boolean requestAssistContextExtras(int requestType, in IAssistDataReceiver receiver,
             in Bundle receiverExtras, in IBinder activityToken,
             boolean focused, boolean newSessionId);
     void resizeTask(int taskId, in Rect bounds, int resizeMode);
@@ -617,7 +618,7 @@
     boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
     void unregisterTaskStackListener(ITaskStackListener listener);
     void moveStackToDisplay(int stackId, int displayId);
-    boolean requestAutofillData(in IResultReceiver receiver, in Bundle receiverExtras,
+    boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
                                 in IBinder activityToken, int flags);
     void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
     int restartUserInBackground(int userId);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6e49bac..3d38dc4 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -16,7 +16,7 @@
 
 package android.view;
 
-import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodClient;
@@ -272,7 +272,7 @@
     /**
      * Used only for assist -- request a screenshot of the current application.
      */
-    boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver);
+    boolean requestAssistScreenshot(IAssistDataReceiver receiver);
 
     /**
      * Called by the status bar to notify Views of changes to System UI visiblity.
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/java/com/android/internal/app/IAssistDataReceiver.aidl
similarity index 74%
rename from core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
rename to core/java/com/android/internal/app/IAssistDataReceiver.aidl
index a987a16..9c9ffef 100644
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ b/core/java/com/android/internal/app/IAssistDataReceiver.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -17,8 +17,10 @@
 package com.android.internal.app;
 
 import android.graphics.Bitmap;
+import android.os.Bundle;
 
 /** @hide */
-oneway interface IAssistScreenshotReceiver {
-    void send(in Bitmap screenshot);
+oneway interface IAssistDataReceiver {
+    void onHandleAssistData(in Bundle resultData);
+    void onHandleAssistScreenshot(in Bitmap screenshot);
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 010995f..eaf605f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -43,6 +43,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
 import android.os.Binder;
@@ -74,11 +75,11 @@
 import android.view.autofill.IAutofillWindowPresenter;
 
 import com.android.internal.R;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
@@ -203,9 +204,9 @@
     /**
      * Receiver of assist data from the app's {@link Activity}.
      */
-    private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
+    private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
         @Override
-        public void send(int resultCode, Bundle resultData) throws RemoteException {
+        public void onHandleAssistData(Bundle resultData) throws RemoteException {
             final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
             if (structure == null) {
                 Slog.e(TAG, "No assist structure - app might have crashed providing it");
@@ -261,6 +262,11 @@
 
             mRemoteFillService.onFillRequest(request);
         }
+
+        @Override
+        public void onHandleAssistScreenshot(Bitmap screenshot) {
+            // Do nothing
+        }
     };
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f2e0493..cf09158 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.BIND_VOICE_INTERACTION;
 import static android.Manifest.permission.CHANGE_CONFIGURATION;
 import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
@@ -366,6 +367,7 @@
 import com.android.internal.app.DumpHeapActivity;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ProcessMap;
 import com.android.internal.app.SystemUserHomeActivity;
@@ -782,7 +784,7 @@
         public final Bundle extras;
         public final Intent intent;
         public final String hint;
-        public final IResultReceiver receiver;
+        public final IAssistDataReceiver receiver;
         public final int userHandle;
         public boolean haveResult = false;
         public Bundle result = null;
@@ -791,7 +793,8 @@
         public Bundle receiverExtras;
 
         public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
-                String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
+                String _hint, IAssistDataReceiver _receiver, Bundle _receiverExtras,
+                int _userHandle) {
             activity = _activity;
             extras = _extras;
             intent = _intent;
@@ -4678,15 +4681,7 @@
             Intent intent, String resolvedType, IVoiceInteractionSession session,
             IVoiceInteractor interactor, int startFlags, ProfilerInfo profilerInfo,
             Bundle bOptions, int userId) {
-        if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: startVoiceActivity() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
+        enforceCallingPermission(BIND_VOICE_INTERACTION, "startVoiceActivity()");
         if (session == null || interactor == null) {
             throw new NullPointerException("null session or interactor");
         }
@@ -4701,15 +4696,7 @@
     @Override
     public int startAssistantActivity(String callingPackage, int callingPid, int callingUid,
             Intent intent, String resolvedType, Bundle bOptions, int userId) {
-        if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
-                != PackageManager.PERMISSION_GRANTED) {
-            final String msg = "Permission Denial: startAssistantActivity() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + Manifest.permission.BIND_VOICE_INTERACTION;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
+        enforceCallingPermission(BIND_VOICE_INTERACTION, "startAssistantActivity()");
         userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
                 ALLOW_FULL_ONLY, "startAssistantActivity", null);
         return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent,
@@ -12938,7 +12925,7 @@
     }
 
     @Override
-    public boolean requestAssistContextExtras(int requestType, IResultReceiver receiver,
+    public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
             Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
         return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
                 activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
@@ -12946,7 +12933,7 @@
     }
 
     @Override
-    public boolean requestAutofillData(IResultReceiver receiver, Bundle receiverExtras,
+    public boolean requestAutofillData(IAssistDataReceiver receiver, Bundle receiverExtras,
             IBinder activityToken, int flags) {
         return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null,
                 receiver, receiverExtras, activityToken, true, true, UserHandle.getCallingUserId(),
@@ -12954,7 +12941,7 @@
     }
 
     private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
-            IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
+            IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
             boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
             int flags) {
         enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
@@ -13022,7 +13009,7 @@
     }
 
     void pendingAssistExtrasTimedOut(PendingAssistExtras pae) {
-        IResultReceiver receiver;
+        IAssistDataReceiver receiver;
         synchronized (this) {
             mPendingAssistExtras.remove(pae);
             receiver = pae.receiver;
@@ -13034,7 +13021,7 @@
             sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
                     pae.receiverExtras);
             try {
-                pae.receiver.send(0, sendBundle);
+                pae.receiver.onHandleAssistData(sendBundle);
             } catch (RemoteException e) {
             }
         }
@@ -13072,7 +13059,7 @@
             }
         }
         // We are now ready to launch the assist activity.
-        IResultReceiver sendReceiver = null;
+        IAssistDataReceiver sendReceiver = null;
         Bundle sendBundle = null;
         synchronized (this) {
             buildAssistBundleLocked(pae, extras);
@@ -13094,7 +13081,7 @@
         }
         if (sendReceiver != null) {
             try {
-                sendReceiver.send(0, sendBundle);
+                sendReceiver.onHandleAssistData(sendBundle);
             } catch (RemoteException e) {
             }
             return;
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
new file mode 100644
index 0000000..60f3d22
--- /dev/null
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2017 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 com.android.server.am;
+
+import static android.app.ActivityManager.ASSIST_CONTEXT_FULL;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAssistDataReceiver;
+import com.android.internal.logging.MetricsLogger;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to asynchronously fetch the assist data and screenshot from the current running
+ * activities. It manages received data and calls back to the owner when the owner is ready to
+ * receive the data itself.
+ */
+public class AssistDataRequester extends IAssistDataReceiver.Stub {
+
+    public static final String KEY_RECEIVER_EXTRA_COUNT = "count";
+    public static final String KEY_RECEIVER_EXTRA_INDEX = "index";
+
+    private IActivityManager mService;
+    private IWindowManager mWindowManager;
+    private Context mContext;
+    private AppOpsManager mAppOpsManager;
+
+    private AssistDataRequesterCallbacks mCallbacks;
+    private Object mCallbacksLock;
+
+    private int mRequestStructureAppOps;
+    private int mRequestScreenshotAppOps;
+    private boolean mCanceled;
+    private int mPendingDataCount;
+    private int mPendingScreenshotCount;
+    private final ArrayList<Bundle> mAssistData = new ArrayList<>();
+    private final ArrayList<Bitmap> mAssistScreenshot = new ArrayList<>();
+
+
+    /**
+     * Interface to handle the events from the fetcher.
+     */
+    public interface AssistDataRequesterCallbacks {
+        /**
+         * @return whether the currently received assist data can be handled by the callbacks.
+         */
+        @GuardedBy("mCallbacksLock")
+        boolean canHandleReceivedAssistDataLocked();
+
+        /**
+         * Called when we receive asynchronous assist data. This call is only made if the
+         * {@param fetchData} argument to requestAssistData() is true, and if the current activity
+         * allows assist data to be fetched.  In addition, the callback will be made with the
+         * {@param mCallbacksLock} held, and only if {@link #canHandleReceivedAssistDataLocked()}
+         * is true.
+         */
+        @GuardedBy("mCallbacksLock")
+        void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount);
+
+        /**
+         * Called when we receive asynchronous assist screenshot. This call is only made if
+         * {@param fetchScreenshot} argument to requestAssistData() is true, and if the current
+         * activity allows assist data to be fetched.  In addition, the callback will be made with
+         * the {@param mCallbacksLock} held, and only if
+         * {@link #canHandleReceivedAssistDataLocked()} is true.
+         */
+        @GuardedBy("mCallbacksLock")
+        void onAssistScreenshotReceivedLocked(Bitmap screenshot);
+    }
+
+    /**
+     * @param callbacks The callbacks to handle the asynchronous reply with the assist data.
+     * @param callbacksLock The lock for the requester to hold when calling any of the
+     *                     {@param callbacks}. The owner should also take care in locking
+     *                     appropriately when calling into this requester.
+     * @param requestStructureAppOps The app ops to check before requesting the assist structure
+     * @param requestScreenshotAppOps The app ops to check before requesting the assist screenshot.
+     *                                This can be {@link AppOpsManager#OP_NONE} to indicate that
+     *                                screenshots should never be fetched.
+     */
+    public AssistDataRequester(Context context, IActivityManager service,
+            IWindowManager windowManager, AppOpsManager appOpsManager,
+            AssistDataRequesterCallbacks callbacks, Object callbacksLock,
+            int requestStructureAppOps, int requestScreenshotAppOps) {
+        mCallbacks = callbacks;
+        mCallbacksLock = callbacksLock;
+        mWindowManager = windowManager;
+        mService = service;
+        mContext = context;
+        mAppOpsManager = appOpsManager;
+        mRequestStructureAppOps = requestStructureAppOps;
+        mRequestScreenshotAppOps = requestScreenshotAppOps;
+    }
+
+    /**
+     * Request that assist data be loaded asynchronously. The resulting data will be provided
+     * through the {@link AssistDataRequesterCallbacks}.
+     *
+     * @param activityTokens the list of visible activities
+     * @param fetchData whether or not to fetch the assist data, only applies if the caller is
+     *     allowed to fetch the assist data, and the current activity allows assist data to be
+     *     fetched from it
+     * @param fetchScreenshot whether or not to fetch the screenshot, only applies if fetchData is
+     *     true, the caller is allowed to fetch the assist data, and the current activity allows
+     *     assist data to be fetched from it
+     */
+    public void requestAssistData(List<IBinder> activityTokens, boolean fetchData,
+            boolean fetchScreenshot, int callingUid, String callingPackage) {
+        // TODO: Better handle the cancel case if a request can be reused
+        // TODO: Known issue, if the assist data is not allowed on the current activity, then no
+        //       assist data is requested for any of the other activities
+
+        // Early exit if there are no activity to fetch for
+        if (activityTokens.isEmpty()) {
+            return;
+        }
+
+        // Ensure that the current activity supports assist data
+        boolean isAssistDataAllowed = false;
+        try {
+            isAssistDataAllowed = mService.isAssistDataAllowedOnCurrentActivity();
+        } catch (RemoteException e) {
+            // Should never happen
+        }
+        fetchData &= isAssistDataAllowed;
+        fetchScreenshot &= fetchData && isAssistDataAllowed
+                && (mRequestScreenshotAppOps != OP_NONE);
+
+        mCanceled = false;
+        mPendingDataCount = 0;
+        mPendingScreenshotCount = 0;
+        mAssistData.clear();
+        mAssistScreenshot.clear();
+
+        if (fetchData) {
+            if (mAppOpsManager.checkOpNoThrow(mRequestStructureAppOps, callingUid, callingPackage)
+                    == MODE_ALLOWED) {
+                final int numActivities = activityTokens.size();
+                for (int i = 0; i < numActivities; i++) {
+                    IBinder topActivity = activityTokens.get(i);
+                    try {
+                        MetricsLogger.count(mContext, "assist_with_context", 1);
+                        Bundle receiverExtras = new Bundle();
+                        receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
+                        receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, numActivities);
+                        if (mService.requestAssistContextExtras(ASSIST_CONTEXT_FULL, this,
+                                receiverExtras, topActivity, /* focused= */ i == 0,
+                                    /* newSessionId= */ i == 0)) {
+                            mPendingDataCount++;
+                        } else if (i == 0) {
+                            // Wasn't allowed... given that, let's not do the screenshot either.
+                            dispatchAssistDataReceived(null);
+                            fetchScreenshot = false;
+                            break;
+                        }
+                    } catch (RemoteException e) {
+                        // Can't happen
+                    }
+                }
+            } else {
+                // Wasn't allowed... given that, let's not do the screenshot either.
+                dispatchAssistDataReceived(null);
+                fetchScreenshot = false;
+            }
+        }
+
+        if (fetchScreenshot) {
+            if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage)
+                    == MODE_ALLOWED) {
+                try {
+                    MetricsLogger.count(mContext, "assist_with_screen", 1);
+                    mPendingScreenshotCount++;
+                    mWindowManager.requestAssistScreenshot(this);
+                } catch (RemoteException e) {
+                    // Can't happen
+                }
+            } else {
+                dispatchAssistScreenshotReceived(null);
+            }
+        }
+    }
+
+    /**
+     * This call should only be made when the callbacks are capable of handling the received assist
+     * data.
+     */
+    public void processPendingAssistData() {
+        final int dataCount = mAssistData.size();
+        for (int i = 0; i < dataCount; i++) {
+            dispatchAssistDataReceived(mAssistData.get(i));
+        }
+        final int screenshotsCount = mAssistScreenshot.size();
+        for (int i = 0; i < screenshotsCount; i++) {
+            dispatchAssistScreenshotReceived(mAssistScreenshot.get(i));
+        }
+    }
+
+    public int getPendingDataCount() {
+        return mPendingDataCount;
+    }
+
+    public int getPendingScreenshotCount() {
+        return mPendingScreenshotCount;
+    }
+
+    /**
+     * Cancels the current request for the assist data.
+     */
+    public void cancel() {
+        // Reset the pending data count, if we receive new assist data after this point, it will
+        // be ignored
+        mCanceled = true;
+        mPendingDataCount = 0;
+        mPendingScreenshotCount = 0;
+        mAssistData.clear();
+        mAssistScreenshot.clear();
+    }
+
+    @Override
+    public void onHandleAssistData(Bundle data) {
+        synchronized (mCallbacksLock) {
+            if (mCanceled) {
+                return;
+            }
+            mPendingDataCount--;
+
+            if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+                // Process any pending data and dispatch the new data as well
+                processPendingAssistData();
+                dispatchAssistDataReceived(data);
+            } else {
+                // Queue up the data for processing later
+                mAssistData.add(data);
+            }
+        }
+    }
+
+    @Override
+    public void onHandleAssistScreenshot(Bitmap screenshot) {
+        synchronized (mCallbacksLock) {
+            if (mCanceled) {
+                return;
+            }
+            mPendingScreenshotCount--;
+
+            if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+                // Process any pending data and dispatch the new data as well
+                processPendingAssistData();
+                dispatchAssistScreenshotReceived(screenshot);
+            } else {
+                // Queue up the data for processing later
+                mAssistScreenshot.add(screenshot);
+            }
+        }
+    }
+
+    private void dispatchAssistDataReceived(Bundle data) {
+        int activityIndex = 0;
+        int activityCount = 0;
+        final Bundle receiverExtras = data != null ? data.getBundle(KEY_RECEIVER_EXTRAS) : null;
+        if (receiverExtras != null) {
+            activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
+            activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
+        }
+        mCallbacks.onAssistDataReceivedLocked(data, activityIndex, activityCount);
+    }
+
+    private void dispatchAssistScreenshotReceived(Bitmap screenshot) {
+        mCallbacks.onAssistScreenshotReceivedLocked(screenshot);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mPendingDataCount="); pw.println(mPendingDataCount);
+        pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
+        pw.print(prefix); pw.print("mPendingScreenshotCount="); pw.println(mPendingScreenshotCount);
+        pw.print(prefix); pw.print("mAssistScreenshot="); pw.println(mAssistScreenshot);
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f31ea67..70035c3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -226,7 +226,7 @@
 import android.view.inputmethod.InputMethodManagerInternal;
 
 import com.android.internal.R;
-import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -3783,7 +3783,7 @@
      * of the target image.
      */
     @Override
-    public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) {
+    public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
         if (!checkCallingPermission(READ_FRAME_BUFFER,
                 "requestAssistScreenshot()")) {
             throw new SecurityException("Requires READ_FRAME_BUFFER permission");
@@ -3795,7 +3795,7 @@
                     1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
                     false /* includeDecor */);
             try {
-                receiver.send(bm);
+                receiver.onHandleAssistScreenshot(bm);
             } catch (RemoteException e) {
             }
         });
diff --git a/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java b/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
new file mode 100644
index 0000000..bec46db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2017 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 com.android.server.am;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.view.IWindowManager;
+
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Note: Currently, we only support fetching the screenshot for the current application, so the
+ * screenshot checks are hardcoded accordingly.
+ *
+ * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AssistDataRequesterTest extends ActivityTestsBase {
+
+    private static final String TAG = AssistDataRequesterTest.class.getSimpleName();
+
+    private static final boolean CURRENT_ACTIVITY_ASSIST_ALLOWED = true;
+    private static final boolean CALLER_ASSIST_STRUCTURE_ALLOWED = true;
+    private static final boolean CALLER_ASSIST_SCREENSHOT_ALLOWED = true;
+    private static final boolean FETCH_DATA = true;
+    private static final boolean FETCH_SCREENSHOTS = true;
+
+    private static final int TEST_UID = 0;
+    private static final String TEST_PACKAGE = "";
+
+    private Context mContext;
+    private AssistDataRequester mDataRequester;
+    private Callbacks mCallbacks;
+    private Object mCallbacksLock;
+    private Handler mHandler;
+    private IActivityManager mAm;
+    private IWindowManager mWm;
+    private AppOpsManager mAppOpsManager;
+
+    /**
+     * The requests to fetch assist data are done incrementally from the text thread, and we
+     * immediately post onto the main thread handler below, which would immediately make the
+     * callback and decrement the pending counts. In order to assert the pending counts, we defer
+     * the callbacks on the test-side until after we flip the gate, after which we can drain the
+     * main thread handler and make assertions on the actual callbacks
+     */
+    private CountDownLatch mGate;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mAm = mock(IActivityManager.class);
+        mWm = mock(IWindowManager.class);
+        mAppOpsManager = mock(AppOpsManager.class);
+        mContext =  InstrumentationRegistry.getContext();
+        mHandler = new Handler(Looper.getMainLooper());
+        mCallbacksLock = new Object();
+        mCallbacks = new Callbacks();
+        mDataRequester = new AssistDataRequester(mContext, mAm, mWm, mAppOpsManager, mCallbacks,
+                mCallbacksLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
+
+        // Gate the continuation of the assist data callbacks until we are ready within the tests
+        mGate = new CountDownLatch(1);
+        doAnswer(invocation -> {
+            mHandler.post(() -> {
+                try {
+                    mGate.await(10, TimeUnit.SECONDS);
+                    mDataRequester.onHandleAssistData(new Bundle());
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Failed to wait", e);
+                }
+            });
+            return true;
+        }).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), anyBoolean(),
+                anyBoolean());
+        doAnswer(invocation -> {
+            mHandler.post(() -> {
+                try {
+                    mGate.await(10, TimeUnit.SECONDS);
+                    mDataRequester.onHandleAssistScreenshot(Bitmap.createBitmap(1, 1,
+                            ARGB_8888));
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Failed to wait", e);
+                }
+            });
+            return true;
+        }).when(mWm).requestAssistScreenshot(any());
+    }
+
+    private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
+            boolean assistScreenshotAllowed) throws Exception {
+        doReturn(currentActivityAssistAllowed).when(mAm).isAssistDataAllowedOnCurrentActivity();
+        doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
+                .checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString());
+        doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
+                .checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString());
+    }
+
+    @Test
+    public void testRequestData() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(5, 5, 1, 1);
+    }
+
+    @Test
+    public void testEmptyActivities_expectNoCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 0, 0, 0);
+    }
+
+    @Test
+    public void testCurrentAppDisallow_expectNoCallbacks() throws Exception {
+        setupMocks(!CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 0, 0, 0);
+    }
+
+    @Test
+    public void testProcessPendingData() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mCallbacks.canHandleReceivedData = false;
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertTrue(mDataRequester.getPendingDataCount() == 5);
+        assertTrue(mDataRequester.getPendingScreenshotCount() == 1);
+        mGate.countDown();
+        waitForIdle(mHandler);
+
+        // Callbacks still not ready to receive, but all pending data is received
+        assertTrue(mDataRequester.getPendingDataCount() == 0);
+        assertTrue(mDataRequester.getPendingScreenshotCount() == 0);
+        assertTrue(mCallbacks.receivedData.isEmpty());
+        assertTrue(mCallbacks.receivedScreenshots.isEmpty());
+
+        mCallbacks.canHandleReceivedData = true;
+        mDataRequester.processPendingAssistData();
+        assertTrue(mCallbacks.receivedData.size() == 5);
+        assertTrue(mCallbacks.receivedScreenshots.size() == 1);
+    }
+
+    @Test
+    public void testNoFetchData_expectNoCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 0, 0, 0);
+    }
+
+    @Test
+    public void testDisallowAssistStructure_expectNullDataCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        // Expect a single null data when the appops is denied
+        assertReceivedDataCount(0, 1, 0, 0);
+    }
+
+    @Test
+    public void testDisallowAssistContextExtras_expectNullDataCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+        doReturn(false).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(),
+                anyBoolean(), anyBoolean());
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        // Expect a single null data when requestAssistContextExtras() fails
+        assertReceivedDataCount(0, 1, 0, 0);
+    }
+
+    @Test
+    public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(5, 5, 0, 0);
+    }
+
+    @Test
+    public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                !CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        // Expect a single null screenshot when the appops is denied
+        assertReceivedDataCount(5, 5, 0, 1);
+    }
+
+    private void assertReceivedDataCount(int numPendingData, int numReceivedData,
+            int numPendingScreenshots, int numReceivedScreenshots) throws Exception {
+        assertTrue("Expected " + numPendingData + " pending data, got "
+                        + mDataRequester.getPendingDataCount(),
+                mDataRequester.getPendingDataCount() == numPendingData);
+        assertTrue("Expected " + numPendingScreenshots + " pending screenshots, got "
+                        + mDataRequester.getPendingScreenshotCount(),
+                mDataRequester.getPendingScreenshotCount() == numPendingScreenshots);
+        mGate.countDown();
+        waitForIdle(mHandler);
+        assertTrue("Expected " + numReceivedData + " data, received "
+                        + mCallbacks.receivedData.size(),
+                mCallbacks.receivedData.size() == numReceivedData);
+        assertTrue("Expected " + numReceivedScreenshots + " screenshots, received "
+                        + mCallbacks.receivedScreenshots.size(),
+                mCallbacks.receivedScreenshots.size() == numReceivedScreenshots);
+    }
+
+    private List<IBinder> createActivityList(int size) {
+        ArrayList<IBinder> activities = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            activities.add(mock(IBinder.class));
+        }
+        return activities;
+    }
+
+    public void waitForIdle(Handler h) throws Exception {
+        if (Looper.myLooper() == h.getLooper()) {
+            throw new RuntimeException("This method can not be called from the waiting looper");
+        }
+        CountDownLatch latch = new CountDownLatch(1);
+        h.post(() -> latch.countDown());
+        latch.await(2, TimeUnit.SECONDS);
+    }
+
+    private static class Callbacks implements AssistDataRequesterCallbacks {
+
+        boolean canHandleReceivedData = true;
+        ArrayList<Bundle> receivedData = new ArrayList<>();
+        ArrayList<Bitmap> receivedScreenshots = new ArrayList<>();
+
+        @Override
+        public boolean canHandleReceivedAssistDataLocked() {
+            return canHandleReceivedData;
+        }
+
+        @Override
+        public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+            receivedData.add(data);
+        }
+
+        @Override
+        public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+            receivedScreenshots.add(screenshot);
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b040a63..7541b92 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -55,6 +55,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 
 class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
@@ -162,13 +163,16 @@
                     mInfo.getServiceInfo().applicationInfo.uid, mHandler);
         }
         List<IBinder> activityTokens = null;
-        if (activityToken == null) {
+        if (activityToken != null) {
+            activityTokens = new ArrayList<>();
+            activityTokens.add(activityToken);
+        } else {
             // Let's get top activities from all visible stacks
             activityTokens = LocalServices.getService(ActivityManagerInternal.class)
                     .getTopVisibleActivities();
         }
         return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
-                activityToken, activityTokens);
+                activityTokens);
     }
 
     public boolean hideSessionLocked() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index d394d63..c9b0eac 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -16,6 +16,13 @@
 
 package com.android.server.voiceinteraction;
 
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
@@ -43,31 +50,24 @@
 import android.service.voice.VoiceInteractionSession;
 import android.util.Slog;
 import android.view.IWindowManager;
-import android.view.WindowManager;
 
 import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IAssistScreenshotReceiver;
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.os.IResultReceiver;
 import com.android.server.LocalServices;
+import com.android.server.am.AssistDataRequester;
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-
-final class VoiceInteractionSessionConnection implements ServiceConnection {
+final class VoiceInteractionSessionConnection implements ServiceConnection,
+        AssistDataRequesterCallbacks {
 
     final static String TAG = "VoiceInteractionServiceManager";
 
-    private static final String KEY_RECEIVER_EXTRA_COUNT = "count";
-    private static final String KEY_RECEIVER_EXTRA_INDEX = "index";
-
     final IBinder mToken = new Binder();
     final Object mLock;
     final ComponentName mSessionComponentName;
@@ -90,27 +90,8 @@
     IVoiceInteractionSessionService mService;
     IVoiceInteractionSession mSession;
     IVoiceInteractor mInteractor;
-    boolean mHaveAssistData;
-    int mPendingAssistDataCount;
-    ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>();
-    boolean mHaveScreenshot;
-    Bitmap mScreenshot;
     ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
-
-    static class AssistDataForActivity {
-        int activityIndex;
-        int activityCount;
-        Bundle data;
-
-        public AssistDataForActivity(Bundle data) {
-            this.data = data;
-            Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
-            if (receiverExtras != null) {
-                activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
-                activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
-            }
-        }
-    }
+    AssistDataRequester mAssistDataRequester;
 
     IVoiceInteractionSessionShowCallback mShowCallback =
             new IVoiceInteractionSessionShowCallback.Stub() {
@@ -146,32 +127,6 @@
         }
     };
 
-    final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
-        @Override
-        public void send(int resultCode, Bundle resultData) throws RemoteException {
-            synchronized (mLock) {
-                if (mShown) {
-                    mHaveAssistData = true;
-                    mAssistData.add(new AssistDataForActivity(resultData));
-                    deliverSessionDataLocked();
-                }
-            }
-        }
-    };
-
-    final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
-        @Override
-        public void send(Bitmap screenshot) throws RemoteException {
-            synchronized (mLock) {
-                if (mShown) {
-                    mHaveScreenshot = true;
-                    mScreenshot = screenshot;
-                    deliverSessionDataLocked();
-                }
-            }
-        }
-    };
-
     public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
             Context context, Callback callback, int callingUid, Handler handler) {
         mLock = lock;
@@ -185,6 +140,9 @@
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         mAppOps = context.getSystemService(AppOpsManager.class);
+        mAssistDataRequester = new AssistDataRequester(mContext, mAm, mIWindowManager,
+                (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
+                this, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
         IBinder permOwner = null;
         try {
             permOwner = mAm.newUriPermissionOwner("voicesession:"
@@ -224,8 +182,7 @@
     }
 
     public boolean showLocked(Bundle args, int flags, int disabledContext,
-            IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken,
-            List<IBinder> topActivities) {
+            IVoiceInteractionSessionShowCallback showCallback, List<IBinder> topActivities) {
         if (mBound) {
             if (!mFullyBound) {
                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -233,75 +190,18 @@
                                 | Context.BIND_FOREGROUND_SERVICE,
                         new UserHandle(mUser));
             }
+
             mShown = true;
-            boolean isAssistDataAllowed = true;
-            try {
-                isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity();
-            } catch (RemoteException e) {
-            }
-            disabledContext |= getUserDisabledShowContextLocked();
-            boolean structureEnabled = isAssistDataAllowed
-                    && (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0;
-            boolean screenshotEnabled = isAssistDataAllowed && structureEnabled
-                    && (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0;
             mShowArgs = args;
             mShowFlags = flags;
-            mHaveAssistData = false;
-            mPendingAssistDataCount = 0;
-            boolean needDisclosure = false;
-            if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
-                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
-                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
-                        && structureEnabled) {
-                    mAssistData.clear();
-                    final int count = activityToken != null ? 1 : topActivities.size();
-                    for (int i = 0; i < count; i++) {
-                        IBinder topActivity = count == 1 ? activityToken : topActivities.get(i);
-                        try {
-                            MetricsLogger.count(mContext, "assist_with_context", 1);
-                            Bundle receiverExtras = new Bundle();
-                            receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
-                            receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, count);
-                            if (mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
-                                    mAssistReceiver, receiverExtras, topActivity,
-                                    /* focused= */ i == 0, /* newSessionId= */ i == 0)) {
-                                needDisclosure = true;
-                                mPendingAssistDataCount++;
-                            } else if (i == 0) {
-                                // Wasn't allowed... given that, let's not do the screenshot either.
-                                mHaveAssistData = true;
-                                mAssistData.clear();
-                                screenshotEnabled = false;
-                                break;
-                            }
-                        } catch (RemoteException e) {
-                        }
-                    }
-                } else {
-                    mHaveAssistData = true;
-                    mAssistData.clear();
-                }
-            } else {
-                mAssistData.clear();
-            }
-            mHaveScreenshot = false;
-            if ((flags&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0) {
-                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
-                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
-                        && screenshotEnabled) {
-                    try {
-                        MetricsLogger.count(mContext, "assist_with_screen", 1);
-                        needDisclosure = true;
-                        mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
-                    } catch (RemoteException e) {
-                    }
-                } else {
-                    mHaveScreenshot = true;
-                    mScreenshot = null;
-                }
-            } else {
-                mScreenshot = null;
-            }
+
+            mAssistDataRequester.requestAssistData(topActivities,
+                    (disabledContext & VoiceInteractionSession.SHOW_WITH_ASSIST) == 0,
+                    (disabledContext & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0,
+                    mCallingUid, mSessionComponentName.getPackageName());
+
+            boolean needDisclosure = mAssistDataRequester.getPendingDataCount() > 0
+                    || mAssistDataRequester.getPendingScreenshotCount() > 0;
             if (needDisclosure && AssistUtils.shouldDisclose(mContext, mSessionComponentName)) {
                 mHandler.post(mShowAssistDisclosureRunnable);
             }
@@ -312,7 +212,6 @@
                     mShowFlags = 0;
                 } catch (RemoteException e) {
                 }
-                deliverSessionDataLocked();
             } else if (showCallback != null) {
                 mPendingShowCallbacks.add(showCallback);
             }
@@ -328,6 +227,59 @@
         return false;
     }
 
+    @Override
+    public boolean canHandleReceivedAssistDataLocked() {
+        return mSession != null;
+    }
+
+    @Override
+    public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+        if (data == null) {
+            try {
+                mSession.handleAssist(null, null, null, 0, 0);
+            } catch (RemoteException e) {
+                // Can't happen
+            }
+        } else {
+            final Bundle assistData = data.getBundle(VoiceInteractionSession.KEY_DATA);
+            final AssistStructure structure = data.getParcelable(
+                    VoiceInteractionSession.KEY_STRUCTURE);
+            final AssistContent content = data.getParcelable(
+                    VoiceInteractionSession.KEY_CONTENT);
+            final int uid = data.getInt(Intent.EXTRA_ASSIST_UID, -1);
+            if (uid >= 0 && content != null) {
+                Intent intent = content.getIntent();
+                if (intent != null) {
+                    ClipData clipData = intent.getClipData();
+                    if (clipData != null && Intent.isAccessUriMode(intent.getFlags())) {
+                        grantClipDataPermissions(clipData, intent.getFlags(), uid,
+                                mCallingUid, mSessionComponentName.getPackageName());
+                    }
+                }
+                ClipData clipData = content.getClipData();
+                if (clipData != null) {
+                    grantClipDataPermissions(clipData, FLAG_GRANT_READ_URI_PERMISSION,
+                            uid, mCallingUid, mSessionComponentName.getPackageName());
+                }
+            }
+            try {
+                mSession.handleAssist(assistData, structure, content, activityIndex,
+                        activityCount);
+            } catch (RemoteException e) {
+                // Can't happen
+            }
+        }
+    }
+
+    @Override
+    public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+        try {
+            mSession.handleScreenshot(screenshot);
+        } catch (RemoteException e) {
+            // Can't happen
+        }
+    }
+
     void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
         if (!"content".equals(uri.getScheme())) {
             return;
@@ -341,7 +293,7 @@
             int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
             uri = ContentProvider.getUriWithoutUserId(uri);
             mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
-                    uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
+                    uri, FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
         } catch (RemoteException e) {
         } catch (SecurityException e) {
             Slog.w(TAG, "Can't propagate permission", e);
@@ -370,89 +322,13 @@
         }
     }
 
-    void deliverSessionDataLocked() {
-        if (mSession == null) {
-            return;
-        }
-        if (mHaveAssistData) {
-            AssistDataForActivity assistData;
-            if (mAssistData.isEmpty()) {
-                // We're not actually going to get any data, deliver some nothing
-                try {
-                    mSession.handleAssist(null, null, null, 0, 0);
-                } catch (RemoteException e) {
-                }
-            } else {
-                while (!mAssistData.isEmpty()) {
-                    if (mPendingAssistDataCount <= 0) {
-                        Slog.e(TAG, "mPendingAssistDataCount is " + mPendingAssistDataCount);
-                    }
-                    mPendingAssistDataCount--;
-                    assistData = mAssistData.remove(0);
-                    if (assistData.data == null) {
-                        try {
-                            mSession.handleAssist(null, null, null, assistData.activityIndex,
-                                    assistData.activityCount);
-                        } catch (RemoteException e) {
-                        }
-                    } else {
-                        deliverSessionDataLocked(assistData);
-                    }
-                }
-            }
-            if (mPendingAssistDataCount <= 0) {
-                mHaveAssistData = false;
-            } // else, more to come
-        }
-        if (mHaveScreenshot) {
-            try {
-                mSession.handleScreenshot(mScreenshot);
-            } catch (RemoteException e) {
-            }
-            mScreenshot = null;
-            mHaveScreenshot = false;
-        }
-    }
-
-    private void deliverSessionDataLocked(AssistDataForActivity assistDataForActivity) {
-        Bundle assistData = assistDataForActivity.data.getBundle(
-                VoiceInteractionSession.KEY_DATA);
-        AssistStructure structure = assistDataForActivity.data.getParcelable(
-                VoiceInteractionSession.KEY_STRUCTURE);
-        AssistContent content = assistDataForActivity.data.getParcelable(
-                VoiceInteractionSession.KEY_CONTENT);
-        int uid = assistDataForActivity.data.getInt(Intent.EXTRA_ASSIST_UID, -1);
-        if (uid >= 0 && content != null) {
-            Intent intent = content.getIntent();
-            if (intent != null) {
-                ClipData data = intent.getClipData();
-                if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
-                    grantClipDataPermissions(data, intent.getFlags(), uid,
-                            mCallingUid, mSessionComponentName.getPackageName());
-                }
-            }
-            ClipData data = content.getClipData();
-            if (data != null) {
-                grantClipDataPermissions(data,
-                        Intent.FLAG_GRANT_READ_URI_PERMISSION,
-                        uid, mCallingUid, mSessionComponentName.getPackageName());
-            }
-        }
-        try {
-            mSession.handleAssist(assistData, structure, content,
-                    assistDataForActivity.activityIndex, assistDataForActivity.activityCount);
-        } catch (RemoteException e) {
-        }
-    }
-
     public boolean hideLocked() {
         if (mBound) {
             if (mShown) {
                 mShown = false;
                 mShowArgs = null;
                 mShowFlags = 0;
-                mHaveAssistData = false;
-                mAssistData.clear();
+                mAssistDataRequester.cancel();
                 mPendingShowCallbacks.clear();
                 if (mSession != null) {
                     try {
@@ -462,8 +338,7 @@
                 }
                 try {
                     mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
-                            Intent.FLAG_GRANT_READ_URI_PERMISSION
-                                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                            FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION,
                             mUser);
                 } catch (RemoteException e) {
                 }
@@ -529,7 +404,7 @@
                 mShowFlags = 0;
             } catch (RemoteException e) {
             }
-            deliverSessionDataLocked();
+            mAssistDataRequester.processPendingAssistData();
         }
         return true;
     }
@@ -587,10 +462,7 @@
             pw.print(prefix); pw.print("mSession="); pw.println(mSession);
             pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
         }
-        pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
-        if (mHaveAssistData) {
-            pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
-        }
+        mAssistDataRequester.dump(prefix, pw);
     }
 
     private Runnable mShowAssistDisclosureRunnable = new Runnable() {