Fix issue #22328792: Fix scalability issues in AssistStructure

We can now stream the AssistStructure across processes, avoiding
IPC size limitations for large structures.  There is also a new
API that gets called on the VoiceInteractionSession if there is
a failure retrieving the assist data.

Also fix issue #22351981: Runtime restart due to ANR in system server,
getting rid of a deadlock.

And also tweak object lifecycles to try to avoid keeping around
in an app the previous AssistStructure after we request a new one.

Change-Id: Ifb136a0d31a14e56a8db6b90768d9fc65557a17f
diff --git a/api/current.txt b/api/current.txt
index e5b190c..0eeb68a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28792,6 +28792,7 @@
     method public android.view.LayoutInflater getLayoutInflater();
     method public android.app.Dialog getWindow();
     method public void hide();
+    method public void onAssistStructureFailure(java.lang.Throwable);
     method public void onBackPressed();
     method public void onCancelRequest(android.service.voice.VoiceInteractionSession.Request);
     method public void onCloseSystemDialogs();
diff --git a/api/system-current.txt b/api/system-current.txt
index 18cde08..ef03bad 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -30941,6 +30941,7 @@
     method public android.view.LayoutInflater getLayoutInflater();
     method public android.app.Dialog getWindow();
     method public void hide();
+    method public void onAssistStructureFailure(java.lang.Throwable);
     method public void onBackPressed();
     method public void onCancelRequest(android.service.voice.VoiceInteractionSession.Request);
     method public void onCloseSystemDialogs();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 828dc0a..2b4d03b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -180,15 +180,14 @@
     final ApplicationThread mAppThread = new ApplicationThread();
     final Looper mLooper = Looper.myLooper();
     final H mH = new H();
-    final ArrayMap<IBinder, ActivityClientRecord> mActivities
-            = new ArrayMap<IBinder, ActivityClientRecord>();
+    final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
     // List of new activities (via ActivityRecord.nextIdle) that should
     // be reported when next we idle.
     ActivityClientRecord mNewActivities = null;
     // Number of activities that are currently visible on-screen.
     int mNumVisibleActivities = 0;
-    final ArrayMap<IBinder, Service> mServices
-            = new ArrayMap<IBinder, Service>();
+    WeakReference<AssistStructure> mLastAssistStructure;
+    final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
     AppBindData mBoundApplication;
     Profiler mProfiler;
     int mCurDefaultDisplayDpi;
@@ -2568,6 +2567,12 @@
     }
 
     public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
+        if (mLastAssistStructure != null) {
+            AssistStructure structure = mLastAssistStructure.get();
+            if (structure != null) {
+                structure.clearSendChannel();
+            }
+        }
         Bundle data = new Bundle();
         AssistStructure structure = null;
         AssistContent content = new AssistContent();
@@ -2597,6 +2602,7 @@
         if (structure == null) {
             structure = new AssistStructure();
         }
