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.
diff --git a/core/java/android/gesture/GestureLibraries.java b/core/java/android/gesture/GestureLibraries.java
new file mode 100644
index 0000000..2ce7a8e
--- /dev/null
+++ b/core/java/android/gesture/GestureLibraries.java
@@ -0,0 +1,139 @@
+/*
+ * 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;
+        }
+    }
+}
diff --git a/core/java/android/gesture/GestureLibrary.java b/core/java/android/gesture/GestureLibrary.java
index 9020d2b..a29c2c8 100644
--- a/core/java/android/gesture/GestureLibrary.java
+++ b/core/java/android/gesture/GestureLibrary.java
@@ -1,5 +1,5 @@
 /*
- * 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;
-
-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 java.util.ArrayList;
 
-import static android.gesture.GestureConstants.LOG_TAG;
+public abstract class GestureLibrary {
+    protected final GestureStore mStore;
 
-/**
- * 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;
-
-    private int mSequenceType = SEQUENCE_SENSITIVE;
-    private int mOrientationStyle = ORIENTATION_SENSITIVE;
-
-    private final String mGestureFileName;
-
-    private final HashMap<String, ArrayList<Gesture>> mNamedGestures =
-            new HashMap<String, ArrayList<Gesture>>();
-
-    private Learner mClassifier;
-
-    private boolean mChanged = false;
-
-    /**
-     * @param path where gesture data is stored
-     */
-    public GestureLibrary(String path) {
-        mGestureFileName = path;
-        mClassifier = new InstanceLearner();
+    protected GestureLibrary() {
+        mStore = new GestureStore();
     }
 
-    /**
-     * Specify how the gesture library will handle orientation. 
-     * Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE
-     * 
-     * @param style
-     */
+    public abstract boolean save();
+
+    public abstract boolean load();
+
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    public Learner getLearner() {
+        return mStore.getLearner();
+    }
+
     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);
     }
 }
diff --git a/core/java/android/gesture/GestureStore.java b/core/java/android/gesture/GestureStore.java
new file mode 100644
index 0000000..ddf1c83
--- /dev/null
+++ b/core/java/android/gesture/GestureStore.java
@@ -0,0 +1,330 @@
+/*
+ * 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.util.Log;
+import android.os.SystemClock;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.DataOutputStream;
+import java.io.DataInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+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 GestureStore:
+//
+//                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 GestureStore {
+    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;
+
+    private int mSequenceType = SEQUENCE_SENSITIVE;
+    private int mOrientationStyle = ORIENTATION_SENSITIVE;
+
+    private final HashMap<String, ArrayList<Gesture>> mNamedGestures =
+            new HashMap<String, ArrayList<Gesture>>();
+
+    private Learner mClassifier;
+
+    private boolean mChanged = false;
+
+    public GestureStore() {
+        mClassifier = new InstanceLearner();
+    }
+
+    /**
+     * Specify how the gesture library will handle orientation. 
+     * Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE
+     * 
+     * @param style
+     */
+    public void setOrientationStyle(int style) {
+        mOrientationStyle = style;
+    }
+
+    public int getOrientationStyle() {
+        return mOrientationStyle;
+    }
+
+    /**
+     * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
+     */
+    public void setSequenceType(int type) {
+        mSequenceType = type;
+    }
+
+    /**
+     * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
+     */
+    public int getSequenceType() {
+        return mSequenceType;
+    }
+
+    /**
+     * Get all the gesture entry names in the library
+     * 
+     * @return a set of strings
+     */
+    public Set<String> getGestureEntries() {
+        return mNamedGestures.keySet();
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Remove a entry of gestures
+     * 
+     * @param entryName the entry name
+     */
+    public void removeEntry(String entryName) {
+        mNamedGestures.remove(entryName);
+        mClassifier.removeInstances(entryName);
+        mChanged = true;
+    }
+
+    /**
+     * 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 void save(OutputStream stream) throws IOException {
+        save(stream, false);
+    }
+
+    public void save(OutputStream stream, boolean closeStream) throws IOException {
+        if (!mChanged) {
+            return;
+        }
+
+        DataOutputStream out = null;
+
+        try {
+            long start;
+            if (PROFILE_LOADING_SAVING) {
+                start = SystemClock.elapsedRealtime();
+            }
+
+            final HashMap<String, ArrayList<Gesture>> maps = mNamedGestures;
+
+            out = new DataOutputStream((stream instanceof BufferedOutputStream) ? out :
+                    new BufferedOutputStream(out, 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;
+        } finally {
+            if (closeStream) GestureUtilities.closeStream(out);
+        }
+    }
+
+    /**
+     * Load the gesture library
+     */
+    public void load(InputStream stream) throws IOException {
+        load(stream, false);
+    }
+
+    public void load(InputStream stream, boolean closeStream) throws IOException {
+        DataInputStream in = null;
+        try {
+            in = new DataInputStream((stream instanceof BufferedInputStream) ? stream :
+                    new BufferedInputStream(stream, 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");
+            }
+        } finally {
+            if (closeStream) GestureUtilities.closeStream(in);
+        }
+    }
+
+    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;
+    }
+}
diff --git a/core/java/android/gesture/Instance.java b/core/java/android/gesture/Instance.java
index 9ac0a96..ef208ac 100755
--- a/core/java/android/gesture/Instance.java
+++ b/core/java/android/gesture/Instance.java
@@ -72,7 +72,7 @@
     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 @@
         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;
