Deprecate SuggestionSpan#ACTION_SUGGESTION_PICKED

This CL deprecates SuggestionSpan#ACTION_SUGGESTION_PICKED and related
constants [1].

There are multiple security concerns, open questions about
compatibility, and maintainance challanges in this protocol.

IME developers can implement their own suggestion picker UI on top of
CursorAnchorInfo API to achieve safer, should give more flexible UI
options, better security, and better compatibility.

 [1]: Ia539de0acf66053e0349daec459d75e36805f6bf
      f9f0100862

Fix: 123160396
Test: make -j checkbuild
Change-Id: I6d39e838ae47488055162cd44b5f553f68869b17
This commit is contained in:
Yohei Yukawa
2019-01-21 09:24:26 -08:00
parent d3e0fd9395
commit 17ace29a10
8 changed files with 64 additions and 154 deletions

View File

@@ -21,7 +21,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Parcel;
@@ -31,7 +30,6 @@ import android.text.ParcelableSpan;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import java.util.Arrays;
@@ -72,9 +70,37 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
*/
public static final int FLAG_AUTO_CORRECTION = 0x0004;
/**
* This action is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
*
* @deprecated For IMEs to receive this kind of user interaction signals, implement IMEs' own
* suggestion picker UI instead of relying on {@link SuggestionSpan}. To retrieve
* bounding boxes for each character of the composing text, use
* {@link android.view.inputmethod.CursorAnchorInfo}.
*/
@Deprecated
public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
/**
* This is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
*
* @deprecated See {@link #ACTION_SUGGESTION_PICKED}.
*/
@Deprecated
public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
/**
* This is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
*
* @deprecated See {@link #ACTION_SUGGESTION_PICKED}.
*/
@Deprecated
public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
/**
* This is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
*
* @deprecated See {@link #ACTION_SUGGESTION_PICKED}.
*/
@Deprecated
public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
public static final int SUGGESTIONS_MAX_SIZE = 5;
@@ -97,8 +123,6 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
private final String mLocaleStringForCompatibility;
@NonNull
private final String mLanguageTag;
private final String mNotificationTargetClassName;
private final String mNotificationTargetPackageName;
private final int mHashCode;
@UnsupportedAppUsage
@@ -137,7 +161,9 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
* {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered. Null values not permitted.
* @param flags Additional flags indicating how this span is handled in TextView
* @param notificationTargetClass if not null, this class will get notified when the user
* selects one of the suggestions.
* selects one of the suggestions. On Android
* {@link android.os.Build.VERSION_CODES#Q} and later this
* parameter is always ignored.
*/
public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags,
Class<?> notificationTargetClass) {
@@ -156,20 +182,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
}
mLocaleStringForCompatibility = sourceLocale == null ? "" : sourceLocale.toString();
mLanguageTag = sourceLocale == null ? "" : sourceLocale.toLanguageTag();
if (context != null) {
mNotificationTargetPackageName = context.getPackageName();
} else {
mNotificationTargetPackageName = null;
}
if (notificationTargetClass != null) {
mNotificationTargetClassName = notificationTargetClass.getCanonicalName();
} else {
mNotificationTargetClassName = "";
}
mHashCode = hashCodeInternal(mSuggestions, mLanguageTag, mLocaleStringForCompatibility,
mNotificationTargetClassName);
mHashCode = hashCodeInternal(mSuggestions, mLanguageTag, mLocaleStringForCompatibility);
initStyle(context);
}
@@ -215,8 +228,6 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
mFlags = src.readInt();
mLocaleStringForCompatibility = src.readString();
mLanguageTag = src.readString();
mNotificationTargetClassName = src.readString();
mNotificationTargetPackageName = src.readString();
mHashCode = src.readInt();
mEasyCorrectUnderlineColor = src.readInt();
mEasyCorrectUnderlineThickness = src.readFloat();
@@ -260,17 +271,15 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
}
/**
* @return The name of 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.
* This is an internal API because only the framework should know the class name.
* @return {@code null}.
*
* @hide
* @deprecated Do not use. Always returns {@code null}.
*/
@Deprecated
@UnsupportedAppUsage
public String getNotificationTargetClassName() {
return mNotificationTargetClassName;
return null;
}
public int getFlags() {
@@ -297,8 +306,6 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
dest.writeInt(mFlags);
dest.writeString(mLocaleStringForCompatibility);
dest.writeString(mLanguageTag);
dest.writeString(mNotificationTargetClassName);
dest.writeString(mNotificationTargetPackageName);
dest.writeInt(mHashCode);
dest.writeInt(mEasyCorrectUnderlineColor);
dest.writeFloat(mEasyCorrectUnderlineThickness);
@@ -332,9 +339,9 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
}
private static int hashCodeInternal(String[] suggestions, @NonNull String languageTag,
@NonNull String localeStringForCompatibility, String notificationTargetClassName) {
@NonNull String localeStringForCompatibility) {
return Arrays.hashCode(new Object[] {Long.valueOf(SystemClock.uptimeMillis()), suggestions,
languageTag, localeStringForCompatibility, notificationTargetClassName});
languageTag, localeStringForCompatibility});
}
public static final Parcelable.Creator<SuggestionSpan> CREATOR =
@@ -390,39 +397,14 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
}
/**
* Notifies a suggestion selection.
* Does nothing.
*
* @deprecated this is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
* @hide
*/
@UnsupportedAppUsage
@Deprecated
public void notifySelection(Context context, String original, int index) {
final Intent intent = new Intent();
if (context == null || mNotificationTargetClassName == null) {
return;
}
// Ensures that only a class in the original IME package will receive the
// notification.
if (mSuggestions == null || index < 0 || index >= mSuggestions.length) {
Log.w(TAG, "Unable to notify the suggestion as the index is out of range index=" + index
+ " length=" + mSuggestions.length);
return;
}
// The package name is not mandatory (legacy from JB), and if the package name
// is missing, we try to notify the suggestion through the input method manager.
if (mNotificationTargetPackageName != null) {
intent.setClassName(mNotificationTargetPackageName, mNotificationTargetClassName);
intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, original);
intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, mSuggestions[index]);
intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, hashCode());
context.sendBroadcast(intent);
} else {
InputMethodManager imm = context.getSystemService(InputMethodManager.class);
if (imm != null) {
imm.notifySuggestionPicked(this, original, index);
}
}
Log.w(TAG, "notifySelection() is deprecated. Does nothing.");
}
}

