SuggestionSpan.java revision 0f0a3945ef98b3c30839ab7b746bf8cc55b78d33
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.text.style; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Color; 22import android.os.Parcel; 23import android.os.Parcelable; 24import android.os.SystemClock; 25import android.text.ParcelableSpan; 26import android.text.TextPaint; 27import android.text.TextUtils; 28import android.widget.TextView; 29 30import java.util.Arrays; 31import java.util.Locale; 32 33/** 34 * Holds suggestion candidates for the text enclosed in this span. 35 * 36 * When such a span is edited in an EditText, double tapping on the text enclosed in this span will 37 * display a popup dialog listing suggestion replacement for that text. The user can then replace 38 * the original text by one of the suggestions. 39 * 40 * These spans should typically be created by the input method to provide correction and alternates 41 * for the text. 42 * 43 * @see TextView#isSuggestionsEnabled() 44 */ 45public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { 46 47 /** 48 * Sets this flag if the suggestions should be easily accessible with few interactions. 49 * This flag should be set for every suggestions that the user is likely to use. 50 */ 51 public static final int FLAG_EASY_CORRECT = 0x0001; 52 53 /** 54 * Sets this flag if the suggestions apply to a misspelled word/text. This type of suggestion is 55 * rendered differently to highlight the error. 56 */ 57 public static final int FLAG_MISSPELLED = 0x0002; 58 59 /** 60 * Sets this flag if the auto correction is about to be applied to a word/text 61 * that the user is typing/composing. This type of suggestion is rendered differently 62 * to indicate the auto correction is happening. 63 */ 64 public static final int FLAG_AUTO_CORRECTION = 0x0004; 65 66 public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED"; 67 public static final String SUGGESTION_SPAN_PICKED_AFTER = "after"; 68 public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before"; 69 public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode"; 70 71 public static final int SUGGESTIONS_MAX_SIZE = 5; 72 73 /* 74 * TODO: Needs to check the validity and add a feature that TextView will change 75 * the current IME to the other IME which is specified in SuggestionSpan. 76 * An IME needs to set the span by specifying the target IME and Subtype of SuggestionSpan. 77 * And the current IME might want to specify any IME as the target IME including other IMEs. 78 */ 79 80 private int mFlags; 81 private final String[] mSuggestions; 82 private final String mLocaleString; 83 private final String mNotificationTargetClassName; 84 private final int mHashCode; 85 86 private float mEasyCorrectUnderlineThickness; 87 private int mEasyCorrectUnderlineColor; 88 89 private float mMisspelledUnderlineThickness; 90 private int mMisspelledUnderlineColor; 91 92 private float mAutoCorrectionUnderlineThickness; 93 private int mAutoCorrectionUnderlineColor; 94 95 /* 96 * TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo 97 * and InputMethodSubtype. 98 */ 99 100 /** 101 * @param context Context for the application 102 * @param suggestions Suggestions for the string under the span 103 * @param flags Additional flags indicating how this span is handled in TextView 104 */ 105 public SuggestionSpan(Context context, String[] suggestions, int flags) { 106 this(context, null, suggestions, flags, null); 107 } 108 109 /** 110 * @param locale Locale of the suggestions 111 * @param suggestions Suggestions for the string under the span 112 * @param flags Additional flags indicating how this span is handled in TextView 113 */ 114 public SuggestionSpan(Locale locale, String[] suggestions, int flags) { 115 this(null, locale, suggestions, flags, null); 116 } 117 118 /** 119 * @param context Context for the application 120 * @param locale locale Locale of the suggestions 121 * @param suggestions Suggestions for the string under the span. Only the first up to 122 * {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered. 123 * @param flags Additional flags indicating how this span is handled in TextView 124 * @param notificationTargetClass if not null, this class will get notified when the user 125 * selects one of the suggestions. 126 */ 127 public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags, 128 Class<?> notificationTargetClass) { 129 final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length); 130 mSuggestions = Arrays.copyOf(suggestions, N); 131 mFlags = flags; 132 if (context != null && locale == null) { 133 mLocaleString = context.getResources().getConfiguration().locale.toString(); 134 } else { 135 mLocaleString = locale.toString(); 136 } 137 138 if (notificationTargetClass != null) { 139 mNotificationTargetClassName = notificationTargetClass.getCanonicalName(); 140 } else { 141 mNotificationTargetClassName = ""; 142 } 143 mHashCode = hashCodeInternal(mSuggestions, mLocaleString, mNotificationTargetClassName); 144 145 initStyle(context); 146 } 147 148 private void initStyle(Context context) { 149 int defStyle = com.android.internal.R.attr.textAppearanceMisspelledSuggestion; 150 TypedArray typedArray = context.obtainStyledAttributes( 151 null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); 152 mMisspelledUnderlineThickness = typedArray.getDimension( 153 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); 154 mMisspelledUnderlineColor = typedArray.getColor( 155 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); 156 157 defStyle = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion; 158 typedArray = context.obtainStyledAttributes( 159 null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); 160 mEasyCorrectUnderlineThickness = typedArray.getDimension( 161 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); 162 mEasyCorrectUnderlineColor = typedArray.getColor( 163 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); 164 165 defStyle = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion; 166 typedArray = context.obtainStyledAttributes( 167 null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); 168 mAutoCorrectionUnderlineThickness = typedArray.getDimension( 169 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); 170 mAutoCorrectionUnderlineColor = typedArray.getColor( 171 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); 172 173 } 174 175 public SuggestionSpan(Parcel src) { 176 mSuggestions = src.readStringArray(); 177 mFlags = src.readInt(); 178 mLocaleString = src.readString(); 179 mNotificationTargetClassName = src.readString(); 180 mHashCode = src.readInt(); 181 mEasyCorrectUnderlineColor = src.readInt(); 182 mEasyCorrectUnderlineThickness = src.readFloat(); 183 mMisspelledUnderlineColor = src.readInt(); 184 mMisspelledUnderlineThickness = src.readFloat(); 185 mAutoCorrectionUnderlineColor = src.readInt(); 186 mAutoCorrectionUnderlineThickness = src.readFloat(); 187 } 188 189 /** 190 * @return an array of suggestion texts for this span 191 */ 192 public String[] getSuggestions() { 193 return mSuggestions; 194 } 195 196 /** 197 * @return the locale of the suggestions 198 */ 199 public String getLocale() { 200 return mLocaleString; 201 } 202 203 /** 204 * @return The name of the class to notify. The class of the original IME package will receive 205 * a notification when the user selects one of the suggestions. The notification will include 206 * the original string, the suggested replacement string as well as the hashCode of this span. 207 * The class will get notified by an intent that has those information. 208 * This is an internal API because only the framework should know the class name. 209 * 210 * @hide 211 */ 212 public String getNotificationTargetClassName() { 213 return mNotificationTargetClassName; 214 } 215 216 public int getFlags() { 217 return mFlags; 218 } 219 220 public void setFlags(int flags) { 221 mFlags = flags; 222 } 223 224 @Override 225 public int describeContents() { 226 return 0; 227 } 228 229 @Override 230 public void writeToParcel(Parcel dest, int flags) { 231 dest.writeStringArray(mSuggestions); 232 dest.writeInt(mFlags); 233 dest.writeString(mLocaleString); 234 dest.writeString(mNotificationTargetClassName); 235 dest.writeInt(mHashCode); 236 dest.writeInt(mEasyCorrectUnderlineColor); 237 dest.writeFloat(mEasyCorrectUnderlineThickness); 238 dest.writeInt(mMisspelledUnderlineColor); 239 dest.writeFloat(mMisspelledUnderlineThickness); 240 dest.writeInt(mAutoCorrectionUnderlineColor); 241 dest.writeFloat(mAutoCorrectionUnderlineThickness); 242 } 243 244 @Override 245 public int getSpanTypeId() { 246 return TextUtils.SUGGESTION_SPAN; 247 } 248 249 @Override 250 public boolean equals(Object o) { 251 if (o instanceof SuggestionSpan) { 252 return ((SuggestionSpan)o).hashCode() == mHashCode; 253 } 254 return false; 255 } 256 257 @Override 258 public int hashCode() { 259 return mHashCode; 260 } 261 262 private static int hashCodeInternal(String[] suggestions, String locale, 263 String notificationTargetClassName) { 264 return Arrays.hashCode(new Object[] {Long.valueOf(SystemClock.uptimeMillis()), suggestions, 265 locale, notificationTargetClassName}); 266 } 267 268 public static final Parcelable.Creator<SuggestionSpan> CREATOR = 269 new Parcelable.Creator<SuggestionSpan>() { 270 @Override 271 public SuggestionSpan createFromParcel(Parcel source) { 272 return new SuggestionSpan(source); 273 } 274 275 @Override 276 public SuggestionSpan[] newArray(int size) { 277 return new SuggestionSpan[size]; 278 } 279 }; 280 281 @Override 282 public void updateDrawState(TextPaint tp) { 283 final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; 284 final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0; 285 final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0; 286 if (easy) { 287 if (!misspelled) { 288 tp.setUnderlineText(mEasyCorrectUnderlineColor, mEasyCorrectUnderlineThickness); 289 } else if (tp.underlineColor == 0) { 290 // Spans are rendered in an arbitrary order. Since misspelled is less prioritary 291 // than just easy, do not apply misspelled if an easy (or a mispelled) has been set 292 tp.setUnderlineText(mMisspelledUnderlineColor, mMisspelledUnderlineThickness); 293 } 294 } else if (autoCorrection) { 295 tp.setUnderlineText(mAutoCorrectionUnderlineColor, mAutoCorrectionUnderlineThickness); 296 } 297 } 298 299 /** 300 * @return The color of the underline for that span, or 0 if there is no underline 301 * 302 * @hide 303 */ 304 public int getUnderlineColor() { 305 // The order here should match what is used in updateDrawState 306 final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; 307 final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0; 308 final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0; 309 if (easy) { 310 if (!misspelled) { 311 return mEasyCorrectUnderlineColor; 312 } else { 313 return mMisspelledUnderlineColor; 314 } 315 } else if (autoCorrection) { 316 return mAutoCorrectionUnderlineColor; 317 } 318 return 0; 319 } 320} 321