OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 } |
OLD | NEW |