1 /*
2  * Copyright (C) 2013 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.inputmethod.keyboard.internal;
18 
19 import android.content.res.TypedArray;
20 import android.graphics.Bitmap;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Paint;
24 import android.graphics.PorterDuff;
25 import android.graphics.PorterDuffXfermode;
26 import android.graphics.Rect;
27 import android.os.Handler;
28 import android.util.SparseArray;
29 
30 import com.android.inputmethod.keyboard.PointerTracker;
31 
32 /**
33  * Draw preview graphics of multiple gesture trails during gesture input.
34  */
35 public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview implements Runnable {
36     private final SparseArray<GestureTrailDrawingPoints> mGestureTrails = new SparseArray<>();
37     private final GestureTrailDrawingParams mDrawingParams;
38     private final Paint mGesturePaint;
39     private int mOffscreenWidth;
40     private int mOffscreenHeight;
41     private int mOffscreenOffsetY;
42     private Bitmap mOffscreenBuffer;
43     private final Canvas mOffscreenCanvas = new Canvas();
44     private final Rect mOffscreenSrcRect = new Rect();
45     private final Rect mDirtyRect = new Rect();
46     private final Rect mGestureTrailBoundsRect = new Rect(); // per trail
47 
48     private final Handler mDrawingHandler = new Handler();
49 
GestureTrailsDrawingPreview(final TypedArray mainKeyboardViewAttr)50     public GestureTrailsDrawingPreview(final TypedArray mainKeyboardViewAttr) {
51         mDrawingParams = new GestureTrailDrawingParams(mainKeyboardViewAttr);
52         final Paint gesturePaint = new Paint();
53         gesturePaint.setAntiAlias(true);
54         gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
55         mGesturePaint = gesturePaint;
56     }
57 
58     @Override
setKeyboardViewGeometry(final int[] originCoords, final int width, final int height)59     public void setKeyboardViewGeometry(final int[] originCoords, final int width,
60             final int height) {
61         super.setKeyboardViewGeometry(originCoords, width, height);
62         mOffscreenOffsetY = (int)(height
63                 * GestureStrokeRecognitionPoints.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
64         mOffscreenWidth = width;
65         mOffscreenHeight = mOffscreenOffsetY + height;
66     }
67 
68     @Override
onDeallocateMemory()69     public void onDeallocateMemory() {
70         freeOffscreenBuffer();
71     }
72 
freeOffscreenBuffer()73     private void freeOffscreenBuffer() {
74         mOffscreenCanvas.setBitmap(null);
75         mOffscreenCanvas.setMatrix(null);
76         if (mOffscreenBuffer != null) {
77             mOffscreenBuffer.recycle();
78             mOffscreenBuffer = null;
79         }
80     }
81 
mayAllocateOffscreenBuffer()82     private void mayAllocateOffscreenBuffer() {
83         if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth
84                 && mOffscreenBuffer.getHeight() == mOffscreenHeight) {
85             return;
86         }
87         freeOffscreenBuffer();
88         mOffscreenBuffer = Bitmap.createBitmap(
89                 mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888);
90         mOffscreenCanvas.setBitmap(mOffscreenBuffer);
91         mOffscreenCanvas.translate(0, mOffscreenOffsetY);
92     }
93 
drawGestureTrails(final Canvas offscreenCanvas, final Paint paint, final Rect dirtyRect)94     private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint,
95             final Rect dirtyRect) {
96         // Clear previous dirty rectangle.
97         if (!dirtyRect.isEmpty()) {
98             paint.setColor(Color.TRANSPARENT);
99             paint.setStyle(Paint.Style.FILL);
100             offscreenCanvas.drawRect(dirtyRect, paint);
101         }
102         dirtyRect.setEmpty();
103         boolean needsUpdatingGestureTrail = false;
104         // Draw gesture trails to offscreen buffer.
105         synchronized (mGestureTrails) {
106             // Trails count == fingers count that have ever been active.
107             final int trailsCount = mGestureTrails.size();
108             for (int index = 0; index < trailsCount; index++) {
109                 final GestureTrailDrawingPoints trail = mGestureTrails.valueAt(index);
110                 needsUpdatingGestureTrail |= trail.drawGestureTrail(offscreenCanvas, paint,
111                         mGestureTrailBoundsRect, mDrawingParams);
112                 // {@link #mGestureTrailBoundsRect} has bounding box of the trail.
113                 dirtyRect.union(mGestureTrailBoundsRect);
114             }
115         }
116         return needsUpdatingGestureTrail;
117     }
118 
119     @Override
run()120     public void run() {
121         // Update preview.
122         invalidateDrawingView();
123     }
124 
125     /**
126      * Draws the preview
127      * @param canvas The canvas where the preview is drawn.
128      */
129     @Override
drawPreview(final Canvas canvas)130     public void drawPreview(final Canvas canvas) {
131         if (!isPreviewEnabled()) {
132             return;
133         }
134         mayAllocateOffscreenBuffer();
135         // Draw gesture trails to offscreen buffer.
136         final boolean needsUpdatingGestureTrail = drawGestureTrails(
137                 mOffscreenCanvas, mGesturePaint, mDirtyRect);
138         if (needsUpdatingGestureTrail) {
139             mDrawingHandler.removeCallbacks(this);
140             mDrawingHandler.postDelayed(this, mDrawingParams.mUpdateInterval);
141         }
142         // Transfer offscreen buffer to screen.
143         if (!mDirtyRect.isEmpty()) {
144             mOffscreenSrcRect.set(mDirtyRect);
145             mOffscreenSrcRect.offset(0, mOffscreenOffsetY);
146             canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null);
147             // Note: Defer clearing the dirty rectangle here because we will get cleared
148             // rectangle on the canvas.
149         }
150     }
151 
152     /**
153      * Set the position of the preview.
154      * @param tracker The new location of the preview is based on the points in PointerTracker.
155      */
156     @Override
setPreviewPosition(final PointerTracker tracker)157     public void setPreviewPosition(final PointerTracker tracker) {
158         if (!isPreviewEnabled()) {
159             return;
160         }
161         GestureTrailDrawingPoints trail;
162         synchronized (mGestureTrails) {
163             trail = mGestureTrails.get(tracker.mPointerId);
164             if (trail == null) {
165                 trail = new GestureTrailDrawingPoints();
166                 mGestureTrails.put(tracker.mPointerId, trail);
167             }
168         }
169         trail.addStroke(tracker.getGestureStrokeDrawingPoints(), tracker.getDownTime());
170 
171         // TODO: Should narrow the invalidate region.
172         invalidateDrawingView();
173     }
174 }
175