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 
17 package com.android.test.voiceinteraction;
18 
19 import android.annotation.Nullable;
20 import android.app.assist.AssistStructure;
21 import android.content.Context;
22 import android.graphics.Canvas;
23 import android.graphics.Matrix;
24 import android.graphics.Paint;
25 import android.graphics.Rect;
26 import android.util.AttributeSet;
27 import android.util.Log;
28 import android.view.View;
29 
30 import java.util.ArrayList;
31 
32 public 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 CharSequence text;
41         final int scrollY;
42         final int[] lineCharOffsets;
43         final int[] lineBaselines;
44 
TextEntry(AssistStructure.ViewNode node, int parentLeft, int parentTop, Matrix matrix)45         TextEntry(AssistStructure.ViewNode node, int parentLeft, int parentTop, Matrix matrix) {
46             int left = parentLeft+node.getLeft();
47             int top = parentTop+node.getTop();
48             bounds = new Rect(left, top, left+node.getWidth(), top+node.getHeight());
49             this.parentLeft = parentLeft;
50             this.parentTop = parentTop;
51             this.matrix = new Matrix(matrix);
52             this.className = node.getClassName();
53             this.text = node.getText() != null ? node.getText() : node.getContentDescription();
54             this.scrollY = node.getScrollY();
55             this.lineCharOffsets = node.getTextLineCharOffsets();
56             this.lineBaselines = node.getTextLineBaselines();
57         }
58     }
59 
60     AssistStructure mAssistStructure;
61     final Paint mFramePaint = new Paint();
62     final Paint mFrameBaselinePaint = new Paint();
63     final Paint mFrameNoTransformPaint = new Paint();
64     final ArrayList<Matrix> mMatrixStack = new ArrayList<>();
65     final ArrayList<TextEntry> mTextRects = new ArrayList<>();
66     final int[] mTmpLocation = new int[2];
67 
AssistVisualizer(Context context, @Nullable AttributeSet attrs)68     public AssistVisualizer(Context context, @Nullable AttributeSet attrs) {
69         super(context, attrs);
70         setWillNotDraw(false);
71         mFramePaint.setColor(0xffff0000);
72         mFramePaint.setStyle(Paint.Style.STROKE);
73         mFramePaint.setStrokeWidth(0);
74         mFrameBaselinePaint.setColor(0xa0b0b000);
75         mFrameBaselinePaint.setStyle(Paint.Style.STROKE);
76         mFrameBaselinePaint.setStrokeWidth(0);
77         float density = getResources().getDisplayMetrics().density;
78         mFramePaint.setShadowLayer(density, density, density, 0xff000000);
79         mFrameNoTransformPaint.setColor(0xff0000ff);
80         mFrameNoTransformPaint.setStyle(Paint.Style.STROKE);
81         mFrameNoTransformPaint.setStrokeWidth(0);
82         mFrameNoTransformPaint.setShadowLayer(density, density, density, 0xff000000);
83     }
84 
setAssistStructure(AssistStructure as)85     public void setAssistStructure(AssistStructure as) {
86         mAssistStructure = as;
87         mTextRects.clear();
88         final int N = as.getWindowNodeCount();
89         if (N > 0) {
90             for (int i=0; i<N; i++) {
91                 AssistStructure.WindowNode windowNode = as.getWindowNodeAt(i);
92                 mMatrixStack.clear();
93                 Matrix matrix = new Matrix();
94                 matrix.setTranslate(windowNode.getLeft(), windowNode.getTop());
95                 mMatrixStack.add(matrix);
96                 buildTextRects(windowNode.getRootViewNode(), 0, windowNode.getLeft(),
97                         windowNode.getTop());
98             }
99         }
100         Log.d(TAG, "Building text rects in " + this + ": found " + mTextRects.size());
101         invalidate();
102     }
103 
logTree()104     public void logTree() {
105         if (mAssistStructure != null) {
106             mAssistStructure.dump();
107         }
108     }
109 
logText()110     public void logText() {
111         final int N = mTextRects.size();
112         for (int i=0; i<N; i++) {
113             TextEntry te = mTextRects.get(i);
114             Log.d(TAG, "View " + te.className + " " + te.bounds.toShortString()
115                     + " in " + te.parentLeft + "," + te.parentTop
116                     + " matrix=" + te.matrix.toShortString() + ": "
117                     + te.text);
118             if (te.lineCharOffsets != null && te.lineBaselines != null) {
119                 final int num = te.lineCharOffsets.length < te.lineBaselines.length
120                         ? te.lineCharOffsets.length : te.lineBaselines.length;
121                 for (int j=0; j<num; j++) {
122                     Log.d(TAG, "  Line #" + j + ": offset=" + te.lineCharOffsets[j]
123                             + " baseline=" + te.lineBaselines[j]);
124                 }
125             }
126         }
127     }
128 
129     public void clearAssistData() {
130         mAssistStructure = null;
131         mTextRects.clear();
132     }
133 
134     void buildTextRects(AssistStructure.ViewNode root, int matrixStackIndex,
135             int parentLeft, int parentTop) {
136         if (root.getVisibility() != View.VISIBLE) {
137             return;
138         }
139         Matrix parentMatrix = mMatrixStack.get(matrixStackIndex);
140         matrixStackIndex++;
141         Matrix matrix;
142         if (mMatrixStack.size() > matrixStackIndex) {
143             matrix = mMatrixStack.get(matrixStackIndex);
144             matrix.set(parentMatrix);
145         } else {
146             matrix = new Matrix(parentMatrix);
147             mMatrixStack.add(matrix);
148         }
149         matrix.preTranslate(root.getLeft(), root.getTop());
150         int left = parentLeft + root.getLeft();
151         int top = parentTop + root.getTop();
152         Matrix transform = root.getTransformation();
153         if (transform != null) {
154             matrix.preConcat(transform);
155         }
156         if (root.getText() != null || root.getContentDescription() != null) {
157             TextEntry te = new TextEntry(root, parentLeft, parentTop, matrix);
158             mTextRects.add(te);
159         }
160         final int N = root.getChildCount();
161         if (N > 0) {
162             left -= root.getScrollX();
163             top -= root.getScrollY();
164             matrix.preTranslate(-root.getScrollX(), -root.getScrollY());
165             for (int i=0; i<N; i++) {
166                 AssistStructure.ViewNode child = root.getChildAt(i);
167                 buildTextRects(child, matrixStackIndex, left, top);
168             }
169         }
170     }
171 
172     @Override
173     protected void onDraw(Canvas canvas) {
174         super.onDraw(canvas);
175         getLocationOnScreen(mTmpLocation);
176         final int N = mTextRects.size();
177         Log.d(TAG, "Drawing text rects in " + this + ": found " + mTextRects.size());
178         for (int i=0; i<N; i++) {
179             TextEntry te = mTextRects.get(i);
180             canvas.drawRect(te.bounds.left - mTmpLocation[0], te.bounds.top - mTmpLocation[1],
181                     te.bounds.right - mTmpLocation[0], te.bounds.bottom - mTmpLocation[1],
182                     mFrameNoTransformPaint);
183         }
184         for (int i=0; i<N; i++) {
185             TextEntry te = mTextRects.get(i);
186             canvas.save();
187             canvas.translate(-mTmpLocation[0], -mTmpLocation[1]);
188             canvas.concat(te.matrix);
189             canvas.drawRect(0, 0, te.bounds.right - te.bounds.left, te.bounds.bottom - te.bounds.top,
190                     mFramePaint);
191             if (te.lineBaselines != null) {
192                 for (int j=0; j<te.lineBaselines.length; j++) {
193                     canvas.drawLine(0, te.lineBaselines[j] - te.scrollY,
194                             te.bounds.right - te.bounds.left, te.lineBaselines[j] - te.scrollY,
195                             mFrameBaselinePaint);
196                 }
197             }
198             canvas.restore();
199         }
200     }
201 }
202