diff options
7 files changed, 136 insertions, 16 deletions
diff --git a/api/current.txt b/api/current.txt index 68549656a134..94e421097115 100644 --- a/api/current.txt +++ b/api/current.txt @@ -19381,17 +19381,22 @@ package android.text.style { public class SuggestionSpan implements android.text.ParcelableSpan { ctor public SuggestionSpan(android.content.Context, java.lang.String[], int); ctor public SuggestionSpan(java.util.Locale, java.lang.String[], int); - ctor public SuggestionSpan(android.content.Context, java.util.Locale, java.lang.String[], int, java.lang.String); + ctor public SuggestionSpan(android.content.Context, java.util.Locale, java.lang.String[], int, java.lang.Class<?>); ctor public SuggestionSpan(android.os.Parcel); method public int describeContents(); method public int getFlags(); method public java.lang.String getLocale(); - method public java.lang.String getOriginalString(); + method public java.lang.Class<?> getNotificationTargetClass(); method public int getSpanTypeId(); method public java.lang.String[] getSuggestions(); method public void writeToParcel(android.os.Parcel, int); + field public static final java.lang.String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED"; field public static final android.os.Parcelable.Creator CREATOR; field public static final int FLAG_VERBATIM = 1; // 0x1 + field public static final int SUGGESTIONS_MAX_SIZE = 5; // 0x5 + field public static final java.lang.String SUGGESTION_SPAN_PICKED_AFTER = "after"; + field public static final java.lang.String SUGGESTION_SPAN_PICKED_BEFORE = "before"; + field public static final java.lang.String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode"; } public class SuperscriptSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan { diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index dcb0898c8fd8..effa5c8f8da4 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -19,8 +19,10 @@ package android.text.style; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemClock; import android.text.ParcelableSpan; import android.text.TextUtils; +import android.util.Log; import java.util.Arrays; import java.util.Locale; @@ -29,6 +31,7 @@ import java.util.Locale; * Holds suggestion candidates of words under this span. */ public class SuggestionSpan implements ParcelableSpan { + private static final String TAG = SuggestionSpan.class.getSimpleName(); /** * Flag for indicating that the input is verbatim. TextView refers to this flag to determine @@ -36,7 +39,12 @@ public class SuggestionSpan implements ParcelableSpan { */ public static final int FLAG_VERBATIM = 0x0001; - private static final int SUGGESTIONS_MAX_SIZE = 5; + public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED"; + public static final String SUGGESTION_SPAN_PICKED_AFTER = "after"; + public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before"; + public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode"; + + public static final int SUGGESTIONS_MAX_SIZE = 5; /* * TODO: Needs to check the validity and add a feature that TextView will change @@ -48,7 +56,9 @@ public class SuggestionSpan implements ParcelableSpan { private final int mFlags; private final String[] mSuggestions; private final String mLocaleString; - private final String mOriginalString; + private final Class<?> mNotificationTargetClass; + private final int mHashCode; + /* * TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo * and InputMethodSubtype. @@ -77,10 +87,11 @@ public class SuggestionSpan implements ParcelableSpan { * @param locale locale Locale of the suggestions * @param suggestions Suggestions for the string under the span * @param flags Additional flags indicating how this span is handled in TextView - * @param originalString originalString for suggestions + * @param notificationTargetClass if not null, this class will get notified when the user + * selects one of the suggestions. */ public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags, - String originalString) { + Class<?> notificationTargetClass) { final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length); mSuggestions = Arrays.copyOf(suggestions, N); mFlags = flags; @@ -89,14 +100,26 @@ public class SuggestionSpan implements ParcelableSpan { } else { mLocaleString = locale.toString(); } - mOriginalString = originalString; + mNotificationTargetClass = notificationTargetClass; + mHashCode = hashCodeInternal( + mFlags, mSuggestions, mLocaleString, mNotificationTargetClass); } public SuggestionSpan(Parcel src) { mSuggestions = src.readStringArray(); mFlags = src.readInt(); mLocaleString = src.readString(); - mOriginalString = src.readString(); + Class<?> tempClass = null; + try { + final String className = src.readString(); + if (!TextUtils.isEmpty(className)) { + tempClass = Class.forName(className); + } + } catch (ClassNotFoundException e) { + Log.i(TAG, "Invalid class name was created."); + } + mNotificationTargetClass = tempClass; + mHashCode = src.readInt(); } /** @@ -114,10 +137,13 @@ public class SuggestionSpan implements ParcelableSpan { } /** - * @return original string of suggestions + * @return The class to notify. The class of the original IME package will receive + * a notification when the user selects one of the suggestions. The notification will include + * the original string, the suggested replacement string as well as the hashCode of this span. + * The class will get notified by an intent that has those information. */ - public String getOriginalString() { - return mOriginalString; + public Class<?> getNotificationTargetClass() { + return mNotificationTargetClass; } public int getFlags() { @@ -134,7 +160,10 @@ public class SuggestionSpan implements ParcelableSpan { dest.writeStringArray(mSuggestions); dest.writeInt(mFlags); dest.writeString(mLocaleString); - dest.writeString(mOriginalString); + dest.writeString(mNotificationTargetClass != null + ? mNotificationTargetClass.getCanonicalName() + : ""); + dest.writeInt(mHashCode); } @Override @@ -142,6 +171,20 @@ public class SuggestionSpan implements ParcelableSpan { return TextUtils.SUGGESTION_SPAN; } + @Override + public int hashCode() { + return mHashCode; + } + + private static int hashCodeInternal(int flags, String[] suggestions,String locale, + Class<?> notificationTargetClass) { + final String cls = notificationTargetClass != null + ? notificationTargetClass.getCanonicalName() + : ""; + return Arrays.hashCode( + new Object[] {SystemClock.uptimeMillis(), flags, suggestions, locale, cls}); + } + public static final Parcelable.Creator<SuggestionSpan> CREATOR = new Parcelable.Creator<SuggestionSpan>() { @Override diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index b4303f4bec44..abe3c2ccd490 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -49,8 +49,9 @@ public class BaseInputConnection implements InputConnection { private static final boolean DEBUG = false; private static final String TAG = "BaseInputConnection"; static final Object COMPOSING = new ComposingText(); - - final InputMethodManager mIMM; + + /** @hide */ + protected final InputMethodManager mIMM; final View mTargetView; final boolean mDummyMode; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 27cbaf702457..ea66d6761c52 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -35,6 +35,7 @@ import android.os.Message; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; +import android.text.style.SuggestionSpan; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -550,7 +551,25 @@ public final class InputMethodManager { public void setFullscreenMode(boolean fullScreen) { mFullscreenMode = fullScreen; } - + + /** @hide */ + public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) { + try { + mService.registerSuggestionSpansForNotification(spans); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** @hide */ + public void notifySuggestionPicked(SuggestionSpan span, String originalString, int index) { + try { + mService.notifySuggestionPicked(span, originalString, index); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + /** * Allows you to discover whether the attached input method is running * in fullscreen mode. Return true if it is fullscreen, entirely covering diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 4ffa4e1dfbec..8039fdac7057 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -17,6 +17,7 @@ package com.android.internal.view; import android.os.ResultReceiver; +import android.text.style.SuggestionSpan; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.EditorInfo; @@ -61,6 +62,8 @@ interface IInputMethodManager { void showMySoftInput(in IBinder token, int flags); void updateStatusIcon(in IBinder token, String packageName, int iconId); void setImeWindowStatus(in IBinder token, int vis, int backDisposition); + void registerSuggestionSpansForNotification(in SuggestionSpan[] spans); + boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index); InputMethodSubtype getCurrentInputMethodSubtype(); boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype); boolean switchToLastInputMethod(in IBinder token); diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index 0d32d4baa1ea..32e733baa98a 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -17,9 +17,10 @@ package com.android.internal.widget; import android.os.Bundle; -import android.os.IBinder; import android.text.Editable; +import android.text.Spanned; import android.text.method.KeyListener; +import android.text.style.SuggestionSpan; import android.util.Log; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; @@ -138,6 +139,11 @@ public class EditableInputConnection extends BaseInputConnection { if (mTextView == null) { return super.commitText(text, newCursorPosition); } + if (text instanceof Spanned) { + Spanned spanned = ((Spanned) text); + SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class); + mIMM.registerSuggestionSpansForNotification(spans); + } mTextView.resetErrorChangedFlag(); boolean success = super.commitText(text, newCursorPosition); diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index c365af587833..7361ef7b9c35 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -64,7 +64,9 @@ import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; +import android.text.style.SuggestionSpan; import android.util.EventLog; +import android.util.LruCache; import android.util.Pair; import android.util.Slog; import android.util.PrintWriterPrinter; @@ -117,6 +119,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final long TIME_TO_RECONNECT = 10*1000; + static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20; + private static final int NOT_A_SUBTYPE_ID = -1; private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; @@ -141,6 +145,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // lock for this class. final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>(); final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>(); + private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans = + new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE); class SessionState { final ClientState client; @@ -965,6 +971,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public void updateStatusIcon(IBinder token, String packageName, int iconId) { int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); @@ -989,6 +996,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); @@ -1008,6 +1016,41 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) { + synchronized (mMethodMap) { + final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); + for (int i = 0; i < spans.length; ++i) { + SuggestionSpan ss = spans[i]; + if (ss.getNotificationTargetClass() != null) { + mSecureSuggestionSpans.put(ss, currentImi); + } + } + } + } + + public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) { + synchronized (mMethodMap) { + final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span); + // TODO: Do not send the intent if the process of the targetImi is already dead. + if (targetImi != null) { + final String[] suggestions = span.getSuggestions(); + if (index < 0 || index >= suggestions.length) return false; + final Class<?> c = span.getNotificationTargetClass(); + final Intent intent = new Intent(); + // Ensures that only a class in the original IME package will receive the + // notification. + intent.setClassName(targetImi.getPackageName(), c.getCanonicalName()); + intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED); + intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString); + intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]); + intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode()); + mContext.sendBroadcast(intent); + return true; + } + } + return false; + } + void updateFromSettingsLocked() { // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and // ENABLED_INPUT_METHODS is taking care of keeping them correctly in |