1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.test.voiceinteraction;
18
19import android.annotation.Nullable;
20import android.app.assist.AssistStructure;
21import android.content.Context;
22import android.graphics.Canvas;
23import android.graphics.Matrix;
24import android.graphics.Paint;
25import android.graphics.Rect;
26import android.util.AttributeSet;
27import android.util.Log;
28import android.view.View;
29
30import java.util.ArrayList;
31
32public class AssistVisualizer extends View {
33    static final String TAG = "AssistVisualizer";
34
35    static class TextEntry {
36        final Rect bounds;
37        final int parentLeft, parentTop;
38        final Matrix matrix;
39        final String className;
40        final float textSize;
41        final int textColor;
42        final CharSequence text;
43        final int scrollY;
44        final int[] lineCharOffsets;
45        final int[] lineBaselines;
46
47        TextEntry(AssistStructure.ViewNode node, int parentLeft, int parentTop, Matrix matrix) {
48            int left = parentLeft+node.getLeft();
49            int top = parentTop+node.getTop();
50            bounds = new Rect(left, top, left+node.getWidth(), top+node.getHeight());
51            this.parentLeft = parentLeft;
52            this.parentTop = parentTop;
53            this.matrix = new Matrix(matrix);
54            this.className = node.getClassName();
55            this.textSize = node.getTextSize();
56            this.textColor = node.getTextColor();
57            this.text = node.getText() != null ? node.getText() : node.getContentDescription();
58            this.scrollY = node.getScrollY();
59            this.lineCharOffsets = node.getTextLineCharOffsets();
60            this.lineBaselines = node.getTextLineBaselines();
61        }
62    }
63
64    AssistStructure mAssistStructure;
65    final Paint mFramePaint = new Paint();
66    final Paint mFrameBaselinePaint = new Paint();
67    final Paint mFrameNoTransformPaint = new Paint();
68    final ArrayList<Matrix> mMatrixStack = new ArrayList<>();
69    final ArrayList<TextEntry> mTextRects = new ArrayList<>();
70    final int[] mTmpLocation = new int[2];
71
72    public AssistVisualizer(Context context, @Nullable AttributeSet attrs) {
73        super(context, attrs);
74        setWillNotDraw(false);
75        mFramePaint.setColor(0xffff0000);
76        mFramePaint.setStyle(Paint.Style.STROKE);
77        mFramePaint.setStrokeWidth(0);
78        mFrameBaselinePaint.setColor(0xa0b0b000);
79        mFrameBaselinePaint.setStyle(Paint.Style.STROKE);
80        mFrameBaselinePaint.setStrokeWidth(0);
81        float density = getResources().getDisplayMetrics().density;
82        mFramePaint.setShadowLayer(density, density, density, 0xff000000);
83        mFrameNoTransformPaint.setColor(0xff0000ff);
84        mFrameNoTransformPaint.setStyle(Paint.Style.STROKE);
85        mFrameNoTransformPaint.setStrokeWidth(0);
86        mFrameNoTransformPaint.setShadowLayer(density, density, density, 0xff000000);
87    }
88
89    public void setAssistStructure(AssistStructure as) {
90        mAssistStructure = as;
91        mTextRects.clear();
92        final int N = as.getWindowNodeCount();
93        if (N > 0) {
94            for (int i=0; i<N; i++) {
95                AssistStructure.WindowNode windowNode = as.getWindowNodeAt(i);
96                mMatrixStack.clear();
97                Matrix matrix = new Matrix();
98                matrix.setTranslate(windowNode.getLeft(), windowNode.getTop());
99                mMatrixStack.add(matrix);
100                buildTextRects(windowNode.getRootViewNode(), 0, windowNode.getLeft(),
101                        windowNode.getTop());
102            }
103        }
104        Log.d(TAG, "Building text rects in " + this + ": found " + mTextRects.size());
105        invalidate();
106    }
107
108    public void logTree() {
109        if (mAssistStructure != null) {
110            mAssistStructure.dump();
111        }
112    }
113
114    public void logText() {
115        final int N = mTextRects.size();
116        for (int i=0; i<N; i++) {
117            TextEntry te = mTextRects.get(i);
118            Log.d(TAG, "View " + te.className + " " + te.bounds.toShortString()
119                    + " in " + te.parentLeft + "," + te.parentTop
120                    + " matrix=" + te.matrix.toShortString()
121                    + " size=" + te.textSize + " color=#" + Integer.toHexString(te.textColor)
122                    + ": "
123                    + te.text);
124            if (te.lineCharOffsets != null && te.lineBaselines != null) {
125                final int num = te.lineCharOffsets.length < te.lineBaselines.length
126                        ? te.lineCharOffsets.length : te.lineBaselines.length;
127                for (int j=0; j<num; j++) {
128                    Log.d(TAG, "  Line #" + j + ": offset=" + te.lineCharOffsets[j]
129                            + " baseline=" + te.lineBaselines[j]);
130                }
131            }
132        }
133    }
134
135    public void clearAssistData() {
136        mAssistStructure = null;
137        mTextRects.clear();
138    }
139
140    void buildTextRects(AssistStructure.ViewNode root, int matrixStackIndex,
141            int parentLeft, int parentTop) {
142        if (root.getVisibility() != View.VISIBLE) {
143            return;
144        }
145        Matrix parentMatrix = mMatrixStack.get(matrixStackIndex);
146        matrixStackIndex++;
147        Matrix matrix;
148        if (mMatrixStack.size() > matrixStackIndex) {
149            matrix = mMatrixStack.get(matrixStackIndex);
150            matrix.set(parentMatrix);
151        } else {
152            matrix = new Matrix(parentMatrix);
153            mMatrixStack.add(matrix);
154        }
155        matrix.preTranslate(root.getLeft(), root.getTop());
156        int left = parentLeft + root.getLeft();
157        int top = parentTop + root.getTop();
158        Matrix transform = root.getTransformation();
159        if (transform != null) {
160            matrix.preConcat(transform);
161        }
162        if (root.getText() != null || root.getContentDescription() != null) {
163            TextEntry te = new TextEntry(root, parentLeft, parentTop, matrix);
164            mTextRects.add(te);
165        }
166        final int N = root.getChildCount();
167        if (N > 0) {
168            left -= root.getScrollX();
169            top -= root.getScrollY();
170            matrix.preTranslate(-root.getScrollX(), -root.getScrollY());
171            for (int i=0; i<N; i++) {
172                AssistStructure.ViewNode child = root.getChildAt(i);
173                buildTextRects(child, matrixStackIndex, left, top);
174            }
175        }
176    }
177
178    @Override
179    protected void onDraw(Canvas canvas) {
180        super.onDraw(canvas);
181        getLocationOnScreen(mTmpLocation);
182        final int N = mTextRects.size();
183        Log.d(TAG, "Drawing text rects in " + this + ": found " + mTextRects.size());
184        for (int i=0; i<N; i++) {
185            TextEntry te = mTextRects.get(i);
186            canvas.drawRect(te.bounds.left - mTmpLocation[0], te.bounds.top - mTmpLocation[1],
187                    te.bounds.right - mTmpLocation[0], te.bounds.bottom - mTmpLocation[1],
188                    mFrameNoTransformPaint);
189        }
190        for (int i=0; i<N; i++) {
191            TextEntry te = mTextRects.get(i);
192            canvas.save();
193            canvas.translate(-mTmpLocation[0], -mTmpLocation[1]);
194            canvas.concat(te.matrix);
195            canvas.drawRect(0, 0, te.bounds.right - te.bounds.left, te.bounds.bottom - te.bounds.top,
196                    mFramePaint);
197            if (te.lineBaselines != null) {
198                for (int j=0; j<te.lineBaselines.length; j++) {
199                    canvas.drawLine(0, te.lineBaselines[j] - te.scrollY,
200                            te.bounds.right - te.bounds.left, te.lineBaselines[j] - te.scrollY,
201                            mFrameBaselinePaint);
202                }
203            }
204            canvas.restore();
205        }
206    }
207}
208