Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 470a5805 authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka
Browse files

Add GestureTrailsPreview class

Change-Id: Ibf9dff6d834d34b4134dbe38a609fd983731eedc
parent a237cd4a
Loading
Loading
Loading
Loading
+201 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.inputmethod.keyboard.internal;

import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Message;
import android.util.SparseArray;
import android.view.View;

import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;

/**
 * Draw gesture trail preview graphics during gesture.
 */
public final class GestureTrailsPreview extends AbstractDrawingPreview {
    private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
            CollectionUtils.newSparseArray();
    private final Params mGesturePreviewTrailParams;
    private final Paint mGesturePaint;
    private int mOffscreenWidth;
    private int mOffscreenHeight;
    private int mOffscreenOffsetY;
    private Bitmap mOffscreenBuffer;
    private final Canvas mOffscreenCanvas = new Canvas();
    private final Rect mOffscreenSrcRect = new Rect();
    private final Rect mDirtyRect = new Rect();
    private final Rect mGesturePreviewTrailBoundsRect = new Rect(); // per trail

    private final DrawingHandler mDrawingHandler;

    private static final class DrawingHandler
            extends StaticInnerHandlerWrapper<GestureTrailsPreview> {
        private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 0;

        private final Params mGesturePreviewTrailParams;

        public DrawingHandler(final GestureTrailsPreview outerInstance,
                final Params gesturePreviewTrailParams) {
            super(outerInstance);
            mGesturePreviewTrailParams = gesturePreviewTrailParams;
        }

        @Override
        public void handleMessage(final Message msg) {
            final GestureTrailsPreview preview = getOuterInstance();
            if (preview == null) return;
            switch (msg.what) {
            case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
                preview.getDrawingView().invalidate();
                break;
            }
        }

        public void postUpdateGestureTrailPreview() {
            removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
            sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
                    mGesturePreviewTrailParams.mUpdateInterval);
        }
    }

    public GestureTrailsPreview(final View drawingView, final TypedArray mainKeyboardViewAttr) {
        super(drawingView);
        mGesturePreviewTrailParams = new Params(mainKeyboardViewAttr);
        mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
        final Paint gesturePaint = new Paint();
        gesturePaint.setAntiAlias(true);
        gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
        mGesturePaint = gesturePaint;
    }

    @Override
    public void setKeyboardGeometry(final int[] originCoords, final int width, final int height) {
        mOffscreenOffsetY = (int)(
                height * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
        mOffscreenWidth = width;
        mOffscreenHeight = mOffscreenOffsetY + height;
    }

    @Override
    public void onDetachFromWindow() {
        freeOffscreenBuffer();
    }

    private void freeOffscreenBuffer() {
        if (mOffscreenBuffer != null) {
            mOffscreenBuffer.recycle();
            mOffscreenBuffer = null;
        }
    }

    private void mayAllocateOffscreenBuffer() {
        if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth
                && mOffscreenBuffer.getHeight() == mOffscreenHeight) {
            return;
        }
        freeOffscreenBuffer();
        mOffscreenBuffer = Bitmap.createBitmap(
                mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888);
        mOffscreenCanvas.setBitmap(mOffscreenBuffer);
        mOffscreenCanvas.translate(0, mOffscreenOffsetY);
    }

    private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint,
            final Rect dirtyRect) {
        // Clear previous dirty rectangle.
        if (!dirtyRect.isEmpty()) {
            paint.setColor(Color.TRANSPARENT);
            paint.setStyle(Paint.Style.FILL);
            offscreenCanvas.drawRect(dirtyRect, paint);
        }
        dirtyRect.setEmpty();
        boolean needsUpdatingGesturePreviewTrail = false;
        // Draw gesture trails to offscreen buffer.
        synchronized (mGesturePreviewTrails) {
            // Trails count == fingers count that have ever been active.
            final int trailsCount = mGesturePreviewTrails.size();
            for (int index = 0; index < trailsCount; index++) {
                final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
                needsUpdatingGesturePreviewTrail |=
                        trail.drawGestureTrail(offscreenCanvas, paint,
                                mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams);
                // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail.
                dirtyRect.union(mGesturePreviewTrailBoundsRect);
            }
        }
        return needsUpdatingGesturePreviewTrail;
    }

    /**
     * Draws the preview
     * @param canvas The canvas where the preview is drawn.
     */
    @Override
    public void drawPreview(final Canvas canvas) {
        if (!isPreviewEnabled()) {
            return;
        }
        mayAllocateOffscreenBuffer();
        // Draw gesture trails to offscreen buffer.
        final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails(
                mOffscreenCanvas, mGesturePaint, mDirtyRect);
        if (needsUpdatingGesturePreviewTrail) {
            mDrawingHandler.postUpdateGestureTrailPreview();
        }
        // Transfer offscreen buffer to screen.
        if (!mDirtyRect.isEmpty()) {
            mOffscreenSrcRect.set(mDirtyRect);
            mOffscreenSrcRect.offset(0, mOffscreenOffsetY);
            canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null);
            // Note: Defer clearing the dirty rectangle here because we will get cleared
            // rectangle on the canvas.
        }
    }

    /**
     * Set the position of the preview.
     * @param tracker The new location of the preview is based on the points in PointerTracker.
     */
    @Override
    public void setPreviewPosition(final PointerTracker tracker) {
        if (!isPreviewEnabled()) {
            return;
        }
        GesturePreviewTrail trail;
        synchronized (mGesturePreviewTrails) {
            trail = mGesturePreviewTrails.get(tracker.mPointerId);
            if (trail == null) {
                trail = new GesturePreviewTrail();
                mGesturePreviewTrails.put(tracker.mPointerId, trail);
            }
        }
        trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());

        // TODO: Should narrow the invalidate region.
        getDrawingView().invalidate();
    }
}
+9 −145
Original line number Diff line number Diff line
@@ -18,79 +18,26 @@ package com.android.inputmethod.keyboard.internal;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Message;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.widget.RelativeLayout;

