1 /* 2 * Copyright (C) 2022 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.google.android.test.handwritingime; 18 19 import android.content.Context; 20 import android.graphics.Canvas; 21 import android.graphics.Color; 22 import android.graphics.Paint; 23 import android.graphics.Path; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.view.WindowManager; 28 import android.view.WindowMetrics; 29 import android.view.inputmethod.CursorAnchorInfo; 30 31 class InkView extends View { 32 private static final long FINISH_TIMEOUT = 1500; 33 private final HandwritingIme.HandwritingFinisher mHwCanceller; 34 private final HandwritingIme.StylusConsumer mConsumer; 35 private final Paint mPaint; 36 private final Path mPath; 37 private float mX, mY; 38 private static final float STYLUS_MOVE_TOLERANCE = 1; 39 private Runnable mFinishRunnable; 40 41 private CursorAnchorInfo mCursorAnchorInfo; 42 private int mBoundsInfoMode; 43 InkView(Context context, HandwritingIme.HandwritingFinisher hwController, HandwritingIme.StylusConsumer consumer)44 InkView(Context context, HandwritingIme.HandwritingFinisher hwController, 45 HandwritingIme.StylusConsumer consumer) { 46 super(context); 47 mHwCanceller = hwController; 48 mConsumer = consumer; 49 50 mPaint = new Paint(); 51 mPaint.setAntiAlias(true); 52 mPaint.setDither(true); 53 mPaint.setColor(Color.GREEN); 54 mPaint.setStyle(Paint.Style.STROKE); 55 mPaint.setStrokeJoin(Paint.Join.ROUND); 56 mPaint.setStrokeCap(Paint.Cap.ROUND); 57 mPaint.setStrokeWidth(14); 58 59 mPath = new Path(); 60 61 WindowManager wm = context.getSystemService(WindowManager.class); 62 WindowMetrics metrics = wm.getCurrentWindowMetrics(); 63 setLayoutParams(new ViewGroup.LayoutParams( 64 metrics.getBounds().width(), metrics.getBounds().height())); 65 } 66 67 @Override onDraw(Canvas canvas)68 protected void onDraw(Canvas canvas) { 69 super.onDraw(canvas); 70 71 canvas.drawPath(mPath, mPaint); 72 canvas.drawARGB(20, 255, 50, 50); 73 BoundsInfoDrawHelper.draw(canvas, this, mBoundsInfoMode, mCursorAnchorInfo); 74 } 75 stylusStart(float x, float y)76 private void stylusStart(float x, float y) { 77 mPath.moveTo(x, y); 78 mX = x; 79 mY = y; 80 } 81 stylusMove(float x, float y)82 private void stylusMove(float x, float y) { 83 float dx = Math.abs(x - mX); 84 float dy = Math.abs(y - mY); 85 if (mPath.isEmpty()) { 86 stylusStart(x, y); 87 } 88 if (dx >= STYLUS_MOVE_TOLERANCE || dy >= STYLUS_MOVE_TOLERANCE) { 89 mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); 90 mX = x; 91 mY = y; 92 } 93 } 94 stylusFinish()95 private void stylusFinish() { 96 mPath.lineTo(mX, mY); 97 // TODO: support offscreen? e.g. mCanvas.drawPath(mPath, mPaint); 98 mPath.reset(); 99 mX = 0; 100 mY = 0; 101 102 } 103 104 105 @Override onTouchEvent(MotionEvent event)106 public boolean onTouchEvent(MotionEvent event) { 107 if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) { 108 mConsumer.onStylusEvent(event); 109 android.util.Log.w(HandwritingIme.TAG, "INK touch onStylusEvent " + event); 110 float x = event.getX(); 111 float y = event.getY(); 112 switch (event.getAction()) { 113 case MotionEvent.ACTION_DOWN: 114 cancelTimer(); 115 stylusStart(x, y); 116 invalidate(); 117 break; 118 case MotionEvent.ACTION_MOVE: 119 stylusMove(x, y); 120 invalidate(); 121 break; 122 123 case MotionEvent.ACTION_UP: 124 scheduleTimer(); 125 break; 126 127 } 128 return true; 129 } 130 return false; 131 } 132 cancelTimer()133 private void cancelTimer() { 134 if (mFinishRunnable != null) { 135 if (getHandler() != null) { 136 getHandler().removeCallbacks(mFinishRunnable); 137 } 138 mFinishRunnable = null; 139 } 140 if (getHandler() != null) { 141 getHandler().removeCallbacksAndMessages(null); 142 } 143 } 144 scheduleTimer()145 private void scheduleTimer() { 146 cancelTimer(); 147 if (getHandler() != null) { 148 postDelayed(getFinishRunnable(), FINISH_TIMEOUT); 149 } 150 } 151 getFinishRunnable()152 private Runnable getFinishRunnable() { 153 mFinishRunnable = () -> { 154 android.util.Log.e(HandwritingIme.TAG, "Hw view timer finishHandwriting "); 155 mHwCanceller.finish(); 156 stylusFinish(); 157 mPath.reset(); 158 invalidate(); 159 }; 160 161 return mFinishRunnable; 162 } 163 setCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo)164 void setCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { 165 mCursorAnchorInfo = cursorAnchorInfo; 166 invalidate(); 167 } 168 setBoundsInfoMode(int boundsInfoMode)169 void setBoundsInfoMode(int boundsInfoMode) { 170 if (boundsInfoMode != mBoundsInfoMode) { 171 invalidate(); 172 } 173 mBoundsInfoMode = boundsInfoMode; 174 } 175 } 176