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