summaryrefslogtreecommitdiffstats
path: root/src/com/android/browser/autocomplete/SuggestedTextController.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/browser/autocomplete/SuggestedTextController.java')
-rw-r--r--src/com/android/browser/autocomplete/SuggestedTextController.java516
1 files changed, 0 insertions, 516 deletions
diff --git a/src/com/android/browser/autocomplete/SuggestedTextController.java b/src/com/android/browser/autocomplete/SuggestedTextController.java
deleted file mode 100644
index 95dfcab..0000000
--- a/src/com/android/browser/autocomplete/SuggestedTextController.java
+++ /dev/null
@@ -1,516 +0,0 @@
-/*
- * Copyright (C) 2010 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 com.android.browser.autocomplete;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.Editable;
-import android.text.Selection;
-import android.text.SpanWatcher;
-import android.text.Spannable;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.View;
-import android.widget.EditText;
-
-import java.util.ArrayList;
-
-import junit.framework.Assert;
-
-
-/**
- * The query editor can show a suggestion, grayed out following the query that the user has
- * entered so far. As the user types new characters, these should replace the grayed suggestion
- * text. This class manages this logic, displaying the suggestion when the user entered text is a
- * prefix of it, and hiding it otherwise.
- *
- * Note, the text in the text view will contain the entire suggestion, not just what the user
- * entered. Instead of retrieving the text from the text view, {@link #getUserText()} should be
- * called on this class.
- */
-public class SuggestedTextController {
- private static final boolean DBG = false;
- private static final String TAG = "Browser.SuggestedTextController";
-
- private final BufferTextWatcher mBufferTextWatcher = new BufferTextWatcher();
- private final BufferSpanWatcher mBufferSpanWatcher = new BufferSpanWatcher();
- private final ArrayList<TextChangeWatcher> mTextWatchers;
- private final TextOwner mTextOwner;
- private final StringBuffer mUserEntered;
- private final SuggestedSpan mSuggested;
- private String mSuggestedText;
- private TextChangeAttributes mCurrentTextChange;
- private boolean mSuspended = false;
-
- /**
- * While this is non-null, any changes made to the cursor position or selection are ignored. Is
- * stored the selection state at the moment when selection change processing was disabled.
- */
- private BufferSelection mTextSelectionBeforeIgnoringChanges;
-
- public SuggestedTextController(final EditText textView, int color) {
- this(new TextOwner() {
- @Override
- public Editable getText() {
- return textView.getText();
- }
- @Override
- public void addTextChangedListener(TextWatcher watcher) {
- textView.addTextChangedListener(watcher);
- }
- @Override
- public void removeTextChangedListener(TextWatcher watcher) {
- textView.removeTextChangedListener(watcher);
- }
- @Override
- public void setText(String text) {
- textView.setText(text);
- }
- }, color);
- }
-
- private void initialize(String userText, int selStart, int selEnd, String suggested) {
- Editable text = mTextOwner.getText();
-
- if (userText == null) userText = "";
- String allText = userText;
- int suggestedStart = allText.length();
- if (suggested != null && userText != null) {
- if (suggested.startsWith(userText.toLowerCase())) {
- allText = suggested;
- }
- }
-
- // allText is at this point either "userText" (not null) or
- // "suggested" if thats not null and starts with userText.
- text.replace(0, text.length(), allText);
- Selection.setSelection(text, selStart, selEnd);
- mUserEntered.replace(0, mUserEntered.length(), userText);
- mSuggestedText = suggested;
- if (suggestedStart < text.length()) {
- text.setSpan(mSuggested, suggestedStart, text.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- } else {
- text.removeSpan(mSuggested);
- }
- text.setSpan(mBufferSpanWatcher, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- mTextOwner.addTextChangedListener(mBufferTextWatcher);
- if (DBG) checkInvariant(text);
- }
-
- private void assertNotIgnoringSelectionChanges() {
- if (mTextSelectionBeforeIgnoringChanges != null) {
- throw new IllegalStateException(
- "Illegal operation while cursor movement processing suspended");
- }
- }
-
- public boolean isCursorHandlingSuspended() {
- return mSuspended;
- }
-
- public Parcelable saveInstanceState(Parcelable superState) {
- assertNotIgnoringSelectionChanges();
- SavedState ss = new SavedState(superState);
- Editable buffer = mTextOwner.getText();
- ss.mUserText = getUserText();
- ss.mSuggestedText = mSuggestedText;
- ss.mSelStart = Selection.getSelectionStart(buffer);
- ss.mSelEnd = Selection.getSelectionEnd(buffer);
- return ss;
- }
-
- public Parcelable restoreInstanceState(Parcelable state) {
- assertNotIgnoringSelectionChanges();
- if (!(state instanceof SavedState)) return state;
- SavedState ss = (SavedState) state;
- if (DBG) {
- Log.d(TAG, "restoreInstanceState t='" + ss.mUserText + "' suggestion='" +
- ss.mSuggestedText + " sel=" + ss.mSelStart + ".." + ss.mSelEnd);
- }
- // remove our listeners so we don't get notifications while re-initialising
- mTextOwner.getText().removeSpan(mBufferSpanWatcher);
- mTextOwner.removeTextChangedListener(mBufferTextWatcher);
- // and initialise will re-add the watchers
- initialize(ss.mUserText, ss.mSelStart, ss.mSelEnd, ss.mSuggestedText);
- notifyUserEnteredChanged();
- return ss.getSuperState();
- }
-
- /**
- * Temporarily stop processing cursor movements and selection changes. While cursor movements
- * are being ignored, the text in the buffer must NOT be changed; doing so will result in an
- * {@link IllegalStateException} being thrown.
- *
- * To stop ignoring cursor movements, call
- * {@link #resumeCursorMovementHandlingAndApplyChanges()}.
- */
- public void suspendCursorMovementHandling() {
- assertNotIgnoringSelectionChanges();
- Editable buffer = mTextOwner.getText();
- mTextSelectionBeforeIgnoringChanges = new BufferSelection(buffer);
- mSuspended = true;
- }
-
- /**
- * Start responding to cursor movements and selection changes again. If the cursor or selection
- * moved while it was being ignored, these changes will be processed now.
- */
- public void resumeCursorMovementHandlingAndApplyChanges() {
- Editable buffer = mTextOwner.getText();
- BufferSelection oldSelection = mTextSelectionBeforeIgnoringChanges;
- mTextSelectionBeforeIgnoringChanges = null;
- BufferSelection newSelection = new BufferSelection(buffer);
- if (oldSelection.mStart != newSelection.mStart) {
- mBufferSpanWatcher.onSpanChanged(buffer, Selection.SELECTION_START,
- oldSelection.mStart, oldSelection.mStart,
- newSelection.mStart, newSelection.mStart);
- }
- if (oldSelection.mEnd != newSelection.mEnd) {
- mBufferSpanWatcher.onSpanChanged(buffer, Selection.SELECTION_END,
- oldSelection.mEnd, oldSelection.mEnd,
- newSelection.mEnd, newSelection.mEnd);
- }
- mSuspended = false;
- }
-
- /**
- * Sets the current suggested text. A portion of this will be added to the user entered text if
- * that is a prefix of the suggestion.
- */
- public void setSuggestedText(String text) {
- assertNotIgnoringSelectionChanges();
- if (!TextUtils.equals(text, mSuggestedText)) {
- if (DBG) Log.d(TAG, "setSuggestedText(" + text + ")");
- mSuggestedText = text;
- if (mCurrentTextChange == null) {
- mCurrentTextChange = new TextChangeAttributes(0, 0, 0);
- Editable buffer = mTextOwner.getText();
- handleTextChanged(buffer);
- }
- }
- }
-
- /**
- * Gets the portion of displayed text that is not suggested.
- */
- public String getUserText() {
- assertNotIgnoringSelectionChanges();
- return mUserEntered.toString();
- }
-
- /**
- * Sets the given text as if it has been entered by the user.
- */
- public void setText(String text) {
- assertNotIgnoringSelectionChanges();
- if (text == null) text = "";
- Editable buffer = mTextOwner.getText();
- buffer.removeSpan(mSuggested);
- // this will cause a handleTextChanged call
- buffer.replace(0, text.length(), text);
- }
-
- public void addUserTextChangeWatcher(TextChangeWatcher watcher) {
- mTextWatchers.add(watcher);
- }
-
- private void handleTextChanged(Editable newText) {
- // When we make changes to the buffer from within this function, it results in recursive
- // calls to beforeTextChanges(), afterTextChanged(). We want to ignore the changes we're
- // making ourself:
- if (mCurrentTextChange.isHandled()) return;
- mCurrentTextChange.setHandled();
- final int pos = mCurrentTextChange.mPos;
- final int countBefore = mCurrentTextChange.mCountBefore;
- final int countAfter = mCurrentTextChange.mCountAfter;
- final int cursorPos = Selection.getSelectionEnd(newText);
- if (DBG) {
- Log.d(TAG, "pos=" + pos +"; countBefore=" + countBefore + "; countAfter=" +
- countAfter + "; cursor=" + cursorPos);
- }
- mUserEntered.replace(pos, pos + countBefore,
- newText.subSequence(pos, pos + countAfter).toString());
- if (DBG) Log.d(TAG, "User entered: '" + mUserEntered + "' all='" + newText + "'");
- final int userLen = mUserEntered.length();
- boolean haveSuggested = newText.getSpanStart(mSuggested) != -1;
- if (mSuggestedText != null &&
- mSuggestedText.startsWith(mUserEntered.toString().toLowerCase())) {
- if (haveSuggested) {
- if (!mSuggestedText.equalsIgnoreCase(newText.toString())) {
- if (countAfter > countBefore) {
- // net insertion
- int len = countAfter - countBefore;
- newText.delete(pos + len, pos + len + len);
- } else {
- // net deletion
- newText.replace(userLen, newText.length(),
- mSuggestedText.substring(userLen));
- if (countBefore == 0) {
- // no change to the text - likely just suggested change
- Selection.setSelection(newText, cursorPos);
- }
- }
- }
- } else {
- // no current suggested text - add it
- newText.insert(userLen, mSuggestedText.substring(userLen));
- // keep the cursor at the end of the user entered text, if that where it was
- // before.
- if (cursorPos == userLen) {
- Selection.setSelection(newText, userLen);
- }
- }
- if (userLen == newText.length()) {
- newText.removeSpan(mSuggested);
- } else {
- newText.setSpan(mSuggested, userLen, newText.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- } else {
- if (newText.getSpanStart(mSuggested) != -1) {
- newText.removeSpan(mSuggested);
- newText.delete(mUserEntered.length(), newText.length());
- }
- }
- if (DBG) checkInvariant(newText);
- mCurrentTextChange = null;
- if (countBefore > 0 || countAfter > 0) {
- notifyUserEnteredChanged();
- }
- }
-
- private void notifyUserEnteredChanged() {
- for (TextChangeWatcher watcher : mTextWatchers) {
- watcher.onTextChanged(mUserEntered.toString());
- }
- }
-
- /**
- * Basic interface for being notified of changes to some text.
- */
- public interface TextChangeWatcher {
- void onTextChanged(String newText);
- }
-
- /**
- * Interface class to wrap required methods from {@link EditText}, or some other class used
- * to test without needing an @{link EditText}.
- */
- public interface TextOwner {
- Editable getText();
- void addTextChangedListener(TextWatcher watcher);
- void removeTextChangedListener(TextWatcher watcher);
- void setText(String text);
- }
-
- /**
- * This class stores the parameters passed to {@link BufferTextWatcher#beforeTextChanged},
- * together with a flag indicating if this invocation has been dealt with yet. We need this
- * information, together with the parameters passed to
- * {@link BufferTextWatcher#afterTextChanged}, to restore our internal state when the buffer is
- * edited.
- *
- * Since the changes we make from within {@link BufferTextWatcher#afterTextChanged} also trigger
- * further recursive calls to {@link BufferTextWatcher#beforeTextChanged} and
- * {@link BufferTextWatcher#afterTextChanged}, this class helps detect these recursive calls so
- * they can be ignored.
- */
- private static class TextChangeAttributes {
- public final int mPos;
- public final int mCountAfter;
- public final int mCountBefore;
- private boolean mHandled;
-
- public TextChangeAttributes(int pos, int countAfter, int countBefore) {
- mPos = pos;
- mCountAfter = countAfter;
- mCountBefore = countBefore;
- }
-
- public void setHandled() {
- mHandled = true;
- }
-
- public boolean isHandled() {
- return mHandled;
- }
- }
-
- /**
- * Encapsulates the state of the text selection (and cursor) within a text buffer.
- */
- private static class BufferSelection {
- final int mStart;
- final int mEnd;
- public BufferSelection(CharSequence text) {
- mStart = Selection.getSelectionStart(text);
- mEnd = Selection.getSelectionEnd(text);
- }
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof BufferSelection)) return super.equals(other);
- BufferSelection otherSel = (BufferSelection) other;
- return this.mStart == otherSel.mStart && this.mEnd == otherSel.mEnd;
- }
- }
-
- private class BufferTextWatcher implements TextWatcher {
- @Override
- public void afterTextChanged(Editable newText) {
- if (DBG) {
- Log.d(TAG, "afterTextChanged('" + newText + "')");
- }
- assertNotIgnoringSelectionChanges();
- handleTextChanged(newText);
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- assertNotIgnoringSelectionChanges();
- if (mCurrentTextChange == null) {
- mCurrentTextChange = new TextChangeAttributes(start, after, count);
- }
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
- }
-
- private class BufferSpanWatcher implements SpanWatcher {
- @Override
- public void onSpanAdded(Spannable text, Object what, int start, int end) {
- }
-
- @Override
- public void onSpanChanged(
- Spannable text, Object what, int ostart, int oend, int nstart, int nend) {
- if (mCurrentTextChange != null) return;
- if (mTextSelectionBeforeIgnoringChanges != null) return;
- if (what == Selection.SELECTION_END) {
- if (DBG) Log.d(TAG, "cursor move to " + nend);
- if (nend > mUserEntered.length()) {
- mUserEntered.replace(0, mUserEntered.length(), text.toString());
- text.removeSpan(mSuggested);
- }
- if (DBG) checkInvariant(text);
- }
- }
-
- @Override
- public void onSpanRemoved(Spannable text, Object what, int start, int end) {
- }
- }
-
- public static class SavedState extends View.BaseSavedState {
- String mUserText;
- String mSuggestedText;
- int mSelStart;
- int mSelEnd;
-
- public SavedState(Parcelable superState) {
- super(superState);
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- super.writeToParcel(out, flags);
- out.writeString(mUserText);
- out.writeString(mSuggestedText);
- out.writeInt(mSelStart);
- out.writeInt(mSelEnd);
- }
-
- @SuppressWarnings("hiding")
- public static final Parcelable.Creator<SavedState> CREATOR
- = new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- @Override
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
-
- private SavedState(Parcel in) {
- super(in);
- mUserText = in.readString();
- mSuggestedText = in.readString();
- mSelStart = in.readInt();
- mSelEnd = in.readInt();
- }
- }
-
- /*
- * The remaining functions are used for testing purposes only.
- * -----------------------------------------------------------
- */
-
- /**
- * Verify that the internal state of this class is consistent.
- */
- @VisibleForTesting
- void checkInvariant(final Spannable s) {
- int suggestedStart = s.getSpanStart(mSuggested);
- int suggestedEnd = s.getSpanEnd(mSuggested);
- int cursorPos = Selection.getSelectionEnd(s);
- if (suggestedStart == -1 || suggestedEnd == -1) {
- suggestedStart = suggestedEnd = s.length();
- }
- String userEntered = getUserText();
- Log.d(TAG, "checkInvariant all='" + s + "' (len " + s.length() + ") sug="
- + suggestedStart + ".." + suggestedEnd + " cursor=" + cursorPos +
- " ue='" + userEntered + "' (len " + userEntered.length() + ")");
- int suggestedLen = suggestedEnd - suggestedStart;
- Assert.assertEquals("Sum of user and suggested text lengths doesn't match total length",
- s.length(), userEntered.length() + suggestedLen);
- Assert.assertEquals("End of user entered text doesn't match start of suggested",
- suggestedStart, userEntered.length());
- Assert.assertTrue("user entered text does not match start of buffer",
- userEntered.toString().equalsIgnoreCase(
- s.subSequence(0, suggestedStart).toString()));
- if (mSuggestedText != null && suggestedStart < s.length()) {
- Assert.assertTrue("User entered is not a prefix of suggested",
- mSuggestedText.startsWith(userEntered.toString().toLowerCase()));
- Assert.assertTrue("Suggested text does not match buffer contents",
- mSuggestedText.equalsIgnoreCase(s.toString().toLowerCase()));
- }
- if (mSuggestedText == null) {
- Assert.assertEquals("Non-zero suggention length with null suggestion", 0, suggestedLen);
- } else {
- Assert.assertTrue("Suggestion text longer than suggestion (" + mSuggestedText.length() +
- ">" + suggestedLen + ")", suggestedLen <= mSuggestedText.length());
- }
- Assert.assertTrue("Cursor within suggested part", cursorPos <= suggestedStart);
- }
-
- @VisibleForTesting
- SuggestedTextController(TextOwner textOwner, int color) {
- mUserEntered = new StringBuffer();
- mSuggested = new SuggestedSpan(color);
- mTextOwner = textOwner;
- mTextWatchers = new ArrayList<TextChangeWatcher>();
- initialize(null, 0, 0, null);
- }
-}