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 float textSize; 41 final int textColor; 42 final CharSequence text; 43 final int scrollY; 44 final int[] lineCharOffsets; 45 final int[] lineBaselines; 46 TextEntry(AssistStructure.ViewNode node, int parentLeft, int parentTop, Matrix matrix)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 AssistVisualizer(Context context, @Nullable AttributeSet attrs)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 setAssistStructure(AssistStructure as)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 logTree()108 public void logTree() { 109 if (mAssistStructure != null) { 110 mAssistStructure.dump(); 111 } 112 } 113 logText()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