summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2015-04-02 18:25:35 -0700
committerDianne Hackborn <hackbod@google.com>2015-04-02 18:43:31 -0700
commit5688b03f7f4fafd671451ff73103be0f2388b32e (patch)
tree03f3362f7919592e14f2bd56957fc4c722397e26
parent3425dae8dc63372e8944dce43f7ed2d567512248 (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.txt2
-rw-r--r--api/system-current.txt2
-rw-r--r--core/java/android/app/AssistStructure.java138
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java13
-rw-r--r--core/java/android/view/ViewAssistStructure.java3
-rw-r--r--tests/VoiceInteraction/res/layout/main.xml8
-rw-r--r--tests/VoiceInteraction/res/values/strings.xml1
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/AssistVisualizer.java2
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/AsyncStructure.java58
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();
+ }
+}