diff --git a/core/java/android/gesture/InstanceLearner.java b/core/java/android/gesture/InstanceLearner.java
index b6feb64..00cdadc 100644
--- a/core/java/android/gesture/InstanceLearner.java
+++ b/core/java/android/gesture/InstanceLearner.java
@@ -38,7 +38,7 @@
                 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);
diff --git a/core/java/android/gesture/LetterRecognizer.java b/core/java/android/gesture/LetterRecognizer.java
index 9e801ed..580fc26 100644
--- a/core/java/android/gesture/LetterRecognizer.java
+++ b/core/java/android/gesture/LetterRecognizer.java
@@ -23,6 +23,7 @@
 import java.io.BufferedInputStream;
 import java.io.DataInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -31,10 +32,9 @@
 import static android.gesture.GestureConstants.LOG_TAG;
 
 public class LetterRecognizer {
-    public final static int RECOGNIZER_LATIN_LOWERCASE = 0;
     static final String GESTURE_FILE_NAME = "letters.gestures";
 
-    private final static int ADJUST_RANGE = 3;    
+    private final static int ADJUST_RANGE = 3;
 
     private SigmoidUnit[] mHiddenLayer;
     private SigmoidUnit[] mOutputLayer;
@@ -42,8 +42,8 @@
     private final String[] mClasses;
 
     private final int mPatchSize;
-    
-    private GestureLibrary mGestureLibrary;
+
+    private GestureLibrary mGestureStore;
 
     private final Comparator<Prediction> mComparator = new PredictionComparator();
 
@@ -69,15 +69,6 @@
         }
     }
 
-    public static LetterRecognizer getLetterRecognizer(Context context, int type) {
-        switch (type) {
-            case RECOGNIZER_LATIN_LOWERCASE: {
-                return createFromResource(context, com.android.internal.R.raw.latin_lowercase);
-            }
-        }
-        return null;
-    }
-
     private LetterRecognizer(int numOfInput, int numOfHidden, String[] classes) {
         mPatchSize = (int) Math.sqrt(numOfInput);
         mHiddenLayer = new SigmoidUnit[numOfHidden];
@@ -137,14 +128,18 @@
         return output;
     }
 