import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.CoordinateUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.SuggestedWords;

public final class PreviewPlacerView extends RelativeLayout {
    private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance();

    // TODO: Separate gesture preview trail drawing code into separate class.
    private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
            CollectionUtils.newSparseArray();
    private final Params mGesturePreviewTrailParams;
    private final Paint mGesturePaint;
    private boolean mDrawsGesturePreviewTrail;
    private int mOffscreenWidth;
    private int mOffscreenHeight;
    private int mOffscreenOffsetY;
    private Bitmap mOffscreenBuffer;
    private final Canvas mOffscreenCanvas = new Canvas();
    private final Rect mOffscreenSrcRect = new Rect();
    private final Rect mDirtyRect = new Rect();
    private final Rect mGesturePreviewTrailBoundsRect = new Rect(); // per trail
    // TODO: Move these AbstractDrawingPvreiew objects to MainKeyboardView.
    private final GestureFloatingPreviewText mGestureFloatingPreviewText;
    private final GestureTrailsPreview mGestureTrailsPreview;
    private final SlidingKeyInputPreview mSlidingKeyInputPreview;

    private final DrawingHandler mDrawingHandler;

    // TODO: Remove drawing handler.
    private static final class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
        private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 0;

        private final Params mGesturePreviewTrailParams;

        public DrawingHandler(final PreviewPlacerView outerInstance,
                final Params gesturePreviewTrailParams) {
            super(outerInstance);
            mGesturePreviewTrailParams = gesturePreviewTrailParams;
        }

        @Override
        public void handleMessage(final Message msg) {
            final PreviewPlacerView placerView = getOuterInstance();
            if (placerView == null) return;
            switch (msg.what) {
            case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
                placerView.invalidate();
                break;
            }
        }

        public void postUpdateGestureTrailPreview() {
            removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
            sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
                    mGesturePreviewTrailParams.mUpdateInterval);
        }
    }

    public PreviewPlacerView(final Context context, final AttributeSet attrs) {
        this(context, attrs, R.attr.keyboardViewStyle);
    }
@@ -104,17 +51,10 @@ public final class PreviewPlacerView extends RelativeLayout {
        // TODO: mGestureFloatingPreviewText could be an instance of GestureFloatingPreviewText or
        // MultiGesturePreviewText, depending on the user's choice in the settings.
        mGestureFloatingPreviewText = new GestureFloatingPreviewText(this, mainKeyboardViewAttr);
        mGesturePreviewTrailParams = new Params(mainKeyboardViewAttr);
        mGestureTrailsPreview = new GestureTrailsPreview(this, mainKeyboardViewAttr);
        mSlidingKeyInputPreview = new SlidingKeyInputPreview(this, mainKeyboardViewAttr);
        mainKeyboardViewAttr.recycle();

        mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);

        final Paint gesturePaint = new Paint();
        gesturePaint.setAntiAlias(true);
        gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
        mGesturePaint = gesturePaint;

        final Paint layerPaint = new Paint();
        layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
        setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
