SuggestionSpan.java revision 9ca4b4377c6c94cca181c57bd9c784386223d274
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 * @hide 64 */ 65 public static final int FLAG_AUTO_CORRECTION = 0x0004; 66 67 public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED"; 68 public static final String SUGGESTION_SPAN_PICKED_AFTER = "after"; 69 public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before"; 70 public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode"; 71 72 public static final int SUGGESTIONS_MAX_SIZE = 5; 73 74 /* 75 * TODO: Needs to check the validity and add a feature that TextView will change 76 * the current IME to the other IME which is specified in SuggestionSpan. 77 * An IME needs to set the span by specifying the target IME and Subtype of SuggestionSpan. 78 * And the current IME might want to specify any IME as the target IME including other IMEs. 79 */ 80 81 private int mFlags; 82 private final String[] mSuggestions; 83 private final String mLocaleString; 84 private final String mNotificationTargetClassName; 85 private final int mHashCode; 86 87 private float mEasyCorrectUnderlineThickness; 88 private int mEasyCorrectUnderlineColor; 89 90 private float mMisspelledUnderlineThickness; 91 private int mMisspelledUnderlineColor; 92 93 private float mAutoCorrectionUnderlineThickness; 94 private int mAutoCorrectionUnderlineColor; 95 96 /* 97 * TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo 98 * and InputMethodSubtype. 99 */ 100 101 /** 102 * @param context Context for the application 103 * @param suggestions Suggestions for the string under the span 104 * @param flags Additional flags indicating how this span is handled in TextView 105 */ 106 public SuggestionSpan(Context context, String[] suggestions, int flags) { 107 this(context, null, suggestions, flags, null); 108 } 109 110 /** 111 * @param locale Locale of the suggestions 112 * @param suggestions Suggestions for the string under the span 113 * @param flags Additional flags indicating how this span is handled in TextView 114 */ 115 public SuggestionSpan(Locale locale, String[] suggestions, int flags) { 116 this(null, locale, suggestions, flags, null); 117 } 118 119 /** 120 * @param context Context for the application 121 * @param locale locale Locale of the suggestions 122 * @param suggestions Suggestions for the string under the span. Only the first up to 123 * {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered. 124 * @param flags Additional flags indicating how this span is handled in TextView 125 * @param notificationTargetClass if not null, this class will get notified when the user 126 * selects one of the suggestions. 127 */ 128 public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags, 129 Class<?> notificationTargetClass) { 130 final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length); 131 mSuggestions = Arrays.copyOf(suggestions, N); 132 mFlags = flags; 133 if (context != null && locale == null) { 134 mLocaleString = context.getResources().getConfiguration().locale.toString(); 135 } else { 136 mLocaleString = locale.toString(); 137 } 138 139 if (notificationTargetClass != null) { 140 mNotificationTargetClassName = notificationTargetClass.getCanonicalName(); 141 } else { 142 mNotificationTargetClassName = ""; 143 } 144 mHashCode = hashCodeInternal(mSuggestions, mLocaleString, mNotificationTargetClassName); 145 146 initStyle(context); 147 } 148 149 private void initStyle(Context context) { 150 int defStyle = com.android.internal.R.attr.textAppearanceMisspelledSuggestion; 151 TypedArray typedArray = context.obtainStyledAttributes( 152 null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); 153 mMisspelledUnderlineThickness = typedArray.getDimension( 154 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); 155 mMisspelledUnderlineColor = typedArray.getColor( 156 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); 157 158 defStyle = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion; 159 typedArray = context.obtainStyledAttributes( 160 null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); 161 mEasyCorrectUnderlineThickness = typedArray.getDimension( 162 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); 163 mEasyCorrectUnderlineColor = typedArray.getColor( 164 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); 165 166 defStyle = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion; 167 typedArray = context.obtainStyledAttributes( 168 null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); 169 mAutoCorrectionUnderlineThickness = typedArray.getDimension( 170 com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); 171 mAutoCorrectionUnderlineColor = typedArray.getColor( 172 com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); 173 174 } 175 176 public SuggestionSpan(Parcel src) { 177 mSuggestions = src.readStringArray(); 178 mFlags = src.readInt(); 179 mLocaleString = src.readString(); 180 mNotificationTargetClassName = src.readString(); 181 mHashCode = src.readInt(); 182 mEasyCorrectUnderlineColor = src.readInt(); 183 mEasyCorrectUnderlineThickness = src.readFloat(); 184 mMisspelledUnderlineColor = src.readInt(); 185 mMisspelledUnderlineThickness = src.readFloat(); 186 mAutoCorrectionUnderlineColor = src.readInt(); 187 mAutoCorrectionUnderlineThickness = src.readFloat(); 188 } 189 190 /** 191 * @return an array of suggestion texts for this span 192 */ 193 public String[] getSuggestions() { 194 return mSuggestions; 195 } 196 197 /** 198 * @return the locale of the suggestions 199 */ 200 public String getLocale() { 201 return mLocaleString; 202 } 203 204 /** 205 * @return The name of the class to notify. The class of the original IME package will receive 206 * a notification when the user selects one of the suggestions. The notification will include 207 * the original string, the suggested replacement string as well as the hashCode of this span. 208 * The class will get notified by an intent that has those information. 209 * This is an internal API because only the framework should know the class name. 210 * 211 * @hide 212 */ 213 public String getNotificationTargetClassName() { 214 return mNotificationTargetClassName; 215 } 216 217 public int getFlags() { 218 return mFlags; 219 } 220 221 public void setFlags(int flags) { 222 mFlags = flags; 223 } 224 225 @Override 226 public int describeContents() { 227 return 0; 228 } 229 230 @Override 231 public void writeToParcel(Parcel dest, int flags) { 232 dest.writeStringArray(mSuggestions); 233 dest.writeInt(mFlags); 234 dest.writeString(mLocaleString); 235 dest.writeString(mNotificationTargetClassName); 236 dest.writeInt(mHashCode); 237 dest.writeInt(mEasyCorrectUnderlineColor); 238 dest.writeFloat(mEasyCorrectUnderlineThickness); 239 dest.writeInt(mMisspelledUnderlineColor); 240 dest.writeFloat(mMisspelledUnderlineThickness); 241 dest.writeInt(mAutoCorrectionUnderlineColor); 242 dest.writeFloat(mAutoCorrectionUnderlineThickness); 243 } 244 245 @Override 246 public int getSpanTypeId() { 247 return TextUtils.SUGGESTION_SPAN; 248 } 249 250 @Override 251 public boolean equals(Object o) { 252 if (o instanceof SuggestionSpan) { 253 return ((SuggestionSpan)o).hashCode() == mHashCode; 254 } 255 return false; 256 } 257 258 @Override 259 public int hashCode() { 260 return mHashCode; 261 } 262 263 private static int hashCodeInternal(String[] suggestions, String locale, 264 String notificationTargetClassName) { 265 return Arrays.hashCode(new Object[] {Long.valueOf(SystemClock.uptimeMillis()), suggestions, 266 locale, notificationTargetClassName}); 267 } 268 269 public static final Parcelable.Creator<SuggestionSpan> CREATOR = 270 new Parcelable.Creator<SuggestionSpan>() { 271 @Override 272 public SuggestionSpan createFromParcel(Parcel source) { 273 return new SuggestionSpan(source); 274 } 275 276 @Override 277 public SuggestionSpan[] newArray(int size) { 278 return new SuggestionSpan[size]; 279 } 280 }; 281 282 @Override 283 public void updateDrawState(TextPaint tp) { 284 final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; 285 final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0; 286 final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0; 287 if (easy) { 288 if (!misspelled) { 289 tp.setUnderlineText(mEasyCorrectUnderlineColor, mEasyCorrectUnderlineThickness); 290 } else if (tp.underlineColor == 0) { 291 // Spans are rendered in an arbitrary order. Since misspelled is less prioritary 292 // than just easy, do not apply misspelled if an easy (or a mispelled) has been set 293 tp.setUnderlineText(mMisspelledUnderlineColor, mMisspelledUnderlineThickness); 294 } 295 } else if (autoCorrection) { 296 tp.setUnderlineText(mAutoCorrectionUnderlineColor, mAutoCorrectionUnderlineThickness); 297 } 298 } 299 300 /** 301 * @return The color of the underline for that span, or 0 if there is no underline 302 * 303 * @hide 304 */ 305 public int getUnderlineColor() { 306 // The order here should match what is used in updateDrawState 307 final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; 308 final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0; 309 final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0; 310 if (easy) { 311 if (!misspelled) { 312 return mEasyCorrectUnderlineColor; 313 } else { 314 return mMisspelledUnderlineColor; 315 } 316 } else if (autoCorrection) { 317 return mAutoCorrectionUnderlineColor; 318 } 319 return 0; 320 } 321} 322