package android.app.assist; import android.app.Activity; import android.content.ComponentName; import android.graphics.Matrix; import android.graphics.Rect; import android.os.BadParcelableException; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.PooledStringReader; import android.os.PooledStringWriter; import android.os.RemoteException; import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewStructure; import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.WindowManagerGlobal; import java.util.ArrayList; /** * Assist data automatically created by the platform's implementation * of {@link android.app.Activity#onProvideAssistData}. */ public class AssistStructure implements Parcelable { static final String TAG = "AssistStructure"; static final boolean DEBUG_PARCEL = false; static final boolean DEBUG_PARCEL_CHILDREN = false; static final boolean DEBUG_PARCEL_TREE = false; static final int VALIDATE_WINDOW_TOKEN = 0x11111111; static final int VALIDATE_VIEW_TOKEN = 0x22222222; boolean mHaveData; ComponentName mActivityComponent; final ArrayList mWindowNodes = new ArrayList<>(); final ArrayList mPendingAsyncChildren = new ArrayList<>(); SendChannel mSendChannel; IBinder mReceiveChannel; Rect mTmpRect = new Rect(); static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1; static final String DESCRIPTOR = "android.app.AssistStructure"; 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); 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); } } } 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 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 is above the IPC limit, 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() > IBinder.MAX_IPC_SIZE) { 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; } void writeView(ViewNode child, Parcel out, PooledStringWriter pwriter, int levelAdj) { if (DEBUG_PARCEL) Log.d(TAG, "write view: at " + out.dataPosition() + ", windows=" + mNumWrittenWindows + ", views=" + mNumWrittenViews + ", level=" + (mCurViewStackPos+levelAdj)); out.writeInt(VALIDATE_VIEW_TOKEN); 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 || DEBUG_PARCEL_CHILDREN) Log.d(TAG, "Preparing to write " + child.mChildren.length + " children: @ #" + mNumWrittenViews + ", level " + (mCurViewStackPos+levelAdj)); out.writeInt(child.mChildren.length); int pos = ++mCurViewStackPos; pushViewStackEntry(child, pos); } } 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++; writeView(child, out, pwriter, 1); 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(VALIDATE_WINDOW_TOKEN); win.writeSelfToParcel(out, pwriter, mTmpMatrix); mNumWrittenWindows++; ViewNode root = win.mRoot; mCurViewStackPos = 0; if (DEBUG_PARCEL_TREE) Log.d(TAG, "Writing initial root view " + root); writeView(root, out, pwriter, 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>16)&0x7fff; val = in.readInt(); mWidth = val&0x7fff; mHeight = (val>>16)&0x7fff; } if ((flags&FLAGS_HAS_SCROLL) != 0) { mScrollX = in.readInt(); mScrollY = in.readInt(); } if ((flags&FLAGS_HAS_MATRIX) != 0) { mMatrix = new Matrix(); in.readFloatArray(reader.mTmpMatrix); mMatrix.setValues(reader.mTmpMatrix); } if ((flags&FLAGS_HAS_ELEVATION) != 0) { mElevation = in.readFloat(); } if ((flags&FLAGS_HAS_ALPHA) != 0) { mAlpha = in.readFloat(); } if ((flags&FLAGS_HAS_CONTENT_DESCRIPTION) != 0) { mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); } if ((flags&FLAGS_HAS_TEXT) != 0) { mText = new ViewNodeText(in, (flags&FLAGS_HAS_COMPLEX_TEXT) == 0); } if ((flags&FLAGS_HAS_EXTRAS) != 0) { mExtras = in.readBundle(); } if ((flags&FLAGS_HAS_CHILDREN) != 0) { final int NCHILDREN = in.readInt(); if (DEBUG_PARCEL_TREE || DEBUG_PARCEL_CHILDREN) Log.d(TAG, "Preparing to read " + NCHILDREN + " children: @ #" + reader.mNumReadViews + ", level " + nestingLevel); mChildren = new ViewNode[NCHILDREN]; for (int i=0; i views = WindowManagerGlobal.getInstance().getRootViews( activity.getActivityToken()); for (int i=0; i 0) { Log.i(TAG, prefix + " Children:"); String cprefix = prefix + " "; for (int i=0; i 0 && (now=SystemClock.uptimeMillis()) < endTime) { try { wait(endTime-now); } catch (InterruptedException e) { } } if (mPendingAsyncChildren.size() > 0) { // We waited too long, assume none of the assist structure is valid. Log.w(TAG, "Skipping assist structure, waiting too long for async children (have " + mPendingAsyncChildren.size() + " remaining"); skipStructure = true; } } return !skipStructure; } /** @hide */ public void clearSendChannel() { if (mSendChannel != null) { mSendChannel.mAssistStructure = null; } } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { if (mHaveData) { // 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(this); } out.writeStrongBinder(mSendChannel); } else { // This object doesn't hold its data, so just propagate along its receive channel. out.writeStrongBinder(mReceiveChannel); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public AssistStructure createFromParcel(Parcel in) { return new AssistStructure(in); } public AssistStructure[] newArray(int size) { return new AssistStructure[size]; } }; }