336 lines
10 KiB
Java
336 lines
10 KiB
Java
![]() |
/*
|
||
|
* 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;
|
||
|
// at most 2 directions can be recognized
|
||
|
public static final int ORIENTATION_SENSITIVE = 2;
|
||
|
// at most 4 directions can be recognized
|
||
|
static final int ORIENTATION_SENSITIVE_4 = 4;
|
||
|
// at most 8 directions can be recognized
|
||
|
static final int ORIENTATION_SENSITIVE_8 = 8;
|
||
|
|
||
|
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, mOrientationStyle, 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public boolean hasChanged() {
|
||
|
return mChanged;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Save the gesture library
|
||
|
*/
|
||
|
public void save(OutputStream stream) throws IOException {
|
||
|
save(stream, false);
|
||
|
}
|
||
|
|
||
|
public void save(OutputStream stream, boolean closeStream) throws IOException {
|
||
|
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) ? stream :
|
||
|
new BufferedOutputStream(stream, 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) GestureUtils.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) GestureUtils.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;
|
||
|
}
|
||
|
}
|