Removes gestures from ListView.

This commit is contained in:
Romain Guy
2009-06-08 22:36:10 -07:00
parent be19df0a4d
commit f642aa2a44
20 changed files with 0 additions and 1757 deletions

View File

@@ -1,279 +0,0 @@
/*
* 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.content.res.Resources;
import android.util.Log;
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;
import java.util.HashMap;
import static android.gesture.GestureConstants.LOG_TAG;
public class LetterRecognizer {
static final String GESTURE_FILE_NAME = "letters.gestures";
private final static int ADJUST_RANGE = 3;
private SigmoidUnit[] mHiddenLayer;
private SigmoidUnit[] mOutputLayer;
private final String[] mClasses;
private final int mPatchSize;
private GestureLibrary mGestureStore;
private final Comparator<Prediction> mComparator = new PredictionComparator();
private static class SigmoidUnit {
final float[] mWeights;
SigmoidUnit(float[] weights) {
mWeights = weights;
}
private float compute(float[] inputs) {
float sum = 0;
final int count = inputs.length;
final float[] weights = mWeights;
for (int i = 0; i < count; i++) {
sum += inputs[i] * weights[i];
}
sum += weights[weights.length - 1];
return 1.0f / (float) (1 + Math.exp(-sum));
}
}
private LetterRecognizer(int numOfInput, int numOfHidden, String[] classes) {
mPatchSize = (int) Math.sqrt(numOfInput);
mHiddenLayer = new SigmoidUnit[numOfHidden];
mClasses = classes;
mOutputLayer = new SigmoidUnit[classes.length];
}
public ArrayList<Prediction> recognize(Gesture gesture) {
return recognize(gesture, null);
}
public ArrayList<Prediction> recognize(Gesture gesture, ArrayList<Prediction> predictions) {
float[] query = GestureUtilities.spatialSampling(gesture, mPatchSize);
predictions = classify(query, predictions);
adjustPrediction(gesture, predictions);
return predictions;
}
private ArrayList<Prediction> classify(float[] vector, ArrayList<Prediction> predictions) {
if (predictions == null) {
predictions = new ArrayList<Prediction>();
} else {
predictions.clear();
}
final float[] intermediateOutput = compute(mHiddenLayer, vector);
final float[] output = compute(mOutputLayer, intermediateOutput);
double sum = 0;
final String[] classes = mClasses;
final int count = classes.length;
for (int i = 0; i < count; i++) {
double score = output[i];
sum += score;
predictions.add(new Prediction(classes[i], score));
}
for (int i = 0; i < count; i++) {
predictions.get(i).score /= sum;
}
Collections.sort(predictions, mComparator);
return predictions;
}
private float[] compute(SigmoidUnit[] layer, float[] input) {
final float[] output = new float[layer.length];
final int count = layer.length;
for (int i = 0; i < count; i++) {
output[i] = layer[i].compute(input);
}
return output;
}
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(stream,
GestureConstants.IO_BUFFER_SIZE));
final int version = in.readShort();
switch (version) {
case 1:
classifier = readV1(in);
break;
default:
Log.d(LOG_TAG, "Couldn't load handwriting data: version " + version +
" not supported");
break;
}
} catch (IOException e) {
Log.d(LOG_TAG, "Failed to load handwriting data:", e);
} finally {
GestureUtilities.closeStream(in);
}
return classifier;
}
private static LetterRecognizer readV1(DataInputStream in) throws IOException {
final int iCount = in.readInt();
final int hCount = in.readInt();
final int oCount = in.readInt();
final String[] classes = new String[oCount];
for (int i = 0; i < classes.length; i++) {
classes[i] = in.readUTF();
}
final LetterRecognizer classifier = new LetterRecognizer(iCount, hCount, classes);
final SigmoidUnit[] hiddenLayer = new SigmoidUnit[hCount];
final SigmoidUnit[] outputLayer = new SigmoidUnit[oCount];
for (int i = 0; i < hCount; i++) {
final float[] weights = new float[iCount + 1];
for (int j = 0; j <= iCount; j++) {
weights[j] = in.readFloat();
}
hiddenLayer[i] = new SigmoidUnit(weights);
}
for (int i = 0; i < oCount; i++) {
final float[] weights = new float[hCount + 1];
for (int j = 0; j <= hCount; j++) {
weights[j] = in.readFloat();
}
outputLayer[i] = new SigmoidUnit(weights);
}
classifier.mHiddenLayer = hiddenLayer;
classifier.mOutputLayer = outputLayer;
return classifier;
}
/**
* 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 (mGestureStore != null) {
return mGestureStore.save();
}
return false;
}
/**
* 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) {
mGestureStore = GestureLibraries.fromFile(GESTURE_FILE_NAME);
mGestureStore.setSequenceType(GestureStore.SEQUENCE_INVARIANT);
mGestureStore.load();
} else {
mGestureStore = null;
}
}
/**
* 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 (mGestureStore != null) {
mGestureStore.addGesture(letter, example);
}
}
private void adjustPrediction(Gesture query, ArrayList<Prediction> predictions) {
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++) {
Prediction prediction = predictions.remove(0);
topNList.put(prediction.name, prediction);
}
final int count = results.size();
for (int j = count - 1; j >= 0 && !topNList.isEmpty(); j--) {
final Prediction item = results.get(j);
final Prediction original = topNList.get(item.name);
if (original != null) {
predictions.add(0, original);
topNList.remove(item.name);
}
}
}
}
private static class PredictionComparator implements Comparator<Prediction> {
public int compare(Prediction object1, Prediction object2) {
double score1 = object1.score;
double score2 = object2.score;
if (score1 > score2) {
return -1;
} else if (score1 < score2) {
return 1;
} else {
return 0;
}
}
}
}

View File

@@ -1,65 +0,0 @@
/*
* 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);
}
}

View File

@@ -41,18 +41,12 @@ import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.KeyCharacterMap;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputMethodManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.gesture.GestureOverlayView;
import android.gesture.Gesture;
import android.gesture.LetterRecognizer;
import android.gesture.Prediction;
import android.gesture.LetterRecognizers;
import com.android.internal.R;
@@ -73,7 +67,6 @@ import java.util.List;
* @attr ref android.R.styleable#AbsListView_cacheColorHint
* @attr ref android.R.styleable#AbsListView_fastScrollEnabled
* @attr ref android.R.styleable#AbsListView_smoothScrollbar
* @attr ref android.R.styleable#AbsListView_gestures
*/
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
@@ -101,31 +94,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
/**
* Disables gestures.
*
* @see #setGestures(int)
* @see #GESTURES_JUMP
* @see #GESTURES_FILTER
*/
public static final int GESTURES_NONE = 0;
/**
* When a letter gesture is recognized the list jumps to a matching position.
*
* @see #setGestures(int)
* @see #GESTURES_NONE
* @see #GESTURES_FILTER
*/
public static final int GESTURES_JUMP = 1;
/**
* When a letter gesture is recognized the letter is added to the filter.
*
* @see #setGestures(int)
* @see #GESTURES_NONE
* @see #GESTURES_JUMP
*/
public static final int GESTURES_FILTER = 2;
/**
* Indicates that we are not in the middle of a touch gesture
*/
@@ -461,18 +429,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
private FastScroller mFastScroller;
/**
* Indicates the type of gestures to use: GESTURES_NONE, GESTURES_FILTER or GESTURES_NONE
*/
private int mGestures;
// Used to implement the gestures overlay
private GestureOverlayView mGesturesOverlay;
private PopupWindow mGesturesPopup;
private ViewTreeObserver.OnGlobalLayoutListener mGesturesLayoutListener;
private boolean mGlobalLayoutListenerAddedGestures;
private boolean mInstallGesturesOverlay;
private boolean mGlobalLayoutListenerAddedFilter;
private int mTouchSlop;
@@ -583,9 +539,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
setSmoothScrollbarEnabled(smoothScrollbar);
int gestures = a.getInt(R.styleable.AbsListView_gestures, GESTURES_NONE);
setGestures(gestures);
a.recycle();
}
@@ -600,160 +553,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
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},
* the user can draw characters on top of this view. When a character is
* recognized and matches a known character, the list will either:</p>
* <ul>
* <li>Jump to the appropriate position ({@link #GESTURES_JUMP})</li>
* <li>Add the character to the current filter ({@link #GESTURES_FILTER})</li>
* </ul>
* <p>Using {@link #GESTURES_JUMP} requires {@link #isFastScrollEnabled()} to
* be true. Using {@link #GESTURES_FILTER} requires {@link #isTextFilterEnabled()}
* to be true.</p>
*
* @param gestures The type of gestures to enable for this list:
* {@link #GESTURES_NONE}, {@link #GESTURES_JUMP} or {@link #GESTURES_FILTER}
*
* @see #GESTURES_NONE
* @see #GESTURES_JUMP
* @see #GESTURES_FILTER
* @see #getGestures()
*/
public void setGestures(int gestures) {
switch (gestures) {
case GESTURES_JUMP:
if (!mFastScrollEnabled) {
throw new IllegalStateException("Jump gestures can only be used with "
+ "fast scroll enabled");
}
break;
case GESTURES_FILTER:
if (!mTextFilterEnabled) {
throw new IllegalStateException("Filter gestures can only be used with "
+ "text filtering enabled");
}
break;
}
final int oldGestures = mGestures;
mGestures = gestures;
// Install overlay later
if (oldGestures == GESTURES_NONE && gestures != GESTURES_NONE) {
mInstallGesturesOverlay = true;
// Uninstall overlay
} else if (oldGestures != GESTURES_NONE && gestures == GESTURES_NONE) {
uninstallGesturesOverlay();
}
}
/**
* Indicates what gestures are enabled on this view.
*
* @return {@link #GESTURES_NONE}, {@link #GESTURES_JUMP} or {@link #GESTURES_FILTER}
*
* @see #GESTURES_NONE
* @see #GESTURES_JUMP
* @see #GESTURES_FILTER
* @see #setGestures(int)
*/
@ViewDebug.ExportedProperty(mapping = {
@ViewDebug.IntToString(from = GESTURES_NONE, to = "NONE"),
@ViewDebug.IntToString(from = GESTURES_JUMP, to = "JUMP"),
@ViewDebug.IntToString(from = GESTURES_FILTER, to = "FILTER")
})
public int getGestures() {
return mGestures;
}
private void dismissGesturesPopup() {
if (mGesturesPopup != null) {
mGesturesPopup.dismiss();
}
}
private void showGesturesPopup() {
// Make sure we have a window before showing the popup
if (getWindowVisibility() == View.VISIBLE) {
installGesturesOverlay();
positionGesturesPopup();
}
}
private void positionGesturesPopup() {
final int[] xy = new int[2];
getLocationOnScreen(xy);
if (!mGesturesPopup.isShowing()) {
mGesturesPopup.showAtLocation(this, Gravity.LEFT | Gravity.TOP, xy[0], xy[1]);
} else {
mGesturesPopup.update(xy[0], xy[1], -1, -1);
}
}
private void installGesturesOverlay() {
mInstallGesturesOverlay = false;
if (mGesturesPopup == null) {
final Context c = getContext();
final LayoutInflater layoutInflater = (LayoutInflater)
c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mGesturesOverlay = (GestureOverlayView)
layoutInflater.inflate(R.layout.list_gestures_overlay, null);
final PopupWindow p = new PopupWindow(c);
p.setFocusable(false);
p.setTouchable(false);
p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
p.setContentView(mGesturesOverlay);
p.setWidth(getWidth());
p.setHeight(getHeight());
p.setBackgroundDrawable(null);
if (mGesturesLayoutListener == null) {
mGesturesLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
if (isShown()) {
showGesturesPopup();
} else if (mGesturesPopup.isShowing()) {
dismissGesturesPopup();
}
}
};
}
getViewTreeObserver().addOnGlobalLayoutListener(mGesturesLayoutListener);
mGlobalLayoutListenerAddedGestures = true;
mGesturesPopup = p;
mGesturesOverlay.removeAllOnGestureListeners();
mGesturesOverlay.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
mGesturesOverlay.addOnGesturePerformedListener(new GesturesProcessor());
}
}
private void uninstallGesturesOverlay() {
dismissGesturesPopup();
mGesturesPopup = null;
if (mGesturesLayoutListener != null) {
getViewTreeObserver().removeGlobalOnLayoutListener(mGesturesLayoutListener);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mGestures != GESTURES_NONE) {
if ((ev.getAction() != MotionEvent.ACTION_DOWN || mFastScroller == null ||
!mFastScroller.isPointInside(ev.getX(), ev.getY())) &&
mGesturesPopup.isShowing()) {
mGesturesOverlay.dispatchTouchEvent(ev);
}
}
return super.dispatchTouchEvent(ev);
}
/**
* 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
@@ -1037,7 +836,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* about having leaked the window.
*/
dismissPopup();
dismissGesturesPopup();
Parcelable superState = super.onSaveInstanceState();
@@ -1325,11 +1123,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
positionPopup();
}
if (mGestures != GESTURES_NONE && visible && mGesturesPopup != null &&
mGesturesPopup.isShowing()) {
positionGesturesPopup();
}
}
return changed;
@@ -1544,13 +1337,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mFastScroller != null) {
mFastScroller.onSizeChanged(w, h, oldw, oldh);
}
if (mInstallGesturesOverlay) {
installGesturesOverlay();
positionGesturesPopup();
} else if (mGesturesPopup != null && mGesturesPopup.isShowing()) {
mGesturesPopup.update(w, h);
}
}
/**
@@ -1737,10 +1523,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
treeObserver.addOnGlobalLayoutListener(this);
}
if (mGestures != GESTURES_NONE && mGesturesPopup != null &&
!mGlobalLayoutListenerAddedGestures) {
treeObserver.addOnGlobalLayoutListener(mGesturesLayoutListener);
}
}
}
@@ -1755,10 +1537,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
treeObserver.removeGlobalOnLayoutListener(this);
mGlobalLayoutListenerAddedFilter = false;
}
if (mGesturesLayoutListener != null && mGesturesPopup != null) {
mGlobalLayoutListenerAddedGestures = false;
treeObserver.removeGlobalOnLayoutListener(mGesturesLayoutListener);
}
}
}
@@ -1773,7 +1551,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
removeCallbacks(mFlingRunnable);
// Always hide the type filter
dismissPopup();
dismissGesturesPopup();
if (touchMode == TOUCH_MODE_OFF) {
// Remember the last selected element
@@ -1784,9 +1561,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Show the type filter only if a filter is in effect
showPopup();
}
if (mGestures != GESTURES_NONE) {
showGesturesPopup();
}
// If we changed touch mode since the last time we had focus
if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
@@ -1907,8 +1681,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final int longPressPosition, final long longPressId) {
boolean handled = false;
dismissGesturesPopup();
if (mOnItemLongClickListener != null) {
handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
longPressPosition, longPressId);
@@ -3829,79 +3601,4 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
}
private class GesturesProcessor implements GestureOverlayView.OnGesturePerformedListener {
private static final double SCORE_THRESHOLD = 0.1;
private LetterRecognizer mRecognizer;
private ArrayList<Prediction> mPredictions;
private final KeyCharacterMap mKeyMap;
private final char[] mHolder;
GesturesProcessor() {
mRecognizer = LetterRecognizers.fromType(getContext(),
LetterRecognizers.RECOGNIZER_LATIN_LOWERCASE);
if (mRecognizer == null) {
setGestures(GESTURES_NONE);
}
if (mGestures == GESTURES_FILTER) {
mKeyMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
mHolder = new char[1];
} else {
mKeyMap = null;
mHolder = null;
}
}
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
mPredictions = mRecognizer.recognize(gesture, mPredictions);
if (!mPredictions.isEmpty()) {
final Prediction prediction = mPredictions.get(0);
if (prediction.score > SCORE_THRESHOLD) {
switch (mGestures) {
case GESTURES_JUMP:
processJump(prediction);
break;
case GESTURES_FILTER:
processFilter(prediction);
break;
}
}
}
}
private void processJump(Prediction prediction) {
final Object[] sections = mFastScroller.getSections();
if (sections != null) {
final String name = prediction.name;
final int count = sections.length;
int index = -1;
for (int i = 0; i < count; i++) {
if (name.equalsIgnoreCase((String) sections[i])) {
index = i;
break;
}
}
if (index != -1) {
final SectionIndexer indexer = mFastScroller.getSectionIndexer();
final int position = indexer.getPositionForSection(index);
setSelection(position);
}
}
}
private void processFilter(Prediction prediction) {
mHolder[0] = prediction.name.charAt(0);
final KeyEvent[] events = mKeyMap.getEvents(mHolder);
if (events != null) {
for (KeyEvent event : events) {
sendToTextFilter(event.getKeyCode(), event.getRepeatCount(),
event);
}
}
}
}
}

Binary file not shown.

View File

@@ -1381,16 +1381,6 @@
will use only the number of items in the adapter and the number of items visible
on screen to determine the scrollbar's properties. -->
<attr name="smoothScrollbar" format="boolean" />
<!-- Defines the type of gesture to enable for the widget. -->
<attr name="gestures">
<!-- No gesture -->
<enum name="none" value="0" />
<!-- Gestures jump to a specific position in the content. This requires
fast scroll to be enabled. -->
<enum name="jump" value="1" />
<!-- Gestures filter the content. This requires text filtering to be enabled. -->
<enum name="filter" value="2" />
</attr>
</declare-styleable>
<declare-styleable name="AbsSpinner">
<!-- Reference to an array resource that will populate the Spinner. For static content,

View File

@@ -1106,7 +1106,6 @@
<public type="attr" name="uncertainGestureColor" />
<public type="attr" name="fadeOffset" />
<public type="attr" name="fadeDuration" />
<public type="attr" name="gestures" />
<public type="attr" name="gestureStrokeType" />
<public type="attr" name="gestureStrokeLengthThreshold" />
<public type="attr" name="gestureStrokeSquarenessThreshold" />