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