summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt9
-rw-r--r--core/java/android/text/style/SuggestionSpan.java63
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java5
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java21
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl3
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java8
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java43
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