| /* | 
 |  * Copyright (C) 2008-2009 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 android.gesture; | 
 |  | 
 | import android.graphics.Bitmap; | 
 | import android.graphics.Canvas; | 
 | import android.graphics.Paint; | 
 | import android.graphics.Path; | 
 | import android.graphics.RectF; | 
 | import android.os.Parcel; | 
 | import android.os.Parcelable; | 
 | import android.util.Log; | 
 |  | 
 | import java.io.IOException; | 
 | import java.io.DataOutputStream; | 
 | import java.io.DataInputStream; | 
 | import java.io.ByteArrayOutputStream; | 
 | import java.io.ByteArrayInputStream; | 
 | import java.util.ArrayList; | 
 | import java.util.concurrent.atomic.AtomicInteger; | 
 |  | 
 | /** | 
 |  * A gesture is a hand-drawn shape on a touch screen. It can have one or multiple strokes. | 
 |  * Each stroke is a sequence of timed points. A user-defined gesture can be recognized by  | 
 |  * a GestureLibrary.  | 
 |  */ | 
 |  | 
 | public class Gesture implements Parcelable { | 
 |     private static final long GESTURE_ID_BASE = System.currentTimeMillis(); | 
 |  | 
 |     private static final int BITMAP_RENDERING_WIDTH = 2; | 
 |  | 
 |     private static final boolean BITMAP_RENDERING_ANTIALIAS = true; | 
 |     private static final boolean BITMAP_RENDERING_DITHER = true; | 
 |  | 
 |     private static final AtomicInteger sGestureCount = new AtomicInteger(0); | 
 |  | 
 |     private final RectF mBoundingBox = new RectF(); | 
 |  | 
 |     // the same as its instance ID | 
 |     private long mGestureID; | 
 |  | 
 |     private final ArrayList<GestureStroke> mStrokes = new ArrayList<GestureStroke>(); | 
 |  | 
 |     public Gesture() { | 
 |         mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public Object clone() { | 
 |         Gesture gesture = new Gesture(); | 
 |         gesture.mBoundingBox.set(mBoundingBox.left, mBoundingBox.top,  | 
 |                                         mBoundingBox.right, mBoundingBox.bottom); | 
 |         final int count = mStrokes.size(); | 
 |         for (int i = 0; i < count; i++) { | 
 |             GestureStroke stroke = mStrokes.get(i); | 
 |             gesture.mStrokes.add((GestureStroke)stroke.clone()); | 
 |         } | 
 |         return gesture; | 
 |     } | 
 |      | 
 |     /** | 
 |      * @return all the strokes of the gesture | 
 |      */ | 
 |     public ArrayList<GestureStroke> getStrokes() { | 
 |         return mStrokes; | 
 |     } | 
 |  | 
 |     /** | 
 |      * @return the number of strokes included by this gesture | 
 |      */ | 
 |     public int getStrokesCount() { | 
 |         return mStrokes.size(); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Adds a stroke to the gesture. | 
 |      *  | 
 |      * @param stroke | 
 |      */ | 
 |     public void addStroke(GestureStroke stroke) { | 
 |         mStrokes.add(stroke); | 
 |         mBoundingBox.union(stroke.boundingBox); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Calculates the total length of the gesture. When there are multiple strokes in | 
 |      * the gesture, this returns the sum of the lengths of all the strokes. | 
 |      *  | 
 |      * @return the length of the gesture | 
 |      */ | 
 |     public float getLength() { | 
 |         int len = 0; | 
 |         final ArrayList<GestureStroke> strokes = mStrokes; | 
 |         final int count = strokes.size(); | 
 |  | 
 |         for (int i = 0; i < count; i++) { | 
 |             len += strokes.get(i).length; | 
 |         } | 
 |  | 
 |         return len; | 
 |     } | 
 |  | 
 |     /** | 
 |      * @return the bounding box of the gesture | 
 |      */ | 
 |     public RectF getBoundingBox() { | 
 |         return mBoundingBox; | 
 |     } | 
 |  | 
 |     public Path toPath() { | 
 |         return toPath(null); | 
 |     } | 
 |  | 
 |     public Path toPath(Path path) { | 
 |         if (path == null) path = new Path(); | 
 |  | 
 |         final ArrayList<GestureStroke> strokes = mStrokes; | 
 |         final int count = strokes.size(); | 
 |  | 
 |         for (int i = 0; i < count; i++) { | 
 |             path.addPath(strokes.get(i).getPath()); | 
 |         } | 
 |  | 
 |         return path; | 
 |     } | 
 |  | 
 |     public Path toPath(int width, int height, int edge, int numSample) { | 
 |         return toPath(null, width, height, edge, numSample); | 
 |     } | 
 |  | 
 |     public Path toPath(Path path, int width, int height, int edge, int numSample) { | 
 |         if (path == null) path = new Path(); | 
 |  | 
 |         final ArrayList<GestureStroke> strokes = mStrokes; | 
 |         final int count = strokes.size(); | 
 |  | 
 |         for (int i = 0; i < count; i++) { | 
 |             path.addPath(strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample)); | 
 |         } | 
 |  | 
 |         return path; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Sets the id of the gesture. | 
 |      *  | 
 |      * @param id | 
 |      */ | 
 |     void setID(long id) { | 
 |         mGestureID = id; | 
 |     } | 
 |  | 
 |     /** | 
 |      * @return the id of the gesture | 
 |      */ | 
 |     public long getID() { | 
 |         return mGestureID; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Creates a bitmap of the gesture with a transparent background. | 
 |      *  | 
 |      * @param width width of the target bitmap | 
 |      * @param height height of the target bitmap | 
 |      * @param edge the edge | 
 |      * @param numSample | 
 |      * @param color | 
 |      * @return the bitmap | 
 |      */ | 
 |     public Bitmap toBitmap(int width, int height, int edge, int numSample, int color) { | 
 |         final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | 
 |         final Canvas canvas = new Canvas(bitmap); | 
 |  | 
 |         canvas.translate(edge, edge); | 
 |  | 
 |         final Paint paint = new Paint(); | 
 |         paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS); | 
 |         paint.setDither(BITMAP_RENDERING_DITHER); | 
 |         paint.setColor(color); | 
 |         paint.setStyle(Paint.Style.STROKE); | 
 |         paint.setStrokeJoin(Paint.Join.ROUND); | 
 |         paint.setStrokeCap(Paint.Cap.ROUND); | 
 |         paint.setStrokeWidth(BITMAP_RENDERING_WIDTH); | 
 |  | 
 |         final ArrayList<GestureStroke> strokes = mStrokes; | 
 |         final int count = strokes.size(); | 
 |  | 
 |         for (int i = 0; i < count; i++) { | 
 |             Path path = strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample); | 
 |             canvas.drawPath(path, paint); | 
 |         } | 
 |  | 
 |         return bitmap; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Creates a bitmap of the gesture with a transparent background. | 
 |      *  | 
 |      * @param width | 
 |      * @param height | 
 |      * @param inset | 
 |      * @param color | 
 |      * @return the bitmap | 
 |      */ | 
 |     public Bitmap toBitmap(int width, int height, int inset, int color) { | 
 |         final Bitmap bitmap = Bitmap.createBitmap(width, height, | 
 |                 Bitmap.Config.ARGB_8888); | 
 |         final Canvas canvas = new Canvas(bitmap); | 
 |  | 
 |         final Paint paint = new Paint(); | 
 |         paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS); | 
 |         paint.setDither(BITMAP_RENDERING_DITHER); | 
 |         paint.setColor(color); | 
 |         paint.setStyle(Paint.Style.STROKE); | 
 |         paint.setStrokeJoin(Paint.Join.ROUND); | 
 |         paint.setStrokeCap(Paint.Cap.ROUND); | 
 |         paint.setStrokeWidth(BITMAP_RENDERING_WIDTH); | 
 |  | 
 |         final Path path = toPath(); | 
 |         final RectF bounds = new RectF(); | 
 |         path.computeBounds(bounds, true); | 
 |  | 
 |         final float sx = (width - 2 * inset) / bounds.width(); | 
 |         final float sy = (height - 2 * inset) / bounds.height(); | 
 |         final float scale = sx > sy ? sy : sx; | 
 |         paint.setStrokeWidth(2.0f / scale); | 
 |  | 
 |         path.offset(-bounds.left + (width - bounds.width() * scale) / 2.0f, | 
 |                 -bounds.top + (height - bounds.height() * scale) / 2.0f); | 
 |  | 
 |         canvas.translate(inset, inset); | 
 |         canvas.scale(scale, scale); | 
 |  | 
 |         canvas.drawPath(path, paint); | 
 |  | 
 |         return bitmap; | 
 |     } | 
 |  | 
 |     void serialize(DataOutputStream out) throws IOException { | 
 |         final ArrayList<GestureStroke> strokes = mStrokes; | 
 |         final int count = strokes.size(); | 
 |  | 
 |         // Write gesture ID | 
 |         out.writeLong(mGestureID); | 
 |         // Write number of strokes | 
 |         out.writeInt(count); | 
 |  | 
 |         for (int i = 0; i < count; i++) { | 
 |             strokes.get(i).serialize(out); | 
 |         } | 
 |     } | 
 |  | 
 |     static Gesture deserialize(DataInputStream in) throws IOException { | 
 |         final Gesture gesture = new Gesture(); | 
 |  | 
 |         // Gesture ID | 
 |         gesture.mGestureID = in.readLong(); | 
 |         // Number of strokes | 
 |         final int count = in.readInt(); | 
 |  | 
 |         for (int i = 0; i < count; i++) { | 
 |             gesture.addStroke(GestureStroke.deserialize(in)); | 
 |         } | 
 |  | 
 |         return gesture; | 
 |     } | 
 |  | 
 |     public static final @android.annotation.NonNull Parcelable.Creator<Gesture> CREATOR = new Parcelable.Creator<Gesture>() { | 
 |         public Gesture createFromParcel(Parcel in) { | 
 |             Gesture gesture = null; | 
 |             final long gestureID = in.readLong(); | 
 |  | 
 |             final DataInputStream inStream = new DataInputStream( | 
 |                     new ByteArrayInputStream(in.createByteArray())); | 
 |  | 
 |             try { | 
 |                 gesture = deserialize(inStream); | 
 |             } catch (IOException e) { | 
 |                 Log.e(GestureConstants.LOG_TAG, "Error reading Gesture from parcel:", e); | 
 |             } finally { | 
 |                 GestureUtils.closeStream(inStream); | 
 |             } | 
 |  | 
 |             if (gesture != null) { | 
 |                 gesture.mGestureID = gestureID; | 
 |             } | 
 |  | 
 |             return gesture; | 
 |         } | 
 |  | 
 |         public Gesture[] newArray(int size) { | 
 |             return new Gesture[size]; | 
 |         } | 
 |     }; | 
 |  | 
 |     public void writeToParcel(Parcel out, int flags) { | 
 |         out.writeLong(mGestureID); | 
 |  | 
 |         boolean result = false; | 
 |         final ByteArrayOutputStream byteStream = | 
 |                 new ByteArrayOutputStream(GestureConstants.IO_BUFFER_SIZE); | 
 |         final DataOutputStream outStream = new DataOutputStream(byteStream); | 
 |  | 
 |         try { | 
 |             serialize(outStream); | 
 |             result = true; | 
 |         } catch (IOException e) { | 
 |             Log.e(GestureConstants.LOG_TAG, "Error writing Gesture to parcel:", e); | 
 |         } finally { | 
 |             GestureUtils.closeStream(outStream); | 
 |             GestureUtils.closeStream(byteStream); | 
 |         } | 
 |  | 
 |         if (result) { | 
 |             out.writeByteArray(byteStream.toByteArray()); | 
 |         } | 
 |     } | 
 |  | 
 |     public int describeContents() { | 
 |         return 0; | 
 |     } | 
 | } | 
 |  |