Update HandwritingIme to show bounds information
Bug: N/A Test: manually tested Change-Id: Id0ae8a133901b26c1a04143d061a460e76c7bc4b
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 com.google.android.test.handwritingime;
|
||||
|
||||
import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_EDITOR_BOUNDS;
|
||||
import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_NONE;
|
||||
import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_VISIBLE_LINE_BOUNDS;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorBoundsInfo;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.graphics.ColorUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BoundsInfoDrawHelper {
|
||||
private static final Paint sPaint = new Paint();
|
||||
private static final int EDITOR_BOUNDS_COLOR =
|
||||
ColorUtils.setAlphaComponent(Color.DKGRAY, 128);
|
||||
private static final int HANDWRITING_BOUNDS_COLOR =
|
||||
ColorUtils.setAlphaComponent(Color.BLUE, 128);
|
||||
private static final int VISIBLE_LINE_BOUNDS_COLOR =
|
||||
ColorUtils.setAlphaComponent(Color.MAGENTA, 128);
|
||||
|
||||
public static void draw(Canvas canvas, View inkView, int boundsInfoMode,
|
||||
CursorAnchorInfo cursorAnchorInfo) {
|
||||
if (boundsInfoMode == BOUNDS_INFO_NONE || cursorAnchorInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The matrix in CursorAnchorInfo transforms the editor coordinates to on-screen
|
||||
// coordinates. We then transform the matrix from the on-screen coordinates to the
|
||||
// inkView's coordinates. So the result matrix transforms the editor coordinates
|
||||
// to the inkView coordinates.
|
||||
final Matrix matrix = cursorAnchorInfo.getMatrix();
|
||||
inkView.transformMatrixToLocal(matrix);
|
||||
|
||||
if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) {
|
||||
drawEditorBoundsInfo(canvas, matrix, cursorAnchorInfo.getEditorBoundsInfo());
|
||||
}
|
||||
|
||||
if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) {
|
||||
drawVisibleLineBounds(canvas, matrix, cursorAnchorInfo.getVisibleLineBounds());
|
||||
}
|
||||
}
|
||||
|
||||
private static void setPaintForEditorBoundsInfo() {
|
||||
sPaint.reset();
|
||||
sPaint.setStyle(Paint.Style.STROKE);
|
||||
sPaint.setStrokeWidth(5f);
|
||||
}
|
||||
|
||||
private static void drawEditorBoundsInfo(Canvas canvas, Matrix matrix,
|
||||
@Nullable EditorBoundsInfo editorBoundsInfo) {
|
||||
if (editorBoundsInfo == null) {
|
||||
return;
|
||||
}
|
||||
final RectF editorBounds = editorBoundsInfo.getEditorBounds();
|
||||
setPaintForEditorBoundsInfo();
|
||||
if (editorBounds != null) {
|
||||
final RectF localEditorBounds = new RectF(editorBounds);
|
||||
matrix.mapRect(localEditorBounds);
|
||||
sPaint.setColor(EDITOR_BOUNDS_COLOR);
|
||||
canvas.drawRect(localEditorBounds, sPaint);
|
||||
}
|
||||
|
||||
final RectF handwritingBounds = editorBoundsInfo.getHandwritingBounds();
|
||||
if (handwritingBounds != null) {
|
||||
final RectF localHandwritingBounds = new RectF(handwritingBounds);
|
||||
matrix.mapRect(localHandwritingBounds);
|
||||
sPaint.setColor(HANDWRITING_BOUNDS_COLOR);
|
||||
canvas.drawRect(localHandwritingBounds, sPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setPaintForVisibleLineBounds() {
|
||||
sPaint.reset();
|
||||
sPaint.setStyle(Paint.Style.STROKE);
|
||||
sPaint.setStrokeWidth(2f);
|
||||
sPaint.setColor(VISIBLE_LINE_BOUNDS_COLOR);
|
||||
}
|
||||
|
||||
private static void drawVisibleLineBounds(Canvas canvas, Matrix matrix,
|
||||
List<RectF> visibleLineBounds) {
|
||||
if (visibleLineBounds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
setPaintForVisibleLineBounds();
|
||||
for (RectF lineBound : visibleLineBounds) {
|
||||
matrix.mapRect(lineBound);
|
||||
canvas.drawRect(lineBound, sPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.DeleteGesture;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.HandwritingGesture;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InsertGesture;
|
||||
@@ -34,6 +36,7 @@ import android.view.inputmethod.RemoveSpaceGesture;
|
||||
import android.view.inputmethod.SelectGesture;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
@@ -43,9 +46,6 @@ import java.util.Random;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
public class HandwritingIme extends InputMethodService {
|
||||
|
||||
public static final int HEIGHT_DP = 100;
|
||||
|
||||
private static final int OP_NONE = 0;
|
||||
private static final int OP_SELECT = 1;
|
||||
private static final int OP_DELETE = 2;
|
||||
@@ -62,6 +62,12 @@ public class HandwritingIme extends InputMethodService {
|
||||
private Spinner mRichGestureGranularitySpinner;
|
||||
private PointF mRichGestureStartPoint;
|
||||
|
||||
static final int BOUNDS_INFO_NONE = 0;
|
||||
static final int BOUNDS_INFO_VISIBLE_LINE_BOUNDS = 1;
|
||||
static final int BOUNDS_INFO_EDITOR_BOUNDS = 2;
|
||||
private int mBoundsInfoMode = BOUNDS_INFO_NONE;
|
||||
private LinearLayout mBoundsInfoCheckBoxes;
|
||||
|
||||
private final IntConsumer mResultConsumer = value -> Log.d(TAG, "Gesture result: " + value);
|
||||
|
||||
interface HandwritingFinisher {
|
||||
@@ -201,12 +207,7 @@ public class HandwritingIme extends InputMethodService {
|
||||
public View onCreateInputView() {
|
||||
Log.d(TAG, "onCreateInputView");
|
||||
final ViewGroup view = new FrameLayout(this);
|
||||
final View inner = new View(this);
|
||||
final float density = getResources().getDisplayMetrics().density;
|
||||
final int height = (int) (HEIGHT_DP * density);
|
||||
view.setPadding(0, 0, 0, 0);
|
||||
view.addView(inner, new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT, height));
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
@@ -214,9 +215,9 @@ public class HandwritingIme extends InputMethodService {
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.addView(getRichGestureActionsSpinner());
|
||||
layout.addView(getRichGestureGranularitySpinner());
|
||||
|
||||
layout.addView(getBoundsInfoCheckBoxes());
|
||||
layout.setBackgroundColor(getColor(R.color.holo_green_light));
|
||||
view.addView(layout);
|
||||
inner.setBackgroundColor(getColor(R.color.holo_green_light));
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -228,7 +229,7 @@ public class HandwritingIme extends InputMethodService {
|
||||
mRichGestureModeSpinner = new Spinner(this);
|
||||
mRichGestureModeSpinner.setPadding(100, 0, 100, 0);
|
||||
mRichGestureModeSpinner.setTooltipText("Handwriting IME mode");
|
||||
String[] items = new String[] {
|
||||
String[] items = new String[]{
|
||||
"Handwriting IME - Rich gesture disabled",
|
||||
"Rich gesture SELECT",
|
||||
"Rich gesture DELETE",
|
||||
@@ -259,6 +260,69 @@ public class HandwritingIme extends InputMethodService {
|
||||
return mRichGestureModeSpinner;
|
||||
}
|
||||
|
||||
private void updateCursorAnchorInfo(int boundsInfoMode) {
|
||||
final InputConnection ic = getCurrentInputConnection();
|
||||
if (ic == null) return;
|
||||
|
||||
if (boundsInfoMode == BOUNDS_INFO_NONE) {
|
||||
ic.requestCursorUpdates(0);
|
||||
return;
|
||||
}
|
||||
|
||||
final int cursorUpdateMode = InputConnection.CURSOR_UPDATE_MONITOR;
|
||||
int cursorUpdateFilter = 0;
|
||||
if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) {
|
||||
cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS;
|
||||
}
|
||||
|
||||
if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) {
|
||||
cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS;
|
||||
}
|
||||
ic.requestCursorUpdates(cursorUpdateMode | cursorUpdateFilter);
|
||||
}
|
||||
|
||||
private void updateBoundsInfoMode() {
|
||||
if (mInk != null) {
|
||||
mInk.setBoundsInfoMode(mBoundsInfoMode);
|
||||
}
|
||||
updateCursorAnchorInfo(mBoundsInfoMode);
|
||||
}
|
||||
|
||||
private View getBoundsInfoCheckBoxes() {
|
||||
if (mBoundsInfoCheckBoxes != null) {
|
||||
return mBoundsInfoCheckBoxes;
|
||||
}
|
||||
mBoundsInfoCheckBoxes = new LinearLayout(this);
|
||||
mBoundsInfoCheckBoxes.setPadding(100, 0, 100, 0);
|
||||
mBoundsInfoCheckBoxes.setOrientation(LinearLayout.HORIZONTAL);
|
||||
|
||||
final CheckBox editorBoundsInfoCheckBox = new CheckBox(this);
|
||||
editorBoundsInfoCheckBox.setText("EditorBoundsInfo");
|
||||
editorBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
mBoundsInfoMode |= BOUNDS_INFO_EDITOR_BOUNDS;
|
||||
} else {
|
||||
mBoundsInfoMode &= ~BOUNDS_INFO_EDITOR_BOUNDS;
|
||||
}
|
||||
updateBoundsInfoMode();
|
||||
});
|
||||
|
||||
final CheckBox visibleLineBoundsInfoCheckBox = new CheckBox(this);
|
||||
visibleLineBoundsInfoCheckBox.setText("VisibleLineBounds");
|
||||
visibleLineBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
mBoundsInfoMode |= BOUNDS_INFO_VISIBLE_LINE_BOUNDS;
|
||||
} else {
|
||||
mBoundsInfoMode &= ~BOUNDS_INFO_VISIBLE_LINE_BOUNDS;
|
||||
}
|
||||
updateBoundsInfoMode();
|
||||
});
|
||||
|
||||
mBoundsInfoCheckBoxes.addView(editorBoundsInfoCheckBox);
|
||||
mBoundsInfoCheckBoxes.addView(visibleLineBoundsInfoCheckBox);
|
||||
return mBoundsInfoCheckBoxes;
|
||||
}
|
||||
|
||||
private View getRichGestureGranularitySpinner() {
|
||||
if (mRichGestureGranularitySpinner != null) {
|
||||
return mRichGestureGranularitySpinner;
|
||||
@@ -294,6 +358,7 @@ public class HandwritingIme extends InputMethodService {
|
||||
Log.d(TAG, "onPrepareStylusHandwriting ");
|
||||
if (mInk == null) {
|
||||
mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer());
|
||||
mInk.setBoundsInfoMode(mBoundsInfoMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,4 +388,16 @@ public class HandwritingIme extends InputMethodService {
|
||||
private boolean areRichGesturesEnabled() {
|
||||
return mRichGestureMode != OP_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
|
||||
if (mInk != null) {
|
||||
mInk.setCursorAnchorInfo(cursorAnchorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartInput(EditorInfo attribute, boolean restarting) {
|
||||
updateCursorAnchorInfo(mBoundsInfoMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
|
||||
class InkView extends View {
|
||||
private static final long FINISH_TIMEOUT = 1500;
|
||||
@@ -37,6 +38,9 @@ class InkView extends View {
|
||||
private static final float STYLUS_MOVE_TOLERANCE = 1;
|
||||
private Runnable mFinishRunnable;
|
||||
|
||||
private CursorAnchorInfo mCursorAnchorInfo;
|
||||
private int mBoundsInfoMode;
|
||||
|
||||
InkView(Context context, HandwritingIme.HandwritingFinisher hwController,
|
||||
HandwritingIme.StylusConsumer consumer) {
|
||||
super(context);
|
||||
@@ -66,6 +70,7 @@ class InkView extends View {
|
||||
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
canvas.drawARGB(20, 255, 50, 50);
|
||||
BoundsInfoDrawHelper.draw(canvas, this, mBoundsInfoMode, mCursorAnchorInfo);
|
||||
}
|
||||
|
||||
private void stylusStart(float x, float y) {
|
||||
@@ -156,4 +161,15 @@ class InkView extends View {
|
||||
return mFinishRunnable;
|
||||
}
|
||||
|
||||
void setCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
|
||||
mCursorAnchorInfo = cursorAnchorInfo;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void setBoundsInfoMode(int boundsInfoMode) {
|
||||
if (boundsInfoMode != mBoundsInfoMode) {
|
||||
invalidate();
|
||||
}
|
||||
mBoundsInfoMode = boundsInfoMode;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user