View File

@@ -980,24 +980,30 @@ public final class InputMethodManager {
InputMethodPrivilegedOperationsRegistry.get(imeToken).updateStatusIcon(null, 0);
}
/** @hide */
/**
* This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
*
* @param spans will be ignored.
*
* @deprecated Do not use.
* @hide
*/
@Deprecated
@UnsupportedAppUsage
public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
try {
mService.registerSuggestionSpansForNotification(spans);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Log.w(TAG, "registerSuggestionSpansForNotification() is deprecated. Does nothing.");
}
/** @hide */
/**
* This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
*
* @deprecated Do not use.
* @hide
*/
@Deprecated
@UnsupportedAppUsage
public void notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
try {
mService.notifySuggestionPicked(span, originalString, index);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Log.w(TAG, "notifySuggestionPicked() is deprecated. Does nothing.");
}
/**

View File

@@ -2901,10 +2901,6 @@ public class Editor {
}
}
// Notify source IME of the suggestion pick. Do this before swapping texts.
targetSuggestionSpan.notifySelection(
mTextView.getContext(), originalText, suggestionInfo.mSuggestionIndex);
// Swap text content between actual text and Suggestion span
final int suggestionStart = suggestionInfo.mSuggestionStart;
final int suggestionEnd = suggestionInfo.mSuggestionEnd;

View File

@@ -17,7 +17,6 @@
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;
@@ -65,8 +64,6 @@ interface IInputMethodManager {
int displayId);
void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
boolean isInputMethodPickerShownForTest();
void registerSuggestionSpansForNotification(in SuggestionSpan[] spans);
boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index);
InputMethodSubtype getCurrentInputMethodSubtype();
boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);

View File

@@ -18,9 +18,7 @@ package com.android.internal.widget;
import android.os.Bundle;
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;
@@ -173,12 +171,6 @@ 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);
mTextView.hideErrorIfUnchanged();