+        mLastAssistStructure = new WeakReference<>(structure);
         IActivityManager mgr = ActivityManagerNative.getDefault();
         try {
             mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content, referrer);
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 73c551f..9673c98 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -30,6 +30,9 @@
 public class AssistStructure implements Parcelable {
     static final String TAG = "AssistStructure";
 
+    static final boolean DEBUG_PARCEL = false;
+    static final boolean DEBUG_PARCEL_TREE = false;
+
     boolean mHaveData;
 
     ComponentName mActivityComponent;
@@ -46,12 +49,40 @@
     static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1;
     static final String DESCRIPTOR = "android.app.AssistStructure";
 
-    final class SendChannel extends Binder {
+    final static class SendChannel extends Binder {
+        volatile AssistStructure mAssistStructure;
+
+        SendChannel(AssistStructure as) {
+            mAssistStructure = as;
+        }
+
         @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
             if (code == TRANSACTION_XFER) {
+                AssistStructure as = mAssistStructure;
+                if (as == null) {
+                    return true;
+                }
+
                 data.enforceInterface(DESCRIPTOR);
-                writeContentToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                IBinder token = data.readStrongBinder();
+                if (DEBUG_PARCEL) Log.d(TAG, "Request for data on " + as
+                        + " using token " + token);
+                if (token != null) {
+                    if (DEBUG_PARCEL) Log.d(TAG, "Resuming partial write of " + token);
+                    if (token instanceof ParcelTransferWriter) {
+                        ParcelTransferWriter xfer = (ParcelTransferWriter)token;
+                        xfer.writeToParcel(as, reply);
+                        return true;
+                    }
+                    Log.w(TAG, "Caller supplied bad token type: " + token);
+                    // Don't write anything; this is the end of the data.
+                    return true;
+                }
+                //long start = SystemClock.uptimeMillis();
+                ParcelTransferWriter xfer = new ParcelTransferWriter(as, reply);
+                xfer.writeToParcel(as, reply);
+                //Log.i(TAG, "Time to parcel: " + (SystemClock.uptimeMillis()-start) + "ms");
                 return true;
             } else {
                 return super.onTransact(code, data, reply, flags);
@@ -59,6 +90,235 @@
         }
     }
 
+    final static class ViewStackEntry {
+        ViewNode node;
+        int curChild;
+        int numChildren;
+    }
+
+    final static class ParcelTransferWriter extends Binder {
+        final boolean mWriteStructure;
+        int mCurWindow;
+        int mNumWindows;
+        final ArrayList<ViewStackEntry> mViewStack = new ArrayList<>();
+        ViewStackEntry mCurViewStackEntry;
+        int mCurViewStackPos;
+        int mNumWrittenWindows;
+        int mNumWrittenViews;
+        final float[] mTmpMatrix = new float[9];
+
+        ParcelTransferWriter(AssistStructure as, Parcel out) {
+            mWriteStructure = as.waitForReady();
+            ComponentName.writeToParcel(as.mActivityComponent, out);
+            mNumWindows = as.mWindowNodes.size();
+            if (mWriteStructure && mNumWindows > 0) {
+                out.writeInt(mNumWindows);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        void writeToParcel(AssistStructure as, Parcel out) {
+            int start = out.dataPosition();
+            mNumWrittenWindows = 0;
+            mNumWrittenViews = 0;
+            boolean more = writeToParcelInner(as, out);
+            Log.i(TAG, "Flattened " + (more ? "partial" : "final") + " assist data: "
+                    + (out.dataPosition() - start)
+                    + " bytes, containing " + mNumWrittenWindows + " windows, "
+                    + mNumWrittenViews + " views");
+        }
+
+        boolean writeToParcelInner(AssistStructure as, Parcel out) {
+            if (mNumWindows == 0) {
+                return false;
+            }
+            if (DEBUG_PARCEL) Log.d(TAG, "Creating PooledStringWriter @ " + out.dataPosition());
+            PooledStringWriter pwriter = new PooledStringWriter(out);
+            while (writeNextEntryToParcel(as, out, pwriter)) {
+                // If the parcel contains more than 100K of data, then we are getting too
+                // large for a single IPC so stop here and let the caller come back when it
+                // is ready for more.
+                if (out.dataSize() > 1024*1024) {
+                    if (DEBUG_PARCEL) Log.d(TAG, "Assist data size is " + out.dataSize()
+                            + " @ pos " + out.dataPosition() + "; returning partial result");
+                    out.writeInt(0);
+                    out.writeStrongBinder(this);
+                    if (DEBUG_PARCEL) Log.d(TAG, "Finishing PooledStringWriter @ "
+                            + out.dataPosition() + ", size " + pwriter.getStringCount());
+                    pwriter.finish();
+                    return true;
+                }
+            }
+            if (DEBUG_PARCEL) Log.d(TAG, "Finishing PooledStringWriter @ "
+                    + out.dataPosition() + ", size " + pwriter.getStringCount());
+            pwriter.finish();
+            mViewStack.clear();
+            return false;
+        }
+
+        void pushViewStackEntry(ViewNode node, int pos) {
+            ViewStackEntry entry;
+            if (pos >= mViewStack.size()) {
+                entry = new ViewStackEntry();
+                mViewStack.add(entry);
+                if (DEBUG_PARCEL_TREE) Log.d(TAG, "New stack entry at " + pos + ": " + entry);
+            } else {
+                entry = mViewStack.get(pos);
+                if (DEBUG_PARCEL_TREE) Log.d(TAG, "Existing stack entry at " + pos + ": " + entry);
+            }
+            entry.node = node;
+            entry.numChildren = node.getChildCount();
+            entry.curChild = 0;
+            mCurViewStackEntry = entry;
+        }
+
+        boolean writeNextEntryToParcel(AssistStructure as, Parcel out, PooledStringWriter pwriter) {
+            // Write next view node if appropriate.
+            if (mCurViewStackEntry != null) {
+                if (mCurViewStackEntry.curChild < mCurViewStackEntry.numChildren) {
+                    // Write the next child in the current view.
+                    if (DEBUG_PARCEL_TREE) Log.d(TAG, "Writing child #"
+                            + mCurViewStackEntry.curChild + " in " + mCurViewStackEntry.node);
+                    ViewNode child = mCurViewStackEntry.node.mChildren[mCurViewStackEntry.curChild];
+                    mCurViewStackEntry.curChild++;
+                    if (DEBUG_PARCEL) Log.d(TAG, "write view: at " + out.dataPosition()
+                            + ", windows=" + mNumWrittenWindows
+                            + ", views=" + mNumWrittenViews);
+                    out.writeInt(1);
+                    int flags = child.writeSelfToParcel(out, pwriter, mTmpMatrix);
+                    mNumWrittenViews++;
+                    // If the child has children, push it on the stack to write them next.
+                    if ((flags&ViewNode.FLAGS_HAS_CHILDREN) != 0) {
+                        if (DEBUG_PARCEL_TREE) Log.d(TAG, "Preparing to write "
+                                + child.mChildren.length + " children under " + child);
+                        out.writeInt(child.mChildren.length);
+                        int pos = ++mCurViewStackPos;
+                        pushViewStackEntry(child, pos);
+                    }
+                    return true;
+                }
+
+                // We are done writing children of the current view; pop off the stack.
+                do {
+                    int pos = --mCurViewStackPos;
+                    if (DEBUG_PARCEL_TREE) Log.d(TAG, "Done with " + mCurViewStackEntry.node
+                            + "; popping up to " + pos);
+                    if (pos < 0) {
+                        // Reached the last view; step to next window.
+                        if (DEBUG_PARCEL_TREE) Log.d(TAG, "Done with view hierarchy!");
+                        mCurViewStackEntry = null;
+                        break;
+                    }
+                    mCurViewStackEntry = mViewStack.get(pos);
+                } while (mCurViewStackEntry.curChild >= mCurViewStackEntry.numChildren);
+                return true;
+            }
+
+            // Write the next window if appropriate.
+            int pos = mCurWindow;
+            if (pos < mNumWindows) {
+                WindowNode win = as.mWindowNodes.get(pos);
+                mCurWindow++;
+                if (DEBUG_PARCEL) Log.d(TAG, "write window #" + pos + ": at " + out.dataPosition()
+                        + ", windows=" + mNumWrittenWindows
+                        + ", views=" + mNumWrittenViews);
+                out.writeInt(1);
+                win.writeSelfToParcel(out, pwriter, mTmpMatrix);
+                mNumWrittenWindows++;
+                ViewNode root = win.mRoot;
+                mCurViewStackPos = 0;
+                if (DEBUG_PARCEL_TREE) Log.d(TAG, "Pushing initial root view " + root);
+                pushViewStackEntry(root, 0);
+                return true;
+            }
+
+            return false;
+        }
+    }
+
+    final class ParcelTransferReader {
+        final float[] mTmpMatrix = new float[9];
+        PooledStringReader mStringReader;
+
+        int mNumReadWindows;
+        int mNumReadViews;
+
+        private final IBinder mChannel;
+        private IBinder mTransferToken;
+        private Parcel mCurParcel;
+
+        ParcelTransferReader(IBinder channel) {
+            mChannel = channel;
+        }
+
+        void go() {
+            fetchData();
+            mActivityComponent = ComponentName.readFromParcel(mCurParcel);
+            final int N = mCurParcel.readInt();
+            if (N > 0) {
+                if (DEBUG_PARCEL) Log.d(TAG, "Creating PooledStringReader @ "
+                        + mCurParcel.dataPosition());
+                mStringReader = new PooledStringReader(mCurParcel);
+                if (DEBUG_PARCEL) Log.d(TAG, "PooledStringReader size = "
+                        + mStringReader.getStringCount());
+                for (int i=0; i<N; i++) {
+                    mWindowNodes.add(new WindowNode(this));
+                }
+            }
+            if (DEBUG_PARCEL) Log.d(TAG, "Finished reading: at " + mCurParcel.dataPosition()
+                    + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows
+                    + ", views=" + mNumReadViews);
+        }
+
+        Parcel readParcel() {
+            if (DEBUG_PARCEL) Log.d(TAG, "readParcel: at " + mCurParcel.dataPosition()
+                    + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows
+                    + ", views=" + mNumReadViews);
+            if (mCurParcel.readInt() != 0) {
+                return mCurParcel;
+            }
+            // We have run out of partial data, need to read another batch.
+            mTransferToken = mCurParcel.readStrongBinder();
+            if (mTransferToken == null) {
+                throw new IllegalStateException(
+                        "Reached end of partial data without transfer token");
+            }
+            if (DEBUG_PARCEL) Log.d(TAG, "Ran out of partial data at "
+                    + mCurParcel.dataPosition() + ", token " + mTransferToken);
+            fetchData();
+            if (DEBUG_PARCEL) Log.d(TAG, "Creating PooledStringReader @ "
+                    + mCurParcel.dataPosition());
+            mStringReader = new PooledStringReader(mCurParcel);
+            if (DEBUG_PARCEL) Log.d(TAG, "PooledStringReader size = "
+                    + mStringReader.getStringCount());
+            if (DEBUG_PARCEL) Log.d(TAG, "readParcel: at " + mCurParcel.dataPosition()
+                    + ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows
+                    + ", views=" + mNumReadViews);
+            mCurParcel.readInt();
+            return mCurParcel;
+        }
+
+        private void fetchData() {
+            Parcel data = Parcel.obtain();
+            data.writeInterfaceToken(DESCRIPTOR);
+            data.writeStrongBinder(mTransferToken);
+            if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken);
+            if (mCurParcel != null) {
+                mCurParcel.recycle();
+            }
+            mCurParcel = Parcel.obtain();
+            try {
+                mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failure reading AssistStructure data", e);
+                throw new IllegalStateException("Failure reading AssistStructure data: " + e);
+            }
+            data.recycle();
+            mNumReadWindows = mNumReadViews = 0;
+        }
+    }
+
     final static class ViewNodeText {
         CharSequence mText;
         float mTextSize;
@@ -145,24 +405,25 @@
             view.dispatchProvideStructure(builder);
         }
 
-        WindowNode(Parcel in, PooledStringReader preader, float[] tmpMatrix) {
+        WindowNode(ParcelTransferReader reader) {
+            Parcel in = reader.readParcel();
+            reader.mNumReadWindows++;
             mX = in.readInt();
             mY = in.readInt();
             mWidth = in.readInt();
             mHeight = in.readInt();
             mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
             mDisplayId = in.readInt();
-            mRoot = new ViewNode(in, preader, tmpMatrix);
+            mRoot = new ViewNode(reader);
         }
 
-        int writeToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) {
+        void writeSelfToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) {
             out.writeInt(mX);
             out.writeInt(mY);
             out.writeInt(mWidth);
             out.writeInt(mHeight);
             TextUtils.writeToParcel(mTitle, out, 0);
             out.writeInt(mDisplayId);
-            return mRoot.writeToParcel(out, pwriter, tmpMatrix);
         }
 
         /**
@@ -287,7 +548,10 @@
         ViewNode() {
         }
 
-        ViewNode(Parcel in, PooledStringReader preader, float[] tmpMatrix) {
+        ViewNode(ParcelTransferReader reader) {
+            final Parcel in = reader.readParcel();
+            reader.mNumReadViews++;
+            final PooledStringReader preader = reader.mStringReader;
             mClassName = preader.readString();
             mFlags = in.readInt();
             final int flags = mFlags;
@@ -320,8 +584,8 @@
             }
             if ((flags&FLAGS_HAS_MATRIX) != 0) {
                 mMatrix = new Matrix();
-                in.readFloatArray(tmpMatrix);
-                mMatrix.setValues(tmpMatrix);
+                in.readFloatArray(reader.mTmpMatrix);
+                mMatrix.setValues(reader.mTmpMatrix);
             }
             if ((flags&FLAGS_HAS_ELEVATION) != 0) {
                 mElevation = in.readFloat();
@@ -342,12 +606,12 @@
                 final int NCHILDREN = in.readInt();
                 mChildren = new ViewNode[NCHILDREN];
                 for (int i=0; i<NCHILDREN; i++) {
-                    mChildren[i] = new ViewNode(in, preader, tmpMatrix);
+                    mChildren[i] = new ViewNode(reader);
                 }
             }
         }
 
-        int writeToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) {
+        int writeSelfToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) {
             int flags = mFlags & ~FLAGS_ALL_CONTROL;
             if (mId != View.NO_ID) {
                 flags |= FLAGS_HAS_ID;
@@ -428,15 +692,7 @@
             if ((flags&FLAGS_HAS_EXTRAS) != 0) {
                 out.writeBundle(mExtras);
             }
-            int N = 1;
-            if ((flags&FLAGS_HAS_CHILDREN) != 0) {
-                final int NCHILDREN = mChildren.length;
-                out.writeInt(NCHILDREN);
-                for (int i=0; i<NCHILDREN; i++) {
-                    N += mChildren[i].writeToParcel(out, pwriter, tmpMatrix);
-                }
-            }
-            return N;
+            return flags;
         }
 
         /**
@@ -1177,22 +1433,11 @@
             return;
         }
         mHaveData = true;
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        data.writeInterfaceToken(DESCRIPTOR);
-        try {
-            mReceiveChannel.transact(TRANSACTION_XFER, data, reply, 0);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failure reading AssistStructure data", e);
-            return;
-        }
-        readContentFromParcel(reply);
-        data.recycle();
-        reply.recycle();
+        ParcelTransferReader reader = new ParcelTransferReader(mReceiveChannel);
+        reader.go();
     }
 
-    void writeContentToParcel(Parcel out, int flags) {
-        // First make sure all content has been created.
+    boolean waitForReady() {
         boolean skipStructure = false;
         synchronized (this) {
             long endTime = SystemClock.uptimeMillis() + 5000;
@@ -1210,30 +1455,14 @@
                 skipStructure = true;
             }
         }
-        int start = out.dataPosition();
-        PooledStringWriter pwriter = new PooledStringWriter(out);
-        float[] tmpMatrix = new float[9];
-        ComponentName.writeToParcel(mActivityComponent, out);
-        final int N = skipStructure ? 0 : mWindowNodes.size();
-        out.writeInt(N);
-        int NV = 0;
-        for (int i=0; i<N; i++) {
-            NV += mWindowNodes.get(i).writeToParcel(out, pwriter, tmpMatrix);
-        }
-        pwriter.finish();
-        Log.i(TAG, "Flattened assist data: " + (out.dataPosition() - start) + " bytes, containing "
-                + N + " windows, " + NV + " views");
+        return !skipStructure;
     }
 
-    void readContentFromParcel(Parcel in) {
-        PooledStringReader preader = new PooledStringReader(in);
-        float[] tmpMatrix = new float[9];
-        mActivityComponent = ComponentName.readFromParcel(in);
-        final int N = in.readInt();
-        for (int i=0; i<N; i++) {
-            mWindowNodes.add(new WindowNode(in, preader, tmpMatrix));
+    /** @hide */
+    public void clearSendChannel() {
+        if (mSendChannel != null) {
+            mSendChannel.mAssistStructure = null;
         }
-        //dump();
     }
 
     public int describeContents() {
@@ -1245,7 +1474,7 @@
             // This object holds its data.  We want to write a send channel that the
             // other side can use to retrieve that data.
             if (mSendChannel == null) {
-                mSendChannel = new SendChannel();
+                mSendChannel = new SendChannel(this);
             }
             out.writeStrongBinder(mSendChannel);
         } else {
diff --git a/core/java/android/os/PooledStringReader.java b/core/java/android/os/PooledStringReader.java
index 7795957..6fc71c7 100644
--- a/core/java/android/os/PooledStringReader.java
+++ b/core/java/android/os/PooledStringReader.java
@@ -36,6 +36,10 @@
         mPool = new String[size];
     }
 
+    public int getStringCount() {
+        return mPool.length;
+    }
+
     public String readString() {
         int idx = mIn.readInt();
         if (idx >= 0) {
diff --git a/core/java/android/os/PooledStringWriter.java b/core/java/android/os/PooledStringWriter.java
index eac297d..ee592d9 100644
--- a/core/java/android/os/PooledStringWriter.java
+++ b/core/java/android/os/PooledStringWriter.java
@@ -67,6 +67,10 @@
         }
     }
 
+    public int getStringCount() {
+        return mPool.size();
+    }
+
     public void finish() {
         final int pos = mOut.dataPosition();
         mOut.setDataPosition(mStart);
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index a7e0e08..e408b36 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -209,16 +209,30 @@
         }
 
         @Override
-        public void handleAssist(Bundle data, AssistStructure structure,
-                AssistContent content) {
+        public void handleAssist(final Bundle data, final AssistStructure structure,
+                final AssistContent content) {
             // We want to pre-warm the AssistStructure before handing it off to the main
-            // thread.  There is a strong argument to be made that it should be handed
-            // through as a separate param rather than part of the assistBundle.
-            if (structure != null) {
-                structure.ensureData();
-            }
-            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOO(MSG_HANDLE_ASSIST,
-                    data, structure, content));
+            // thread.  We also want to do this on a separate thread, so that if the app
+            // is for some reason slow (due to slow filling in of async children in the
+            // structure), we don't block other incoming IPCs (such as the screenshot) to
+            // us (since we are a oneway interface, they get serialized).  (Okay?)
+            Thread retriever = new Thread("AssistStructure retriever") {
+                @Override
+                public void run() {
+                    Throwable failure = null;
+                    if (structure != null) {
+                        try {
+                            structure.ensureData();
+                        } catch (Throwable e) {
+                            Log.w(TAG, "Failure retrieving AssistStructure", e);
+                            failure = e;
+                        }
+                    }
+                    mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_HANDLE_ASSIST,
+                            data, failure == null ? structure : null, failure, content));
+                }
+            };
+            retriever.start();
         }
 
         @Override
@@ -689,8 +703,8 @@
                     args = (SomeArgs)msg.obj;
                     if (DEBUG) Log.d(TAG, "onHandleAssist: data=" + args.arg1
                             + " structure=" + args.arg2 + " content=" + args.arg3);
-                    onHandleAssist((Bundle) args.arg1, (AssistStructure) args.arg2,
-                            (AssistContent) args.arg3);
+                    doOnHandleAssist((Bundle) args.arg1, (AssistStructure) args.arg2,
+                            (Throwable) args.arg3, (AssistContent) args.arg4);
                     break;
                 case MSG_HANDLE_SCREENSHOT:
                     if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj);
