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