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
17package com.android.inputmethod.keyboard.internal;
18
19import android.content.res.TypedArray;
20import android.graphics.Bitmap;
21import android.graphics.Canvas;
22import android.graphics.Color;
23import android.graphics.Paint;
24import android.graphics.PorterDuff;
25import android.graphics.PorterDuffXfermode;
26import android.graphics.Rect;
27import android.os.Handler;
28import android.util.SparseArray;
29
30import com.android.inputmethod.keyboard.PointerTracker;
31
32/**
33 * Draw preview graphics of multiple gesture trails during gesture input.
34 */
35public 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
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
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
69    public void onDeallocateMemory() {
70        freeOffscreenBuffer();
71    }
72
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
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
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
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
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
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