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

Commit 14191c7d authored by Romain Guy's avatar Romain Guy Committed by The Android Open Source Project
Browse files

am b6d99b7d: Modify how GestureLibrary stores its data. The XML format is now...

am b6d99b7d: Modify how GestureLibrary stores its data. The XML format is now replaced by a more efficient binary format which should speed up saving/loading. The format is very similar to the one used by the letters recognizer. The format is documented in GestureLibrary.java.

Merge commit 'b6d99b7d'

* commit 'b6d99b7d':
  Modify how GestureLibrary stores its data. The XML format is now replaced by a more efficient binary format which should speed up saving/loading. The format is very similar to the one used by the letters recognizer. The format is documented in GestureLibrary.java.
parents c8a57c4b b6d99b7d
Loading
Loading
Loading
Loading
+18 −9
Original line number Diff line number Diff line
@@ -17,22 +17,31 @@
    package="com.android.gesture.example"
    android:versionCode="1"
    android:versionName="1.0.0">

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_SDCARD" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name="com.android.gesture.example.GestureEntry"

        <activity
            android:name="com.android.gesture.example.GestureEntry"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name="com.android.gesture.example.GestureLibViewer"/>
        <activity android:name="com.android.gesture.example.ContactListGestureOverlay"

        <activity
            android:name="com.android.gesture.example.ContactListGestureOverlay"
            android:label="@string/overlay_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest> 
+90 −77
Original line number Diff line number Diff line
@@ -23,12 +23,17 @@ import android.graphics.Path;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;

import org.xmlpull.v1.XmlSerializer;
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 static com.android.gesture.GestureConstants.LOG_TAG;

/**
 * A gesture can have a single or multiple strokes
 */
@@ -149,10 +154,12 @@ public class Gesture implements Parcelable {
     * @return the bitmap
     */
    public Bitmap toBitmap(int width, int height, int edge, int numSample, int color) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);

        canvas.translate(edge, edge);
        Paint paint = new Paint();

        final Paint paint = new Paint();
        paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
        paint.setDither(BITMAP_RENDERING_DITHER);
        paint.setColor(color);
@@ -182,10 +189,12 @@ public class Gesture implements Parcelable {
     * @return the bitmap
     */
    public Bitmap toBitmap(int width, int height, int edge, int color) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);

        canvas.translate(edge, edge);
        Paint paint = new Paint();

        final Paint paint = new Paint();
        paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
        paint.setDither(BITMAP_RENDERING_DITHER);
        paint.setColor(color);
