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

Commit b6d99b7d authored by Romain Guy's avatar Romain Guy
Browse files

Modify how GestureLibrary stores its data. The XML format is now replaced by a...

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.
parent aeed1816
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