@@ -1111,9 +1125,45 @@
         mContentFrame.requestApplyInsets();
     }
 
+    void doOnHandleAssist(Bundle data, AssistStructure structure, Throwable failure,
+            AssistContent content) {
+        if (failure != null) {
+            onAssistStructureFailure(failure);
+        }
+        onHandleAssist(data, structure, content);
+    }
+
+    /**
+     * Called when there has been a failure transferring the {@link AssistStructure} to
+     * the assistant.  This may happen, for example, if the data is too large and results
+     * in an out of memory exception, or the client has provided corrupt data.  This will
+     * be called immediately before {@link #onHandleAssist} and the AssistStructure supplied
+     * there afterwards will be null.
+     *
+     * @param failure The failure exception that was thrown when building the
+     * {@link AssistStructure}.
+     */
+    public void onAssistStructureFailure(Throwable failure) {
+    }
+
+    /**
+     * Called to receive data from the application that the user was currently viewing when
+     * an assist session is started.
+     *
+     * @param data Arbitrary data supplied by the app through
+     * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
+     * @param structure If available, the structure definition of all windows currently
+     * displayed by the app; if structure has been turned off by the user, will be null.
+     * @param content Additional content data supplied by the app through
+     * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}.
+     */
     public void onHandleAssist(Bundle data, AssistStructure structure, AssistContent content) {
     }
 
