diff options
author | Dianne Hackborn <hackbod@google.com> | 2015-04-02 18:25:35 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2015-04-02 18:43:31 -0700 |
commit | 5688b03f7f4fafd671451ff73103be0f2388b32e (patch) | |
tree | 03f3362f7919592e14f2bd56957fc4c722397e26 | |
parent | 3425dae8dc63372e8944dce43f7ed2d567512248 (diff) |
Add quick and dirty async AssistStructure building.
New APIs on ViewAssistStructure all the app to request to
build a sub-tree asynchronously and indicate when it is done
with that. The overall AssistStructure is now only flattened
and transfered on-demand, when the app receiving it requests
its data -- and at that point we can wait for any asynchronous
building to complete.
New AsyncStructure view is a very simple example of using this
to asynchronously build a child view.
Change-Id: I14f9199bee64915ad3dc80b2190916ec874308af
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | api/system-current.txt | 2 | ||||
-rw-r--r-- | core/java/android/app/AssistStructure.java | 138 | ||||
-rw-r--r-- | core/java/android/service/voice/VoiceInteractionSession.java | 13 | ||||
-rw-r--r-- | core/java/android/view/ViewAssistStructure.java | 3 | ||||
-rw-r--r-- | tests/VoiceInteraction/res/layout/main.xml | 8 | ||||
-rw-r--r-- | tests/VoiceInteraction/res/values/strings.xml | 1 | ||||
-rw-r--r-- | tests/VoiceInteraction/src/com/android/test/voiceinteraction/AssistVisualizer.java | 2 | ||||
-rw-r--r-- | tests/VoiceInteraction/src/com/android/test/voiceinteraction/AsyncStructure.java | 58 |
9 files changed, 213 insertions, 14 deletions
diff --git a/api/current.txt b/api/current.txt index 380fcb4b4927..9a05d1f2e617 100644 --- a/api/current.txt +++ b/api/current.txt @@ -35232,6 +35232,8 @@ package android.view { public abstract class ViewAssistStructure { ctor public ViewAssistStructure(); + method public abstract void asyncCommit(); + method public abstract android.view.ViewAssistStructure asyncNewChild(int); method public abstract void clearExtras(); method public abstract android.os.Bundle editExtras(); method public abstract int getChildCount(); diff --git a/api/system-current.txt b/api/system-current.txt index 40accaaa50d8..23a1f6a44feb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -37777,6 +37777,8 @@ package android.view { public abstract class ViewAssistStructure { ctor public ViewAssistStructure(); + method public abstract void asyncCommit(); + method public abstract android.view.ViewAssistStructure asyncNewChild(int); method public abstract void clearExtras(); method public abstract android.os.Bundle editExtras(); method public abstract int getChildCount(); diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java index e31c8215324e..1e159a3b5add 100644 --- a/core/java/android/app/AssistStructure.java +++ b/core/java/android/app/AssistStructure.java @@ -20,11 +20,15 @@ import android.content.ComponentName; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; +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.TextPaint; import android.text.TextUtils; import android.util.Log; @@ -49,12 +53,35 @@ final public class AssistStructure implements Parcelable { */ public static final String ASSIST_KEY = "android:assist_structure"; - final ComponentName mActivityComponent; + boolean mHaveData; + + ComponentName mActivityComponent; final ArrayList<WindowNode> mWindowNodes = new ArrayList<>(); + final ArrayList<ViewNodeBuilder> 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 class SendChannel extends Binder { + @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + if (code == TRANSACTION_XFER) { + data.enforceInterface(DESCRIPTOR); + writeContentToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + return true; + } else { + return super.onTransact(code, data, reply, flags); + } + } + } + final static class ViewNodeText { CharSequence mText; int mTextSelectionStart; @@ -112,7 +139,7 @@ final public class AssistStructure implements Parcelable { mHeight = rect.height(); mTitle = root.getTitle(); mRoot = new ViewNode(); - ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot); + ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false); view.dispatchProvideAssistStructure(builder); } @@ -425,10 +452,12 @@ final public class AssistStructure implements Parcelable { static class ViewNodeBuilder extends ViewAssistStructure { final AssistStructure mAssist; final ViewNode mNode; + final boolean mAsync; - ViewNodeBuilder(AssistStructure assist, ViewNode node) { + ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async) { mAssist = assist; mNode = node; + mAsync = async; } @Override @@ -628,7 +657,32 @@ final public class AssistStructure implements Parcelable { public ViewAssistStructure newChild(int index) { ViewNode node = new ViewNode(); mNode.mChildren[index] = node; - return new ViewNodeBuilder(mAssist, node); + return new ViewNodeBuilder(mAssist, node, false); + } + + @Override + public ViewAssistStructure asyncNewChild(int index) { + synchronized (mAssist) { + ViewNode node = new ViewNode(); + mNode.mChildren[index] = node; + ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true); + mAssist.mPendingAsyncChildren.add(builder); + return builder; + } + } + + @Override + public void asyncCommit() { + synchronized (mAssist) { + if (!mAsync) { + throw new IllegalStateException("Child " + this + + " was not created with ViewAssistStructure.asyncNewChild"); + } + if (!mAssist.mPendingAsyncChildren.remove(this)) { + throw new IllegalStateException("Child " + this + " already committed"); + } + mAssist.notifyAll(); + } } @Override @@ -638,6 +692,7 @@ final public class AssistStructure implements Parcelable { } AssistStructure(Activity activity) { + mHaveData = true; mActivityComponent = activity.getComponentName(); ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews( activity.getActivityToken()); @@ -648,13 +703,7 @@ final public class AssistStructure implements Parcelable { } AssistStructure(Parcel in) { - PooledStringReader preader = new PooledStringReader(in); - mActivityComponent = ComponentName.readFromParcel(in); - final int N = in.readInt(); - for (int i=0; i<N; i++) { - mWindowNodes.add(new WindowNode(in, preader)); - } - //dump(); + mReceiveChannel = in.readStrongBinder(); } /** @hide */ @@ -731,6 +780,7 @@ final public class AssistStructure implements Parcelable { } public ComponentName getActivityComponent() { + ensureData(); return mActivityComponent; } @@ -738,6 +788,7 @@ final public class AssistStructure implements Parcelable { * Return the number of window contents that have been collected in this assist data. */ public int getWindowNodeCount() { + ensureData(); return mWindowNodes.size(); } @@ -746,6 +797,7 @@ final public class AssistStructure implements Parcelable { * @param index Which window to retrieve, may be 0 to {@link #getWindowNodeCount()}-1. */ public WindowNode getWindowNodeAt(int index) { + ensureData(); return mWindowNodes.get(index); } @@ -753,11 +805,47 @@ final public class AssistStructure implements Parcelable { return 0; } - public void writeToParcel(Parcel out, int flags) { + /** @hide */ + public void ensureData() { + if (mHaveData) { + 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(); + } + + void writeContentToParcel(Parcel out, int flags) { + // First make sure all content has been created. + boolean skipStructure = false; + synchronized (this) { + long endTime = SystemClock.uptimeMillis() + 5000; + long now; + while (mPendingAsyncChildren.size() > 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. + skipStructure = true; + } + } int start = out.dataPosition(); PooledStringWriter pwriter = new PooledStringWriter(out); ComponentName.writeToParcel(mActivityComponent, out); - final int N = mWindowNodes.size(); + final int N = skipStructure ? 0 : mWindowNodes.size(); out.writeInt(N); for (int i=0; i<N; i++) { mWindowNodes.get(i).writeToParcel(out, pwriter); @@ -766,6 +854,30 @@ final public class AssistStructure implements Parcelable { Log.i(TAG, "Flattened assist data: " + (out.dataPosition() - start) + " bytes"); } + void readContentFromParcel(Parcel in) { + PooledStringReader preader = new PooledStringReader(in); + mActivityComponent = ComponentName.readFromParcel(in); + final int N = in.readInt(); + for (int i=0; i<N; i++) { + mWindowNodes.add(new WindowNode(in, preader)); + } + //dump(); + } + + 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(); + } + 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<AssistStructure> CREATOR = new Parcelable.Creator<AssistStructure>() { public AssistStructure createFromParcel(Parcel in) { diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 3245f55f622d..49ab797891f7 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -16,6 +16,7 @@ package android.service.voice; +import android.app.AssistStructure; import android.app.Dialog; import android.app.Instrumentation; import android.app.VoiceInteractor; @@ -175,6 +176,18 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { @Override public void handleAssist(Bundle assistBundle) { + // 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 (assistBundle != null) { + Bundle assistContext = assistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT); + if (assistContext != null) { + AssistStructure as = AssistStructure.getAssistStructure(assistContext); + if (as != null) { + as.ensureData(); + } + } + } mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_ASSIST, assistBundle)); } diff --git a/core/java/android/view/ViewAssistStructure.java b/core/java/android/view/ViewAssistStructure.java index c05ed6ff9fc7..7d263c540764 100644 --- a/core/java/android/view/ViewAssistStructure.java +++ b/core/java/android/view/ViewAssistStructure.java @@ -73,6 +73,9 @@ public abstract class ViewAssistStructure { public abstract int getChildCount(); public abstract ViewAssistStructure newChild(int index); + public abstract ViewAssistStructure asyncNewChild(int index); + public abstract void asyncCommit(); + /** @hide */ public abstract Rect getTempRect(); } diff --git a/tests/VoiceInteraction/res/layout/main.xml b/tests/VoiceInteraction/res/layout/main.xml index 3d7a41893d6f..092d37dc4a20 100644 --- a/tests/VoiceInteraction/res/layout/main.xml +++ b/tests/VoiceInteraction/res/layout/main.xml @@ -26,6 +26,14 @@ android:text="@string/start" /> + <com.android.test.voiceinteraction.AsyncStructure + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="64dp" + android:paddingBottom="64dp" + android:text="@string/asyncStructure" + /> + </LinearLayout> diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml index 5331457e22b1..942c93196b84 100644 --- a/tests/VoiceInteraction/res/values/strings.xml +++ b/tests/VoiceInteraction/res/values/strings.xml @@ -17,6 +17,7 @@ <resources> <string name="start">Start</string> + <string name="asyncStructure">(Async structure goes here)</string> <string name="confirm">Confirm</string> <string name="abort">Abort</string> <string name="complete">Complete</string> diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/AssistVisualizer.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/AssistVisualizer.java index 782d112594dd..8a723418b591 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/AssistVisualizer.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/AssistVisualizer.java @@ -45,8 +45,8 @@ public class AssistVisualizer extends View { } public void setAssistStructure(AssistStructure as) { - mAssistStructure.dump(); mAssistStructure = as; + mAssistStructure.dump(); mTextRects.clear(); final int N = as.getWindowNodeCount(); if (N > 0) { diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/AsyncStructure.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/AsyncStructure.java new file mode 100644 index 000000000000..73e04e525878 --- /dev/null +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/AsyncStructure.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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.test.voiceinteraction; + +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewAssistStructure; +import android.widget.TextView; + +/** + * Test for asynchronously creating additional assist structure. + */ +public class AsyncStructure extends TextView { + public AsyncStructure(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onProvideVirtualAssistStructure(ViewAssistStructure structure) { + structure.setChildCount(1); + final ViewAssistStructure child = structure.asyncNewChild(0); + final int width = getWidth(); + final int height = getHeight(); + (new Thread() { + @Override + public void run() { + // Simulate taking a long time to build this. + try { + sleep(2000); + } catch (InterruptedException e) { + } + child.setClassName(AsyncStructure.class.getName()); + child.setVisibility(View.VISIBLE); + child.setDimens(width / 4, height / 4, 0, 0, width / 2, height / 2); + child.setEnabled(true); + child.setContentDescription("This is some async content"); + child.setText("We could have lots and lots of async text!"); + child.asyncCommit(); + } + }).start(); + } +} |