Suggestions popup window is dismissed on tap outside.
But now the tap is not handled at all, as it was before. To do this, the popup window
is now focusable. As a result, the TextView's window loses focus. We hide the
cursor to prevent a non-blinking visible cursor. We should also fake the
state of the parent TextView to keep it visually focussed.
SuggestionRangeSpan and SpellCheckSpan had to made Parcelable since they are recreatedi
when the TextView is re-created when the popup is dismissed.
Change-Id: Ic99b2c4f02c282394f214938dd19168547af4886
diff --git a/api/current.txt b/api/current.txt
index 88a708c..8cd2140 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1605,6 +1605,7 @@
field public static final int TextAppearance_StatusBar_EventContent_Title = 16973928; // 0x1030068
field public static final int TextAppearance_StatusBar_Icon = 16973926; // 0x1030066
field public static final int TextAppearance_StatusBar_Title = 16973925; // 0x1030065
+ field public static final int TextAppearance_SuggestionHighlight = 16974104; // 0x1030118
field public static final int TextAppearance_Theme = 16973888; // 0x1030040
field public static final int TextAppearance_Theme_Dialog = 16973896; // 0x1030048
field public static final int TextAppearance_Widget = 16973897; // 0x1030049
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 83ef6ce..b8b54f4 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -31,9 +31,11 @@
import android.text.style.RelativeSizeSpan;
import android.text.style.ReplacementSpan;
import android.text.style.ScaleXSpan;
+import android.text.style.SpellCheckSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
+import android.text.style.SuggestionRangeSpan;
import android.text.style.SuggestionSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TextAppearanceSpan;
@@ -579,6 +581,10 @@
public static final int ANNOTATION = 18;
/** @hide */
public static final int SUGGESTION_SPAN = 19;
+ /** @hide */
+ public static final int SPELL_CHECK_SPAN = 20;
+ /** @hide */
+ public static final int SUGGESTION_RANGE_SPAN = 21;
/**
* Flatten a CharSequence and whatever styles can be copied across processes
@@ -734,6 +740,14 @@
readSpan(p, sp, new SuggestionSpan(p));
break;
+ case SPELL_CHECK_SPAN:
+ readSpan(p, sp, new SpellCheckSpan(p));
+ break;
+
+ case SUGGESTION_RANGE_SPAN:
+ readSpan(p, sp, new SuggestionRangeSpan());
+ break;
+
default:
throw new RuntimeException("bogus span encoding " + kind);
}
diff --git a/core/java/android/text/style/SpellCheckSpan.java b/core/java/android/text/style/SpellCheckSpan.java
index 9b23177..caaae99 100644
--- a/core/java/android/text/style/SpellCheckSpan.java
+++ b/core/java/android/text/style/SpellCheckSpan.java
@@ -16,6 +16,10 @@
package android.text.style;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+
/**
* A SpellCheckSpan is an internal data structure created by the TextView's SpellChecker to
* annotate portions of the text that are about to or currently being spell checked. They are
@@ -23,7 +27,7 @@
*
* @hide
*/
-public class SpellCheckSpan {
+public class SpellCheckSpan implements ParcelableSpan {
private boolean mSpellCheckInProgress;
@@ -31,6 +35,10 @@
mSpellCheckInProgress = false;
}
+ public SpellCheckSpan(Parcel src) {
+ mSpellCheckInProgress = (src.readInt() != 0);
+ }
+
public void setSpellCheckInProgress() {
mSpellCheckInProgress = true;
}
@@ -38,4 +46,19 @@
public boolean isSpellCheckInProgress() {
return mSpellCheckInProgress;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSpellCheckInProgress ? 1 : 0);
+ }
+
+ @Override
+ public int getSpanTypeId() {
+ return TextUtils.SPELL_CHECK_SPAN;
+ }
}
diff --git a/core/java/android/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java
new file mode 100644
index 0000000..fc91697
--- /dev/null
+++ b/core/java/android/text/style/SuggestionRangeSpan.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 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.text.style;
+
+import android.graphics.Color;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+/**
+ * A SuggestionRangeSpan is used to show which part of an EditText is affected by a suggestion
+ * popup window.
+ *
+ * @hide
+ */
+public class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan {
+ @Override
+ public void updateDrawState(TextPaint tp) {
+ tp.setColor(Color.GREEN);
+ }
+
+ public SuggestionRangeSpan() { /* Nothing to do*/ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) { /* Nothing to do*/ }
+
+ @Override
+ public int getSpanTypeId() {
+ return TextUtils.SUGGESTION_RANGE_SPAN;
+ }
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 683a984..d015326 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -84,10 +84,10 @@
import android.text.style.ClickableSpan;
import android.text.style.ParagraphStyle;
import android.text.style.SpellCheckSpan;
+import android.text.style.SuggestionRangeSpan;
import android.text.style.SuggestionSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.URLSpan;
-import android.text.style.UnderlineSpan;
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
import android.util.AttributeSet;
@@ -2894,7 +2894,6 @@
sp.removeSpan(cw);
}
- // hideControllers would do it, but it gets called after this method on rotation
sp.removeSpan(mSuggestionRangeSpan);
ss.text = sp;
@@ -5099,10 +5098,9 @@
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
- boolean areSuggestionsShown = areSuggestionsShown();
boolean isInSelectionMode = mSelectionActionMode != null;
- if (areSuggestionsShown || isInSelectionMode) {
+ if (isInSelectionMode) {
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
@@ -5115,10 +5113,6 @@
state.handleUpEvent(event);
}
if (event.isTracking() && !event.isCanceled()) {
- if (areSuggestionsShown) {
- hideSuggestions();
- return true;
- }
if (isInSelectionMode) {
stopSelectionActionMode();
return true;
@@ -5282,10 +5276,6 @@
// Has to be done on key down (and not on key up) to correctly be intercepted.
case KeyEvent.KEYCODE_BACK:
- if (areSuggestionsShown()) {
- hideSuggestions();
- return -1;
- }
if (mSelectionActionMode != null) {
stopSelectionActionMode();
return -1;
@@ -7634,12 +7624,6 @@
getSpellChecker().addSpellCheckSpan((SpellCheckSpan) what);
}
}
-
- if (what instanceof SuggestionSpan) {
- if (newStart < 0) {
- Log.d("spellcheck", "REMOVE suggspan " + mText.subSequence(oldStart, oldEnd));
- }
- }
}
/**
@@ -7956,9 +7940,6 @@
}
hideControllers();
-
- removeSpans(0, mText.length(), SuggestionSpan.class);
- removeSpans(0, mText.length(), SpellCheckSpan.class);
}
startStopMarquee(hasWindowFocus);
@@ -9202,11 +9183,6 @@
}
}
- private static class SuggestionRangeSpan extends UnderlineSpan {
- // TODO themable, would be nice to make it a child class of TextAppearanceSpan, but
- // there is no way to have underline and TextAppearanceSpan.
- }
-
private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnClickListener {
private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE;
private static final int NO_SUGGESTIONS = -1;
@@ -9214,13 +9190,42 @@
private WordIterator mSuggestionWordIterator;
private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan
[(int) (AVERAGE_HIGHLIGHTS_PER_SUGGESTION * MAX_NUMBER_SUGGESTIONS)];
+ private boolean mCursorWasVisibleBeforeSuggestions;
+
+ private class CustomPopupWindow extends PopupWindow {
+ public CustomPopupWindow(Context context, int defStyle) {
+ super(context, null, defStyle);
+ }
+
+ @Override
+ public void dismiss() {
+ super.dismiss();
+
+ if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
+ ((Editable) mText).removeSpan(mSuggestionRangeSpan);
+ }
+
+ setCursorVisible(mCursorWasVisibleBeforeSuggestions);
+ if (hasInsertionController()) {
+ getInsertionController().show();
+ }
+ }
+ }
+
+ public SuggestionsPopupWindow() {
+ for (int i = 0; i < mHighlightSpans.length; i++) {
+ mHighlightSpans[i] = new TextAppearanceSpan(mContext,
+ android.R.style.TextAppearance_SuggestionHighlight);
+ }
+ mCursorWasVisibleBeforeSuggestions = mCursorVisible;
+ }
@Override
protected void createPopupWindow() {
- mPopupWindow = new PopupWindow(TextView.this.mContext, null,
+ mPopupWindow = new CustomPopupWindow(TextView.this.mContext,
com.android.internal.R.attr.textSuggestionsWindowStyle);
mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
- mPopupWindow.setOutsideTouchable(true);
+ mPopupWindow.setFocusable(true);
mPopupWindow.setClippingEnabled(false);
}
@@ -9294,6 +9299,8 @@
if (!(mText instanceof Editable)) return;
if (updateSuggestions()) {
+ mCursorWasVisibleBeforeSuggestions = mCursorVisible;
+ setCursorVisible(false);
super.show();
}
}
@@ -9318,9 +9325,6 @@
@Override
public void hide() {
super.hide();
- if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
- ((Editable) mText).removeSpan(mSuggestionRangeSpan);
- }
}
private boolean updateSuggestions() {
@@ -9559,7 +9563,7 @@
final int spanEnd = suggestionInfo.spanEnd;
if (spanStart != NO_SUGGESTIONS) {
// SuggestionSpans are removed by replace: save them before
- Editable editable = ((Editable) mText);
+ Editable editable = (Editable) mText;
SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
SuggestionSpan.class);
final int length = suggestionSpans.length;
@@ -9578,7 +9582,7 @@
final String suggestion = textView.getText().subSequence(
suggestionStart, suggestionEnd).toString();
final String originalText = mText.subSequence(spanStart, spanEnd).toString();
- ((Editable) mText).replace(spanStart, spanEnd, suggestion);
+ editable.replace(spanStart, spanEnd, suggestion);
// A replacement on a misspelled text removes the misspelled flag.
// TODO restore the flag if the misspelled word is selected back?
@@ -9630,12 +9634,6 @@
mSuggestionsPopupWindow.show();
}
- void hideSuggestions() {
- if (mSuggestionsPopupWindow != null) {
- mSuggestionsPopupWindow.hide();
- }
- }
-
boolean areSuggestionsShown() {
return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing();
}
@@ -10585,7 +10583,6 @@
mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow());
hideInsertionPointCursorController();
- hideSuggestions();
}
public void hide() {
@@ -10697,7 +10694,6 @@
private void hideControllers() {
hideInsertionPointCursorController();
stopSelectionActionMode();
- hideSuggestions();
}
/**
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index d8d613a..3cbd682 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -885,11 +885,9 @@
<item name="android:textSize">30sp</item>
</style>
- <!-- @hide -->
<style name="TextAppearance.SuggestionHighlight">
<item name="android:textSize">18sp</item>
<item name="android:textColor">@android:color/suggestion_highlight_text</item>
- <item name="android:textStyle">bold</item>
</style>
<!-- Preference Styles -->