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