-    private static LetterRecognizer createFromResource(Context context, int resourceID) {
+    static LetterRecognizer createFromResource(Context context, int resourceID) {
         final Resources resources = context.getResources();
+        final InputStream stream = resources.openRawResource(resourceID);
+        return createFromStream(stream);
+    }
 
+    static LetterRecognizer createFromStream(InputStream stream) {
         DataInputStream in = null;
         LetterRecognizer classifier = null;
 
         try {
-            in = new DataInputStream(new BufferedInputStream(resources.openRawResource(resourceID),
+            in = new DataInputStream(new BufferedInputStream(stream,
                     GestureConstants.IO_BUFFER_SIZE));
 
             final int version = in.readShort();
@@ -206,49 +201,49 @@
     }
 
     /**
-     * TODO: Publish this API once we figure out where we should save the personzlied
+     * TODO: Publish this API once we figure out where we should save the personalized
      * gestures, and how to do so across all apps
      *
      * @hide
      */
     public boolean save() {
-        if (mGestureLibrary != null) {
-            return mGestureLibrary.save();
+        if (mGestureStore != null) {
+            return mGestureStore.save();
         }
         return false;
     }
 
     /**
-     * TODO: Publish this API once we figure out where we should save the personzlied
+     * TODO: Publish this API once we figure out where we should save the personalized
      * gestures, and how to do so across all apps
      *
      * @hide
      */
     public void setPersonalizationEnabled(boolean enabled) {
         if (enabled) {
-            mGestureLibrary = new GestureLibrary(GESTURE_FILE_NAME);
-            mGestureLibrary.setSequenceType(GestureLibrary.SEQUENCE_INVARIANT);
-            mGestureLibrary.load();
+            mGestureStore = GestureLibraries.fromFile(GESTURE_FILE_NAME);
+            mGestureStore.setSequenceType(GestureStore.SEQUENCE_INVARIANT);
+            mGestureStore.load();
         } else {
-            mGestureLibrary = null;
+            mGestureStore = null;
         }
     }
 
     /**
-     * TODO: Publish this API once we figure out where we should save the personzlied
+     * TODO: Publish this API once we figure out where we should save the personalized
      * gestures, and how to do so across all apps
      *
      * @hide
      */
     public void addExample(String letter, Gesture example) {
-        if (mGestureLibrary != null) {
-            mGestureLibrary.addGesture(letter, example);
+        if (mGestureStore != null) {
+            mGestureStore.addGesture(letter, example);
         }
     }
-    
+
     private void adjustPrediction(Gesture query, ArrayList<Prediction> predictions) {
-        if (mGestureLibrary != null) {
-            final ArrayList<Prediction> results = mGestureLibrary.recognize(query);
+        if (mGestureStore != null) {
+            final ArrayList<Prediction> results = mGestureStore.recognize(query);
             final HashMap<String, Prediction> topNList = new HashMap<String, Prediction>();
 
             for (int j = 0; j < ADJUST_RANGE; j++) {
diff --git a/core/java/android/gesture/LetterRecognizers.java b/core/java/android/gesture/LetterRecognizers.java
new file mode 100644
index 0000000..e3f45a0
--- /dev/null
+++ b/core/java/android/gesture/LetterRecognizers.java
@@ -0,0 +1,65 @@
+/*
+ * 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.content.Context;
+import android.util.Log;
+import static android.gesture.GestureConstants.LOG_TAG;
+import static android.gesture.LetterRecognizer.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+public final class LetterRecognizers {
+    public final static int RECOGNIZER_LATIN_LOWERCASE = 0;
+
+    private LetterRecognizers() {
+    }
+
+    public static LetterRecognizer fromType(Context context, int type) {
+        switch (type) {
+            case RECOGNIZER_LATIN_LOWERCASE: {
+                return createFromResource(context, com.android.internal.R.raw.latin_lowercase);
+            }
+        }
+        return null;
+    }
+
+    public static LetterRecognizer fromResource(Context context, int resourceId) {
+        return createFromResource(context, resourceId);
+    }
+
+    public static LetterRecognizer fromFile(String path) {
+        return fromFile(new File(path));
+    }
+
+    public static LetterRecognizer fromFile(File file) {
+        try {
+            return createFromStream(new FileInputStream(file));
+        } catch (FileNotFoundException e) {
+            Log.d(LOG_TAG, "Failed to load handwriting data from file " + file, e);
+        }
+        return null;
+    }
+
+    public static LetterRecognizer fromStream(InputStream stream) {
+        return createFromStream(stream);
+    }
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index f60aba3..ec02143 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -52,6 +52,7 @@
 import android.gesture.Gesture;
 import android.gesture.LetterRecognizer;
 import android.gesture.Prediction;
+import android.gesture.LetterRecognizers;
 
 import com.android.internal.R;
 
@@ -94,7 +95,7 @@
     public static final int TRANSCRIPT_MODE_NORMAL = 1;
     /**
      * The list will automatically scroll to the bottom, no matter what items
-     * are currently visible. 
+     * are currently visible.
      *
      * @see #setTranscriptMode(int)
      */
@@ -156,7 +157,7 @@
      * Indicates the view is in the process of being flung
      */
     static final int TOUCH_MODE_FLING = 4;
-    
+
     /**
      * Indicates that the user is currently dragging the fast scroll thumb
      */
@@ -349,7 +350,7 @@
      * bitmap cache after scrolling.
      */
     boolean mScrollingCacheEnabled;
-    
+
     /**
      * Whether or not to enable the fast scroll feature on this list
      */
@@ -422,7 +423,7 @@
      * The last CheckForTap runnable we posted, if any
      */
     private Runnable mPendingCheckForTap;
-    
+
     /**
      * The last CheckForKeyLongPress runnable we posted, if any
      */
@@ -576,7 +577,7 @@
 
         int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
         setCacheColorHint(color);
-        
+
         boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
         setFastScrollEnabled(enableFastScroll);
 
@@ -605,7 +606,7 @@
         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
         mDensityScale = getContext().getResources().getDisplayMetrics().density;
     }
-    
+
     /**
      * <p>Sets the type of gestures to use with this list. When gestures are enabled,
      * that is if the <code>gestures</code> parameter is not {@link #GESTURES_NONE},
@@ -663,7 +664,7 @@
      * @see #GESTURES_NONE
      * @see #GESTURES_JUMP
      * @see #GESTURES_FILTER
-     * @see #setGestures(int) 
+     * @see #setGestures(int)
      */
     @ViewDebug.ExportedProperty(mapping = {
         @ViewDebug.IntToString(from = GESTURES_NONE, to = "NONE"),
@@ -737,7 +738,7 @@
             mGesturesOverlay.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
             mGesturesOverlay.addOnGesturePerformedListener(new GesturesProcessor());
 
-            mPreviousGesturing = false;            
+            mPreviousGesturing = false;
         }
     }
 
@@ -778,10 +779,10 @@
     }
 
     /**
-     * Enables fast scrolling by letting the user quickly scroll through lists by 
-     * dragging the fast scroll thumb. The adapter attached to the list may want 
+     * Enables fast scrolling by letting the user quickly scroll through lists by
+     * dragging the fast scroll thumb. The adapter attached to the list may want
      * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
-     * jump between sections of the list. 
+     * jump between sections of the list.
      * @see SectionIndexer
      * @see #isFastScrollEnabled()
      * @param enabled whether or not to enable fast scrolling
@@ -799,7 +800,7 @@
             }
         }
     }
-    
+
     /**
      * Returns the current state of the fast scroll feature.
      * @see #setFastScrollEnabled(boolean)
@@ -809,10 +810,10 @@
     public boolean isFastScrollEnabled() {
         return mFastScrollEnabled;
     }
-    
+
     /**
      * If fast scroll is visible, then don't draw the vertical scrollbar.
-     * @hide 
+     * @hide
      */
     @Override
     protected boolean isVerticalScrollBarHidden() {
@@ -830,11 +831,11 @@
      * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
      * is based solely on the number of items in the adapter and the position of the
      * visible items inside the adapter. This provides a stable scrollbar as the user
-     * navigates through a list of items with varying heights. 
+     * navigates through a list of items with varying heights.
      *
      * @param enabled Whether or not to enable smooth scrollbar.
      *
-     * @see #setSmoothScrollbarEnabled(boolean) 
+     * @see #setSmoothScrollbarEnabled(boolean)
      * @attr ref android.R.styleable#AbsListView_smoothScrollbar
      */
     public void setSmoothScrollbarEnabled(boolean enabled) {
@@ -1142,7 +1143,7 @@
     /**
      * Sets the initial value for the text filter.
      * @param filterText The text to use for the filter.
-     * 
+     *
      * @see #setTextFilterEnabled
      */
     public void setFilterText(String filterText) {
@@ -1168,7 +1169,7 @@
     }
 
     /**
-     * Returns the list's text filter, if available. 
+     * Returns the list's text filter, if available.
      * @return the list's text filter or null if filtering isn't enabled
      */
     public CharSequence getTextFilter() {
@@ -1177,7 +1178,7 @@
         }
         return null;
     }
-    
+
     @Override
     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
@@ -1740,7 +1741,7 @@
             System.arraycopy(state, enabledPos + 1, state, enabledPos,
                     state.length - enabledPos - 1);
         }
-        
+
         return state;
     }
 
@@ -1851,16 +1852,16 @@
      */
     private class WindowRunnnable {
         private int mOriginalAttachCount;
-        
+
         public void rememberWindowAttachCount() {
             mOriginalAttachCount = getWindowAttachCount();
         }
-        
+
         public boolean sameWindow() {
             return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
         }
     }
-    
+
     private class PerformClick extends WindowRunnnable implements Runnable {
         View mChild;
         int mClickMotionPosition;
@@ -1887,7 +1888,7 @@
                 final long longPressId = mAdapter.getItemId(mMotionPosition);
 
                 boolean handled = false;
-                if (sameWindow() && !mDataChanged) { 
+                if (sameWindow() && !mDataChanged) {
                     handled = performLongPress(child, longPressPosition, longPressId);
                 }
                 if (handled) {
@@ -1901,7 +1902,7 @@
             }
         }
     }
-    
+
     private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
         public void run() {
             if (isPressed() && mSelectedPosition >= 0) {
@@ -1930,7 +1931,7 @@
         boolean handled = false;
 
         dismissGesturesPopup();
-        
+
         if (mOnItemLongClickListener != null) {
             handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
                     longPressPosition, longPressId);
@@ -2079,7 +2080,7 @@
                             mTouchMode = TOUCH_MODE_DONE_WAITING;
                         }
                     } else {
-                        mTouchMode = TOUCH_MODE_DONE_WAITING;                        
+                        mTouchMode = TOUCH_MODE_DONE_WAITING;
                     }
                 }
             }
@@ -2138,7 +2139,7 @@
             boolean intercepted = mFastScroller.onTouchEvent(ev);
             if (intercepted) {
                 return true;
-            }            
+            }
         }
 
         final int action = ev.getAction();
@@ -2326,10 +2327,10 @@
             }
 
             setPressed(false);
-            
+
             // Need to redraw since we probably aren't drawing the selector anymore
             invalidate();
-            
+
             final Handler handler = getHandler();
             if (handler != null) {
                 handler.removeCallbacks(mPendingCheckForLongPress);
@@ -2373,7 +2374,7 @@
 
         return true;
     }
-    
+
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
@@ -2388,14 +2389,14 @@
         int x = (int) ev.getX();
         int y = (int) ev.getY();
         View v;
-        
+
         if (mFastScroller != null) {
             boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
             if (intercepted) {
                 return true;
             }
         }
-        
+
         switch (action) {
         case MotionEvent.ACTION_DOWN: {
             int motionPosition = findMotionRow(y);
@@ -3245,7 +3246,7 @@
         }
         return null;
     }
-    
+
     /**
      * For filtering we proxy an input connection to an internal text editor,
      * and this allows the proxying to happen.
@@ -3254,7 +3255,7 @@
     public boolean checkInputConnectionProxy(View view) {
         return view == mTextFilter;
     }
-    
+
     /**
      * Creates the window for the text filter and populates it with an EditText field;
      *
@@ -3647,7 +3648,7 @@
             mCurrentScrap = scrapViews[0];
             mScrapViews = scrapViews;
         }
-        
+
         public boolean shouldRecycleViewType(int viewType) {
             return viewType >= 0;
         }
@@ -3862,8 +3863,8 @@
         private final char[] mHolder;
 
         GesturesProcessor() {
-            mRecognizer = LetterRecognizer.getLetterRecognizer(getContext(),
-                    LetterRecognizer.RECOGNIZER_LATIN_LOWERCASE);
+            mRecognizer = LetterRecognizers.fromType(getContext(),
+                    LetterRecognizers.RECOGNIZER_LATIN_LOWERCASE);
             if (mRecognizer == null) {
                 setGestures(GESTURES_NONE);
             }