+    /**
+     * Called to receive a screenshot of what the user was currently viewing when an assist
+     * session is started.  Will be null if screenshots are disabled by the user.
+     */
     public void onHandleScreenshot(Bitmap screenshot) {
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 88a0c8f..6e94647 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -498,13 +498,11 @@
         @Override
         public void run() {
             Slog.w(TAG, "getAssistContextExtras failed: timeout retrieving from " + activity);
-            synchronized (ActivityManagerService.this) {
-                synchronized (this) {
-                    haveResult = true;
-                    notifyAll();
-                }
-                pendingAssistExtrasTimedOutLocked(this);
+            synchronized (this) {
+                haveResult = true;
+                notifyAll();
             }
+            pendingAssistExtrasTimedOut(this);
         }
     }
 
@@ -10753,9 +10751,13 @@
         }
     }
 
-    void pendingAssistExtrasTimedOutLocked(PendingAssistExtras pae) {
-        mPendingAssistExtras.remove(pae);
-        if (pae.receiver != null) {
+    void pendingAssistExtrasTimedOut(PendingAssistExtras pae) {
+        IResultReceiver receiver;
+        synchronized (this) {
+            mPendingAssistExtras.remove(pae);
+            receiver = pae.receiver;
+        }
+        if (receiver != null) {
             // Caller wants result sent back to them.
             try {
                 pae.receiver.send(0, null);