Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(392)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java

Issue 2650113004: [WIP] Add support for Android SuggestionSpans when editing text (Closed)
Patch Set: Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2012 The Chromium Authors. All rights reserved. 1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.content.browser.input; 5 package org.chromium.content.browser.input;
6 6
7 import android.annotation.TargetApi;
8 import android.content.Context;
7 import android.content.res.Configuration; 9 import android.content.res.Configuration;
10 import android.graphics.Color;
11 import android.graphics.Rect;
12 import android.graphics.drawable.ColorDrawable;
13 import android.graphics.drawable.Drawable;
8 import android.os.Build; 14 import android.os.Build;
9 import android.os.ResultReceiver; 15 import android.os.ResultReceiver;
10 import android.os.SystemClock; 16 import android.os.SystemClock;
17 import android.text.Spannable;
18 import android.text.Spanned;
11 import android.text.SpannableString; 19 import android.text.SpannableString;
12 import android.text.TextUtils; 20 import android.text.TextUtils;
13 import android.text.style.BackgroundColorSpan; 21 import android.text.style.BackgroundColorSpan;
14 import android.text.style.CharacterStyle; 22 import android.text.style.CharacterStyle;
23 import android.text.style.SuggestionSpan;
24 import android.text.style.TextAppearanceSpan;
15 import android.text.style.UnderlineSpan; 25 import android.text.style.UnderlineSpan;
26 import android.util.DisplayMetrics;
27 import android.view.Gravity;
16 import android.view.KeyCharacterMap; 28 import android.view.KeyCharacterMap;
17 import android.view.KeyEvent; 29 import android.view.KeyEvent;
30 import android.view.LayoutInflater;
18 import android.view.View; 31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.ViewGroup.LayoutParams;
19 import android.view.inputmethod.BaseInputConnection; 34 import android.view.inputmethod.BaseInputConnection;
35 import android.view.inputmethod.CursorAnchorInfo;
20 import android.view.inputmethod.EditorInfo; 36 import android.view.inputmethod.EditorInfo;
21 import android.view.inputmethod.InputConnection; 37 import android.view.inputmethod.InputConnection;
38 import android.view.WindowManager;
39 import android.widget.AdapterView;
40 import android.widget.AdapterView.OnItemClickListener;
41 import android.widget.BaseAdapter;
42 import android.widget.LinearLayout;
43 import android.widget.ListView;
44 import android.widget.PopupWindow;
45 import android.widget.PopupWindow.OnDismissListener;
46 import android.widget.TextView;
47
48 import java.lang.reflect.InvocationTargetException;
49 import java.lang.reflect.Method;
22 50
23 import org.chromium.base.Log; 51 import org.chromium.base.Log;
24 import org.chromium.base.VisibleForTesting; 52 import org.chromium.base.VisibleForTesting;
25 import org.chromium.base.annotations.CalledByNative; 53 import org.chromium.base.annotations.CalledByNative;
26 import org.chromium.base.annotations.JNINamespace; 54 import org.chromium.base.annotations.JNINamespace;
27 import org.chromium.blink_public.web.WebInputEventModifier; 55 import org.chromium.blink_public.web.WebInputEventModifier;
28 import org.chromium.blink_public.web.WebInputEventType; 56 import org.chromium.blink_public.web.WebInputEventType;
29 import org.chromium.blink_public.web.WebTextInputMode; 57 import org.chromium.blink_public.web.WebTextInputMode;
58 import org.chromium.content.R;
30 import org.chromium.content.browser.RenderCoordinates; 59 import org.chromium.content.browser.RenderCoordinates;
31 import org.chromium.content.browser.picker.InputDialogContainer; 60 import org.chromium.content.browser.picker.InputDialogContainer;
32 import org.chromium.ui.base.ime.TextInputType; 61 import org.chromium.ui.base.ime.TextInputType;
33 62
34 /** 63 /**
35 * Adapts and plumbs android IME service onto the chrome text input API. 64 * Adapts and plumbs android IME service onto the chrome text input API.
36 * ImeAdapter provides an interface in both ways native <-> java: 65 * ImeAdapter provides an interface in both ways native <-> java:
37 * 1. InputConnectionAdapter notifies native code of text composition state and 66 * 1. InputConnectionAdapter notifies native code of text composition state and
38 * dispatch key events from java -> WebKit. 67 * dispatch key events from java -> WebKit.
39 * 2. Native ImeAdapter notifies java side to clear composition text. 68 * 2. Native ImeAdapter notifies java side to clear composition text.
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
97 private ChromiumBaseInputConnection mInputConnection; 126 private ChromiumBaseInputConnection mInputConnection;
98 private ChromiumBaseInputConnection.Factory mInputConnectionFactory; 127 private ChromiumBaseInputConnection.Factory mInputConnectionFactory;
99 128
100 private final ImeAdapterDelegate mViewEmbedder; 129 private final ImeAdapterDelegate mViewEmbedder;
101 // This holds the information necessary for constructing CursorAnchorInfo, a nd notifies to 130 // This holds the information necessary for constructing CursorAnchorInfo, a nd notifies to
102 // InputMethodManager on appropriate timing, depending on how IME requested the information 131 // InputMethodManager on appropriate timing, depending on how IME requested the information
103 // via InputConnection. The update request is per InputConnection, hence for each time it is 132 // via InputConnection. The update request is per InputConnection, hence for each time it is
104 // re-created, the monitoring status will be reset. 133 // re-created, the monitoring status will be reset.
105 private final CursorAnchorInfoController mCursorAnchorInfoController; 134 private final CursorAnchorInfoController mCursorAnchorInfoController;
106 135
136 SuggestionsPopupWindow mSuggestionsPopupWindow;
137 private SuggestionInfo[] mSuggestionInfos = new SuggestionInfo[0];
138
107 private int mTextInputType = TextInputType.NONE; 139 private int mTextInputType = TextInputType.NONE;
108 private int mTextInputFlags; 140 private int mTextInputFlags;
109 private int mTextInputMode = WebTextInputMode.kDefault; 141 private int mTextInputMode = WebTextInputMode.kDefault;
110 142
111 // Keep the current configuration to detect the change when onConfigurationC hanged() is called. 143 // Keep the current configuration to detect the change when onConfigurationC hanged() is called.
112 private Configuration mCurrentConfig; 144 private Configuration mCurrentConfig;
113 145
114 private int mLastSelectionStart; 146 private int mLastSelectionStart;
115 private int mLastSelectionEnd; 147 private int mLastSelectionEnd;
116 private String mLastText; 148 private String mLastText;
117 private int mLastCompositionStart; 149 private int mLastCompositionStart;
118 private int mLastCompositionEnd; 150 private int mLastCompositionEnd;
119 151
152 private CursorAnchorInfo mLastCursorAnchorInfo;
153
120 /** 154 /**
121 * @param wrapper InputMethodManagerWrapper that should receive all the call directed to 155 * @param wrapper InputMethodManagerWrapper that should receive all the call directed to
122 * InputMethodManager. 156 * InputMethodManager.
123 * @param embedder The view that is used for callbacks from ImeAdapter. 157 * @param embedder The view that is used for callbacks from ImeAdapter.
124 */ 158 */
125 public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embe dder) { 159 public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embe dder) {
126 mInputMethodManagerWrapper = wrapper; 160 mInputMethodManagerWrapper = wrapper;
127 mViewEmbedder = embedder; 161 mViewEmbedder = embedder;
128 // Deep copy newConfig so that we can notice the difference. 162 // Deep copy newConfig so that we can notice the difference.
129 mCurrentConfig = new Configuration( 163 mCurrentConfig = new Configuration(
130 mViewEmbedder.getAttachedView().getResources().getConfiguration( )); 164 mViewEmbedder.getAttachedView().getResources().getConfiguration( ));
131 // CursorAnchroInfo is supported only after L. 165 // CursorAnchorInfo is supported only after L.
132 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 166 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
133 mCursorAnchorInfoController = CursorAnchorInfoController.create(wrap per, 167 mCursorAnchorInfoController = CursorAnchorInfoController.create(
134 new CursorAnchorInfoController.ComposingTextDelegate() { 168 wrapper, new CursorAnchorInfoController.ComposingTextDelegat e() {
135 @Override 169 @Override
136 public CharSequence getText() { 170 public CharSequence getText() {
137 return mLastText; 171 return mLastText;
138 } 172 }
139 @Override 173 @Override
140 public int getSelectionStart() { 174 public int getSelectionStart() {
141 return mLastSelectionStart; 175 return mLastSelectionStart;
142 } 176 }
143 @Override 177 @Override
144 public int getSelectionEnd() { 178 public int getSelectionEnd() {
145 return mLastSelectionEnd; 179 return mLastSelectionEnd;
146 } 180 }
147 @Override 181 @Override
148 public int getComposingTextStart() { 182 public int getComposingTextStart() {
149 return mLastCompositionStart; 183 return mLastCompositionStart;
150 } 184 }
151 @Override 185 @Override
152 public int getComposingTextEnd() { 186 public int getComposingTextEnd() {
153 return mLastCompositionEnd; 187 return mLastCompositionEnd;
154 } 188 }
155 }); 189 }, this);
156 } else { 190 } else {
157 mCursorAnchorInfoController = null; 191 mCursorAnchorInfoController = null;
158 } 192 }
159 } 193 }
160 194
161 private void createInputConnectionFactory() { 195 private void createInputConnectionFactory() {
162 if (mInputConnectionFactory != null) return; 196 if (mInputConnectionFactory != null) return;
163 mInputConnectionFactory = new ThreadedInputConnectionFactory(mInputMetho dManagerWrapper); 197 mInputConnectionFactory = new ThreadedInputConnectionFactory(mInputMetho dManagerWrapper);
164 } 198 }
165 199
(...skipping 551 matching lines...) Expand 10 before | Expand all | Expand 10 after
717 */ 751 */
718 public void onUpdateFrameInfo(RenderCoordinates renderCoordinates, boolean h asInsertionMarker, 752 public void onUpdateFrameInfo(RenderCoordinates renderCoordinates, boolean h asInsertionMarker,
719 boolean isInsertionMarkerVisible, float insertionMarkerHorizontal, 753 boolean isInsertionMarkerVisible, float insertionMarkerHorizontal,
720 float insertionMarkerTop, float insertionMarkerBottom) { 754 float insertionMarkerTop, float insertionMarkerBottom) {
721 if (mCursorAnchorInfoController == null) return; 755 if (mCursorAnchorInfoController == null) return;
722 mCursorAnchorInfoController.onUpdateFrameInfo(renderCoordinates, hasInse rtionMarker, 756 mCursorAnchorInfoController.onUpdateFrameInfo(renderCoordinates, hasInse rtionMarker,
723 isInsertionMarkerVisible, insertionMarkerHorizontal, insertionMa rkerTop, 757 isInsertionMarkerVisible, insertionMarkerHorizontal, insertionMa rkerTop,
724 insertionMarkerBottom, mViewEmbedder.getAttachedView()); 758 insertionMarkerBottom, mViewEmbedder.getAttachedView());
725 } 759 }
726 760
761 /**
762 * Called by CursorAnchorInfoController to notify ImeAdapter when there's
763 * updated CursorAnchorInfo
764 */
765 public void updateCursorAnchorInfo(View view, CursorAnchorInfo cursorAnchorI nfo) {
766 mLastCursorAnchorInfo = cursorAnchorInfo;
767 if (mSuggestionsPopupWindow != null) {
768 mSuggestionsPopupWindow.updatePosition();
769 }
770 }
771
772 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
773 private class SuggestionsPopupWindow implements OnItemClickListener, OnDismi ssListener {
aelias_OOO_until_Jul13 2017/01/25 03:34:26 Please move this into a new .java file.
774 protected PopupWindow mPopupWindow;
775 protected ViewGroup mContentView;
776 int mPositionX, mPositionY;
777 int mClippingLimitLeft, mClippingLimitRight;
778 private Rect mTempRect;
779
780 private SuggestionAdapter mSuggestionsAdapter;
781 private final TextAppearanceSpan mHighlightSpan;
782 private TextView mDeleteButton;
783 private ListView mSuggestionListView;
784 private int mContainerMarginWidth;
785 private int mContainerMarginTop;
786 private double mLastInsertionMarkerBottom = 0.0;
787 private LinearLayout mContainerView;
788
789 private boolean mDismissedByItemTap = false;
790
791 protected void createPopupWindow() {
792 mPopupWindow = new PopupWindow();
793 mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED) ;
794 mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARE NT));
795
796 mPopupWindow.setFocusable(true);
797 mPopupWindow.setClippingEnabled(false);
798
799 mPopupWindow.setOnDismissListener(this);
800 }
801
802 protected void initContentView() {
803 final LayoutInflater inflater =
804 (LayoutInflater) mInputMethodManagerWrapper.getContext().get SystemService(
805 Context.LAYOUT_INFLATER_SERVICE);
806 mContentView =
807 (ViewGroup) inflater.inflate(R.layout.text_edit_suggestion_c ontainer, null);
808 mContainerView =
809 (LinearLayout) mContentView.findViewById(R.id.suggestionWind owContainer);
810 ViewGroup.MarginLayoutParams lp =
811 (ViewGroup.MarginLayoutParams) mContainerView.getLayoutParam s();
812 mContainerMarginWidth = lp.leftMargin + lp.rightMargin;
813 mContainerMarginTop = lp.topMargin;
814 mClippingLimitLeft = lp.leftMargin;
815 mClippingLimitRight = lp.rightMargin;
816
817 mSuggestionListView = (ListView) mContentView.findViewById(R.id.sugg estionContainer);
818
819 mSuggestionsAdapter = new SuggestionAdapter();
820 mSuggestionListView.setAdapter(mSuggestionsAdapter);
821 mSuggestionListView.setOnItemClickListener(this);
822
823 mDeleteButton = (TextView) mContentView.findViewById(R.id.deleteButt on);
824 mDeleteButton.setOnClickListener(new View.OnClickListener() {
825 public void onClick(View v) {
826 nativeDeleteSuggestionHighlight(mNativeImeAdapterAndroid);
827 mDismissedByItemTap = true;
828 mPopupWindow.dismiss();
829 }
830 });
831 }
832
833 public SuggestionsPopupWindow() {
834 mHighlightSpan = new TextAppearanceSpan(
835 mInputMethodManagerWrapper.getContext(), R.style.SuggestionH ighlight);
836
837 createPopupWindow();
838
839 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
840 mPopupWindow.setWindowLayoutType(
841 WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
842 }
843 mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
844 mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
845 initContentView();
846
847 LayoutParams wrapContent = new LayoutParams(
848 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams. WRAP_CONTENT);
849 mContentView.setLayoutParams(wrapContent);
850 mPopupWindow.setContentView(mContentView);
851 }
852
853 public void updatePosition() {
854 if (!mPopupWindow.isShowing()) {
855 return;
856 }
857 show();
858 }
859
860 public boolean isShowing() {
861 return mPopupWindow.isShowing();
862 }
863
864 private class SuggestionAdapter extends BaseAdapter {
865 private LayoutInflater mInflater =
866 (LayoutInflater) mInputMethodManagerWrapper.getContext().get SystemService(
867 Context.LAYOUT_INFLATER_SERVICE);
868 @Override
869 public int getCount() {
870 return mSuggestionInfos.length;
871 }
872 @Override
873 public Object getItem(int position) {
874 return mSuggestionInfos[position];
875 }
876 @Override
877 public long getItemId(int position) {
878 return position;
879 }
880 @Override
881 public View getView(int position, View convertView, ViewGroup parent ) {
882 TextView textView = (TextView) convertView;
883 if (textView == null) {
884 textView = (TextView) mInflater.inflate(
885 R.layout.text_edit_suggestion_item, parent, false);
886 }
887 final SuggestionInfo suggestionInfo = mSuggestionInfos[position] ;
888 SpannableString textToSet = new SpannableString(
889 suggestionInfo.mPrefix + suggestionInfo.mText + suggesti onInfo.mSuffix);
890 textToSet.setSpan(mHighlightSpan, suggestionInfo.mPrefix.length( ),
891 suggestionInfo.mPrefix.length() + suggestionInfo.mText.l ength(),
892 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
893 textView.setText(textToSet);
894 return textView;
895 }
896 }
897
898 protected void measureContent() {
899 final DisplayMetrics displayMetrics =
900 mInputMethodManagerWrapper.getContext().getResources().getDi splayMetrics();
901 final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec(
902 displayMetrics.widthPixels, View.MeasureSpec.AT_MOST);
903 final int verticalMeasure = View.MeasureSpec.makeMeasureSpec(
904 displayMetrics.heightPixels, View.MeasureSpec.AT_MOST);
905 int width = 0;
906 View view = null;
907 for (int i = 0; i < mSuggestionInfos.length; i++) {
908 view = mSuggestionsAdapter.getView(i, view, mContentView);
909 view.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
910 view.measure(horizontalMeasure, verticalMeasure);
911 width = Math.max(width, view.getMeasuredWidth());
912 }
913 mDeleteButton.measure(horizontalMeasure, verticalMeasure);
914 width = Math.max(width, mDeleteButton.getMeasuredWidth());
915 width += mContainerView.getPaddingLeft() + mContainerView.getPadding Right()
916 + mContainerMarginWidth;
917 // Enforce the width based on actual text widths
918 mContentView.measure(View.MeasureSpec.makeMeasureSpec(width, View.Me asureSpec.EXACTLY),
919 verticalMeasure);
920 Drawable popupBackground = mPopupWindow.getBackground();
921 if (popupBackground != null) {
922 if (mTempRect == null) mTempRect = new Rect();
923 popupBackground.getPadding(mTempRect);
924 width += mTempRect.left + mTempRect.right;
925 }
926 mPopupWindow.setWidth(width);
927 }
928
929 public void show() {
930 mSuggestionsAdapter.notifyDataSetChanged();
931 // We get the insertion point coords relative to the WebView bounds.
932 // We need to render the popup relative to the display
933 int[] embedderViewCoords = new int[2];
934 mViewEmbedder.getAttachedView().getLocationOnScreen(embedderViewCoor ds);
935
936 final DisplayMetrics displayMetrics =
937 mInputMethodManagerWrapper.getContext().getResources().getDi splayMetrics();
938
939 float density = displayMetrics.density;
940
941 measureContent();
942 int width = mContentView.getMeasuredWidth();
943
944 int positionX = 0;
945 int positionY = 0;
946
947 positionX = Math.round(
948 mLastCursorAnchorInfo.getInsertionMarkerHorizontal() * densi ty - width / 2.0f);
949 double insertionMarkerBottom = mLastCursorAnchorInfo.getInsertionMar kerBottom();
950 if (Double.isNaN(insertionMarkerBottom)) {
951 // certain operations, e.g. rotating the device while the menu
aelias_OOO_until_Jul13 2017/01/25 03:34:26 Seems this is no longer needed after changwan@'s f
952 // is open, can cause getInsertionMarkerBottom() to start
953 // returning NaN, just keep using the previous value in this cas e
954 insertionMarkerBottom = mLastInsertionMarkerBottom;
955 } else {
956 mLastInsertionMarkerBottom = insertionMarkerBottom;
957 }
958 positionY = (int) Math.round(
959 (embedderViewCoords[1] + insertionMarkerBottom - 24) * densi ty);
960
961 // horizontal clipping
962 width = mContentView.getMeasuredWidth();
963 positionX =
964 Math.min(displayMetrics.widthPixels - width + mClippingLimit Right, positionX);
965 positionX = Math.max(-mClippingLimitLeft, positionX);
966
967 // vertical clipping
968 final int height = mContentView.getMeasuredHeight();
969 positionY = Math.min(positionY, displayMetrics.heightPixels - height );
970
971 if (isShowing()) {
972 mPopupWindow.update(positionX, positionY, -1, -1);
973 } else {
974 mPopupWindow.showAtLocation(
975 mViewEmbedder.getAttachedView(), Gravity.NO_GRAVITY, pos itionX, positionY);
976 }
977 }
978
979 @Override
980 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
981 SuggestionInfo suggestionInfo = mSuggestionInfos[position];
982 nativeApplySuggestionReplacement(mNativeImeAdapterAndroid,
983 suggestionInfo.mDocumentMarkerID, suggestionInfo.mSuggestion Index);
984 // replaceWithSuggestion(suggestionInfo);
985 // hideWithCleanUp();
986 mDismissedByItemTap = true;
aelias_OOO_until_Jul13 2017/01/25 03:34:26 Can we instead not dismiss at all at just yet? We
rlanday 2017/01/25 18:48:10 I don't understand what you're asking here, the me
987 mPopupWindow.dismiss();
988 }
989
990 public void closeMenu() {
aelias_OOO_until_Jul13 2017/01/25 03:34:26 This has no callsites, please remove.
rlanday 2017/01/25 18:48:10 ah, I added this when I was thinking we might want
991 mPopupWindow.dismiss();
992 }
993
994 @Override
995 public void onDismiss() {
996 if (!mDismissedByItemTap) {
997 nativeCloseSuggestionMenu(mNativeImeAdapterAndroid);
998 }
999 mDismissedByItemTap = false;
1000 }
1001 }
1002
727 @CalledByNative 1003 @CalledByNative
728 private void populateUnderlinesFromSpans(CharSequence text, long underlines) { 1004 private void populateUnderlinesFromSpans(CharSequence text, long underlines) {
729 if (DEBUG_LOGS) { 1005 if (DEBUG_LOGS) {
730 Log.w(TAG, "populateUnderlinesFromSpans: text [%s], underlines [%d]" , text, underlines); 1006 Log.w(TAG, "populateUnderlinesFromSpans: text [%s], underlines [%d]" , text, underlines);
731 } 1007 }
732 if (!(text instanceof SpannableString)) return; 1008 if (!(text instanceof SpannableString)) return;
733 1009
734 SpannableString spannableString = ((SpannableString) text); 1010 SpannableString spannableString = ((SpannableString) text);
735 CharacterStyle spans[] = 1011 CharacterStyle spans[] =
736 spannableString.getSpans(0, text.length(), CharacterStyle.class) ; 1012 spannableString.getSpans(0, text.length(), CharacterStyle.class) ;
737 for (CharacterStyle span : spans) { 1013 for (CharacterStyle span : spans) {
738 if (span instanceof BackgroundColorSpan) { 1014 if (span instanceof BackgroundColorSpan) {
739 nativeAppendBackgroundColorSpan(underlines, spannableString.getS panStart(span), 1015 nativeAppendBackgroundColorSpan(underlines, spannableString.getS panStart(span),
740 spannableString.getSpanEnd(span), 1016 spannableString.getSpanEnd(span),
741 ((BackgroundColorSpan) span).getBackgroundColor()); 1017 ((BackgroundColorSpan) span).getBackgroundColor());
1018 } else if (span instanceof SuggestionSpan) {
1019 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
1020 // The suggestion menu is only supported on Lollipop and new er
1021 continue;
1022 }
1023
1024 int underlineColor = 0;
1025 // Use getUnderlineColor method by reflection to avoid having to reimplement
aelias_OOO_until_Jul13 2017/01/25 03:34:26 The method is public, it's just @hide. You can pr
rlanday 2017/01/25 18:48:10 Hmm I think I tried that but I'll look into it aga
rlanday 2017/01/26 00:23:05 Trying to call span.getUnderlineColor() directly r
1026 try {
1027 Method getUnderlineColor = SuggestionSpan.class.getMethod("g etUnderlineColor");
1028 underlineColor = (int) getUnderlineColor.invoke((SuggestionS pan) span);
1029 } catch (IllegalAccessException | InvocationTargetException | No SuchMethodException
1030 | RuntimeException e) {
1031 continue;
1032 }
1033
1034 SuggestionSpan suggestionSpan = (SuggestionSpan) span;
1035 nativeAppendSuggestionSpan(underlines, spannableString.getSpanSt art(suggestionSpan),
1036 spannableString.getSpanEnd(suggestionSpan), underlineCol or,
1037 suggestionSpan.getFlags(), suggestionSpan.getSuggestions ());
1038
742 } else if (span instanceof UnderlineSpan) { 1039 } else if (span instanceof UnderlineSpan) {
743 nativeAppendUnderlineSpan(underlines, spannableString.getSpanSta rt(span), 1040 nativeAppendUnderlineSpan(underlines, spannableString.getSpanSta rt(span),
744 spannableString.getSpanEnd(span)); 1041 spannableString.getSpanEnd(span));
745 } 1042 }
746 } 1043 }
747 } 1044 }
748 1045
749 @CalledByNative 1046 @CalledByNative
750 private void cancelComposition() { 1047 private void cancelComposition() {
751 if (DEBUG_LOGS) Log.w(TAG, "cancelComposition"); 1048 if (DEBUG_LOGS) Log.w(TAG, "cancelComposition");
752 if (mInputConnection != null) restartInput(); 1049 if (mInputConnection != null) restartInput();
753 } 1050 }
754 1051
755 @CalledByNative 1052 @CalledByNative
756 private void setCharacterBounds(float[] characterBounds) { 1053 private void setCharacterBounds(float[] characterBounds) {
757 if (mCursorAnchorInfoController == null) return; 1054 if (mCursorAnchorInfoController == null) return;
758 mCursorAnchorInfoController.setCompositionCharacterBounds(characterBound s, 1055 mCursorAnchorInfoController.setCompositionCharacterBounds(characterBound s,
759 mViewEmbedder.getAttachedView()); 1056 mViewEmbedder.getAttachedView());
760 } 1057 }
761 1058
1059 public class SuggestionInfo {
1060 int mDocumentMarkerID;
1061 int mSuggestionIndex;
1062 String mPrefix;
1063 String mText;
1064 String mSuffix;
1065
1066 SuggestionInfo(int documentMarkerID, int suggestionIndex, String prefix, String text,
1067 String suffix) {
1068 mDocumentMarkerID = documentMarkerID;
1069 mSuggestionIndex = suggestionIndex;
1070 mPrefix = prefix;
1071 mText = text;
1072 mSuffix = suffix;
1073 }
1074 }
1075
1076 @CalledByNative
1077 private void showSuggestionMenu(SuggestionInfo[] suggestionInfos) {
1078 mSuggestionInfos = suggestionInfos;
1079 if (mSuggestionsPopupWindow == null) {
1080 mSuggestionsPopupWindow = new SuggestionsPopupWindow();
1081 }
1082
1083 mSuggestionsPopupWindow.show();
1084 }
1085
762 @CalledByNative 1086 @CalledByNative
763 private void detach() { 1087 private void detach() {
764 if (DEBUG_LOGS) Log.w(TAG, "detach"); 1088 if (DEBUG_LOGS) Log.w(TAG, "detach");
765 mNativeImeAdapterAndroid = 0; 1089 mNativeImeAdapterAndroid = 0;
766 if (mCursorAnchorInfoController != null) { 1090 if (mCursorAnchorInfoController != null) {
767 mCursorAnchorInfoController.focusedNodeChanged(false); 1091 mCursorAnchorInfoController.focusedNodeChanged(false);
768 } 1092 }
769 } 1093 }
770 1094
771 private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyE vent event, 1095 private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyE vent event,
772 int type, int modifiers, long timestampMs, int keyCode, int scanCode , 1096 int type, int modifiers, long timestampMs, int keyCode, int scanCode ,
773 boolean isSystemKey, int unicodeChar); 1097 boolean isSystemKey, int unicodeChar);
774 private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end);
775 private static native void nativeAppendBackgroundColorSpan(long underlinePtr , int start, 1098 private static native void nativeAppendBackgroundColorSpan(long underlinePtr , int start,
776 int end, int backgroundColor); 1099 int end, int backgroundColor);
1100 private static native void nativeAppendSuggestionSpan(long underlinePtr, int start, int end,
1101 int underlineColor, int flags, String[] suggestions);
1102 private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end);
777 private native void nativeSetComposingText(long nativeImeAdapterAndroid, Cha rSequence text, 1103 private native void nativeSetComposingText(long nativeImeAdapterAndroid, Cha rSequence text,
778 String textStr, int newCursorPosition); 1104 String textStr, int newCursorPosition);
779 private native void nativeCommitText( 1105 private native void nativeCommitText(
780 long nativeImeAdapterAndroid, CharSequence text, String textStr, int newCursorPosition); 1106 long nativeImeAdapterAndroid, CharSequence text, String textStr, int newCursorPosition);
781 private native void nativeFinishComposingText(long nativeImeAdapterAndroid); 1107 private native void nativeFinishComposingText(long nativeImeAdapterAndroid);
782 private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid); 1108 private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid);
783 private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterA ndroid, 1109 private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterA ndroid,
784 int start, int end); 1110 int start, int end);
785 private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, i nt start, int end); 1111 private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, i nt start, int end);
786 private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid , 1112 private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid ,
787 int before, int after); 1113 int before, int after);
788 private native void nativeResetImeAdapter(long nativeImeAdapterAndroid); 1114 private native void nativeResetImeAdapter(long nativeImeAdapterAndroid);
789 private native boolean nativeRequestTextInputStateUpdate(long nativeImeAdapt erAndroid); 1115 private native boolean nativeRequestTextInputStateUpdate(long nativeImeAdapt erAndroid);
790 private native void nativeRequestCursorUpdate(long nativeImeAdapterAndroid, 1116 private native void nativeRequestCursorUpdate(long nativeImeAdapterAndroid,
791 boolean immediateRequest, boolean monitorRequest); 1117 boolean immediateRequest, boolean monitorRequest);
1118 private native void nativeApplySuggestionReplacement(
1119 long nativeImeAdapterAndroid, int documentMarkerID, int suggestionIn dex);
1120 private native void nativeDeleteSuggestionHighlight(long nativeImeAdapterAnd roid);
1121 private native void nativeCloseSuggestionMenu(long nativeImeAdapterAndroid);
792 } 1122 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698