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.Message;
28import android.util.SparseArray;
29import android.view.View;
30
31import com.android.inputmethod.keyboard.PointerTracker;
32import com.android.inputmethod.keyboard.internal.GestureTrail.Params;
33import com.android.inputmethod.latin.utils.CollectionUtils;
34import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
35
36/**
37 * Draw gesture trail preview graphics during gesture.
38 */
39public final class GestureTrailsPreview extends AbstractDrawingPreview {
40    private final SparseArray<GestureTrail> mGestureTrails = CollectionUtils.newSparseArray();
41    private final Params mGestureTrailParams;
42    private final Paint mGesturePaint;
43    private int mOffscreenWidth;
44    private int mOffscreenHeight;
45    private int mOffscreenOffsetY;
46    private Bitmap mOffscreenBuffer;
47    private final Canvas mOffscreenCanvas = new Canvas();
48    private final Rect mOffscreenSrcRect = new Rect();
49    private final Rect mDirtyRect = new Rect();
50    private final Rect mGestureTrailBoundsRect = new Rect(); // per trail
51
52    private final DrawingHandler mDrawingHandler;
53
54    private static final class DrawingHandler
55            extends StaticInnerHandlerWrapper<GestureTrailsPreview> {
56        private static final int MSG_UPDATE_GESTURE_TRAIL = 0;
57
58        private final Params mGestureTrailParams;
59
60        public DrawingHandler(final GestureTrailsPreview outerInstance,
61                final Params gestureTrailParams) {
62            super(outerInstance);
63            mGestureTrailParams = gestureTrailParams;
64        }
65
66        @Override
67        public void handleMessage(final Message msg) {
68            final GestureTrailsPreview preview = getOuterInstance();
69            if (preview == null) return;
70            switch (msg.what) {
71            case MSG_UPDATE_GESTURE_TRAIL:
72                preview.getDrawingView().invalidate();
73                break;
74            }
75        }
76
77        public void postUpdateGestureTrailPreview() {
78            removeMessages(MSG_UPDATE_GESTURE_TRAIL);
79            sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_TRAIL),
80                    mGestureTrailParams.mUpdateInterval);
81        }
82    }
83
84    public GestureTrailsPreview(final View drawingView, final TypedArray mainKeyboardViewAttr) {
85        super(drawingView);
86        mGestureTrailParams = new Params(mainKeyboardViewAttr);
87        mDrawingHandler = new DrawingHandler(this, mGestureTrailParams);
88        final Paint gesturePaint = new Paint();
89        gesturePaint.setAntiAlias(true);
90        gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
91        mGesturePaint = gesturePaint;
92    }
93
94    @Override
95    public void setKeyboardGeometry(final int[] originCoords, final int width, final int height) {
96        mOffscreenOffsetY = (int)(
97                height * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
98        mOffscreenWidth = width;
99        mOffscreenHeight = mOffscreenOffsetY + height;
100    }
101
102    @Override
103    public void onDetachFromWindow() {
104        freeOffscreenBuffer();
105    }
106
107    public void deallocateMemory() {
108        freeOffscreenBuffer();
109    }
110
111    private void freeOffscreenBuffer() {
112        mOffscreenCanvas.setBitmap(null);
113        mOffscreenCanvas.setMatrix(null);
114        if (mOffscreenBuffer != null) {
115            mOffscreenBuffer.recycle();
116            mOffscreenBuffer = null;
117        }
118    }
119
120    private void mayAllocateOffscreenBuffer() {
121        if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth
122                && mOffscreenBuffer.getHeight() == mOffscreenHeight) {
123            return;
124        }
125        freeOffscreenBuffer();
126        mOffscreenBuffer = Bitmap.createBitmap(
127                mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888);
128        mOffscreenCanvas.setBitmap(mOffscreenBuffer);
129        mOffscreenCanvas.translate(0, mOffscreenOffsetY);
130    }
131
132    private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint,
133            final Rect dirtyRect) {
134        // Clear previous dirty rectangle.
135        if (!dirtyRect.isEmpty()) {
136            paint.setColor(Color.TRANSPARENT);
137            paint.setStyle(Paint.Style.FILL);
138            offscreenCanvas.drawRect(dirtyRect, paint);
139        }
140        dirtyRect.setEmpty();
141        boolean needsUpdatingGestureTrail = false;
142        // Draw gesture trails to offscreen buffer.
143        synchronized (mGestureTrails) {
144            // Trails count == fingers count that have ever been active.
145            final int trailsCount = mGestureTrails.size();
146            for (int index = 0; index < trailsCount; index++) {
147                final GestureTrail trail = mGestureTrails.valueAt(index);
148                needsUpdatingGestureTrail |= trail.drawGestureTrail(offscreenCanvas, paint,
149                        mGestureTrailBoundsRect, mGestureTrailParams);
150                // {@link #mGestureTrailBoundsRect} has bounding box of the trail.
151                dirtyRect.union(mGestureTrailBoundsRect);
152            }
153        }
154        return needsUpdatingGestureTrail;
155    }
156
157    /**
158     * Draws the preview
159     * @param canvas The canvas where the preview is drawn.
160     */
161    @Override
162    public void drawPreview(final Canvas canvas) {
163        if (!isPreviewEnabled()) {
164            return;
165        }
166        mayAllocateOffscreenBuffer();
167        // Draw gesture trails to offscreen buffer.
168        final boolean needsUpdatingGestureTrail = drawGestureTrails(
169                mOffscreenCanvas, mGesturePaint, mDirtyRect);
170        if (needsUpdatingGestureTrail) {
171            mDrawingHandler.postUpdateGestureTrailPreview();
172        }
173        // Transfer offscreen buffer to screen.
174        if (!mDirtyRect.isEmpty()) {
175            mOffscreenSrcRect.set(mDirtyRect);
176            mOffscreenSrcRect.offset(0, mOffscreenOffsetY);
177            canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null);
178            // Note: Defer clearing the dirty rectangle here because we will get cleared
179            // rectangle on the canvas.
180        }
181    }
182
183    /**
184     * Set the position of the preview.
185     * @param tracker The new location of the preview is based on the points in PointerTracker.
186     */
187    @Override
188    public void setPreviewPosition(final PointerTracker tracker) {
189        if (!isPreviewEnabled()) {
190            return;
191        }
192        GestureTrail trail;
193        synchronized (mGestureTrails) {
194            trail = mGestureTrails.get(tracker.mPointerId);
195            if (trail == null) {
196                trail = new GestureTrail();
197                mGestureTrails.put(tracker.mPointerId, trail);
198            }
199        }
200        trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());
201
202        // TODO: Should narrow the invalidate region.
203        getDrawingView().invalidate();
204    }
205}
206