@@ -193,53 +202,44 @@ public class Gesture implements Parcelable {
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);
        ArrayList<GestureStroke> strokes = mStrokes;
        int count = strokes.size();

        final ArrayList<GestureStroke> strokes = mStrokes;
        final int count = strokes.size();

        for (int i = 0; i < count; i++) {
            GestureStroke stroke = strokes.get(i);
            stroke.draw(canvas, paint);
            strokes.get(i).draw(canvas, paint);
        }

        return bitmap;
    }

    /**
     * Save the gesture as XML
     * 
     * @param namespace
     * @param serializer
     * @throws IOException
     */
    void toXML(String namespace, XmlSerializer serializer) throws IOException {
        serializer.startTag(namespace, GestureConstants.XML_TAG_GESTURE);
        serializer.attribute(namespace, GestureConstants.XML_TAG_ID, Long.toString(mGestureID));
        ArrayList<GestureStroke> strokes = mStrokes;
        int count = strokes.size();
    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++) {
            GestureStroke stroke = strokes.get(i);
            stroke.toXML(namespace, serializer);
            strokes.get(i).serialize(out);
        }
        serializer.endTag(namespace, GestureConstants.XML_TAG_GESTURE);
    }

    /**
     * Create the gesture from a string
     * 
     * @param str
     */
    public void createFromString(String str) {
        int startIndex = 0;
        int endIndex;
        while ((endIndex =
                str.indexOf(GestureConstants.STRING_GESTURE_DELIIMITER, startIndex + 1)) != -1) {
            String token = str.substring(startIndex, endIndex);
            if (startIndex > 0) { // stroke tokens
                addStroke(GestureStroke.createFromString(token));
            } else { // id token
                mGestureID = Long.parseLong(token);
            }
            startIndex = endIndex + 1;
    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;
    }

    /**
@@ -247,12 +247,14 @@ public class Gesture implements Parcelable {
     */
    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        final StringBuilder str = new StringBuilder();
        str.append(mGestureID);
        ArrayList<GestureStroke> strokes = mStrokes;
        int count = strokes.size();

        final ArrayList<GestureStroke> strokes = mStrokes;
        final int count = strokes.size();

        for (int i = 0; i < count; i++) {
            GestureStroke stroke = strokes.get(i);
            final GestureStroke stroke = strokes.get(i);
            str.append(GestureConstants.STRING_GESTURE_DELIIMITER);
            str.append(stroke.toString());
        }
@@ -262,9 +264,24 @@ public class Gesture implements Parcelable {

    public static final Parcelable.Creator<Gesture> CREATOR = new Parcelable.Creator<Gesture>() {
        public Gesture createFromParcel(Parcel in) {
            String str = in.readString();
            Gesture gesture = new Gesture();
            gesture.createFromString(str);
            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(LOG_TAG, "Error reading Gesture from parcel:", e);
            } finally {
                GestureUtilities.closeStream(inStream);
            }

            if (gesture != null) {
                gesture.mGestureID = gestureID;
            }

            return gesture;
        }

@@ -273,35 +290,31 @@ public class Gesture implements Parcelable {
        }
    };

    /**
     * Build a gesture from a byte array
     * 
     * @param bytes
     * @return the gesture
     */
    static Gesture buildFromArray(byte[] bytes) {
        String str = new String(bytes);
        Gesture gesture = new Gesture();
        gesture.createFromString(str);
        return gesture;
    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(LOG_TAG, "Error writing Gesture to parcel:", e);
        } finally {
            GestureUtilities.closeStream(outStream);
            GestureUtilities.closeStream(byteStream);
        }

    /**
     * Save a gesture to a byte array
     * 
     * @param stroke
     * @return the byte array
     */
    static byte[] saveToArray(Gesture stroke) {
        String str = stroke.toString();
        return str.getBytes();
        if (result) {
            out.writeByteArray(byteStream.toByteArray());
        }

    public void writeToParcel(Parcel out, int flags) {
        out.writeString(toString());
    }

    public int describeContents() {
        return CONTENTS_FILE_DESCRIPTOR;
        return 0;
    }
}
+5 −8
Original line number Diff line number Diff line
@@ -17,16 +17,13 @@
package com.android.gesture;

interface GestureConstants {
    static final String XML_TAG_LIBRARY = "library";
    static final String XML_TAG_ENTRY = "entry";
    static final String XML_TAG_GESTURE = "gesture";
    static final String XML_TAG_STROKE = "stroke";
    static final String XML_TAG_ID = "id";
    static final String XML_TAG_NAME = "name";
    static final String STRING_GESTURE_DELIIMITER = "#";
    static final String STRING_STROKE_DELIIMITER = ",";

    static final int STROKE_STRING_BUFFER_SIZE = 1024;
    static final int STROKE_POINT_BUFFER_SIZE = 100; // number of points
    static final int IO_BUFFER_SIZE = 8 * 1024; // 8K
    String LOG_TAG = "GestureLibrary";

    static final int IO_BUFFER_SIZE = 32 * 1024; // 32K

    static final String LOG_TAG = "Gestures";
}
+109 −113
Original line number Diff line number Diff line
@@ -16,16 +16,8 @@

package com.android.gesture;

import android.util.Config;
import android.util.Log;
import android.util.Xml;
import android.util.Xml.Encoding;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xmlpull.v1.XmlSerializer;
import android.os.SystemClock;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -33,10 +25,12 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.util.Map;

import static com.android.gesture.GestureConstants.LOG_TAG;

@@ -44,10 +38,28 @@ import static com.android.gesture.GestureConstants.LOG_TAG;
 * GestureLibrary maintains gesture examples and makes predictions on a new
 * gesture
 */
//
//    File format for GestureLibrary:
//
//                Nb. bytes   Java type   Description
//                -----------------------------------
//    Header
//                2 bytes     short       File format version number
//                4 bytes     int         Number of entries
//    Entry
//                X bytes     UTF String  Entry name
//                4 bytes     int         Number of gestures
//    Gesture
//                8 bytes     long        Gesture ID
//                4 bytes     int         Number of strokes
//    Stroke
//                4 bytes     int         Number of points
//    Point
//                4 bytes     float       X coordinate of the point
//                4 bytes     float       Y coordinate of the point
//                8 bytes     long        Time stamp
//
public class GestureLibrary {

    private static final String NAMESPACE = "";

    public static final int SEQUENCE_INVARIANT = 1;
    // when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed
    public static final int SEQUENCE_SENSITIVE = 2;
@@ -56,12 +68,16 @@ public class GestureLibrary {
    public static final int ORIENTATION_INVARIANT = 1;
    public static final int ORIENTATION_SENSITIVE = 2;

    private static final short FILE_FORMAT_VERSION = 1;

    private static final boolean PROFILE_LOADING_SAVING = false;

    private int mSequenceType = SEQUENCE_SENSITIVE;
    private int mOrientationStyle = ORIENTATION_SENSITIVE;

    private final String mGestureFileName;

    private final HashMap<String, ArrayList<Gesture>> mEntryName2gestures =
    private final HashMap<String, ArrayList<Gesture>> mNamedGestures =
            new HashMap<String, ArrayList<Gesture>>();

    private Learner mClassifier;
@@ -104,7 +120,7 @@ public class GestureLibrary {
     * @return a set of strings
     */
    public Set<String> getGestureEntries() {
        return mEntryName2gestures.keySet();
        return mNamedGestures.keySet();
    }

    /**
@@ -128,10 +144,10 @@ public class GestureLibrary {
        if (entryName == null || entryName.length() == 0) {
            return;
        }
        ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
        ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
        if (gestures == null) {
            gestures = new ArrayList<Gesture>();
            mEntryName2gestures.put(entryName, gestures);
            mNamedGestures.put(entryName, gestures);
        }
        gestures.add(gesture);
        mClassifier.addInstance(Instance.createInstance(mSequenceType, gesture, entryName));
@@ -146,7 +162,7 @@ public class GestureLibrary {
     * @param gesture
     */
    public void removeGesture(String entryName, Gesture gesture) {
        ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
        ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
        if (gestures == null) {
            return;
        }
@@ -155,7 +171,7 @@ public class GestureLibrary {

        // if there are no more samples, remove the entry automatically
        if (gestures.isEmpty()) {
            mEntryName2gestures.remove(entryName);
            mNamedGestures.remove(entryName);
        }

        mClassifier.removeInstance(gesture.getID());
@@ -169,7 +185,7 @@ public class GestureLibrary {
     * @param entryName the entry name
     */
    public void removeEntireEntry(String entryName) {
        mEntryName2gestures.remove(entryName);
        mNamedGestures.remove(entryName);
        mClassifier.removeInstances(entryName);
        mChanged = true;
    }
@@ -181,7 +197,7 @@ public class GestureLibrary {
     * @return the list of gestures that is under this name
     */
    public ArrayList<Gesture> getGestures(String entryName) {
        ArrayList<Gesture> gestures = mEntryName2gestures.get(entryName);
        ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
        if (gestures != null) {
            return new ArrayList<Gesture>(gestures);
        } else {
@@ -198,7 +214,7 @@ public class GestureLibrary {
        }

        boolean result = false;
        PrintWriter writer = null;
        DataOutputStream out = null;

        try {
            File file = new File(mGestureFileName);
@@ -208,40 +224,48 @@ public class GestureLibrary {
                }
            }

            writer = new PrintWriter(new BufferedOutputStream(new FileOutputStream(
                    mGestureFileName), GestureConstants.IO_BUFFER_SIZE));
            long start;
            if (PROFILE_LOADING_SAVING) {
                start = SystemClock.elapsedRealtime();
            }

            final HashMap<String, ArrayList<Gesture>> maps = mNamedGestures;

            out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file),
                    GestureConstants.IO_BUFFER_SIZE));
            // Write version number
            out.writeShort(FILE_FORMAT_VERSION);
            // Write number of entries
            out.writeInt(maps.size());

            final XmlSerializer serializer = Xml.newSerializer();
            serializer.setOutput(writer);
            serializer.startDocument(Encoding.ISO_8859_1.name(), null);
            serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY);
            for (Map.Entry<String, ArrayList<Gesture>> entry : maps.entrySet()) {
                final String key = entry.getKey();
                final ArrayList<Gesture> examples = entry.getValue();
                final int count = examples.size();

            final HashMap<String, ArrayList<Gesture>> maps = mEntryName2gestures;
                // Write entry name
                out.writeUTF(key);
                // Write number of examples for this entry
                out.writeInt(count);

            for (String key : maps.keySet()) {
                ArrayList<Gesture> examples = maps.get(key);
                // save an entry
                serializer.startTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY);
                serializer.attribute(NAMESPACE, GestureConstants.XML_TAG_NAME, key);
                int count = examples.size();
                for (int i = 0; i < count; i++) {
                    Gesture gesture = examples.get(i);
                    // save each gesture in the entry
                    gesture.toXML(NAMESPACE, serializer);
                    examples.get(i).serialize(out);
                }
                serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_ENTRY);
            }

            serializer.endTag(NAMESPACE, GestureConstants.XML_TAG_LIBRARY);
            serializer.endDocument();
            serializer.flush();
            out.flush();

            if (PROFILE_LOADING_SAVING) {
                long end = SystemClock.elapsedRealtime();
                Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms");
            }

            mChanged = false;
            result = true;
        } catch (IOException ex) {
            Log.d(LOG_TAG, "Failed to save gestures:", ex);
        } finally {
            GestureUtilities.closeStream(writer);
            GestureUtilities.closeStream(out);
        }

        return result;
@@ -255,90 +279,62 @@ public class GestureLibrary {

        final File file = new File(mGestureFileName);
        if (file.exists()) {
            BufferedInputStream in = null;
            DataInputStream in = null;
            try {
                if (Config.DEBUG) {
                    Log.v(LOG_TAG, "Load from " + mGestureFileName);
                }
                in = new BufferedInputStream(new FileInputStream(
                        mGestureFileName), GestureConstants.IO_BUFFER_SIZE);
                Xml.parse(in, Encoding.ISO_8859_1, new CompactInkHandler());
                result = true;
            } catch (SAXException ex) {
                Log.d(LOG_TAG, "Failed to load gestures:", ex);
            } catch (IOException ex) {
                Log.d(LOG_TAG, "Failed to load gestures:", ex);
            } finally {
                GestureUtilities.closeStream(in);
            }
        }

        return result;
    }

    private class CompactInkHandler implements ContentHandler {
        final StringBuilder mBuffer = new StringBuilder(GestureConstants.STROKE_STRING_BUFFER_SIZE);

        String mEntryName;

        Gesture mCurrentGesture = null;
        ArrayList<Gesture> mGestures;
                in = new DataInputStream(new BufferedInputStream(
                        new FileInputStream(mGestureFileName), GestureConstants.IO_BUFFER_SIZE));

        CompactInkHandler() {
                long start;
                if (PROFILE_LOADING_SAVING) {
                    start = SystemClock.elapsedRealtime();
                }

        public void characters(char[] ch, int start, int length) {
            mBuffer.append(ch, start, length);
                // Read file format version number
                final short versionNumber = in.readShort();
                switch (versionNumber) {
                    case 1:
                        readFormatV1(in);
                        break;
                }

        public void endDocument() {
                if (PROFILE_LOADING_SAVING) {
                    long end = SystemClock.elapsedRealtime();
                    Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms");
                }

        public void endElement(String uri, String localName, String qName) {
            if (localName.equals(GestureConstants.XML_TAG_ENTRY)) {
                mEntryName2gestures.put(mEntryName, mGestures);
                mGestures = null;
            } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) {
                mGestures.add(mCurrentGesture);
                mClassifier.addInstance(Instance.createInstance(mSequenceType,
                        mCurrentGesture, mEntryName));
                mCurrentGesture = null;
            } else if (localName.equals(GestureConstants.XML_TAG_STROKE)) {
                mCurrentGesture.addStroke(GestureStroke.createFromString(mBuffer.toString()));
                mBuffer.setLength(0);
            }
        }

        public void endPrefixMapping(String prefix) {
                result = true;
            } catch (IOException ex) {
                Log.d(LOG_TAG, "Failed to load gestures:", ex);
            } finally {
                GestureUtilities.closeStream(in);
            }

        public void ignorableWhitespace(char[] ch, int start, int length) {
        }

        public void processingInstruction(String target, String data) {
        return result;
    }

        public void setDocumentLocator(Locator locator) {
        }
    private void readFormatV1(DataInputStream in) throws IOException {
        final Learner classifier = mClassifier;
        final HashMap<String, ArrayList<Gesture>> namedGestures = mNamedGestures;
        namedGestures.clear();

        public void skippedEntity(String name) {
        }
        // Number of entries in the library
        final int entriesCount = in.readInt();

        public void startDocument() {
        }
        for (int i = 0; i < entriesCount; i++) {
            // Entry name
            final String name = in.readUTF();
            // Number of gestures
            final int gestureCount = in.readInt();

        public void startElement(String uri, String localName, String qName, Attributes attributes) {
            if (localName.equals(GestureConstants.XML_TAG_ENTRY)) {
                mGestures = new ArrayList<Gesture>();
                mEntryName = attributes.getValue(NAMESPACE, GestureConstants.XML_TAG_NAME);
            } else if (localName.equals(GestureConstants.XML_TAG_GESTURE)) {
                mCurrentGesture = new Gesture();
                mCurrentGesture.setID(Long.parseLong(attributes.getValue(NAMESPACE,
                        GestureConstants.XML_TAG_ID)));
            }
            final ArrayList<Gesture> gestures = new ArrayList<Gesture>(gestureCount);
            for (int j = 0; j < gestureCount; j++) {
                final Gesture gesture = Gesture.deserialize(in);
                gestures.add(gesture);
                classifier.addInstance(Instance.createInstance(mSequenceType, gesture, name));
            }

        public void startPrefixMapping(String prefix, String uri) {
            namedGestures.put(name, gestures);
        }
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.gesture;

import java.io.DataInputStream;
import java.io.IOException;

/**
 * A timed point of a gesture stroke
 */
@@ -31,4 +34,13 @@ public class GesturePoint {
        this.y = y;
        timestamp = t;
    }

    static GesturePoint deserialize(DataInputStream in) throws IOException {
        // Read X and Y
        final float x = in.readFloat();
        final float y = in.readFloat();
        // Read timestamp
        final long timeStamp = in.readLong();
        return new GesturePoint(x, y, timeStamp);
    }
}
Loading