SuggestionSpan.java revision e6d368218918f911b1954296dab25bf84147b4c6
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 mUnderlineThickness;
80    private int mUnderlineColor;
81
82    /*
83     * TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo
84     * and InputMethodSubtype.
85     */
86
87    /**
88     * @param context Context for the application
89     * @param suggestions Suggestions for the string under the span
90     * @param flags Additional flags indicating how this span is handled in TextView
91     */
92    public SuggestionSpan(Context context, String[] suggestions, int flags) {
93        this(context, null, suggestions, flags, null);
94    }
95
96    /**
97     * @param locale Locale of the suggestions
98     * @param suggestions Suggestions for the string under the span
99     * @param flags Additional flags indicating how this span is handled in TextView
100     */
101    public SuggestionSpan(Locale locale, String[] suggestions, int flags) {
102        this(null, locale, suggestions, flags, null);
103    }
104
105    /**
106     * @param context Context for the application
107     * @param locale locale Locale of the suggestions
108     * @param suggestions Suggestions for the string under the span
109     * @param flags Additional flags indicating how this span is handled in TextView
110     * @param notificationTargetClass if not null, this class will get notified when the user
111     * selects one of the suggestions.
112     */
113    public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags,
114            Class<?> notificationTargetClass) {
115        final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length);
116        mSuggestions = Arrays.copyOf(suggestions, N);
117        mFlags = flags;
118        if (context != null && locale == null) {
119            mLocaleString = context.getResources().getConfiguration().locale.toString();
120        } else {
121            mLocaleString = locale.toString();
122        }
123
124        if (notificationTargetClass != null) {
125            mNotificationTargetClassName = notificationTargetClass.getCanonicalName();
126        } else {
127            mNotificationTargetClassName = "";
128        }
129        mHashCode = hashCodeInternal(mSuggestions, mLocaleString, mNotificationTargetClassName);
130
131        initStyle(context);
132    }
133
134    private void initStyle(Context context) {
135        int defStyle = 0;
136        if ((getFlags() & FLAG_MISSPELLED) != 0) {
137            defStyle = com.android.internal.R.attr.textAppearanceMisspelledSuggestion;
138        } else if ((getFlags() & FLAG_EASY_CORRECT) != 0) {
139            defStyle = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion;
140        } else {
141            // No style is applied.
142            mUnderlineThickness = 0;
143            mUnderlineColor = 0;
144            return;
145        }
146
147        TypedArray typedArray = context.obtainStyledAttributes(null,
148                com.android.internal.R.styleable.SuggestionSpan,
149                defStyle, 0);
150
151        mUnderlineThickness = typedArray.getDimension(
152                com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
153        mUnderlineColor = 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        mUnderlineColor = src.readInt();
164        mUnderlineThickness = src.readFloat();
165    }
166
167    /**
168     * @return an array of suggestion texts for this span
169     */
170    public String[] getSuggestions() {
171        return mSuggestions;
172    }
173
174    /**
175     * @return the locale of the suggestions
176     */
177    public String getLocale() {
178        return mLocaleString;
179    }
180
181    /**
182     * @return The name of the class to notify. The class of the original IME package will receive
183     * a notification when the user selects one of the suggestions. The notification will include
184     * the original string, the suggested replacement string as well as the hashCode of this span.
185     * The class will get notified by an intent that has those information.
186     * This is an internal API because only the framework should know the class name.
187     *
188     * @hide
189     */
190    public String getNotificationTargetClassName() {
191        return mNotificationTargetClassName;
192    }
193
194    public int getFlags() {
195        return mFlags;
196    }
197
198    public void setFlags(int flags) {
199        mFlags = flags;
200    }
201
202    @Override
203    public int describeContents() {
204        return 0;
205    }
206
207    @Override
208    public void writeToParcel(Parcel dest, int flags) {
209        dest.writeStringArray(mSuggestions);
210        dest.writeInt(mFlags);
211        dest.writeString(mLocaleString);
212        dest.writeString(mNotificationTargetClassName);
213        dest.writeInt(mHashCode);
214        dest.writeInt(mUnderlineColor);
215        dest.writeFloat(mUnderlineThickness);
216    }
217
218    @Override
219    public int getSpanTypeId() {
220        return TextUtils.SUGGESTION_SPAN;
221    }
222
223    @Override
224    public boolean equals(Object o) {
225        if (o instanceof SuggestionSpan) {
226            return ((SuggestionSpan)o).hashCode() == mHashCode;
227        }
228        return false;
229    }
230
231    @Override
232    public int hashCode() {
233        return mHashCode;
234    }
235
236    private static int hashCodeInternal(String[] suggestions, String locale,
237            String notificationTargetClassName) {
238        return Arrays.hashCode(new Object[] {Long.valueOf(SystemClock.uptimeMillis()), suggestions,
239                locale, notificationTargetClassName});
240    }
241
242    public static final Parcelable.Creator<SuggestionSpan> CREATOR =
243            new Parcelable.Creator<SuggestionSpan>() {
244        @Override
245        public SuggestionSpan createFromParcel(Parcel source) {
246            return new SuggestionSpan(source);
247        }
248
249        @Override
250        public SuggestionSpan[] newArray(int size) {
251            return new SuggestionSpan[size];
252        }
253    };
254
255    @Override
256    public void updateDrawState(TextPaint tp) {
257        tp.setUnderlineText(mUnderlineColor, mUnderlineThickness);
258    }
259}
260