blob: 25153fc5e24f70ae1227305254b590381a9f4390 [file] [log] [blame]
/*
* 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 android.app;
import android.content.ComponentName;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PooledStringReader;
import android.os.PooledStringWriter;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewAssistStructure;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import android.widget.Checkable;
import java.util.ArrayList;
/**
* Assist data automatically created by the platform's implementation
* of {@link Activity#onProvideAssistData}. Retrieve it from the assist
* data with {@link #getAssistStructure(android.os.Bundle)}.
*/
final public class AssistStructure implements Parcelable {
static final String TAG = "AssistStructure";
/**
* Key name this data structure is stored in the Bundle generated by
* {@link Activity#onProvideAssistData}.
*/
public static final String ASSIST_KEY = "android:assist_structure";
final ComponentName mActivityComponent;
final ArrayList<ViewNodeImpl> mRootViews = new ArrayList<>();
ViewAssistStructureImpl mTmpViewAssistStructureImpl = new ViewAssistStructureImpl();
Bundle mTmpExtras = new Bundle();
final static class ViewAssistStructureImpl extends ViewAssistStructure {
CharSequence mText;
int mTextSelectionStart = -1;
int mTextSelectionEnd = -1;
int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED;
int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED;
float mTextSize = 0;
int mTextStyle = 0;
CharSequence mHint;
@Override
public void setText(CharSequence text) {
mText = text;
mTextSelectionStart = mTextSelectionEnd = -1;
}
@Override
public void setText(CharSequence text, int selectionStart, int selectionEnd) {
mText = text;
mTextSelectionStart = selectionStart;
mTextSelectionEnd = selectionEnd;
}
@Override
public void setTextPaint(TextPaint paint) {
mTextColor = paint.getColor();
mTextBackgroundColor = paint.bgColor;
mTextSize = paint.getTextSize();
mTextStyle = 0;
Typeface tf = paint.getTypeface();
if (tf != null) {
if (tf.isBold()) {
mTextStyle |= ViewNode.TEXT_STYLE_BOLD;
}
if (tf.isItalic()) {
mTextStyle |= ViewNode.TEXT_STYLE_ITALIC;
}
}
int pflags = paint.getFlags();
if ((pflags& Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
mTextStyle |= ViewNode.TEXT_STYLE_BOLD;
}
if ((pflags& Paint.UNDERLINE_TEXT_FLAG) != 0) {
mTextStyle |= ViewNode.TEXT_STYLE_UNDERLINE;
}
if ((pflags& Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
mTextStyle |= ViewNode.TEXT_STYLE_STRIKE_THRU;
}
}
@Override
public void setHint(CharSequence hint) {
mHint = hint;
}
@Override
public CharSequence getText() {
return mText;
}
@Override
public int getTextSelectionStart() {
return mTextSelectionStart;
}
@Override
public int getTextSelectionEnd() {
return mTextSelectionEnd;
}
@Override
public CharSequence getHint() {
return mHint;
}
}
final static class ViewNodeTextImpl {
final CharSequence mText;
final int mTextSelectionStart;
final int mTextSelectionEnd;
int mTextColor;
int mTextBackgroundColor;
float mTextSize;
int mTextStyle;
final String mHint;
ViewNodeTextImpl(ViewAssistStructureImpl data) {
mText = data.mText;
mTextSelectionStart = data.mTextSelectionStart;
mTextSelectionEnd = data.mTextSelectionEnd;
mTextColor = data.mTextColor;
mTextBackgroundColor = data.mTextBackgroundColor;
mTextSize = data.mTextSize;
mTextStyle = data.mTextStyle;
mHint = data.mHint != null ? data.mHint.toString() : null;
}
ViewNodeTextImpl(Parcel in) {
mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mTextSelectionStart = in.readInt();
mTextSelectionEnd = in.readInt();
mTextColor = in.readInt();
mTextBackgroundColor = in.readInt();
mTextSize = in.readFloat();
mTextStyle = in.readInt();
mHint = in.readString();
}
void writeToParcel(Parcel out) {
TextUtils.writeToParcel(mText, out, 0);
out.writeInt(mTextSelectionStart);
out.writeInt(mTextSelectionEnd);
out.writeInt(mTextColor);
out.writeInt(mTextBackgroundColor);
out.writeFloat(mTextSize);
out.writeInt(mTextStyle);
out.writeString(mHint);
}
}
final static class ViewNodeImpl {
final int mX;
final int mY;
final int mScrollX;
final int mScrollY;
final int mWidth;
final int mHeight;
static final int FLAGS_DISABLED = 0x00000001;
static final int FLAGS_VISIBILITY_MASK = View.VISIBLE|View.INVISIBLE|View.GONE;
static final int FLAGS_FOCUSABLE = 0x00000010;
static final int FLAGS_FOCUSED = 0x00000020;
static final int FLAGS_ACCESSIBILITY_FOCUSED = 0x04000000;
static final int FLAGS_SELECTED = 0x00000040;
static final int FLAGS_ACTIVATED = 0x40000000;
static final int FLAGS_CHECKABLE = 0x00000100;
static final int FLAGS_CHECKED = 0x00000200;
static final int FLAGS_CLICKABLE = 0x00004000;
static final int FLAGS_LONG_CLICKABLE = 0x00200000;
final int mFlags;
final String mClassName;
final String mContentDescription;
final ViewNodeTextImpl mText;
final Bundle mExtras;
final ViewNodeImpl[] mChildren;
ViewNodeImpl(AssistStructure assistStructure, View view, int left, int top,
CharSequence contentDescription) {
mX = left;
mY = top;
mScrollX = view.getScrollX();
mScrollY = view.getScrollY();
mWidth = view.getWidth();
mHeight = view.getHeight();
int flags = view.getVisibility();
if (!view.isEnabled()) {
flags |= FLAGS_DISABLED;
}
if (!view.isClickable()) {
flags |= FLAGS_CLICKABLE;
}
if (!view.isFocusable()) {
flags |= FLAGS_FOCUSABLE;
}
if (!view.isFocused()) {
flags |= FLAGS_FOCUSED;
}
if (!view.isAccessibilityFocused()) {
flags |= FLAGS_ACCESSIBILITY_FOCUSED;
}
if (!view.isSelected()) {
flags |= FLAGS_SELECTED;
}
if (!view.isActivated()) {
flags |= FLAGS_ACTIVATED;
}
if (!view.isLongClickable()) {
flags |= FLAGS_LONG_CLICKABLE;
}
if (view instanceof Checkable) {
flags |= FLAGS_CHECKABLE;
if (((Checkable)view).isChecked()) {
flags |= FLAGS_CHECKED;
}
}
mFlags = flags;
mClassName = view.getAccessibilityClassName().toString();
mContentDescription = contentDescription != null ? contentDescription.toString() : null;
final ViewAssistStructureImpl viewData = assistStructure.mTmpViewAssistStructureImpl;
final Bundle extras = assistStructure.mTmpExtras;
view.onProvideAssistStructure(viewData, extras);
if (viewData.mText != null || viewData.mHint != null) {
mText = new ViewNodeTextImpl(viewData);
assistStructure.mTmpViewAssistStructureImpl = new ViewAssistStructureImpl();
} else {
mText = null;
}
if (!extras.isEmpty()) {
mExtras = extras;
assistStructure.mTmpExtras = new Bundle();
} else {
mExtras = null;
}
if (view instanceof ViewGroup) {
ViewGroup vg = (ViewGroup)view;
final int NCHILDREN = vg.getChildCount();
if (NCHILDREN > 0) {
mChildren = new ViewNodeImpl[NCHILDREN];
for (int i=0; i<NCHILDREN; i++) {
mChildren[i] = new ViewNodeImpl(assistStructure, vg.getChildAt(i));
}
} else {
mChildren = null;
}
} else {
mChildren = null;
}
}
ViewNodeImpl(AssistStructure assistStructure, View view) {
this(assistStructure, view, view.getLeft(), view.getTop(), view.getContentDescription());
}
ViewNodeImpl(Parcel in, PooledStringReader preader) {
mX = in.readInt();
mY = in.readInt();
mScrollX = in.readInt();
mScrollY = in.readInt();
mWidth = in.readInt();
mHeight = in.readInt();
mFlags = in.readInt();
mClassName = preader.readString();
mContentDescription = in.readString();
if (in.readInt() != 0) {
mText = new ViewNodeTextImpl(in);
} else {
mText = null;
}
mExtras = in.readBundle();
final int NCHILDREN = in.readInt();
if (NCHILDREN > 0) {
mChildren = new ViewNodeImpl[NCHILDREN];
for (int i=0; i<NCHILDREN; i++) {
mChildren[i] = new ViewNodeImpl(in, preader);
}
} else {
mChildren = null;
}
}
void writeToParcel(Parcel out, PooledStringWriter pwriter) {
out.writeInt(mX);
out.writeInt(mY);
out.writeInt(mScrollX);
out.writeInt(mScrollY);
out.writeInt(mWidth);
out.writeInt(mHeight);
out.writeInt(mFlags);
pwriter.writeString(mClassName);
out.writeString(mContentDescription);
if (mText != null) {
out.writeInt(1);
mText.writeToParcel(out);
} else {
out.writeInt(0);
}
out.writeBundle(mExtras);
if (mChildren != null) {
final int NCHILDREN = mChildren.length;
out.writeInt(NCHILDREN);
for (int i=0; i<NCHILDREN; i++) {
mChildren[i].writeToParcel(out, pwriter);
}
} else {
out.writeInt(0);
}
}
}
/**
* Provides access to information about a single view in the assist data.
*/
static public class ViewNode {
/**
* Magic value for text color that has not been defined, which is very unlikely
* to be confused with a real text color.
*/
public static final int TEXT_COLOR_UNDEFINED = 1;
public static final int TEXT_STYLE_BOLD = 1<<0;
public static final int TEXT_STYLE_ITALIC = 1<<1;
public static final int TEXT_STYLE_UNDERLINE = 1<<2;
public static final int TEXT_STYLE_STRIKE_THRU = 1<<3;
ViewNodeImpl mImpl;
public ViewNode() {
}
public int getLeft() {
return mImpl.mX;
}
public int getTop() {
return mImpl.mY;
}
public int getScrollX() {
return mImpl.mScrollX;
}
public int getScrollY() {
return mImpl.mScrollY;
}
public int getWidth() {
return mImpl.mWidth;
}
public int getHeight() {
return mImpl.mHeight;
}
public int getVisibility() {
return mImpl.mFlags&ViewNodeImpl.FLAGS_VISIBILITY_MASK;
}
public boolean isEnabled() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_DISABLED) == 0;
}
public boolean isClickable() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_CLICKABLE) != 0;
}
public boolean isFocusable() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_FOCUSABLE) != 0;
}
public boolean isFocused() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_FOCUSED) != 0;
}
public boolean isAccessibilityFocused() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_ACCESSIBILITY_FOCUSED) != 0;
}
public boolean isCheckable() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_CHECKABLE) != 0;
}
public boolean isChecked() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_CHECKED) != 0;
}
public boolean isSelected() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_SELECTED) != 0;
}
public boolean isActivated() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_ACTIVATED) != 0;
}
public boolean isLongClickable() {
return (mImpl.mFlags&ViewNodeImpl.FLAGS_LONG_CLICKABLE) != 0;
}
public String getClassName() {
return mImpl.mClassName;
}
public String getContentDescription() {
return mImpl.mContentDescription;
}
public CharSequence getText() {
return mImpl.mText != null ? mImpl.mText.mText : null;
}
public int getTextSelectionStart() {
return mImpl.mText != null ? mImpl.mText.mTextSelectionStart : -1;
}
public int getTextSelectionEnd() {
return mImpl.mText != null ? mImpl.mText.mTextSelectionEnd : -1;
}
public int getTextColor() {
return mImpl.mText != null ? mImpl.mText.mTextColor : TEXT_COLOR_UNDEFINED;
}
public int getTextBackgroundColor() {
return mImpl.mText != null ? mImpl.mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED;
}
public float getTextSize() {
return mImpl.mText != null ? mImpl.mText.mTextSize : 0;
}
public int getTextStyle() {
return mImpl.mText != null ? mImpl.mText.mTextStyle : 0;
}
public String getHint() {
return mImpl.mText != null ? mImpl.mText.mHint : null;
}
public Bundle getExtras() {
return mImpl.mExtras;
}
public int getChildCount() {
return mImpl.mChildren != null ? mImpl.mChildren.length : 0;
}
public void getChildAt(int index, ViewNode outNode) {
outNode.mImpl = mImpl.mChildren[index];
}
}
AssistStructure(Activity activity) {
mActivityComponent = activity.getComponentName();
ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
activity.getActivityToken());
for (int i=0; i<views.size(); i++) {
ViewRootImpl root = views.get(i);
View view = root.getView();
Rect rect = new Rect();
view.getBoundsOnScreen(rect);
CharSequence title = root.getTitle();
mRootViews.add(new ViewNodeImpl(this, view, rect.left, rect.top,
title != null ? title : view.getContentDescription()));
}
}
AssistStructure(Parcel in) {
PooledStringReader preader = new PooledStringReader(in);
mActivityComponent = ComponentName.readFromParcel(in);
final int N = in.readInt();
for (int i=0; i<N; i++) {
mRootViews.add(new ViewNodeImpl(in, preader));
}
//dump();
}
/** @hide */
public void dump() {
Log.i(TAG, "Activity: " + mActivityComponent.flattenToShortString());
ViewNode node = new ViewNode();
final int N = getWindowCount();
for (int i=0; i<N; i++) {
Log.i(TAG, "Window #" + i + ":");
getWindowAt(i, node);
dump(" ", node);
}
}
void dump(String prefix, ViewNode node) {
Log.i(TAG, prefix + "View [" + node.getLeft() + "," + node.getTop()
+ " " + node.getWidth() + "x" + node.getHeight() + "]" + " " + node.getClassName());
int scrollX = node.getScrollX();
int scrollY = node.getScrollY();
if (scrollX != 0 || scrollY != 0) {
Log.i(TAG, prefix + " Scroll: " + scrollX + "," + scrollY);
}
String contentDescription = node.getContentDescription();
if (contentDescription != null) {
Log.i(TAG, prefix + " Content description: " + contentDescription);
}
CharSequence text = node.getText();
if (text != null) {
Log.i(TAG, prefix + " Text (sel " + node.getTextSelectionStart() + "-"
+ node.getTextSelectionEnd() + "): " + text);
Log.i(TAG, prefix + " Text size: " + node.getTextSize() + " , style: #"
+ node.getTextStyle());
Log.i(TAG, prefix + " Text color fg: #" + Integer.toHexString(node.getTextColor())
+ ", bg: #" + Integer.toHexString(node.getTextBackgroundColor()));
}
String hint = node.getHint();
if (hint != null) {
Log.i(TAG, prefix + " Hint: " + hint);
}
Bundle extras = node.getExtras();
if (extras != null) {
Log.i(TAG, prefix + " Extras: " + extras);
}
final int NCHILDREN = node.getChildCount();
if (NCHILDREN > 0) {
Log.i(TAG, prefix + " Children:");
String cprefix = prefix + " ";
ViewNode cnode = new ViewNode();
for (int i=0; i<NCHILDREN; i++) {
node.getChildAt(i, cnode);
dump(cprefix, cnode);
}
}
}
/**
* Retrieve the framework-generated AssistStructure that is stored within
* the Bundle filled in by {@link Activity#onProvideAssistData}.
*/
public static AssistStructure getAssistStructure(Bundle assistBundle) {
return assistBundle.getParcelable(ASSIST_KEY);
}
public ComponentName getActivityComponent() {
return mActivityComponent;
}
/**
* Return the number of window contents that have been collected in this assist data.
*/
public int getWindowCount() {
return mRootViews.size();
}
/**
* Return the root view for one of the windows in the assist data.
* @param index Which window to retrieve, may be 0 to {@link #getWindowCount()}-1.
* @param outNode Node in which to place the window's root view.
*/
public void getWindowAt(int index, ViewNode outNode) {
outNode.mImpl = mRootViews.get(index);
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
int start = out.dataPosition();
PooledStringWriter pwriter = new PooledStringWriter(out);
ComponentName.writeToParcel(mActivityComponent, out);
final int N = mRootViews.size();
out.writeInt(N);
for (int i=0; i<N; i++) {
mRootViews.get(i).writeToParcel(out, pwriter);
}
pwriter.finish();
Log.i(TAG, "Flattened assist data: " + (out.dataPosition() - start) + " bytes");
}
public static final Parcelable.Creator<AssistStructure> CREATOR
= new Parcelable.Creator<AssistStructure>() {
public AssistStructure createFromParcel(Parcel in) {
return new AssistStructure(in);
}
public AssistStructure[] newArray(int size) {
return new AssistStructure[size];
}
};
}