@@ -124,36 +64,21 @@ public final class PreviewPlacerView extends RelativeLayout {
            final int height) {
        CoordinateUtils.copy(mKeyboardViewOrigin, originCoords);
        mGestureFloatingPreviewText.setKeyboardGeometry(originCoords, width, height);
        mGestureTrailsPreview.setKeyboardGeometry(originCoords, width, height);
        mSlidingKeyInputPreview.setKeyboardGeometry(originCoords, width, height);
        mOffscreenOffsetY = (int)(
                height * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
        mOffscreenWidth = width;
        mOffscreenHeight = mOffscreenOffsetY + height;
    }

    // TODO: Move this method to MainKeyboardView
    public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
            final boolean drawsGestureFloatingPreviewText) {
        mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
        mGestureFloatingPreviewText.setPreviewEnabled(drawsGestureFloatingPreviewText);
        mGestureTrailsPreview.setPreviewEnabled(drawsGesturePreviewTrail);
    }

    // TODO: Move this method to MainKeyboardView
    public void invalidatePointer(final PointerTracker tracker) {
        mGestureFloatingPreviewText.setPreviewPosition(tracker);

        if (mDrawsGesturePreviewTrail) {
            GesturePreviewTrail trail;
            synchronized (mGesturePreviewTrails) {
                trail = mGesturePreviewTrails.get(tracker.mPointerId);
                if (trail == null) {
                    trail = new GesturePreviewTrail();
                    mGesturePreviewTrails.put(tracker.mPointerId, trail);
                }
            }
            trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());

            // TODO: Should narrow the invalidate region.
            invalidate();
        }
        mGestureTrailsPreview.setPreviewPosition(tracker);
    }

    // TODO: Move this method to MainKeyboardView
@@ -170,27 +95,8 @@ public final class PreviewPlacerView extends RelativeLayout {
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mGestureFloatingPreviewText.onDetachFromWindow();
        mGestureTrailsPreview.onDetachFromWindow();
        mSlidingKeyInputPreview.onDetachFromWindow();
        freeOffscreenBuffer();
    }

    private void freeOffscreenBuffer() {
        if (mOffscreenBuffer != null) {
            mOffscreenBuffer.recycle();
            mOffscreenBuffer = null;
        }
    }

    private void mayAllocateOffscreenBuffer() {
        if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth
                && mOffscreenBuffer.getHeight() == mOffscreenHeight) {
            return;
        }
        freeOffscreenBuffer();
        mOffscreenBuffer = Bitmap.createBitmap(
                mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888);
        mOffscreenCanvas.setBitmap(mOffscreenBuffer);
        mOffscreenCanvas.translate(0, mOffscreenOffsetY);
    }

    @Override
@@ -199,54 +105,12 @@ public final class PreviewPlacerView extends RelativeLayout {
        final int originX = CoordinateUtils.x(mKeyboardViewOrigin);
        final int originY = CoordinateUtils.y(mKeyboardViewOrigin);
        canvas.translate(originX, originY);
        if (mDrawsGesturePreviewTrail) {
            mayAllocateOffscreenBuffer();
            // Draw gesture trails to offscreen buffer.
            final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails(
                    mOffscreenCanvas, mGesturePaint, mDirtyRect);
            if (needsUpdatingGesturePreviewTrail) {
                mDrawingHandler.postUpdateGestureTrailPreview();
            }
            // Transfer offscreen buffer to screen.
            if (!mDirtyRect.isEmpty()) {
                mOffscreenSrcRect.set(mDirtyRect);
                mOffscreenSrcRect.offset(0, mOffscreenOffsetY);
                canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null);
                // Note: Defer clearing the dirty rectangle here because we will get cleared
                // rectangle on the canvas.
            }
        }
        mGestureFloatingPreviewText.drawPreview(canvas);
        mGestureTrailsPreview.drawPreview(canvas);
        mSlidingKeyInputPreview.drawPreview(canvas);
        canvas.translate(-originX, -originY);
    }

    private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint,
            final Rect dirtyRect) {
        // Clear previous dirty rectangle.
        if (!dirtyRect.isEmpty()) {
            paint.setColor(Color.TRANSPARENT);
            paint.setStyle(Paint.Style.FILL);
            offscreenCanvas.drawRect(dirtyRect, paint);
        }
        dirtyRect.setEmpty();
        boolean needsUpdatingGesturePreviewTrail = false;
        // Draw gesture trails to offscreen buffer.
        synchronized (mGesturePreviewTrails) {
            // Trails count == fingers count that have ever been active.
            final int trailsCount = mGesturePreviewTrails.size();
            for (int index = 0; index < trailsCount; index++) {
                final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
                needsUpdatingGesturePreviewTrail |=
                        trail.drawGestureTrail(offscreenCanvas, paint,
                                mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams);
                // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail.
                dirtyRect.union(mGesturePreviewTrailBoundsRect);
            }
        }
        return needsUpdatingGesturePreviewTrail;
    }

    // TODO: Move this method to MainKeyboardView.
    public void setGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
        mGestureFloatingPreviewText.setSuggetedWords(suggestedWords);