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

Commit a5f50b8f authored by Android (Google) Code Review's avatar Android (Google) Code Review
Browse files

Merge change 2746 into donut

* changes:
  Modify the base gestures API to use streams instead of files. Adds new wrappers to easily load/save gestures from files, resources, etc. Do the same for the letters recognizer.
parents 0f5179c7 0a63716e
Loading
Loading
Loading
Loading
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.util.Log;
import static android.gesture.GestureConstants.*;
import android.content.Context;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;

public final class GestureLibraries {
    private GestureLibraries() {
    }

    public static GestureLibrary fromFile(String path) {
        return fromFile(new File(path));
    }

    public static GestureLibrary fromFile(File path) {
        return new FileGestureLibrary(path);
    }

    public static GestureLibrary fromPrivateFile(Context context, String name) {
        return fromFile(context.getFileStreamPath(name));
    }

    public static GestureLibrary fromRawResource(Context context, int resourceId) {
        return new ResourceGestureLibrary(context, resourceId);
    }

    private static class FileGestureLibrary extends GestureLibrary {
        private final File mPath;

        public FileGestureLibrary(File path) {
            mPath = path;
        }

        @Override
        public boolean isReadOnly() {
            return !mPath.canWrite();
        }

        public boolean save() {
            final File file = mPath;
            if (!file.canWrite()) return false;

            if (!file.getParentFile().exists()) {
                if (!file.getParentFile().mkdirs()) {
                    return false;
                }
            }

            boolean result = false;
            try {
                mStore.save(new FileOutputStream(file), true);
                result = true;
            } catch (FileNotFoundException e) {
                Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
            } catch (IOException e) {
                Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
            }

            return result;
        }

        public boolean load() {
            boolean result = false;
            final File file = mPath;
            if (file.exists() && file.canRead()) {
                try {
                    mStore.load(new FileInputStream(file), true);
                    result = true;
                } catch (FileNotFoundException e) {
                    Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
                } catch (IOException e) {
                    Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
                }
            }

            return result;
        }
    }

    private static class ResourceGestureLibrary extends GestureLibrary {
        private final WeakReference<Context> mContext;
        private final int mResourceId;

        public ResourceGestureLibrary(Context context, int resourceId) {
            mContext = new WeakReference<Context>(context);
            mResourceId = resourceId;
        }

        @Override
        public boolean isReadOnly() {
            return true;
        }

        public boolean save() {
            return false;
        }

        public boolean load() {
            boolean result = false;
            final Context context = mContext.get();
            if (context != null) {
                final InputStream in = context.getResources().openRawResource(mResourceId);
                try {
                    mStore.load(in, true);
                    result = true;
                } catch (IOException e) {
                    Log.d(LOG_TAG, "Could not load the gesture library from raw resource " +
                            context.getResources().getResourceName(mResourceId), e);
                }
            }

            return result;
        }
    }
}
+25 −294
Original line number Diff line number Diff line
/*
 * Copyright (C) 2008-2009 The Android Open Source Project
 * Copyright (C) 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.
@@ -14,337 +14,68 @@
 * limitations under the License.
 */

package android.gesture;

