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.CollectionUtils; 34 import com.android.inputmethod.latin.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 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 private void freeOffscreenBuffer() { 108 if (mOffscreenBuffer != null) { 109 mOffscreenBuffer.recycle(); 110 mOffscreenBuffer = null; 111 } 112 } 113 114 private void mayAllocateOffscreenBuffer() { 115 if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth 116 && mOffscreenBuffer.getHeight() == mOffscreenHeight) { 117 return; 118 } 119 freeOffscreenBuffer(); 120 mOffscreenBuffer = Bitmap.createBitmap( 121 mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888); 122 mOffscreenCanvas.setBitmap(mOffscreenBuffer); 123 mOffscreenCanvas.translate(0, mOffscreenOffsetY); 124 } 125 126 private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint, 127 final Rect dirtyRect) { 128 // Clear previous dirty rectangle. 129 if (!dirtyRect.isEmpty()) { 130 paint.setColor(Color.TRANSPARENT); 131 paint.setStyle(Paint.Style.FILL); 132 offscreenCanvas.drawRect(dirtyRect, paint); 133 } 134 dirtyRect.setEmpty(); 135 boolean needsUpdatingGestureTrail = false; 136 // Draw gesture trails to offscreen buffer. 137 synchronized (mGestureTrails) { 138 // Trails count == fingers count that have ever been active. 139 final int trailsCount = mGestureTrails.size(); 140 for (int index = 0; index < trailsCount; index++) { 141 final GestureTrail trail = mGestureTrails.valueAt(index); 142 needsUpdatingGestureTrail |= trail.drawGestureTrail(offscreenCanvas, paint, 143 mGestureTrailBoundsRect, mGestureTrailParams); 144 // {@link #mGestureTrailBoundsRect} has bounding box of the trail. 145 dirtyRect.union(mGestureTrailBoundsRect); 146 } 147 } 148 return needsUpdatingGestureTrail; 149 } 150 151 /** 152 * Draws the preview 153 * @param canvas The canvas where the preview is drawn. 154 */ 155 @Override 156 public void drawPreview(final Canvas canvas) { 157 if (!isPreviewEnabled()) { 158 return; 159 } 160 mayAllocateOffscreenBuffer(); 161 // Draw gesture trails to offscreen buffer. 162 final boolean needsUpdatingGestureTrail = drawGestureTrails( 163 mOffscreenCanvas, mGesturePaint, mDirtyRect); 164 if (needsUpdatingGestureTrail) { 165 mDrawingHandler.postUpdateGestureTrailPreview(); 166 } 167 // Transfer offscreen buffer to screen. 168 if (!mDirtyRect.isEmpty()) { 169 mOffscreenSrcRect.set(mDirtyRect); 170 mOffscreenSrcRect.offset(0, mOffscreenOffsetY); 171 canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null); 172 // Note: Defer clearing the dirty rectangle here because we will get cleared 173 // rectangle on the canvas. 174 } 175 } 176 177 /** 178 * Set the position of the preview. 179 * @param tracker The new location of the preview is based on the points in PointerTracker. 180 */ 181 @Override 182 public void setPreviewPosition(final PointerTracker tracker) { 183 if (!isPreviewEnabled()) { 184 return; 185 } 186 GestureTrail trail; 187 synchronized (mGestureTrails) { 188 trail = mGestureTrails.get(tracker.mPointerId); 189 if (trail == null) { 190 trail = new GestureTrail(); 191 mGestureTrails.put(tracker.mPointerId, trail); 192 } 193 } 194 trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime()); 195 196 // TODO: Should narrow the invalidate region. 197 getDrawingView().invalidate(); 198 } 199 } 200