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.Message; 28 import android.util.SparseArray; 29 import android.view.View; 30 31 import com.android.inputmethod.keyboard.PointerTracker; 32 import com.android.inputmethod.keyboard.internal.GestureTrail.Params; 33 import com.android.inputmethod.latin.utils.CollectionUtils; 34 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; 35 36 /** 37 * Draw gesture trail preview graphics during gesture. 38 */ 39 public 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 DrawingHandler(final GestureTrailsPreview outerInstance, final Params gestureTrailParams)60 public DrawingHandler(final GestureTrailsPreview outerInstance, 61 final Params gestureTrailParams) { 62 super(outerInstance); 63 mGestureTrailParams = gestureTrailParams; 64 } 65 66 @Override handleMessage(final Message msg)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 postUpdateGestureTrailPreview()77 public void postUpdateGestureTrailPreview() { 78 removeMessages(MSG_UPDATE_GESTURE_TRAIL); 79 sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_TRAIL), 80 mGestureTrailParams.mUpdateInterval); 81 } 82 } 83 GestureTrailsPreview(final View drawingView, final TypedArray mainKeyboardViewAttr)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 setKeyboardGeometry(final int[] originCoords, final int width, final int height)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 onDetachFromWindow()103 public void onDetachFromWindow() { 104 freeOffscreenBuffer(); 105 } 106 deallocateMemory()107 public void deallocateMemory() { 108 freeOffscreenBuffer(); 109 } 110 freeOffscreenBuffer()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 mayAllocateOffscreenBuffer()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 drawGestureTrails(final Canvas offscreenCanvas, final Paint paint, final Rect dirtyRect)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 drawPreview(final Canvas canvas)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 setPreviewPosition(final PointerTracker tracker)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