import android.util.Log;
import android.os.SystemClock;
package android.gesture;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
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 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 {
    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;

    // ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures
    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;
import java.util.ArrayList;

    private int mSequenceType = SEQUENCE_SENSITIVE;
    private int mOrientationStyle = ORIENTATION_SENSITIVE;
public abstract class GestureLibrary {
    protected final GestureStore mStore;

    private final String mGestureFileName;
    protected GestureLibrary() {
        mStore = new GestureStore();
    }

    private final HashMap<String, ArrayList<Gesture>> mNamedGestures =
            new HashMap<String, ArrayList<Gesture>>();
    public abstract boolean save();

    private Learner mClassifier;
    public abstract boolean load();

    private boolean mChanged = false;
    public boolean isReadOnly() {
        return false;
    }

    /**
     * @param path where gesture data is stored
     */
    public GestureLibrary(String path) {
        mGestureFileName = path;
        mClassifier = new InstanceLearner();
    public Learner getLearner() {
        return mStore.getLearner();
    }

    /**
     * Specify how the gesture library will handle orientation. 
     * Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE
     * 
     * @param style
     */
    public void setOrientationStyle(int style) {
        mOrientationStyle = style;
        mStore.setOrientationStyle(style);
    }

    public int getOrientationStyle() {
        return mOrientationStyle;
        return mStore.getOrientationStyle();
    }

    /**
     * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
     */
    public void setSequenceType(int type) {
        mSequenceType = type;
        mStore.setSequenceType(type);
    }

    /**
     * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
     */
    public int getSequenceType() {
        return mSequenceType;
        return mStore.getSequenceType();
    }

    /**
     * Get all the gesture entry names in the library
     * 
     * @return a set of strings
     */
    public Set<String> getGestureEntries() {
        return mNamedGestures.keySet();
        return mStore.getGestureEntries();
    }

    /**
     * Recognize a gesture
     * 
     * @param gesture the query
     * @return a list of predictions of possible entries for a given gesture
     */
    public ArrayList<Prediction> recognize(Gesture gesture) {
        Instance instance = Instance.createInstance(mSequenceType, mOrientationStyle, gesture, null);
        return mClassifier.classify(mSequenceType, instance.vector);
        return mStore.recognize(gesture);
    }

    /**
     * Add a gesture for the entry
     * 
     * @param entryName entry name
     * @param gesture
     */
    public void addGesture(String entryName, Gesture gesture) {
        if (entryName == null || entryName.length() == 0) {
            return;
        }
        ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
        if (gestures == null) {
            gestures = new ArrayList<Gesture>();
            mNamedGestures.put(entryName, gestures);
        }
        gestures.add(gesture);
        mClassifier.addInstance(Instance.createInstance(mSequenceType, mOrientationStyle, gesture, entryName));
        mChanged = true;
        mStore.addGesture(entryName, gesture);
    }

    /**
     * Remove a gesture from the library. If there are no more gestures for the
     * given entry, the gesture entry will be removed.
     * 
     * @param entryName entry name
     * @param gesture
     */
    public void removeGesture(String entryName, Gesture gesture) {
        ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
        if (gestures == null) {
            return;
        }

        gestures.remove(gesture);

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

        mClassifier.removeInstance(gesture.getID());

        mChanged = true;
        mStore.removeGesture(entryName, gesture);
    }

    /**
     * Remove a entry of gestures
     * 
     * @param entryName the entry name
     */
    public void removeEntry(String entryName) {
        mNamedGestures.remove(entryName);
        mClassifier.removeInstances(entryName);
        mChanged = true;
        mStore.removeEntry(entryName);
    }

    /**
     * Get all the gestures of an entry
     * 
     * @param entryName
     * @return the list of gestures that is under this name
     */
    public ArrayList<Gesture> getGestures(String entryName) {
        ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
        if (gestures != null) {
            return new ArrayList<Gesture>(gestures);
        } else {
            return null;
        }
    }

    /**
     * Save the gesture library
     */
    public boolean save() {
        if (!mChanged) {
            return true;
        }

        boolean result = false;
        DataOutputStream out = null;

        try {
            File file = new File(mGestureFileName);
            if (!file.getParentFile().exists()) {
                if (!file.getParentFile().mkdirs()) {
                    return false;
                }
            }

            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());

            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();

                // Write entry name
                out.writeUTF(key);
                // Write number of examples for this entry
                out.writeInt(count);

                for (int i = 0; i < count; i++) {
                    examples.get(i).serialize(out);
                }
            }

            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(out);
        }

        return result;
    }

    /**
     * Load the gesture library
     */
    public boolean load() {
        boolean result = false;

        final File file = new File(mGestureFileName);
        if (file.exists()) {
            DataInputStream in = null;
            try {
                in = new DataInputStream(new BufferedInputStream(
                        new FileInputStream(mGestureFileName), GestureConstants.IO_BUFFER_SIZE));

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

                // Read file format version number
                final short versionNumber = in.readShort();
                switch (versionNumber) {
                    case 1:
                        readFormatV1(in);
                        break;
                }

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

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

        return result;
    }

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

        // Number of entries in the library
        final int entriesCount = in.readInt();

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

            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, mOrientationStyle, gesture, name));
            }

            namedGestures.put(name, gestures);
        }
    }
    
    Learner getLearner() {
        return mClassifier;
        return mStore.getGestures(entryName);
    }
}
+330 −0

File added.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ class Instance {
    static Instance createInstance(int sequenceType, int orientationType, Gesture gesture, String label) {
        float[] pts;
        Instance instance;
        if (sequenceType == GestureLibrary.SEQUENCE_SENSITIVE) {
        if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
            pts = temporalSampler(orientationType, gesture);
            instance = new Instance(gesture.getID(), pts, label);
            instance.normalize();
@@ -94,7 +94,7 @@ class Instance {
        float orientation = (float)Math.atan2(pts[1] - center[1], pts[0] - center[0]);

        float adjustment = -orientation;
        if (orientationType == GestureLibrary.ORIENTATION_SENSITIVE) {
        if (orientationType == GestureStore.ORIENTATION_SENSITIVE) {
            int count = ORIENTATIONS.length;
            for (int i = 0; i < count; i++) {
                float delta = ORIENTATIONS[i] - orientation;
+1 −1
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ class InstanceLearner extends Learner {
                continue;
            }
            double distance;
            if (sequenceType == GestureLibrary.SEQUENCE_SENSITIVE) {
            if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
                distance = GestureUtilities.cosineDistance(sample.vector, vector);
            } else {
                distance = GestureUtilities.squaredEuclideanDistance(sample.vector, vector);
Loading