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