Editor.java revision 1271e2cc80b01d577e9db339459ef0222bb9320d
1d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne/* 2d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Copyright (C) 2012 The Android Open Source Project 3d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 4d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Licensed under the Apache License, Version 2.0 (the "License"); 5d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * you may not use this file except in compliance with the License. 6d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * You may obtain a copy of the License at 7d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 8d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * http://www.apache.org/licenses/LICENSE-2.0 9d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 10d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Unless required by applicable law or agreed to in writing, software 11d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * distributed under the License is distributed on an "AS IS" BASIS, 12d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * See the License for the specific language governing permissions and 14d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * limitations under the License. 15d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 16d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 17d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunnepackage android.widget; 18d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 19d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.R; 20d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.content.ClipData; 21d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.content.ClipData.Item; 22d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.content.Context; 23d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.content.Intent; 24d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.content.pm.PackageManager; 25d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.content.res.TypedArray; 26d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.graphics.Canvas; 27d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.graphics.Color; 28d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.graphics.Paint; 29d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.graphics.Path; 30d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.graphics.Rect; 31d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.graphics.RectF; 32d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.graphics.drawable.Drawable; 33d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.inputmethodservice.ExtractEditText; 34d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.os.Bundle; 35d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.os.Handler; 36d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.os.SystemClock; 37d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.provider.Settings; 38d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.DynamicLayout; 39d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.Editable; 40d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.InputType; 41d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.Layout; 42d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.ParcelableSpan; 43d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.Selection; 44d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.SpanWatcher; 45d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.Spannable; 46d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.SpannableStringBuilder; 47d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.Spanned; 48d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.StaticLayout; 49d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.TextUtils; 50d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.TextWatcher; 51d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.method.KeyListener; 52d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.method.MetaKeyKeyListener; 53d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.method.MovementMethod; 54d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.method.PasswordTransformationMethod; 55d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.method.WordIterator; 56d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.style.EasyEditSpan; 57d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.style.SuggestionRangeSpan; 58d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.style.SuggestionSpan; 59d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.style.TextAppearanceSpan; 60d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.text.style.URLSpan; 61d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.util.DisplayMetrics; 62d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.util.Log; 63d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.ActionMode; 64d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.ActionMode.Callback; 65d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.DisplayList; 66d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.DragEvent; 67d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.Gravity; 68d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.HardwareCanvas; 69d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.LayoutInflater; 70d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.Menu; 71d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.MenuItem; 72d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.MotionEvent; 73d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.View; 74d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.ViewConfiguration; 75d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.ViewGroup; 76d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.View.DragShadowBuilder; 77d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.View.OnClickListener; 78d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.ViewGroup.LayoutParams; 79d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.ViewParent; 80d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.ViewTreeObserver; 81d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.WindowManager; 82d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.inputmethod.CorrectionInfo; 83d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.inputmethod.EditorInfo; 84d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.inputmethod.ExtractedText; 85d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.inputmethod.ExtractedTextRequest; 86d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.inputmethod.InputConnection; 87d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.view.inputmethod.InputMethodManager; 88d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.widget.AdapterView.OnItemClickListener; 89d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.widget.TextView.Drawables; 90d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport android.widget.TextView.OnEditorActionListener; 91d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 92d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport com.android.internal.util.ArrayUtils; 93d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport com.android.internal.widget.EditableInputConnection; 94d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 95d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport java.text.BreakIterator; 96d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport java.util.Arrays; 97d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport java.util.Comparator; 98d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunneimport java.util.HashMap; 99d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 100d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne/** 101d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Helper class used by TextView to handle editable text views. 102d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 103d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @hide 104d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 105d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunnepublic class Editor { 106d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne static final int BLINK = 500; 107d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final float[] TEMP_POSITION = new float[2]; 108d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20; 109d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 110d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Cursor Controllers. 111d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne InsertionPointCursorController mInsertionPointCursorController; 112d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SelectionModifierCursorController mSelectionModifierCursorController; 113d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ActionMode mSelectionActionMode; 114d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mInsertionControllerEnabled; 115d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mSelectionControllerEnabled; 116d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 117d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Used to highlight a word when it is corrected by the IME 118d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne CorrectionHighlighter mCorrectionHighlighter; 119d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 120d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne InputContentType mInputContentType; 121d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne InputMethodState mInputMethodState; 122d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 123d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne DisplayList[] mTextDisplayLists; 124d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 125d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mFrozenWithFocus; 126d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mSelectionMoved; 127d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mTouchFocusSelected; 128d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 129d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne KeyListener mKeyListener; 130d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int mInputType = EditorInfo.TYPE_NULL; 131d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 132d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mDiscardNextActionUp; 133d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mIgnoreActionUpEvent; 134d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 135d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne long mShowCursor; 136d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Blink mBlink; 137d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 138d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mCursorVisible = true; 139d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mSelectAllOnFocus; 140d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mTextIsSelectable; 141d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 142d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne CharSequence mError; 143d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mErrorWasChanged; 144d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ErrorPopup mErrorPopup; 145d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 146d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * This flag is set if the TextView tries to display an error before it 147d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * is attached to the window (so its position is still unknown). 148d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * It causes the error to be shown later, when onAttachedToWindow() 149d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * is called. 150d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 151d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mShowErrorAfterAttach; 152d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 153d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mInBatchEditControllers; 154d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 155d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionsPopupWindow mSuggestionsPopupWindow; 156d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionRangeSpan mSuggestionRangeSpan; 157d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Runnable mShowSuggestionRunnable; 158d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 159d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final Drawable[] mCursorDrawable = new Drawable[2]; 160d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int mCursorCount; // Current number of used mCursorDrawable: 0 (resource=0), 1 or 2 (split) 161d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 162d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private Drawable mSelectHandleLeft; 163d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private Drawable mSelectHandleRight; 164d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private Drawable mSelectHandleCenter; 165d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 166d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Global listener that detects changes in the global position of the TextView 167d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private PositionListener mPositionListener; 168d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 169d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne float mLastDownPositionX, mLastDownPositionY; 170d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Callback mCustomSelectionActionModeCallback; 171d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 172d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Set when this TextView gained focus with some text selected. Will start selection mode. 173d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mCreatedWithASelection; 174d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 175d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private EasyEditSpanController mEasyEditSpanController; 176d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 177d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne WordIterator mWordIterator; 178d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SpellChecker mSpellChecker; 179d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 180d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private Rect mTempRect; 181d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 182d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private TextView mTextView; 183d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 184d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Editor(TextView textView) { 185d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView = textView; 186d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEasyEditSpanController = new EasyEditSpanController(); 187d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.addTextChangedListener(mEasyEditSpanController); 188d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 189d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 190d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onAttachedToWindow() { 191d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mShowErrorAfterAttach) { 192d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne showError(); 193d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mShowErrorAfterAttach = false; 194d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 195d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 196d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ViewTreeObserver observer = mTextView.getViewTreeObserver(); 197d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // No need to create the controller. 198d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The get method will add the listener on controller creation. 199d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mInsertionPointCursorController != null) { 200d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne observer.addOnTouchModeChangeListener(mInsertionPointCursorController); 201d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 202d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectionModifierCursorController != null) { 203d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne observer.addOnTouchModeChangeListener(mSelectionModifierCursorController); 204d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 205d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateSpellCheckSpans(0, mTextView.getText().length(), 206d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne true /* create the spell checker if needed */); 207d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 208d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 209d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onDetachedFromWindow() { 210d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mError != null) { 211d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideError(); 212d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 213d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 214d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mBlink != null) { 215d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mBlink.removeCallbacks(mBlink); 216d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 217d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 218d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mInsertionPointCursorController != null) { 219d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInsertionPointCursorController.onDetached(); 220d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 221d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 222d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectionModifierCursorController != null) { 223d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionModifierCursorController.onDetached(); 224d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 225d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 226d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mShowSuggestionRunnable != null) { 227d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.removeCallbacks(mShowSuggestionRunnable); 228d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 229d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 230d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne invalidateTextDisplayList(); 231d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 232d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSpellChecker != null) { 233d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSpellChecker.closeSession(); 234d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Forces the creation of a new SpellChecker next time this window is created. 235d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Will handle the cases where the settings has been changed in the meantime. 236d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSpellChecker = null; 237d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 238d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 239d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideControllers(); 240d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 241d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 242d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void showError() { 243d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.getWindowToken() == null) { 244d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mShowErrorAfterAttach = true; 245d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 246d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 247d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 248d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mErrorPopup == null) { 249d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne LayoutInflater inflater = LayoutInflater.from(mTextView.getContext()); 250d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final TextView err = (TextView) inflater.inflate( 251d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.layout.textview_hint, null); 252d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 253d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float scale = mTextView.getResources().getDisplayMetrics().density; 254d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup = new ErrorPopup(err, (int)(200 * scale + 0.5f), (int)(50 * scale + 0.5f)); 255d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup.setFocusable(false); 256d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The user is entering text, so the input method is needed. We 257d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // don't want the popup to be displayed on top of it. 258d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); 259d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 260d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 261d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne TextView tv = (TextView) mErrorPopup.getContentView(); 262d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne chooseSize(mErrorPopup, mError, tv); 263d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne tv.setText(mError); 264d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 265d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup.showAsDropDown(mTextView, getErrorX(), getErrorY()); 266d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup.fixDirection(mErrorPopup.isAboveAnchor()); 267d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 268d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 269d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void setError(CharSequence error, Drawable icon) { 270d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mError = TextUtils.stringOrSpannedString(error); 271d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorWasChanged = true; 272d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final Drawables dr = mTextView.mDrawables; 273d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (dr != null) { 274d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne switch (mTextView.getResolvedLayoutDirection()) { 275d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne default: 276d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case View.LAYOUT_DIRECTION_LTR: 277d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon, 278d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne dr.mDrawableBottom); 279d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 280d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case View.LAYOUT_DIRECTION_RTL: 281d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.setCompoundDrawables(icon, dr.mDrawableTop, dr.mDrawableRight, 282d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne dr.mDrawableBottom); 283d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 284d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 285d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 286d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.setCompoundDrawables(null, null, icon, null); 287d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 288d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 289d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mError == null) { 290d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mErrorPopup != null) { 291d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mErrorPopup.isShowing()) { 292d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup.dismiss(); 293d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 294d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 295d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup = null; 296d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 297d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 298d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.isFocused()) { 299d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne showError(); 300d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 301d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 302d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 303d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 304d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void hideError() { 305d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mErrorPopup != null) { 306d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mErrorPopup.isShowing()) { 307d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup.dismiss(); 308d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 309d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 310d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 311d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mShowErrorAfterAttach = false; 312d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 313d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 314d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 315d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Returns the Y offset to make the pointy top of the error point 316d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * at the middle of the error icon. 317d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 318d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int getErrorX() { 319d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /* 320d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * The "25" is the distance between the point and the right edge 321d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * of the background 322d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 323d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float scale = mTextView.getResources().getDisplayMetrics().density; 324d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 325d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final Drawables dr = mTextView.mDrawables; 326d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.getWidth() - mErrorPopup.getWidth() - mTextView.getPaddingRight() - 327d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f); 328d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 329d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 330d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 331d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Returns the Y offset to make the pointy top of the error point 332d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * at the bottom of the error icon. 333d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 334d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int getErrorY() { 335d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /* 336d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Compound, not extended, because the icon is not clipped 337d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * if the text height is smaller. 338d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 339d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int compoundPaddingTop = mTextView.getCompoundPaddingTop(); 340d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int vspace = mTextView.getBottom() - mTextView.getTop() - 341d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getCompoundPaddingBottom() - compoundPaddingTop; 342d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 343d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final Drawables dr = mTextView.mDrawables; 344d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int icontop = compoundPaddingTop + 345d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2; 346d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 347d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /* 348d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * The "2" is the distance between the point and the top edge 349d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * of the background. 350d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 351d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float scale = mTextView.getResources().getDisplayMetrics().density; 352d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return icontop + (dr != null ? dr.mDrawableHeightRight : 0) - mTextView.getHeight() - 353d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (int) (2 * scale + 0.5f); 354d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 355d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 356d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void createInputContentTypeIfNeeded() { 357d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mInputContentType == null) { 358d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInputContentType = new InputContentType(); 359d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 360d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 361d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 362d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void createInputMethodStateIfNeeded() { 363d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mInputMethodState == null) { 364d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInputMethodState = new InputMethodState(); 365d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 366d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 367d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 368d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean isCursorVisible() { 369d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The default value is true, even when there is no associated Editor 370d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mCursorVisible && mTextView.isTextEditable(); 371d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 372d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 373d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void prepareCursorControllers() { 374d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean windowSupportsHandles = false; 375d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 376d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ViewGroup.LayoutParams params = mTextView.getRootView().getLayoutParams(); 377d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (params instanceof WindowManager.LayoutParams) { 378d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params; 379d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW 380d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW; 381d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 382d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 383d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean enabled = windowSupportsHandles && mTextView.getLayout() != null; 384d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInsertionControllerEnabled = enabled && isCursorVisible(); 385d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionControllerEnabled = enabled && mTextView.textCanBeSelected(); 386d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 387d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!mInsertionControllerEnabled) { 388d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideInsertionPointCursorController(); 389d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mInsertionPointCursorController != null) { 390d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInsertionPointCursorController.onDetached(); 391d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInsertionPointCursorController = null; 392d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 393d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 394d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 395d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!mSelectionControllerEnabled) { 396d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne stopSelectionActionMode(); 397d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectionModifierCursorController != null) { 398d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionModifierCursorController.onDetached(); 399d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionModifierCursorController = null; 400d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 401d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 402d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 403d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 404d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void hideInsertionPointCursorController() { 405d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mInsertionPointCursorController != null) { 406d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInsertionPointCursorController.hide(); 407d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 408d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 409d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 410d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 411d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Hides the insertion controller and stops text selection mode, hiding the selection controller 412d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 413d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void hideControllers() { 414d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideCursorControllers(); 415d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideSpanControllers(); 416d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 417d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 418d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void hideSpanControllers() { 419d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mEasyEditSpanController != null) { 420d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEasyEditSpanController.hide(); 421d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 422d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 423d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 424d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void hideCursorControllers() { 425d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) { 426d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Should be done before hide insertion point controller since it triggers a show of it 427d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionsPopupWindow.hide(); 428d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 429d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideInsertionPointCursorController(); 430d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne stopSelectionActionMode(); 431d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 432d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 433d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 434d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Create new SpellCheckSpans on the modified region. 435d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 436d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void updateSpellCheckSpans(int start, int end, boolean createSpellChecker) { 437d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.isTextEditable() && mTextView.isSuggestionsEnabled() && 438d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne !(mTextView instanceof ExtractEditText)) { 439d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSpellChecker == null && createSpellChecker) { 440d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSpellChecker = new SpellChecker(mTextView); 441d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 442d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSpellChecker != null) { 443d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSpellChecker.spellCheck(start, end); 444d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 445d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 446d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 447d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 448d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onScreenStateChanged(int screenState) { 449d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne switch (screenState) { 450d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case View.SCREEN_STATE_ON: 451d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne resumeBlink(); 452d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 453d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case View.SCREEN_STATE_OFF: 454d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suspendBlink(); 455d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 456d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 457d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 458d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 459d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void suspendBlink() { 460d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mBlink != null) { 461d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mBlink.cancel(); 462d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 463d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 464d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 465d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void resumeBlink() { 466d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mBlink != null) { 467d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mBlink.uncancel(); 468d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne makeBlink(); 469d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 470d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 471d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 472d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void adjustInputType(boolean password, boolean passwordInputType, 473d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean webPasswordInputType, boolean numberPasswordInputType) { 474d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // mInputType has been set from inputType, possibly modified by mInputMethod. 475d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Specialize mInputType to [web]password if we have a text class and the original input 476d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // type was a password. 477d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 478d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (password || passwordInputType) { 479d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) 480d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 481d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 482d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (webPasswordInputType) { 483d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) 484d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD; 485d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 486d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) { 487d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (numberPasswordInputType) { 488d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) 489d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; 490d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 491d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 492d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 493d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 494d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) { 495d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int wid = tv.getPaddingLeft() + tv.getPaddingRight(); 496d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int ht = tv.getPaddingTop() + tv.getPaddingBottom(); 497d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 498d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int defaultWidthInPixels = mTextView.getResources().getDimensionPixelSize( 499d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.dimen.textview_error_popup_default_width); 500d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Layout l = new StaticLayout(text, tv.getPaint(), defaultWidthInPixels, 501d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Layout.Alignment.ALIGN_NORMAL, 1, 0, true); 502d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne float max = 0; 503d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < l.getLineCount(); i++) { 504d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne max = Math.max(max, l.getLineWidth(i)); 505d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 506d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 507d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /* 508d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Now set the popup size to be big enough for the text plus the border capped 509d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * to DEFAULT_MAX_POPUP_WIDTH 510d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 511d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne pop.setWidth(wid + (int) Math.ceil(max)); 512d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne pop.setHeight(ht + l.getHeight()); 513d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 514d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 515d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void setFrame() { 516d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mErrorPopup != null) { 517d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne TextView tv = (TextView) mErrorPopup.getContentView(); 518d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne chooseSize(mErrorPopup, mError, tv); 519d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup.update(mTextView, getErrorX(), getErrorY(), 520d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mErrorPopup.getWidth(), mErrorPopup.getHeight()); 521d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 522d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 523d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 524d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 525d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Unlike {@link TextView#textCanBeSelected()}, this method is based on the <i>current</i> state 526d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * of the TextView. textCanBeSelected() has to be true (this is one of the conditions to have 527d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient. 528d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 529d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean canSelectText() { 530d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return hasSelectionController() && mTextView.getText().length() != 0; 531d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 532d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 533d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 534d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * It would be better to rely on the input type for everything. A password inputType should have 535d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * a password transformation. We should hence use isPasswordInputType instead of this method. 536d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 537d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * We should: 538d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * - Call setInputType in setKeyListener instead of changing the input type directly (which 539d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * would install the correct transformation). 540d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * - Refuse the installation of a non-password transformation in setTransformation if the input 541d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * type is password. 542d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 543d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * However, this is like this for legacy reasons and we cannot break existing apps. This method 544d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * is useful since it matches what the user can see (obfuscated text or not). 545d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 546d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @return true if the current transformation method is of the password type. 547d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 548d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean hasPasswordTransformationMethod() { 549d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.getTransformationMethod() instanceof PasswordTransformationMethod; 550d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 551d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 552d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 553d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Adjusts selection to the word under last touch offset. 554d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Return true if the operation was successfully performed. 555d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 556d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean selectCurrentWord() { 557d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!canSelectText()) { 558d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 559d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 560d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 561d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (hasPasswordTransformationMethod()) { 562d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Always select all on a password field. 563d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Cut/copy menu entries are not available for passwords, but being able to select all 564d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // is however useful to delete or paste to replace the entire content. 565d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.selectAllText(); 566d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 567d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 568d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int inputType = mTextView.getInputType(); 569d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int klass = inputType & InputType.TYPE_MASK_CLASS; 570d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int variation = inputType & InputType.TYPE_MASK_VARIATION; 571d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 572d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Specific text field types: select the entire text for these 573d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (klass == InputType.TYPE_CLASS_NUMBER || 574d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne klass == InputType.TYPE_CLASS_PHONE || 575d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne klass == InputType.TYPE_CLASS_DATETIME || 576d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne variation == InputType.TYPE_TEXT_VARIATION_URI || 577d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || 578d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS || 579d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne variation == InputType.TYPE_TEXT_VARIATION_FILTER) { 580d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.selectAllText(); 581d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 582d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 583d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne long lastTouchOffsets = getLastTouchOffsets(); 584d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int minOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets); 585d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets); 586d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 587d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Safety check in case standard touch event handling has been bypassed 588d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (minOffset < 0 || minOffset >= mTextView.getText().length()) return false; 589d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (maxOffset < 0 || maxOffset >= mTextView.getText().length()) return false; 590d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 591d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int selectionStart, selectionEnd; 592d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 593d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // If a URLSpan (web address, email, phone...) is found at that position, select it. 594d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne URLSpan[] urlSpans = ((Spanned) mTextView.getText()). 595d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getSpans(minOffset, maxOffset, URLSpan.class); 596d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (urlSpans.length >= 1) { 597d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne URLSpan urlSpan = urlSpans[0]; 598d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionStart = ((Spanned) mTextView.getText()).getSpanStart(urlSpan); 599d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionEnd = ((Spanned) mTextView.getText()).getSpanEnd(urlSpan); 600d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 601d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final WordIterator wordIterator = getWordIterator(); 602d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne wordIterator.setCharSequence(mTextView.getText(), minOffset, maxOffset); 603d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 604d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionStart = wordIterator.getBeginning(minOffset); 605d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionEnd = wordIterator.getEnd(maxOffset); 606d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 607d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (selectionStart == BreakIterator.DONE || selectionEnd == BreakIterator.DONE || 608d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionStart == selectionEnd) { 609d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Possible when the word iterator does not properly handle the text's language 610d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne long range = getCharRange(minOffset); 611d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionStart = TextUtils.unpackRangeStartFromLong(range); 612d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionEnd = TextUtils.unpackRangeEndFromLong(range); 613d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 614d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 615d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 616d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd); 617d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return selectionEnd > selectionStart; 618d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 619d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 620d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onLocaleChanged() { 621d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Will be re-created on demand in getWordIterator with the proper new locale 622d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mWordIterator = null; 623d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 624d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 625d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 626d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @hide 627d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 628d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public WordIterator getWordIterator() { 629d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mWordIterator == null) { 630d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mWordIterator = new WordIterator(mTextView.getTextServicesLocale()); 631d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 632d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mWordIterator; 633d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 634d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 635d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private long getCharRange(int offset) { 636d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int textLength = mTextView.getText().length(); 637d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offset + 1 < textLength) { 638d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final char currentChar = mTextView.getText().charAt(offset); 639d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final char nextChar = mTextView.getText().charAt(offset + 1); 640d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (Character.isSurrogatePair(currentChar, nextChar)) { 641d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return TextUtils.packRangeInLong(offset, offset + 2); 642d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 643d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 644d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offset < textLength) { 645d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return TextUtils.packRangeInLong(offset, offset + 1); 646d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 647d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offset - 2 >= 0) { 648d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final char previousChar = mTextView.getText().charAt(offset - 1); 649d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final char previousPreviousChar = mTextView.getText().charAt(offset - 2); 650d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (Character.isSurrogatePair(previousPreviousChar, previousChar)) { 651d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return TextUtils.packRangeInLong(offset - 2, offset); 652d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 653d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 654d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offset - 1 >= 0) { 655d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return TextUtils.packRangeInLong(offset - 1, offset); 656d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 657d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return TextUtils.packRangeInLong(offset, offset); 658d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 659d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 660d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean touchPositionIsInSelection() { 661d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int selectionStart = mTextView.getSelectionStart(); 662d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int selectionEnd = mTextView.getSelectionEnd(); 663d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 664d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (selectionStart == selectionEnd) { 665d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 666d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 667d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 668d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (selectionStart > selectionEnd) { 669d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int tmp = selectionStart; 670d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionStart = selectionEnd; 671d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionEnd = tmp; 672d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd); 673d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 674d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 675d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SelectionModifierCursorController selectionController = getSelectionController(); 676d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int minOffset = selectionController.getMinTouchOffset(); 677d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int maxOffset = selectionController.getMaxTouchOffset(); 678d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 679d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return ((minOffset >= selectionStart) && (maxOffset < selectionEnd)); 680d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 681d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 682d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private PositionListener getPositionListener() { 683d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mPositionListener == null) { 684d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionListener = new PositionListener(); 685d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 686d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mPositionListener; 687d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 688d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 689d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private interface TextViewPositionListener { 690d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void updatePosition(int parentPositionX, int parentPositionY, 691d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean parentPositionChanged, boolean parentScrolled); 692d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 693d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 694d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean isPositionVisible(int positionX, int positionY) { 695d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne synchronized (TEMP_POSITION) { 696d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float[] position = TEMP_POSITION; 697d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne position[0] = positionX; 698d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne position[1] = positionY; 699d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne View view = mTextView; 700d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 701d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne while (view != null) { 702d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (view != mTextView) { 703d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Local scroll is already taken into account in positionX/Y 704d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne position[0] -= view.getScrollX(); 705d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne position[1] -= view.getScrollY(); 706d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 707d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 708d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (position[0] < 0 || position[1] < 0 || 709d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne position[0] > view.getWidth() || position[1] > view.getHeight()) { 710d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 711d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 712d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 713d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!view.getMatrix().isIdentity()) { 714d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne view.getMatrix().mapPoints(position); 715d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 716d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 717d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne position[0] += view.getLeft(); 718d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne position[1] += view.getTop(); 719d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 720d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ViewParent parent = view.getParent(); 721d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (parent instanceof View) { 722d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne view = (View) parent; 723d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 724d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // We've reached the ViewRoot, stop iterating 725d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne view = null; 726d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 727d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 728d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 729d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 730d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // We've been able to walk up the view hierarchy and the position was never clipped 731d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 732d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 733d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 734d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean isOffsetVisible(int offset) { 735d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Layout layout = mTextView.getLayout(); 736d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int line = layout.getLineForOffset(offset); 737d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int lineBottom = layout.getLineBottom(line); 738d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int primaryHorizontal = (int) layout.getPrimaryHorizontal(offset); 739d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return isPositionVisible(primaryHorizontal + mTextView.viewportToContentHorizontalOffset(), 740d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne lineBottom + mTextView.viewportToContentVerticalOffset()); 741d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 742d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 743d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed 744d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * in the view. Returns false when the position is in the empty space of left/right of text. 745d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 746d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean isPositionOnText(float x, float y) { 747d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Layout layout = mTextView.getLayout(); 748d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (layout == null) return false; 749d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 750d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int line = mTextView.getLineAtCoordinate(y); 751d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne x = mTextView.convertToLocalHorizontalCoordinate(x); 752d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 753d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (x < layout.getLineLeft(line)) return false; 754d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (x > layout.getLineRight(line)) return false; 755d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 756d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 757d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 758d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean performLongClick(boolean handled) { 759d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Long press in empty space moves cursor and shows the Paste affordance if available. 760d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) && 761d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInsertionControllerEnabled) { 762d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int offset = mTextView.getOffsetForPosition(mLastDownPositionX, 763d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mLastDownPositionY); 764d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne stopSelectionActionMode(); 765d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), offset); 766d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getInsertionController().showWithActionPopup(); 767d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne handled = true; 768d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 769d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 770d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!handled && mSelectionActionMode != null) { 771d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (touchPositionIsInSelection()) { 772d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Start a drag 773d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int start = mTextView.getSelectionStart(); 774d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int end = mTextView.getSelectionEnd(); 775d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne CharSequence selectedText = mTextView.getTransformedText(start, end); 776d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ClipData data = ClipData.newPlainText(null, selectedText); 777d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne DragLocalState localState = new DragLocalState(mTextView, start, end); 778d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0); 779d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne stopSelectionActionMode(); 780d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 781d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getSelectionController().hide(); 782d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectCurrentWord(); 783d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getSelectionController().show(); 784d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 785d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne handled = true; 786d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 787d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 788d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Start a new selection 789d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!handled) { 790d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne handled = startSelectionActionMode(); 791d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 792d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 793d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return handled; 794d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 795d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 796d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private long getLastTouchOffsets() { 797d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SelectionModifierCursorController selectionController = getSelectionController(); 798d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int minOffset = selectionController.getMinTouchOffset(); 799d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int maxOffset = selectionController.getMaxTouchOffset(); 800d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return TextUtils.packRangeInLong(minOffset, maxOffset); 801d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 802d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 803d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onFocusChanged(boolean focused, int direction) { 804d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mShowCursor = SystemClock.uptimeMillis(); 805d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ensureEndedBatchEdit(); 806d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 807d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (focused) { 808d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int selStart = mTextView.getSelectionStart(); 809d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int selEnd = mTextView.getSelectionEnd(); 810d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 811d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection 812d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // mode for these, unless there was a specific selection already started. 813d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 && 814d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selEnd == mTextView.getText().length(); 815d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 816d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCreatedWithASelection = mFrozenWithFocus && mTextView.hasSelection() && 817d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne !isFocusHighlighted; 818d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 819d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) { 820d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // If a tap was used to give focus to that view, move cursor at tap position. 821d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Has to be done before onTakeFocus, which can be overloaded. 822d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int lastTapPosition = getLastTapPosition(); 823d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (lastTapPosition >= 0) { 824d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), lastTapPosition); 825d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 826d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 827d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Note this may have to be moved out of the Editor class 828d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne MovementMethod mMovement = mTextView.getMovementMethod(); 829d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mMovement != null) { 830d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mMovement.onTakeFocus(mTextView, (Spannable) mTextView.getText(), direction); 831d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 832d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 833d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The DecorView does not have focus when the 'Done' ExtractEditText button is 834d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // pressed. Since it is the ViewAncestor's mView, it requests focus before 835d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // ExtractEditText clears focus, which gives focus to the ExtractEditText. 836d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // This special case ensure that we keep current selection in that case. 837d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // It would be better to know why the DecorView does not have focus at that time. 838d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (((mTextView instanceof ExtractEditText) || mSelectionMoved) && 839d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selStart >= 0 && selEnd >= 0) { 840d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /* 841d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Someone intentionally set the selection, so let them 842d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * do whatever it is that they wanted to do instead of 843d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * the default on-focus behavior. We reset the selection 844d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * here instead of just skipping the onTakeFocus() call 845d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * because some movement methods do something other than 846d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * just setting the selection in theirs and we still 847d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * need to go through that path. 848d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 849d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), selStart, selEnd); 850d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 851d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 852d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectAllOnFocus) { 853d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.selectAllText(); 854d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 855d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 856d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTouchFocusSelected = true; 857d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 858d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 859d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mFrozenWithFocus = false; 860d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionMoved = false; 861d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 862d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mError != null) { 863d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne showError(); 864d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 865d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 866d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne makeBlink(); 867d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 868d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mError != null) { 869d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideError(); 870d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 871d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Don't leave us in the middle of a batch edit. 872d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.onEndBatchEdit(); 873d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 874d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView instanceof ExtractEditText) { 875d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // terminateTextSelectionMode removes selection, which we want to keep when 876d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // ExtractEditText goes out of focus. 877d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int selStart = mTextView.getSelectionStart(); 878d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int selEnd = mTextView.getSelectionEnd(); 879d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideControllers(); 880d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), selStart, selEnd); 881d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 882d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideControllers(); 883d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne downgradeEasyCorrectionSpans(); 884d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 885d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 886d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // No need to create the controller 887d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectionModifierCursorController != null) { 888d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionModifierCursorController.resetTouchOffsets(); 889d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 890d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 891d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 892d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 893d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 894d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Downgrades to simple suggestions all the easy correction spans that are not a spell check 895d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * span. 896d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 897d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void downgradeEasyCorrectionSpans() { 898d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne CharSequence text = mTextView.getText(); 899d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (text instanceof Spannable) { 900d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spannable spannable = (Spannable) text; 901d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan[] suggestionSpans = spannable.getSpans(0, 902d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne spannable.length(), SuggestionSpan.class); 903d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < suggestionSpans.length; i++) { 904d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int flags = suggestionSpans[i].getFlags(); 905d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 906d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne && (flags & SuggestionSpan.FLAG_MISSPELLED) == 0) { 907d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne flags &= ~SuggestionSpan.FLAG_EASY_CORRECT; 908d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionSpans[i].setFlags(flags); 909d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 910d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 911d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 912d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 913d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 914d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void sendOnTextChanged(int start, int after) { 915d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateSpellCheckSpans(start, start + after, false); 916d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 917d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Hide the controllers as soon as text is modified (typing, procedural...) 918d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // We do not hide the span controllers, since they can be added when a new text is 919d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // inserted into the text view (voice IME). 920d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideCursorControllers(); 921d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 922d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 923d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int getLastTapPosition() { 924d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // No need to create the controller at that point, no last tap position saved 925d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectionModifierCursorController != null) { 926d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset(); 927d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (lastTapPosition >= 0) { 928d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Safety check, should not be possible. 929d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (lastTapPosition > mTextView.getText().length()) { 930d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne lastTapPosition = mTextView.getText().length(); 931d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 932d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return lastTapPosition; 933d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 934d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 935d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 936d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return -1; 937d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 938d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 939d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onWindowFocusChanged(boolean hasWindowFocus) { 940d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (hasWindowFocus) { 941d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mBlink != null) { 942d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mBlink.uncancel(); 943d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne makeBlink(); 944d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 945d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 946d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mBlink != null) { 947d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mBlink.cancel(); 948d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 949d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mInputContentType != null) { 950d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInputContentType.enterDown = false; 951d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 952d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Order matters! Must be done before onParentLostFocus to rely on isShowingUp 953d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideControllers(); 954d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSuggestionsPopupWindow != null) { 955d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionsPopupWindow.onParentLostFocus(); 956d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 957d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 958d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Don't leave us in the middle of a batch edit. 959d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.onEndBatchEdit(); 960d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 961d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 962d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 963d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onTouchEvent(MotionEvent event) { 964d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (hasSelectionController()) { 965d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getSelectionController().onTouchEvent(event); 966d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 967d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 968d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mShowSuggestionRunnable != null) { 969d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.removeCallbacks(mShowSuggestionRunnable); 970d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mShowSuggestionRunnable = null; 971d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 972d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 973d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 974d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mLastDownPositionX = event.getX(); 975d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mLastDownPositionY = event.getY(); 976d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 977d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Reset this state; it will be re-set if super.onTouchEvent 978d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // causes focus to move to the view. 979d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTouchFocusSelected = false; 980d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mIgnoreActionUpEvent = false; 981d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 982d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 983d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 984d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void beginBatchEdit() { 985d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInBatchEditControllers = true; 986d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final InputMethodState ims = mInputMethodState; 987d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (ims != null) { 988d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int nesting = ++ims.mBatchEditNesting; 989d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (nesting == 1) { 990d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mCursorChanged = false; 991d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedDelta = 0; 992d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (ims.mContentChanged) { 993d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // We already have a pending change from somewhere else, 994d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // so turn this into a full update. 995d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedStart = 0; 996d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedEnd = mTextView.getText().length(); 997d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 998d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedStart = EXTRACT_UNKNOWN; 999d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedEnd = EXTRACT_UNKNOWN; 1000d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mContentChanged = false; 1001d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1002d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.onBeginBatchEdit(); 1003d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1004d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1005d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1006d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1007d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void endBatchEdit() { 1008d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInBatchEditControllers = false; 1009d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final InputMethodState ims = mInputMethodState; 1010d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (ims != null) { 1011d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int nesting = --ims.mBatchEditNesting; 1012d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (nesting == 0) { 1013d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne finishBatchEdit(ims); 1014d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1015d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1016d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1017d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1018d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void ensureEndedBatchEdit() { 1019d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final InputMethodState ims = mInputMethodState; 1020d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (ims != null && ims.mBatchEditNesting != 0) { 1021d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mBatchEditNesting = 0; 1022d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne finishBatchEdit(ims); 1023d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1024d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1025d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1026d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void finishBatchEdit(final InputMethodState ims) { 1027d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.onEndBatchEdit(); 1028d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1029d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (ims.mContentChanged || ims.mSelectionModeChanged) { 1030d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.updateAfterEdit(); 1031d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne reportExtractedText(); 1032d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else if (ims.mCursorChanged) { 1033d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Cheezy way to get us to report the current cursor location. 1034d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.invalidateCursor(); 1035d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1036d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1037d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1038d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne static final int EXTRACT_NOTHING = -2; 1039d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne static final int EXTRACT_UNKNOWN = -1; 1040d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1041d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 1042d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN, 1043d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne EXTRACT_UNKNOWN, outText); 1044d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1045d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1046d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean extractTextInternal(ExtractedTextRequest request, 1047d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int partialStartOffset, int partialEndOffset, int delta, 1048d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ExtractedText outText) { 1049d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final CharSequence content = mTextView.getText(); 1050d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (content != null) { 1051d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (partialStartOffset != EXTRACT_NOTHING) { 1052d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int N = content.length(); 1053d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (partialStartOffset < 0) { 1054d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.partialStartOffset = outText.partialEndOffset = -1; 1055d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialStartOffset = 0; 1056d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialEndOffset = N; 1057d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 1058d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Now use the delta to determine the actual amount of text 1059d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // we need. 1060d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialEndOffset += delta; 1061d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Adjust offsets to ensure we contain full spans. 1062d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (content instanceof Spanned) { 1063d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spanned spanned = (Spanned)content; 1064d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Object[] spans = spanned.getSpans(partialStartOffset, 1065d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialEndOffset, ParcelableSpan.class); 1066d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int i = spans.length; 1067d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne while (i > 0) { 1068d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne i--; 1069d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int j = spanned.getSpanStart(spans[i]); 1070d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (j < partialStartOffset) partialStartOffset = j; 1071d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne j = spanned.getSpanEnd(spans[i]); 1072d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (j > partialEndOffset) partialEndOffset = j; 1073d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1074d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1075d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.partialStartOffset = partialStartOffset; 1076d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.partialEndOffset = partialEndOffset - delta; 1077d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1078d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (partialStartOffset > N) { 1079d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialStartOffset = N; 1080d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else if (partialStartOffset < 0) { 1081d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialStartOffset = 0; 1082d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1083d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (partialEndOffset > N) { 1084d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialEndOffset = N; 1085d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else if (partialEndOffset < 0) { 1086d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialEndOffset = 0; 1087d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1088d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1089d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) { 1090d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.text = content.subSequence(partialStartOffset, 1091d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialEndOffset); 1092d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 1093d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.text = TextUtils.substring(content, partialStartOffset, 1094d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne partialEndOffset); 1095d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1096d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 1097d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.partialStartOffset = 0; 1098d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.partialEndOffset = 0; 1099d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.text = ""; 1100d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1101d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.flags = 0; 1102d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (MetaKeyKeyListener.getMetaState(content, MetaKeyKeyListener.META_SELECTING) != 0) { 1103d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.flags |= ExtractedText.FLAG_SELECTING; 1104d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1105d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.isSingleLine()) { 1106d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.flags |= ExtractedText.FLAG_SINGLE_LINE; 1107d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1108d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.startOffset = 0; 1109d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.selectionStart = mTextView.getSelectionStart(); 1110d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne outText.selectionEnd = mTextView.getSelectionEnd(); 1111d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 1112d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1113d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 1114d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1115d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1116d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean reportExtractedText() { 1117d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final Editor.InputMethodState ims = mInputMethodState; 1118d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (ims != null) { 1119d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean contentChanged = ims.mContentChanged; 1120d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (contentChanged || ims.mSelectionModeChanged) { 1121d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mContentChanged = false; 1122d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mSelectionModeChanged = false; 1123d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ExtractedTextRequest req = ims.mExtracting; 1124d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (req != null) { 1125d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne InputMethodManager imm = InputMethodManager.peekInstance(); 1126d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (imm != null) { 1127d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (TextView.DEBUG_EXTRACT) Log.v(TextView.LOG_TAG, 1128d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne "Retrieving extracted start=" + ims.mChangedStart + 1129d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne " end=" + ims.mChangedEnd + 1130d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne " delta=" + ims.mChangedDelta); 1131d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (ims.mChangedStart < 0 && !contentChanged) { 1132d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedStart = EXTRACT_NOTHING; 1133d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1134d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, 1135d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedDelta, ims.mTmpExtracted)) { 1136d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (TextView.DEBUG_EXTRACT) Log.v(TextView.LOG_TAG, 1137d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne "Reporting extracted start=" + 1138d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mTmpExtracted.partialStartOffset + 1139d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne " end=" + ims.mTmpExtracted.partialEndOffset + 1140d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ": " + ims.mTmpExtracted.text); 1141d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne imm.updateExtractedText(mTextView, req.token, ims.mTmpExtracted); 1142d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedStart = EXTRACT_UNKNOWN; 1143d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedEnd = EXTRACT_UNKNOWN; 1144d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mChangedDelta = 0; 1145d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mContentChanged = false; 1146d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 1147d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1148d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1149d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1150d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1151d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1152d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 1153d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1154d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1155d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onDraw(Canvas canvas, Layout layout, Path highlight, Paint highlightPaint, 1156d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int cursorOffsetVertical) { 1157d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int selectionStart = mTextView.getSelectionStart(); 1158d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int selectionEnd = mTextView.getSelectionEnd(); 1159d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1160d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final InputMethodState ims = mInputMethodState; 1161d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (ims != null && ims.mBatchEditNesting == 0) { 1162d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne InputMethodManager imm = InputMethodManager.peekInstance(); 1163d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (imm != null) { 1164d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (imm.isActive(mTextView)) { 1165d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean reported = false; 1166d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (ims.mContentChanged || ims.mSelectionModeChanged) { 1167d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // We are in extract mode and the content has changed 1168d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // in some way... just report complete new text to the 1169d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // input method. 1170d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne reported = reportExtractedText(); 1171d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1172d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!reported && highlight != null) { 1173d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int candStart = -1; 1174d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int candEnd = -1; 1175d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.getText() instanceof Spannable) { 1176d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spannable sp = (Spannable) mTextView.getText(); 1177d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne candStart = EditableInputConnection.getComposingSpanStart(sp); 1178d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne candEnd = EditableInputConnection.getComposingSpanEnd(sp); 1179d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1180d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne imm.updateSelection(mTextView, 1181d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectionStart, selectionEnd, candStart, candEnd); 1182d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1183d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1184d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1185d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (imm.isWatchingCursor(mTextView) && highlight != null) { 1186d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne highlight.computeBounds(ims.mTmpRectF, true); 1187d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0; 1188d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1189d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne canvas.getMatrix().mapPoints(ims.mTmpOffset); 1190d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]); 1191d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1192d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mTmpRectF.offset(0, cursorOffsetVertical); 1193d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1194d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5), 1195d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (int)(ims.mTmpRectF.top + 0.5), 1196d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (int)(ims.mTmpRectF.right + 0.5), 1197d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (int)(ims.mTmpRectF.bottom + 0.5)); 1198d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1199d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne imm.updateCursor(mTextView, 1200d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top, 1201d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom); 1202d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1203d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1204d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1205d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1206d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCorrectionHighlighter != null) { 1207d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCorrectionHighlighter.draw(canvas, cursorOffsetVertical); 1208d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1209d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1210d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (highlight != null && selectionStart == selectionEnd && mCursorCount > 0) { 1211d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne drawCursor(canvas, cursorOffsetVertical); 1212d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Rely on the drawable entirely, do not draw the cursor line. 1213d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Has to be done after the IMM related code above which relies on the highlight. 1214d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne highlight = null; 1215d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1216d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1217d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) { 1218d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne drawHardwareAccelerated(canvas, layout, highlight, highlightPaint, 1219d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne cursorOffsetVertical); 1220d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 1221d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical); 1222d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1223d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1224d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1225d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight, 1226d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Paint highlightPaint, int cursorOffsetVertical) { 1227d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int width = mTextView.getWidth(); 1228d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int height = mTextView.getHeight(); 1229d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1230d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final long lineRange = layout.getLineRangeForDraw(canvas); 1231d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); 1232d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); 1233d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (lastLine < 0) return; 1234d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1235d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne layout.drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical, 1236d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne firstLine, lastLine); 1237d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1238d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (layout instanceof DynamicLayout) { 1239d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextDisplayLists == null) { 1240d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)]; 1241d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1242d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1243d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne DynamicLayout dynamicLayout = (DynamicLayout) layout; 1244157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne int[] blockEndLines = dynamicLayout.getBlockEndLines(); 1245d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int[] blockIndices = dynamicLayout.getBlockIndices(); 1246d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int numberOfBlocks = dynamicLayout.getNumberOfBlocks(); 1247d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1248d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int endOfPreviousBlock = -1; 1249d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int searchStartIndex = 0; 1250d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < numberOfBlocks; i++) { 1251157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne int blockEndLine = blockEndLines[i]; 1252d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int blockIndex = blockIndices[i]; 1253d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1254d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX; 1255d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (blockIsInvalid) { 1256d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks, 1257d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne searchStartIndex); 1258157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne // Note how dynamic layout's internal block indices get updated from Editor 1259d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne blockIndices[i] = blockIndex; 1260d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne searchStartIndex = blockIndex + 1; 1261d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1262d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1263d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne DisplayList blockDisplayList = mTextDisplayLists[blockIndex]; 1264d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (blockDisplayList == null) { 1265d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne blockDisplayList = mTextDisplayLists[blockIndex] = 1266d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getHardwareRenderer().createDisplayList("Text " + blockIndex); 1267d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 1268d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (blockIsInvalid) blockDisplayList.invalidate(); 1269d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1270d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1271d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!blockDisplayList.isValid()) { 1272157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne final int blockBeginLine = endOfPreviousBlock + 1; 1273157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne final int top = layout.getLineTop(blockBeginLine); 1274157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne final int bottom = layout.getLineBottom(blockEndLine); 1275157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne 1276d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final HardwareCanvas hardwareCanvas = blockDisplayList.start(); 1277d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne try { 1278157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne hardwareCanvas.setViewport(width, bottom - top); 1279d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The dirty rect should always be null for a display list 1280d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hardwareCanvas.onPreDraw(null); 1281157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne // drawText is always relative to TextView's origin, this translation brings 1282157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne // this range of text back to the top of the viewport 1283157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne hardwareCanvas.translate(0, -top); 1284157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine); 1285157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne hardwareCanvas.translate(0, top); 1286d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } finally { 1287d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hardwareCanvas.onPostDraw(); 1288d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne blockDisplayList.end(); 12891271e2cc80b01d577e9db339459ef0222bb9320dChet Haase blockDisplayList.setLeftTopRightBottom(0, top, width, bottom); 12901271e2cc80b01d577e9db339459ef0222bb9320dChet Haase // Same as drawDisplayList below, handled by our TextView's parent 12911271e2cc80b01d577e9db339459ef0222bb9320dChet Haase blockDisplayList.setClipChildren(false); 1292d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1293d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1294d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1295157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne // TODO When View.USE_DISPLAY_LIST_PROPERTIES is the only code path, the 1296157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne // width and height parameters should be removed and the bounds set above in 1297157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne // setLeftTopRightBottom should be used instead for quick rejection. 12981271e2cc80b01d577e9db339459ef0222bb9320dChet Haase ((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, null, 1299157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne 0 /* no child clipping, our TextView parent enforces it */); 1300157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne endOfPreviousBlock = blockEndLine; 1301d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1302d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 1303d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Boring layout is used for empty and hint text 1304d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne layout.drawText(canvas, firstLine, lastLine); 1305d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1306d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1307d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1308d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int getAvailableDisplayListIndex(int[] blockIndices, int numberOfBlocks, 1309d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int searchStartIndex) { 1310d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int length = mTextDisplayLists.length; 1311d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = searchStartIndex; i < length; i++) { 1312d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean blockIndexFound = false; 1313d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int j = 0; j < numberOfBlocks; j++) { 1314d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (blockIndices[j] == i) { 1315d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne blockIndexFound = true; 1316d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 1317d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1318d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1319d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (blockIndexFound) continue; 1320d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return i; 1321d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1322d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1323d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // No available index found, the pool has to grow 1324d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int newSize = ArrayUtils.idealIntArraySize(length + 1); 1325d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne DisplayList[] displayLists = new DisplayList[newSize]; 1326d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne System.arraycopy(mTextDisplayLists, 0, displayLists, 0, length); 1327d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextDisplayLists = displayLists; 1328d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return length; 1329d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1330d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1331d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void drawCursor(Canvas canvas, int cursorOffsetVertical) { 1332d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean translate = cursorOffsetVertical != 0; 1333d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (translate) canvas.translate(0, cursorOffsetVertical); 1334d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < mCursorCount; i++) { 1335d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCursorDrawable[i].draw(canvas); 1336d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1337d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (translate) canvas.translate(0, -cursorOffsetVertical); 1338d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1339d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1340ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne /** 1341ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne * Invalidates all the sub-display lists that overlap the specified character range 1342ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne */ 1343ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne void invalidateTextDisplayList(Layout layout, int start, int end) { 1344ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne if (mTextDisplayLists != null && layout instanceof DynamicLayout) { 1345ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne final int firstLine = layout.getLineForOffset(start); 1346ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne final int lastLine = layout.getLineForOffset(end); 1347ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne 1348ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne DynamicLayout dynamicLayout = (DynamicLayout) layout; 1349ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne int[] blockEndLines = dynamicLayout.getBlockEndLines(); 1350ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne int[] blockIndices = dynamicLayout.getBlockIndices(); 1351ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne final int numberOfBlocks = dynamicLayout.getNumberOfBlocks(); 1352ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne 1353ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne int i = 0; 1354ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne // Skip the blocks before firstLine 1355ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne while (i < numberOfBlocks) { 1356ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne if (blockEndLines[i] >= firstLine) break; 1357ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne i++; 1358ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne } 1359ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne 1360ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne // Invalidate all subsequent blocks until lastLine is passed 1361ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne while (i < numberOfBlocks) { 1362ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne final int blockIndex = blockIndices[i]; 1363ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne if (blockIndex != DynamicLayout.INVALID_BLOCK_INDEX) { 1364ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne mTextDisplayLists[blockIndex].invalidate(); 1365ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne } 1366ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne if (blockEndLines[i] >= lastLine) break; 1367ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne i++; 1368ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne } 1369ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne } 1370ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne } 1371ebc86af1dc186c77f723c8970951e8ff00b4866bGilles Debunne 1372d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void invalidateTextDisplayList() { 1373d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextDisplayLists != null) { 1374d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < mTextDisplayLists.length; i++) { 1375d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextDisplayLists[i] != null) mTextDisplayLists[i].invalidate(); 1376d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1377d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1378d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1379d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1380d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void updateCursorsPositions() { 1381d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.mCursorDrawableRes == 0) { 1382d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCursorCount = 0; 1383d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 1384d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1385d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1386d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Layout layout = mTextView.getLayout(); 1387d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int offset = mTextView.getSelectionStart(); 1388d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int line = layout.getLineForOffset(offset); 1389d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int top = layout.getLineTop(line); 1390d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int bottom = layout.getLineTop(line + 1); 1391d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1392d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCursorCount = layout.isLevelBoundary(offset) ? 2 : 1; 1393d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1394d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int middle = bottom; 1395d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCursorCount == 2) { 1396d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)} 1397d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne middle = (top + bottom) >> 1; 1398d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1399d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1400d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateCursorPosition(0, top, middle, layout.getPrimaryHorizontal(offset)); 1401d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1402d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCursorCount == 2) { 1403d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset)); 1404d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1405d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1406d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1407d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1408d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @return true if the selection mode was actually started. 1409d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1410d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean startSelectionActionMode() { 1411d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectionActionMode != null) { 1412d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Selection action mode is already started 1413d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 1414d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1415d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1416d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!canSelectText() || !mTextView.requestFocus()) { 1417d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Log.w(TextView.LOG_TAG, 1418d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne "TextView does not support text selection. Action mode cancelled."); 1419d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 1420d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1421d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1422d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!mTextView.hasSelection()) { 1423d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // There may already be a selection on device rotation 1424d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!selectCurrentWord()) { 1425d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // No word found under cursor or text selection not permitted. 1426d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 1427d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1428d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1429d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1430d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean willExtract = extractedTextModeWillBeStarted(); 1431d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1432d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Do not start the action mode when extracted text will show up full screen, which would 1433d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // immediately hide the newly created action bar and would be visually distracting. 1434d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!willExtract) { 1435d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ActionMode.Callback actionModeCallback = new SelectionActionModeCallback(); 1436d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionActionMode = mTextView.startActionMode(actionModeCallback); 1437d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1438d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1439d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean selectionStarted = mSelectionActionMode != null || willExtract; 1440d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (selectionStarted && !mTextView.isTextSelectable()) { 1441d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Show the IME to be able to replace text, except when selecting non editable text. 1442d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final InputMethodManager imm = InputMethodManager.peekInstance(); 1443d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (imm != null) { 1444d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne imm.showSoftInput(mTextView, 0, null); 1445d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1446d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1447d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1448d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return selectionStarted; 1449d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1450d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1451d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean extractedTextModeWillBeStarted() { 1452d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!(mTextView instanceof ExtractEditText)) { 1453d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final InputMethodManager imm = InputMethodManager.peekInstance(); 1454d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return imm != null && imm.isFullscreenMode(); 1455d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1456d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 1457d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1458d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1459d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1460d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @return <code>true</code> if the cursor/current selection overlaps a {@link SuggestionSpan}. 1461d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1462d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean isCursorInsideSuggestionSpan() { 1463d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne CharSequence text = mTextView.getText(); 1464d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!(text instanceof Spannable)) return false; 1465d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1466d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan[] suggestionSpans = ((Spannable) text).getSpans( 1467d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getSelectionStart(), mTextView.getSelectionEnd(), SuggestionSpan.class); 1468d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return (suggestionSpans.length > 0); 1469d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1470d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1471d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1472d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @return <code>true</code> if the cursor is inside an {@link SuggestionSpan} with 1473d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * {@link SuggestionSpan#FLAG_EASY_CORRECT} set. 1474d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1475d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean isCursorInsideEasyCorrectionSpan() { 1476d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spannable spannable = (Spannable) mTextView.getText(); 1477d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan[] suggestionSpans = spannable.getSpans(mTextView.getSelectionStart(), 1478d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getSelectionEnd(), SuggestionSpan.class); 1479d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < suggestionSpans.length; i++) { 1480d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if ((suggestionSpans[i].getFlags() & SuggestionSpan.FLAG_EASY_CORRECT) != 0) { 1481d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 1482d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1483d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1484d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 1485d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1486d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1487d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onTouchUpEvent(MotionEvent event) { 1488d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean selectAllGotFocus = mSelectAllOnFocus && mTextView.didTouchFocusSelect(); 1489d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideControllers(); 1490d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne CharSequence text = mTextView.getText(); 1491d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!selectAllGotFocus && text.length() > 0) { 1492d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Move cursor 1493d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY()); 1494d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) text, offset); 1495d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSpellChecker != null) { 1496d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // When the cursor moves, the word that was typed may need spell check 1497d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSpellChecker.onSelectionChanged(); 1498d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1499d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!extractedTextModeWillBeStarted()) { 1500d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (isCursorInsideEasyCorrectionSpan()) { 1501d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mShowSuggestionRunnable = new Runnable() { 1502d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void run() { 1503d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne showSuggestions(); 1504d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1505d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne }; 1506d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // removeCallbacks is performed on every touch 1507d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.postDelayed(mShowSuggestionRunnable, 1508d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ViewConfiguration.getDoubleTapTimeout()); 1509d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else if (hasInsertionController()) { 1510d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getInsertionController().show(); 1511d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1512d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1513d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1514d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1515d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1516d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void stopSelectionActionMode() { 1517d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectionActionMode != null) { 1518d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // This will hide the mSelectionModifierCursorController 1519d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionActionMode.finish(); 1520d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1521d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1522d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1523d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1524d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @return True if this view supports insertion handles. 1525d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1526d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean hasInsertionController() { 1527d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mInsertionControllerEnabled; 1528d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1529d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1530d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1531d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @return True if this view supports selection handles. 1532d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1533d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean hasSelectionController() { 1534d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mSelectionControllerEnabled; 1535d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1536d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1537d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne InsertionPointCursorController getInsertionController() { 1538d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!mInsertionControllerEnabled) { 1539d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return null; 1540d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1541d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1542d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mInsertionPointCursorController == null) { 1543d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mInsertionPointCursorController = new InsertionPointCursorController(); 1544d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1545d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ViewTreeObserver observer = mTextView.getViewTreeObserver(); 1546d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne observer.addOnTouchModeChangeListener(mInsertionPointCursorController); 1547d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1548d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1549d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mInsertionPointCursorController; 1550d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1551d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1552d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SelectionModifierCursorController getSelectionController() { 1553d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!mSelectionControllerEnabled) { 1554d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return null; 1555d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1556d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1557d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectionModifierCursorController == null) { 1558d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionModifierCursorController = new SelectionModifierCursorController(); 1559d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1560d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ViewTreeObserver observer = mTextView.getViewTreeObserver(); 1561d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne observer.addOnTouchModeChangeListener(mSelectionModifierCursorController); 1562d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1563d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1564d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mSelectionModifierCursorController; 1565d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1566d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1567d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) { 1568d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCursorDrawable[cursorIndex] == null) 1569d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCursorDrawable[cursorIndex] = mTextView.getResources().getDrawable( 1570d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.mCursorDrawableRes); 1571d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1572d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTempRect == null) mTempRect = new Rect(); 1573d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCursorDrawable[cursorIndex].getPadding(mTempRect); 1574d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth(); 1575d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne horizontal = Math.max(0.5f, horizontal - 0.5f); 1576d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int left = (int) (horizontal) - mTempRect.left; 1577d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width, 1578d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne bottom + mTempRect.bottom); 1579d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1580d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1581d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1582d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Called by the framework in response to a text auto-correction (such as fixing a typo using a 1583d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * a dictionnary) from the current input method, provided by it calling 1584d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default 1585d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * implementation flashes the background of the corrected word to provide feedback to the user. 1586d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 1587d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @param info The auto correct info about the text that was corrected. 1588d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1589d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onCommitCorrection(CorrectionInfo info) { 1590d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCorrectionHighlighter == null) { 1591d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCorrectionHighlighter = new CorrectionHighlighter(); 1592d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 1593d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCorrectionHighlighter.invalidate(false); 1594d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1595d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1596d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCorrectionHighlighter.highlight(info); 1597d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1598d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1599d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void showSuggestions() { 1600d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSuggestionsPopupWindow == null) { 1601d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionsPopupWindow = new SuggestionsPopupWindow(); 1602d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1603d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideControllers(); 1604d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionsPopupWindow.show(); 1605d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1606d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1607d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean areSuggestionsShown() { 1608d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing(); 1609d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1610d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1611d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onScrollChanged() { 1612157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne if (mPositionListener != null) { 1613157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne mPositionListener.onScrollChanged(); 1614157aafcbee0eabda798a3be406ccc4200ee86756Gilles Debunne } 1615d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1616d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1617d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1618d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @return True when the TextView isFocused and has a valid zero-length selection (cursor). 1619d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1620d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean shouldBlink() { 1621d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!isCursorVisible() || !mTextView.isFocused()) return false; 1622d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1623d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int start = mTextView.getSelectionStart(); 1624d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (start < 0) return false; 1625d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1626d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int end = mTextView.getSelectionEnd(); 1627d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (end < 0) return false; 1628d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1629d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return start == end; 1630d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1631d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1632d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void makeBlink() { 1633d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (shouldBlink()) { 1634d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mShowCursor = SystemClock.uptimeMillis(); 1635d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mBlink == null) mBlink = new Blink(); 1636d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mBlink.removeCallbacks(mBlink); 1637d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mBlink.postAtTime(mBlink, mShowCursor + BLINK); 1638d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 1639d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mBlink != null) mBlink.removeCallbacks(mBlink); 1640d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1641d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1642d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1643d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class Blink extends Handler implements Runnable { 1644d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mCancelled; 1645d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1646d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void run() { 1647d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCancelled) { 1648d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 1649d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1650d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1651d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne removeCallbacks(Blink.this); 1652d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1653d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (shouldBlink()) { 1654d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.getLayout() != null) { 1655d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.invalidateCursorPath(); 1656d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1657d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1658d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne postAtTime(this, SystemClock.uptimeMillis() + BLINK); 1659d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1660d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1661d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1662d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void cancel() { 1663d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!mCancelled) { 1664d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne removeCallbacks(Blink.this); 1665d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCancelled = true; 1666d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1667d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1668d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1669d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void uncancel() { 1670d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCancelled = false; 1671d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1672d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1673d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1674d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) { 1675d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne TextView shadowView = (TextView) View.inflate(mTextView.getContext(), 1676d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.layout.text_drag_thumbnail, null); 1677d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1678d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (shadowView == null) { 1679d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne throw new IllegalArgumentException("Unable to inflate text drag thumbnail"); 1680d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1681d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1682d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) { 1683d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH); 1684d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1685d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne shadowView.setText(text); 1686d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne shadowView.setTextColor(mTextView.getTextColors()); 1687d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1688d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne shadowView.setTextAppearance(mTextView.getContext(), R.styleable.Theme_textAppearanceLarge); 1689d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne shadowView.setGravity(Gravity.CENTER); 1690d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1691d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne shadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 1692d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ViewGroup.LayoutParams.WRAP_CONTENT)); 1693d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1694d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 1695d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne shadowView.measure(size, size); 1696d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1697d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne shadowView.layout(0, 0, shadowView.getMeasuredWidth(), shadowView.getMeasuredHeight()); 1698d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne shadowView.invalidate(); 1699d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return new DragShadowBuilder(shadowView); 1700d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1701d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1702d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static class DragLocalState { 1703d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public TextView sourceTextView; 1704d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int start, end; 1705d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1706d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public DragLocalState(TextView sourceTextView, int start, int end) { 1707d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne this.sourceTextView = sourceTextView; 1708d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne this.start = start; 1709d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne this.end = end; 1710d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1711d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1712d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1713d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onDrop(DragEvent event) { 1714d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne StringBuilder content = new StringBuilder(""); 1715d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ClipData clipData = event.getClipData(); 1716d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int itemCount = clipData.getItemCount(); 1717d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i=0; i < itemCount; i++) { 1718d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Item item = clipData.getItemAt(i); 1719acb69bb909d098cea284df47d794c17171d84c91Dianne Hackborn content.append(item.coerceToStyledText(mTextView.getContext())); 1720d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1721d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1722d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY()); 1723d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1724d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Object localState = event.getLocalState(); 1725d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne DragLocalState dragLocalState = null; 1726d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (localState instanceof DragLocalState) { 1727d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne dragLocalState = (DragLocalState) localState; 1728d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1729d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean dragDropIntoItself = dragLocalState != null && 1730d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne dragLocalState.sourceTextView == mTextView; 1731d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1732d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (dragDropIntoItself) { 1733d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offset >= dragLocalState.start && offset < dragLocalState.end) { 1734d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // A drop inside the original selection discards the drop. 1735d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 1736d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1737d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1738d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1739d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int originalLength = mTextView.getText().length(); 1740d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne long minMax = mTextView.prepareSpacesAroundPaste(offset, offset, content); 1741d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int min = TextUtils.unpackRangeStartFromLong(minMax); 1742d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int max = TextUtils.unpackRangeEndFromLong(minMax); 1743d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1744d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), max); 1745d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.replaceText_internal(min, max, content); 1746d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1747d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (dragDropIntoItself) { 1748d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int dragSourceStart = dragLocalState.start; 1749d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int dragSourceEnd = dragLocalState.end; 1750d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (max <= dragSourceStart) { 1751d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Inserting text before selection has shifted positions 1752d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int shift = mTextView.getText().length() - originalLength; 1753d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne dragSourceStart += shift; 1754d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne dragSourceEnd += shift; 1755d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1756d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1757d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Delete original selection 1758d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.deleteText_internal(dragSourceStart, dragSourceEnd); 1759d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1760d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Make sure we do not leave two adjacent spaces. 1761d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne CharSequence t = mTextView.getTransformedText(dragSourceStart - 1, dragSourceStart + 1); 1762d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if ( (dragSourceStart == 0 || Character.isSpaceChar(t.charAt(0))) && 1763d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (dragSourceStart == mTextView.getText().length() || 1764d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Character.isSpaceChar(t.charAt(1))) ) { 1765d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int pos = dragSourceStart == mTextView.getText().length() ? 1766d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne dragSourceStart - 1 : dragSourceStart; 1767d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.deleteText_internal(pos, pos + 1); 1768d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1769d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1770d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1771d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1772d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1773d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related 1774d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * pop-up should be displayed. 1775d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1776d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne class EasyEditSpanController implements TextWatcher { 1777d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1778d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs 1779d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1780d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private EasyEditPopupWindow mPopupWindow; 1781d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1782d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private EasyEditSpan mEasyEditSpan; 1783d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1784d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private Runnable mHidePopup; 1785d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1786d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void hide() { 1787d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mPopupWindow != null) { 1788d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.hide(); 1789d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.removeCallbacks(mHidePopup); 1790d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1791d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne removeSpans(mTextView.getText()); 1792d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEasyEditSpan = null; 1793d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1794d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1795d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void beforeTextChanged(CharSequence s, int start, int count, int after) { 1796d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Intentionally empty 1797d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1798d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1799d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void afterTextChanged(Editable s) { 1800d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Intentionally empty 1801d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1802d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1803d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1804d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Monitors the changes in the text. 1805d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 1806d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * <p>{@link SpanWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used, 1807d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * as the notifications are not sent when a spannable (with spans) is inserted. 1808d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1809d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onTextChanged(CharSequence buffer, int start, int before, int after) { 1810d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne adjustSpans(buffer, start, after); 1811d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1812d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.getWindowVisibility() != View.VISIBLE) { 1813d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The window is not visible yet, ignore the text change. 1814d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 1815d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1816d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1817d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.getLayout() == null) { 1818d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The view has not been layout yet, ignore the text change 1819d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 1820d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1821d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1822d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne InputMethodManager imm = InputMethodManager.peekInstance(); 1823d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!(mTextView instanceof ExtractEditText) && imm != null && imm.isFullscreenMode()) { 1824d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The input is in extract mode. We do not have to handle the easy edit in the 1825d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // original TextView, as the ExtractEditText will do 1826d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 1827d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1828d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1829d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Remove the current easy edit span, as the text changed, and remove the pop-up 1830d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // (if any) 1831d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mEasyEditSpan != null) { 1832d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (buffer instanceof Spannable) { 1833d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ((Spannable) buffer).removeSpan(mEasyEditSpan); 1834d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1835d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEasyEditSpan = null; 1836d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1837d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mPopupWindow != null && mPopupWindow.isShowing()) { 1838d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.hide(); 1839d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1840d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1841d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Display the new easy edit span (if any). 1842d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (buffer instanceof Spanned) { 1843d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEasyEditSpan = getSpan((Spanned) buffer); 1844d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mEasyEditSpan != null) { 1845d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mPopupWindow == null) { 1846d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow = new EasyEditPopupWindow(); 1847d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mHidePopup = new Runnable() { 1848d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 1849d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void run() { 1850d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hide(); 1851d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1852d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne }; 1853d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1854d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.show(mEasyEditSpan); 1855d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.removeCallbacks(mHidePopup); 1856d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS); 1857d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1858d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1859d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1860d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1861d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1862d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Adjusts the spans by removing all of them except the last one. 1863d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1864d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void adjustSpans(CharSequence buffer, int start, int after) { 1865d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // This method enforces that only one easy edit span is attached to the text. 1866d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // A better way to enforce this would be to listen for onSpanAdded, but this method 1867d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // cannot be used in this scenario as no notification is triggered when a text with 1868d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // spans is inserted into a text. 1869d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (buffer instanceof Spannable) { 1870d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spannable spannable = (Spannable) buffer; 1871d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne EasyEditSpan[] spans = spannable.getSpans(start, start + after, EasyEditSpan.class); 1872d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (spans.length > 0) { 1873d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Assuming there was only one EasyEditSpan before, we only need check to 1874d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // check for a duplicate if a new one is found in the modified interval 1875d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne spans = spannable.getSpans(0, spannable.length(), EasyEditSpan.class); 1876d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 1; i < spans.length; i++) { 1877d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne spannable.removeSpan(spans[i]); 1878d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1879d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1880d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1881d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1882d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1883d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1884d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Removes all the {@link EasyEditSpan} currently attached. 1885d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1886d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void removeSpans(CharSequence buffer) { 1887d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (buffer instanceof Spannable) { 1888d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spannable spannable = (Spannable) buffer; 1889d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(), 1890d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne EasyEditSpan.class); 1891d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < spans.length; i++) { 1892d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne spannable.removeSpan(spans[i]); 1893d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1894d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1895d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1896d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1897d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private EasyEditSpan getSpan(Spanned spanned) { 1898d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(), 1899d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne EasyEditSpan.class); 1900d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (easyEditSpans.length == 0) { 1901d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return null; 1902d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 1903d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return easyEditSpans[0]; 1904d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1905d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1906d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1907d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1908d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 1909d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled 1910d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * by {@link EasyEditSpanController}. 1911d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 1912d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class EasyEditPopupWindow extends PinnedPopupWindow 1913d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne implements OnClickListener { 1914d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int POPUP_TEXT_LAYOUT = 1915d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.layout.text_edit_action_popup_text; 1916d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private TextView mDeleteTextView; 1917d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private EasyEditSpan mEasyEditSpan; 1918d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1919d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 1920d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void createPopupWindow() { 1921d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow = new PopupWindow(mTextView.getContext(), null, 1922d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.attr.textSelectHandleWindowStyle); 1923d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 1924d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setClippingEnabled(true); 1925d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1926d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1927d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 1928d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void initContentView() { 1929d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne LinearLayout linearLayout = new LinearLayout(mTextView.getContext()); 1930d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne linearLayout.setOrientation(LinearLayout.HORIZONTAL); 1931d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView = linearLayout; 1932d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView.setBackgroundResource( 1933d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.drawable.text_edit_side_paste_window); 1934d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1935d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne LayoutInflater inflater = (LayoutInflater)mTextView.getContext(). 1936d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getSystemService(Context.LAYOUT_INFLATER_SERVICE); 1937d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1938d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne LayoutParams wrapContent = new LayoutParams( 1939d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 1940d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1941d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); 1942d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDeleteTextView.setLayoutParams(wrapContent); 1943d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDeleteTextView.setText(com.android.internal.R.string.delete); 1944d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDeleteTextView.setOnClickListener(this); 1945d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView.addView(mDeleteTextView); 1946d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1947d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1948d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void show(EasyEditSpan easyEditSpan) { 1949d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEasyEditSpan = easyEditSpan; 1950d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super.show(); 1951d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1952d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1953d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 1954d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onClick(View view) { 1955d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (view == mDeleteTextView) { 1956d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Editable editable = (Editable) mTextView.getText(); 1957d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int start = editable.getSpanStart(mEasyEditSpan); 1958d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int end = editable.getSpanEnd(mEasyEditSpan); 1959d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (start >= 0 && end >= 0) { 1960d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.deleteText_internal(start, end); 1961d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1962d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1963d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1964d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1965d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 1966d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int getTextOffset() { 1967d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Place the pop-up at the end of the span 1968d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Editable editable = (Editable) mTextView.getText(); 1969d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return editable.getSpanEnd(mEasyEditSpan); 1970d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1971d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1972d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 1973d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int getVerticalLocalPosition(int line) { 1974d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.getLayout().getLineBottom(line); 1975d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1976d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1977d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 1978d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int clipVertically(int positionY) { 1979d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // As we display the pop-up below the span, no vertical clipping is required. 1980d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return positionY; 1981d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1982d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 1983d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1984d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class PositionListener implements ViewTreeObserver.OnPreDrawListener { 1985d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // 3 handles 1986d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others) 1987d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final int MAXIMUM_NUMBER_OF_LISTENERS = 6; 1988d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private TextViewPositionListener[] mPositionListeners = 1989d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS]; 1990d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS]; 1991d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mPositionHasChanged = true; 1992d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Absolute position of the TextView with respect to its parent window 1993d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mPositionX, mPositionY; 1994d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mNumberOfListeners; 1995d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mScrollHasChanged; 1996d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int[] mTempCoords = new int[2]; 1997d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 1998d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) { 1999d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mNumberOfListeners == 0) { 2000d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updatePosition(); 2001d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ViewTreeObserver vto = mTextView.getViewTreeObserver(); 2002d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne vto.addOnPreDrawListener(this); 2003d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2004d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2005d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int emptySlotIndex = -1; 2006d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) { 2007d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne TextViewPositionListener listener = mPositionListeners[i]; 2008d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (listener == positionListener) { 2009d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 2010d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else if (emptySlotIndex < 0 && listener == null) { 2011d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne emptySlotIndex = i; 2012d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2013d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2014d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2015d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionListeners[emptySlotIndex] = positionListener; 2016d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCanMove[emptySlotIndex] = canMove; 2017d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mNumberOfListeners++; 2018d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2019d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2020d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void removeSubscriber(TextViewPositionListener positionListener) { 2021d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) { 2022d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mPositionListeners[i] == positionListener) { 2023d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionListeners[i] = null; 2024d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mNumberOfListeners--; 2025d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 2026d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2027d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2028d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2029d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mNumberOfListeners == 0) { 2030d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ViewTreeObserver vto = mTextView.getViewTreeObserver(); 2031d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne vto.removeOnPreDrawListener(this); 2032d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2033d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2034d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2035d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int getPositionX() { 2036d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mPositionX; 2037d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2038d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2039d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int getPositionY() { 2040d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mPositionY; 2041d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2042d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2043d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2044d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean onPreDraw() { 2045d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updatePosition(); 2046d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2047d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) { 2048d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) { 2049d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne TextViewPositionListener positionListener = mPositionListeners[i]; 2050d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (positionListener != null) { 2051d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionListener.updatePosition(mPositionX, mPositionY, 2052d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionHasChanged, mScrollHasChanged); 2053d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2054d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2055d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2056d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2057d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mScrollHasChanged = false; 2058d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 2059d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2060d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2061d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void updatePosition() { 2062d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getLocationInWindow(mTempCoords); 2063d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2064d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY; 2065d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2066d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionX = mTempCoords[0]; 2067d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionY = mTempCoords[1]; 2068d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2069d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2070d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onScrollChanged() { 2071d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mScrollHasChanged = true; 2072d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2073d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2074d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2075d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private abstract class PinnedPopupWindow implements TextViewPositionListener { 2076d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected PopupWindow mPopupWindow; 2077d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected ViewGroup mContentView; 2078d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int mPositionX, mPositionY; 2079d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2080d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected abstract void createPopupWindow(); 2081d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected abstract void initContentView(); 2082d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected abstract int getTextOffset(); 2083d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected abstract int getVerticalLocalPosition(int line); 2084d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected abstract int clipVertically(int positionY); 2085d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2086d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public PinnedPopupWindow() { 2087d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne createPopupWindow(); 2088d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2089d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); 2090d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); 2091d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); 2092d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2093d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne initContentView(); 2094d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2095d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 2096d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ViewGroup.LayoutParams.WRAP_CONTENT); 2097d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView.setLayoutParams(wrapContent); 2098d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2099d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setContentView(mContentView); 2100d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2101d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2102d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void show() { 2103d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getPositionListener().addSubscriber(this, false /* offset is fixed */); 2104d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2105d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne computeLocalPosition(); 2106d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2107d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final PositionListener positionListener = getPositionListener(); 2108d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updatePosition(positionListener.getPositionX(), positionListener.getPositionY()); 2109d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2110d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2111d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void measureContent() { 2112d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final DisplayMetrics displayMetrics = mTextView.getResources().getDisplayMetrics(); 2113d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView.measure( 2114d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, 2115d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne View.MeasureSpec.AT_MOST), 2116d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels, 2117d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne View.MeasureSpec.AT_MOST)); 2118d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2119d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2120d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /* The popup window will be horizontally centered on the getTextOffset() and vertically 2121d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * positioned according to viewportToContentHorizontalOffset. 2122d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 2123d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * This method assumes that mContentView has properly been measured from its content. */ 2124d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void computeLocalPosition() { 2125d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne measureContent(); 2126d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int width = mContentView.getMeasuredWidth(); 2127d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int offset = getTextOffset(); 2128d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionX = (int) (mTextView.getLayout().getPrimaryHorizontal(offset) - width / 2.0f); 2129d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionX += mTextView.viewportToContentHorizontalOffset(); 2130d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2131d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int line = mTextView.getLayout().getLineForOffset(offset); 2132d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionY = getVerticalLocalPosition(line); 2133d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionY += mTextView.viewportToContentVerticalOffset(); 2134d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2135d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2136d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void updatePosition(int parentPositionX, int parentPositionY) { 2137d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int positionX = parentPositionX + mPositionX; 2138d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int positionY = parentPositionY + mPositionY; 2139d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2140d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionY = clipVertically(positionY); 2141d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2142d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Horizontal clipping 2143d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final DisplayMetrics displayMetrics = mTextView.getResources().getDisplayMetrics(); 2144d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int width = mContentView.getMeasuredWidth(); 2145d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionX = Math.min(displayMetrics.widthPixels - width, positionX); 2146d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionX = Math.max(0, positionX); 2147d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2148d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (isShowing()) { 2149d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.update(positionX, positionY, -1, -1); 2150d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 2151d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.showAtLocation(mTextView, Gravity.NO_GRAVITY, 2152d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionX, positionY); 2153d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2154d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2155d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2156d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void hide() { 2157d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.dismiss(); 2158d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getPositionListener().removeSubscriber(this); 2159d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2160d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2161d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2162d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void updatePosition(int parentPositionX, int parentPositionY, 2163d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean parentPositionChanged, boolean parentScrolled) { 2164d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Either parentPositionChanged or parentScrolled is true, check if still visible 2165d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (isShowing() && isOffsetVisible(getTextOffset())) { 2166d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (parentScrolled) computeLocalPosition(); 2167d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updatePosition(parentPositionX, parentPositionY); 2168d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 2169d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hide(); 2170d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2171d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2172d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2173d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean isShowing() { 2174d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mPopupWindow.isShowing(); 2175d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2176d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2177d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2178d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener { 2179d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE; 2180d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int ADD_TO_DICTIONARY = -1; 2181d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int DELETE_TEXT = -2; 2182d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private SuggestionInfo[] mSuggestionInfos; 2183d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mNumberOfSuggestions; 2184d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mCursorWasVisibleBeforeSuggestions; 2185d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mIsShowingUp = false; 2186d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private SuggestionAdapter mSuggestionsAdapter; 2187d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final Comparator<SuggestionSpan> mSuggestionSpanComparator; 2188d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final HashMap<SuggestionSpan, Integer> mSpansLengths; 2189d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2190d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class CustomPopupWindow extends PopupWindow { 2191d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public CustomPopupWindow(Context context, int defStyle) { 2192d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super(context, null, defStyle); 2193d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2194d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2195d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2196d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void dismiss() { 2197d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super.dismiss(); 2198d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2199d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getPositionListener().removeSubscriber(SuggestionsPopupWindow.this); 2200d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2201d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Safe cast since show() checks that mTextView.getText() is an Editable 2202d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ((Spannable) mTextView.getText()).removeSpan(mSuggestionRangeSpan); 2203d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2204d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.setCursorVisible(mCursorWasVisibleBeforeSuggestions); 2205d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (hasInsertionController()) { 2206d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getInsertionController().show(); 2207d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2208d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2209d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2210d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2211d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public SuggestionsPopupWindow() { 2212d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCursorWasVisibleBeforeSuggestions = mCursorVisible; 2213d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionSpanComparator = new SuggestionSpanComparator(); 2214d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSpansLengths = new HashMap<SuggestionSpan, Integer>(); 2215d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2216d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2217d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2218d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void createPopupWindow() { 2219d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow = new CustomPopupWindow(mTextView.getContext(), 2220d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.attr.textSuggestionsWindowStyle); 2221d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 2222d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setFocusable(true); 2223d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setClippingEnabled(false); 2224d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2225d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2226d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2227d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void initContentView() { 2228d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ListView listView = new ListView(mTextView.getContext()); 2229d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionsAdapter = new SuggestionAdapter(); 2230d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne listView.setAdapter(mSuggestionsAdapter); 2231d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne listView.setOnItemClickListener(this); 2232d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView = listView; 2233d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2234d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Inflate the suggestion items once and for all. + 2 for add to dictionary and delete 2235d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionInfos = new SuggestionInfo[MAX_NUMBER_SUGGESTIONS + 2]; 2236d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < mSuggestionInfos.length; i++) { 2237d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionInfos[i] = new SuggestionInfo(); 2238d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2239d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2240d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2241d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean isShowingUp() { 2242d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mIsShowingUp; 2243d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2244d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2245d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onParentLostFocus() { 2246d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mIsShowingUp = false; 2247d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2248d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2249d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class SuggestionInfo { 2250d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int suggestionStart, suggestionEnd; // range of actual suggestion within text 2251d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents 2252d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int suggestionIndex; // the index of this suggestion inside suggestionSpan 2253d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SpannableStringBuilder text = new SpannableStringBuilder(); 2254d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mTextView.getContext(), 2255d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne android.R.style.TextAppearance_SuggestionHighlight); 2256d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2257d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2258d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class SuggestionAdapter extends BaseAdapter { 2259d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private LayoutInflater mInflater = (LayoutInflater) mTextView.getContext(). 2260d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2261d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2262d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2263d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int getCount() { 2264d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mNumberOfSuggestions; 2265d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2266d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2267d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2268d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public Object getItem(int position) { 2269d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mSuggestionInfos[position]; 2270d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2271d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2272d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2273d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public long getItemId(int position) { 2274d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return position; 2275d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2276d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2277d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2278d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public View getView(int position, View convertView, ViewGroup parent) { 2279d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne TextView textView = (TextView) convertView; 2280d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2281d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (textView == null) { 2282d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne textView = (TextView) mInflater.inflate(mTextView.mTextEditSuggestionItemLayout, 2283d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne parent, false); 2284d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2285d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2286d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final SuggestionInfo suggestionInfo = mSuggestionInfos[position]; 2287d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne textView.setText(suggestionInfo.text); 2288d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2289d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) { 2290d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne textView.setCompoundDrawablesWithIntrinsicBounds( 2291d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.drawable.ic_suggestions_add, 0, 0, 0); 2292d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else if (suggestionInfo.suggestionIndex == DELETE_TEXT) { 2293d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne textView.setCompoundDrawablesWithIntrinsicBounds( 2294d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.drawable.ic_suggestions_delete, 0, 0, 0); 2295d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 2296d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne textView.setCompoundDrawables(null, null, null, null); 2297d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2298d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2299d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return textView; 2300d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2301d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2302d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2303d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class SuggestionSpanComparator implements Comparator<SuggestionSpan> { 2304d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int compare(SuggestionSpan span1, SuggestionSpan span2) { 2305d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int flag1 = span1.getFlags(); 2306d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int flag2 = span2.getFlags(); 2307d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (flag1 != flag2) { 2308d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The order here should match what is used in updateDrawState 2309d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean easy1 = (flag1 & SuggestionSpan.FLAG_EASY_CORRECT) != 0; 2310d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean easy2 = (flag2 & SuggestionSpan.FLAG_EASY_CORRECT) != 0; 2311d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean misspelled1 = (flag1 & SuggestionSpan.FLAG_MISSPELLED) != 0; 2312d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean misspelled2 = (flag2 & SuggestionSpan.FLAG_MISSPELLED) != 0; 2313d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (easy1 && !misspelled1) return -1; 2314d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (easy2 && !misspelled2) return 1; 2315d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (misspelled1) return -1; 2316d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (misspelled2) return 1; 2317d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2318d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2319d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mSpansLengths.get(span1).intValue() - mSpansLengths.get(span2).intValue(); 2320d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2321d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2322d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2323d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 2324d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Returns the suggestion spans that cover the current cursor position. The suggestion 2325d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * spans are sorted according to the length of text that they are attached to. 2326d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 2327d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private SuggestionSpan[] getSuggestionSpans() { 2328d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int pos = mTextView.getSelectionStart(); 2329d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spannable spannable = (Spannable) mTextView.getText(); 2330d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class); 2331d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2332d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSpansLengths.clear(); 2333d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (SuggestionSpan suggestionSpan : suggestionSpans) { 2334d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int start = spannable.getSpanStart(suggestionSpan); 2335d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int end = spannable.getSpanEnd(suggestionSpan); 2336d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSpansLengths.put(suggestionSpan, Integer.valueOf(end - start)); 2337d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2338d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2339d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The suggestions are sorted according to their types (easy correction first, then 2340d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // misspelled) and to the length of the text that they cover (shorter first). 2341d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Arrays.sort(suggestionSpans, mSuggestionSpanComparator); 2342d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return suggestionSpans; 2343d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2344d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2345d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2346d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void show() { 2347d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!(mTextView.getText() instanceof Editable)) return; 2348d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2349d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (updateSuggestions()) { 2350d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCursorWasVisibleBeforeSuggestions = mCursorVisible; 2351d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.setCursorVisible(false); 2352d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mIsShowingUp = true; 2353d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super.show(); 2354d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2355d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2356d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2357d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2358d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void measureContent() { 2359d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final DisplayMetrics displayMetrics = mTextView.getResources().getDisplayMetrics(); 2360d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec( 2361d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne displayMetrics.widthPixels, View.MeasureSpec.AT_MOST); 2362d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int verticalMeasure = View.MeasureSpec.makeMeasureSpec( 2363d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne displayMetrics.heightPixels, View.MeasureSpec.AT_MOST); 2364d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2365d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int width = 0; 2366d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne View view = null; 2367d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < mNumberOfSuggestions; i++) { 2368d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne view = mSuggestionsAdapter.getView(i, view, mContentView); 2369d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne view.getLayoutParams().width = LayoutParams.WRAP_CONTENT; 2370d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne view.measure(horizontalMeasure, verticalMeasure); 2371d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne width = Math.max(width, view.getMeasuredWidth()); 2372d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2373d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2374d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Enforce the width based on actual text widths 2375d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView.measure( 2376d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), 2377d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne verticalMeasure); 2378d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2379d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Drawable popupBackground = mPopupWindow.getBackground(); 2380d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (popupBackground != null) { 2381d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTempRect == null) mTempRect = new Rect(); 2382d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne popupBackground.getPadding(mTempRect); 2383d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne width += mTempRect.left + mTempRect.right; 2384d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2385d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setWidth(width); 2386d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2387d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2388d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2389d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int getTextOffset() { 2390d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.getSelectionStart(); 2391d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2392d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2393d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2394d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int getVerticalLocalPosition(int line) { 2395d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.getLayout().getLineBottom(line); 2396d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2397d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2398d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2399d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int clipVertically(int positionY) { 2400d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int height = mContentView.getMeasuredHeight(); 2401d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final DisplayMetrics displayMetrics = mTextView.getResources().getDisplayMetrics(); 2402d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return Math.min(positionY, displayMetrics.heightPixels - height); 2403d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2404d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2405d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2406d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void hide() { 2407d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super.hide(); 2408d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2409d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2410d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean updateSuggestions() { 2411d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spannable spannable = (Spannable) mTextView.getText(); 2412d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan[] suggestionSpans = getSuggestionSpans(); 2413d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2414d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int nbSpans = suggestionSpans.length; 2415d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Suggestions are shown after a delay: the underlying spans may have been removed 2416d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (nbSpans == 0) return false; 2417d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2418d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mNumberOfSuggestions = 0; 2419d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int spanUnionStart = mTextView.getText().length(); 2420d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int spanUnionEnd = 0; 2421d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2422d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan misspelledSpan = null; 2423d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int underlineColor = 0; 2424d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2425d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) { 2426d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan suggestionSpan = suggestionSpans[spanIndex]; 2427d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int spanStart = spannable.getSpanStart(suggestionSpan); 2428d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int spanEnd = spannable.getSpanEnd(suggestionSpan); 2429d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne spanUnionStart = Math.min(spanStart, spanUnionStart); 2430d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne spanUnionEnd = Math.max(spanEnd, spanUnionEnd); 2431d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2432d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if ((suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) { 2433d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne misspelledSpan = suggestionSpan; 2434d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2435d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2436d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The first span dictates the background color of the highlighted text 2437d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (spanIndex == 0) underlineColor = suggestionSpan.getUnderlineColor(); 2438d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2439d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne String[] suggestions = suggestionSpan.getSuggestions(); 2440d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int nbSuggestions = suggestions.length; 2441d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) { 2442d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne String suggestion = suggestions[suggestionIndex]; 2443d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2444d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean suggestionIsDuplicate = false; 2445d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < mNumberOfSuggestions; i++) { 2446d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSuggestionInfos[i].text.toString().equals(suggestion)) { 2447d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan otherSuggestionSpan = mSuggestionInfos[i].suggestionSpan; 2448d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan); 2449d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan); 2450d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) { 2451d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionIsDuplicate = true; 2452d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 2453d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2454d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2455d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2456d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2457d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!suggestionIsDuplicate) { 2458d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; 2459d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionSpan = suggestionSpan; 2460d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionIndex = suggestionIndex; 2461d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.text.replace(0, suggestionInfo.text.length(), suggestion); 2462d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2463d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mNumberOfSuggestions++; 2464d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2465d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) { 2466d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Also end outer for loop 2467d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne spanIndex = nbSpans; 2468d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 2469d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2470d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2471d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2472d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2473d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2474d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < mNumberOfSuggestions; i++) { 2475d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd); 2476d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2477d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2478d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Add "Add to dictionary" item if there is a span with the misspelled flag 2479d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (misspelledSpan != null) { 2480d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int misspelledStart = spannable.getSpanStart(misspelledSpan); 2481d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int misspelledEnd = spannable.getSpanEnd(misspelledSpan); 2482d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (misspelledStart >= 0 && misspelledEnd > misspelledStart) { 2483d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; 2484d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionSpan = misspelledSpan; 2485d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionIndex = ADD_TO_DICTIONARY; 2486d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.text.replace(0, suggestionInfo.text.length(), mTextView. 2487d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getContext().getString(com.android.internal.R.string.addToDictionary)); 2488d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0, 2489d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 2490d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2491d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mNumberOfSuggestions++; 2492d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2493d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2494d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2495d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Delete item 2496d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; 2497d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionSpan = null; 2498d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionIndex = DELETE_TEXT; 2499d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.text.replace(0, suggestionInfo.text.length(), 2500d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getContext().getString(com.android.internal.R.string.deleteText)); 2501d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0, 2502d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 2503d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mNumberOfSuggestions++; 2504d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2505d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan(); 2506d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (underlineColor == 0) { 2507d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Fallback on the default highlight color when the first span does not provide one 2508d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionRangeSpan.setBackgroundColor(mTextView.mHighlightColor); 2509d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 2510d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float BACKGROUND_TRANSPARENCY = 0.4f; 2511d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY); 2512d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionRangeSpan.setBackgroundColor( 2513d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (underlineColor & 0x00FFFFFF) + (newAlpha << 24)); 2514d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2515d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne spannable.setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd, 2516d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 2517d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2518d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSuggestionsAdapter.notifyDataSetChanged(); 2519d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 2520d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2521d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2522d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart, 2523d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int unionEnd) { 2524d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final Spannable text = (Spannable) mTextView.getText(); 2525d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int spanStart = text.getSpanStart(suggestionInfo.suggestionSpan); 2526d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int spanEnd = text.getSpanEnd(suggestionInfo.suggestionSpan); 2527d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2528d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Adjust the start/end of the suggestion span 2529d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionStart = spanStart - unionStart; 2530d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart 2531d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne + suggestionInfo.text.length(); 2532d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2533d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 2534d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 2535d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2536d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Add the text before and after the span. 2537d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final String textAsString = text.toString(); 2538d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.text.insert(0, textAsString.substring(unionStart, spanStart)); 2539d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.text.append(textAsString.substring(spanEnd, unionEnd)); 2540d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2541d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2542d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2543d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 2544d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Editable editable = (Editable) mTextView.getText(); 2545d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionInfo suggestionInfo = mSuggestionInfos[position]; 2546d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2547d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (suggestionInfo.suggestionIndex == DELETE_TEXT) { 2548d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int spanUnionStart = editable.getSpanStart(mSuggestionRangeSpan); 2549d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int spanUnionEnd = editable.getSpanEnd(mSuggestionRangeSpan); 2550d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (spanUnionStart >= 0 && spanUnionEnd > spanUnionStart) { 2551d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Do not leave two adjacent spaces after deletion, or one at beginning of text 2552d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (spanUnionEnd < editable.length() && 2553d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Character.isSpaceChar(editable.charAt(spanUnionEnd)) && 2554d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (spanUnionStart == 0 || 2555d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Character.isSpaceChar(editable.charAt(spanUnionStart - 1)))) { 2556d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne spanUnionEnd = spanUnionEnd + 1; 2557d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2558d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.deleteText_internal(spanUnionStart, spanUnionEnd); 2559d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2560d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hide(); 2561d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 2562d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2563d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2564d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int spanStart = editable.getSpanStart(suggestionInfo.suggestionSpan); 2565d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan); 2566d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (spanStart < 0 || spanEnd <= spanStart) { 2567d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Span has been removed 2568d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hide(); 2569d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 2570d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2571d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2572d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final String originalText = editable.toString().substring(spanStart, spanEnd); 2573d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2574d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) { 2575d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT); 2576d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne intent.putExtra("word", originalText); 2577d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne intent.putExtra("locale", mTextView.getTextServicesLocale().toString()); 2578d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); 2579d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getContext().startActivity(intent); 2580d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // There is no way to know if the word was indeed added. Re-check. 2581d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // TODO The ExtractEditText should remove the span in the original text instead 2582d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne editable.removeSpan(suggestionInfo.suggestionSpan); 25832eb70fb257623de7d32e8c1a878f4c03b71846d1Gilles Debunne Selection.setSelection(editable, spanEnd); 2584d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateSpellCheckSpans(spanStart, spanEnd, false); 2585d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 2586d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // SuggestionSpans are removed by replace: save them before 2587d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd, 2588d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SuggestionSpan.class); 2589d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int length = suggestionSpans.length; 2590d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int[] suggestionSpansStarts = new int[length]; 2591d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int[] suggestionSpansEnds = new int[length]; 2592d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int[] suggestionSpansFlags = new int[length]; 2593d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < length; i++) { 2594d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final SuggestionSpan suggestionSpan = suggestionSpans[i]; 2595d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan); 2596d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan); 2597d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan); 2598d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2599d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Remove potential misspelled flags 2600d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int suggestionSpanFlags = suggestionSpan.getFlags(); 2601d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) { 2602d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED; 2603d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT; 2604d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionSpan.setFlags(suggestionSpanFlags); 2605d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2606d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2607d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2608d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int suggestionStart = suggestionInfo.suggestionStart; 2609d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int suggestionEnd = suggestionInfo.suggestionEnd; 2610d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final String suggestion = suggestionInfo.text.subSequence( 2611d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionStart, suggestionEnd).toString(); 2612d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.replaceText_internal(spanStart, spanEnd, suggestion); 2613d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2614d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Notify source IME of the suggestion pick. Do this before swaping texts. 2615d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!TextUtils.isEmpty( 2616d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionSpan.getNotificationTargetClassName())) { 2617d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne InputMethodManager imm = InputMethodManager.peekInstance(); 2618d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (imm != null) { 2619d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText, 2620d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionInfo.suggestionIndex); 2621d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2622d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2623d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2624d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Swap text content between actual text and Suggestion span 2625d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions(); 2626d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestions[suggestionInfo.suggestionIndex] = originalText; 2627d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2628d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Restore previous SuggestionSpans 2629d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int lengthDifference = suggestion.length() - (spanEnd - spanStart); 2630d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int i = 0; i < length; i++) { 2631d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Only spans that include the modified region make sense after replacement 2632d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Spans partially included in the replaced region are removed, there is no 2633d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // way to assign them a valid range after replacement 2634d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (suggestionSpansStarts[i] <= spanStart && 2635d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionSpansEnds[i] >= spanEnd) { 2636d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.setSpan_internal(suggestionSpans[i], suggestionSpansStarts[i], 2637d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne suggestionSpansEnds[i] + lengthDifference, suggestionSpansFlags[i]); 2638d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2639d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2640d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2641d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Move cursor at the end of the replaced word 2642d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int newCursorPosition = spanEnd + lengthDifference; 2643d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.setCursorPosition_internal(newCursorPosition, newCursorPosition); 2644d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2645d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2646d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hide(); 2647d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2648d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2649d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2650d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 2651d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * An ActionMode Callback class that is used to provide actions while in text selection mode. 2652d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * 2653d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending 2654d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * on which of these this TextView supports. 2655d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 2656d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class SelectionActionModeCallback implements ActionMode.Callback { 2657d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2658d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2659d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean onCreateActionMode(ActionMode mode, Menu menu) { 2660d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne TypedArray styledAttributes = mTextView.getContext().obtainStyledAttributes( 2661d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.styleable.SelectionModeDrawables); 2662d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2663d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean allowText = mTextView.getContext().getResources().getBoolean( 2664d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); 2665d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2666d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mode.setTitle(mTextView.getContext().getString( 2667d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.string.textSelectionCABTitle)); 2668d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mode.setSubtitle(null); 2669d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mode.setTitleOptionalHint(true); 2670d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2671d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int selectAllIconId = 0; // No icon by default 2672d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!allowText) { 2673d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Provide an icon, text will not be displayed on smaller screens. 2674d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne selectAllIconId = styledAttributes.getResourceId( 2675d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0); 2676d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2677d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2678d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). 2679d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setIcon(selectAllIconId). 2680d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setAlphabeticShortcut('a'). 2681d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setShowAsAction( 2682d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); 2683d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2684d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.canCut()) { 2685d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut). 2686d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setIcon(styledAttributes.getResourceId( 2687d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)). 2688d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setAlphabeticShortcut('x'). 2689d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setShowAsAction( 2690d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); 2691d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2692d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2693d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.canCopy()) { 2694d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne menu.add(0, TextView.ID_COPY, 0, com.android.internal.R.string.copy). 2695d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setIcon(styledAttributes.getResourceId( 2696d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)). 2697d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setAlphabeticShortcut('c'). 2698d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setShowAsAction( 2699d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); 2700d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2701d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2702d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.canPaste()) { 2703d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne menu.add(0, TextView.ID_PASTE, 0, com.android.internal.R.string.paste). 2704d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setIcon(styledAttributes.getResourceId( 2705d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)). 2706d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setAlphabeticShortcut('v'). 2707d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setShowAsAction( 2708d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); 2709d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2710d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2711d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne styledAttributes.recycle(); 2712d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2713d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCustomSelectionActionModeCallback != null) { 2714d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) { 2715d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The custom mode can choose to cancel the action mode 2716d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 2717d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2718d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2719d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2720d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (menu.hasVisibleItems() || mode.getCustomView() != null) { 2721d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getSelectionController().show(); 2722d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 2723d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 2724d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 2725d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2726d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2727d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2728d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2729d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 2730d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCustomSelectionActionModeCallback != null) { 2731d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu); 2732d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2733d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 2734d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2735d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2736d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2737d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 2738d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCustomSelectionActionModeCallback != null && 2739d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) { 2740d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 2741d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2742d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.onTextContextMenuItem(item.getItemId()); 2743d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2744d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2745d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2746d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onDestroyActionMode(ActionMode mode) { 2747d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mCustomSelectionActionModeCallback != null) { 2748d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mCustomSelectionActionModeCallback.onDestroyActionMode(mode); 2749d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2750d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), mTextView.getSelectionEnd()); 2751d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2752d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectionModifierCursorController != null) { 2753d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionModifierCursorController.hide(); 2754d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2755d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2756d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectionActionMode = null; 2757d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2758d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2759d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2760d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener { 2761d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int POPUP_TEXT_LAYOUT = 2762d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.layout.text_edit_action_popup_text; 2763d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private TextView mPasteTextView; 2764d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private TextView mReplaceTextView; 2765d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2766d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2767d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void createPopupWindow() { 2768d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow = new PopupWindow(mTextView.getContext(), null, 2769d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.attr.textSelectHandleWindowStyle); 2770d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupWindow.setClippingEnabled(true); 2771d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2772d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2773d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2774d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void initContentView() { 2775d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne LinearLayout linearLayout = new LinearLayout(mTextView.getContext()); 2776d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne linearLayout.setOrientation(LinearLayout.HORIZONTAL); 2777d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView = linearLayout; 2778d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView.setBackgroundResource( 2779d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.drawable.text_edit_paste_window); 2780d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2781d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne LayoutInflater inflater = (LayoutInflater) mTextView.getContext(). 2782d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2783d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2784d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne LayoutParams wrapContent = new LayoutParams( 2785d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 2786d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2787d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); 2788d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPasteTextView.setLayoutParams(wrapContent); 2789d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView.addView(mPasteTextView); 2790d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPasteTextView.setText(com.android.internal.R.string.paste); 2791d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPasteTextView.setOnClickListener(this); 2792d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2793d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); 2794d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mReplaceTextView.setLayoutParams(wrapContent); 2795d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContentView.addView(mReplaceTextView); 2796d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mReplaceTextView.setText(com.android.internal.R.string.replace); 2797d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mReplaceTextView.setOnClickListener(this); 2798d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2799d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2800d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2801d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void show() { 2802d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean canPaste = mTextView.canPaste(); 2803d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean canSuggest = mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan(); 2804d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE); 2805d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE); 2806d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2807d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!canPaste && !canSuggest) return; 2808d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2809d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super.show(); 2810d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2811d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2812d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2813d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onClick(View view) { 2814d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (view == mPasteTextView && mTextView.canPaste()) { 2815d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.onTextContextMenuItem(TextView.ID_PASTE); 2816d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hide(); 2817d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else if (view == mReplaceTextView) { 2818d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2; 2819d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne stopSelectionActionMode(); 2820d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), middle); 2821d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne showSuggestions(); 2822d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2823d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2824d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2825d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2826d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int getTextOffset() { 2827d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2; 2828d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2829d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2830d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2831d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int getVerticalLocalPosition(int line) { 2832d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.getLayout().getLineTop(line) - mContentView.getMeasuredHeight(); 2833d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2834d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2835d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2836d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int clipVertically(int positionY) { 2837d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (positionY < 0) { 2838d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int offset = getTextOffset(); 2839d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final Layout layout = mTextView.getLayout(); 2840d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int line = layout.getLineForOffset(offset); 2841d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionY += layout.getLineBottom(line) - layout.getLineTop(line); 2842d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionY += mContentView.getMeasuredHeight(); 2843d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2844d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Assumes insertion and selection handles share the same height 2845d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final Drawable handle = mTextView.getResources().getDrawable( 2846d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.mTextSelectHandleRes); 2847d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionY += handle.getIntrinsicHeight(); 2848d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2849d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2850d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return positionY; 2851d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2852d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2853d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2854d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private abstract class HandleView extends View implements TextViewPositionListener { 2855d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected Drawable mDrawable; 2856d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected Drawable mDrawableLtr; 2857d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected Drawable mDrawableRtl; 2858d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final PopupWindow mContainer; 2859d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Position with respect to the parent TextView 2860d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mPositionX, mPositionY; 2861d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mIsDragging; 2862d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Offset from touch position to mPosition 2863d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private float mTouchToWindowOffsetX, mTouchToWindowOffsetY; 2864d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int mHotspotX; 2865d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up 2866d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private float mTouchOffsetY; 2867d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Where the touch position should be on the handle to ensure a maximum cursor visibility 2868d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private float mIdealVerticalOffset; 2869d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Parent's (TextView) previous position in window 2870d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mLastParentX, mLastParentY; 2871d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Transient action popup window for Paste and Replace actions 2872d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected ActionPopupWindow mActionPopupWindow; 2873d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Previous text character offset 2874d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mPreviousOffset = -1; 2875d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Previous text character offset 2876d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mPositionHasChanged = true; 2877d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Used to delay the appearance of the action popup window 2878d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private Runnable mActionPopupShower; 2879d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2880d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public HandleView(Drawable drawableLtr, Drawable drawableRtl) { 2881d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super(mTextView.getContext()); 2882d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContainer = new PopupWindow(mTextView.getContext(), null, 2883d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.attr.textSelectHandleWindowStyle); 2884d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContainer.setSplitTouchEnabled(true); 2885d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContainer.setClippingEnabled(false); 2886d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); 2887d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContainer.setContentView(this); 2888d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2889d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDrawableLtr = drawableLtr; 2890d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDrawableRtl = drawableRtl; 2891d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2892d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateDrawable(); 2893d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2894d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int handleHeight = mDrawable.getIntrinsicHeight(); 2895d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTouchOffsetY = -0.3f * handleHeight; 2896d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mIdealVerticalOffset = 0.7f * handleHeight; 2897d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2898d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2899d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void updateDrawable() { 2900d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int offset = getCurrentCursorOffset(); 2901d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean isRtlCharAtOffset = mTextView.getLayout().isRtlCharAt(offset); 2902d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr; 2903d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset); 2904d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2905d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2906d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun); 2907d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2908d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Touch-up filter: number of previous positions remembered 2909d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int HISTORY_SIZE = 5; 2910d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150; 2911d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350; 2912d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE]; 2913d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final int[] mPreviousOffsets = new int[HISTORY_SIZE]; 2914d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mPreviousOffsetIndex = 0; 2915d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mNumberPreviousOffsets = 0; 2916d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2917d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void startTouchUpFilter(int offset) { 2918d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mNumberPreviousOffsets = 0; 2919d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne addPositionToTouchUpFilter(offset); 2920d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2921d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2922d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void addPositionToTouchUpFilter(int offset) { 2923d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE; 2924d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPreviousOffsets[mPreviousOffsetIndex] = offset; 2925d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis(); 2926d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mNumberPreviousOffsets++; 2927d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2928d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2929d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void filterOnTouchUp() { 2930d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final long now = SystemClock.uptimeMillis(); 2931d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int i = 0; 2932d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int index = mPreviousOffsetIndex; 2933d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE); 2934d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) { 2935d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne i++; 2936d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE; 2937d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2938d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2939d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (i > 0 && i < iMax && 2940d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) { 2941d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionAtCursorOffset(mPreviousOffsets[index], false); 2942d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2943d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2944d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2945d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean offsetHasBeenChanged() { 2946d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mNumberPreviousOffsets > 1; 2947d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2948d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2949d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 2950d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 2951d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); 2952d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2953d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2954d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void show() { 2955d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (isShowing()) return; 2956d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2957d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getPositionListener().addSubscriber(this, true /* local position may change */); 2958d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2959d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Make sure the offset is always considered new, even when focusing at same position 2960d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPreviousOffset = -1; 2961d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionAtCursorOffset(getCurrentCursorOffset(), false); 2962d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2963d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideActionPopupWindow(); 2964d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2965d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2966d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void dismiss() { 2967d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mIsDragging = false; 2968d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContainer.dismiss(); 2969d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne onDetached(); 2970d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2971d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2972d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void hide() { 2973d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne dismiss(); 2974d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2975d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getPositionListener().removeSubscriber(this); 2976d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2977d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2978d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void showActionPopupWindow(int delay) { 2979d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mActionPopupWindow == null) { 2980d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mActionPopupWindow = new ActionPopupWindow(); 2981d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2982d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mActionPopupShower == null) { 2983d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mActionPopupShower = new Runnable() { 2984d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void run() { 2985d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mActionPopupWindow.show(); 2986d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2987d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne }; 2988d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 2989d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.removeCallbacks(mActionPopupShower); 2990d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2991d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.postDelayed(mActionPopupShower, delay); 2992d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2993d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 2994d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void hideActionPopupWindow() { 2995d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mActionPopupShower != null) { 2996d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.removeCallbacks(mActionPopupShower); 2997d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 2998d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mActionPopupWindow != null) { 2999d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mActionPopupWindow.hide(); 3000d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3001d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3002d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3003d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean isShowing() { 3004d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mContainer.isShowing(); 3005d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3006d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3007d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean isVisible() { 3008d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Always show a dragging handle. 3009d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mIsDragging) { 3010d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 3011d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3012d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3013d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.isInBatchEditMode()) { 3014d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return false; 3015d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3016d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3017d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return isPositionVisible(mPositionX + mHotspotX, mPositionY); 3018d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3019d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3020d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public abstract int getCurrentCursorOffset(); 3021d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3022d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected abstract void updateSelection(int offset); 3023d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3024d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public abstract void updatePosition(float x, float y); 3025d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3026d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void positionAtCursorOffset(int offset, boolean parentScrolled) { 3027d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // A HandleView relies on the layout, which may be nulled by external methods 3028d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Layout layout = mTextView.getLayout(); 3029d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (layout == null) { 3030d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Will update controllers' state, hiding them and stopping selection mode if needed 3031d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne prepareCursorControllers(); 3032d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 3033d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3034d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3035d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean offsetChanged = offset != mPreviousOffset; 3036d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offsetChanged || parentScrolled) { 3037d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offsetChanged) { 3038d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateSelection(offset); 3039d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne addPositionToTouchUpFilter(offset); 3040d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3041d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int line = layout.getLineForOffset(offset); 3042d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3043d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX); 3044d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionY = layout.getLineBottom(line); 3045d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3046d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Take TextView's padding and scroll into account. 3047d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionX += mTextView.viewportToContentHorizontalOffset(); 3048d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionY += mTextView.viewportToContentVerticalOffset(); 3049d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3050d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPreviousOffset = offset; 3051d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionHasChanged = true; 3052d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3053d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3054d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3055d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void updatePosition(int parentPositionX, int parentPositionY, 3056d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean parentPositionChanged, boolean parentScrolled) { 3057d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled); 3058d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (parentPositionChanged || mPositionHasChanged) { 3059d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mIsDragging) { 3060d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Update touchToWindow offset in case of parent scrolling while dragging 3061d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) { 3062d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTouchToWindowOffsetX += parentPositionX - mLastParentX; 3063d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTouchToWindowOffsetY += parentPositionY - mLastParentY; 3064d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mLastParentX = parentPositionX; 3065d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mLastParentY = parentPositionY; 3066d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3067d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3068d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne onHandleMoved(); 3069d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3070d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3071d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (isVisible()) { 3072d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int positionX = parentPositionX + mPositionX; 3073d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int positionY = parentPositionY + mPositionY; 3074d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (isShowing()) { 3075d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContainer.update(positionX, positionY, -1, -1); 3076d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3077d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mContainer.showAtLocation(mTextView, Gravity.NO_GRAVITY, 3078d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionX, positionY); 3079d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3080d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3081d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (isShowing()) { 3082d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne dismiss(); 3083d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3084d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3085d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3086d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPositionHasChanged = false; 3087d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3088d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3089d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3090d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3091d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected void onDraw(Canvas c) { 3092d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop); 3093d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDrawable.draw(c); 3094d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3095d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3096d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3097d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean onTouchEvent(MotionEvent ev) { 3098d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne switch (ev.getActionMasked()) { 3099d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_DOWN: { 3100d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne startTouchUpFilter(getCurrentCursorOffset()); 3101d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTouchToWindowOffsetX = ev.getRawX() - mPositionX; 3102d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTouchToWindowOffsetY = ev.getRawY() - mPositionY; 3103d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3104d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final PositionListener positionListener = getPositionListener(); 3105d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mLastParentX = positionListener.getPositionX(); 3106d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mLastParentY = positionListener.getPositionY(); 3107d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mIsDragging = true; 3108d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3109d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3110d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3111d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_MOVE: { 3112d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float rawX = ev.getRawX(); 3113d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float rawY = ev.getRawY(); 3114d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3115d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Vertical hysteresis: vertical down movement tends to snap to ideal offset 3116d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY; 3117d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float currentVerticalOffset = rawY - mPositionY - mLastParentY; 3118d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne float newVerticalOffset; 3119d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (previousVerticalOffset < mIdealVerticalOffset) { 3120d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset); 3121d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset); 3122d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3123d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset); 3124d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset); 3125d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3126d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTouchToWindowOffsetY = newVerticalOffset + mLastParentY; 3127d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3128d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX; 3129d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY; 3130d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3131d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updatePosition(newPosX, newPosY); 3132d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3133d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3134d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3135d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_UP: 3136d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne filterOnTouchUp(); 3137d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mIsDragging = false; 3138d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3139d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3140d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_CANCEL: 3141d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mIsDragging = false; 3142d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3143d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3144d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 3145d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3146d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3147d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean isDragging() { 3148d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mIsDragging; 3149d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3150d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3151d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onHandleMoved() { 3152d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideActionPopupWindow(); 3153d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3154d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3155d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onDetached() { 3156d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideActionPopupWindow(); 3157d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3158d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3159d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3160d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class InsertionHandleView extends HandleView { 3161d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000; 3162d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds 3163d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3164d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow 3165d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private float mDownPositionX, mDownPositionY; 3166d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private Runnable mHider; 3167d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3168d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public InsertionHandleView(Drawable drawable) { 3169d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super(drawable, drawable); 3170d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3171d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3172d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3173d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void show() { 3174d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super.show(); 3175d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3176d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final long durationSinceCutOrCopy = 3177d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SystemClock.uptimeMillis() - TextView.LAST_CUT_OR_COPY_TIME; 3178d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) { 3179d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne showActionPopupWindow(0); 3180d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3181d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3182d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideAfterDelay(); 3183d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3184d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3185d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void showWithActionPopup() { 3186d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne show(); 3187d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne showActionPopupWindow(0); 3188d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3189d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3190d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void hideAfterDelay() { 3191d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mHider == null) { 3192d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mHider = new Runnable() { 3193d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void run() { 3194d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hide(); 3195d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3196d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne }; 3197d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3198d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne removeHiderCallback(); 3199d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3200d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT); 3201d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3202d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3203d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void removeHiderCallback() { 3204d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mHider != null) { 3205d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.removeCallbacks(mHider); 3206d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3207d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3208d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3209d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3210d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int getHotspotX(Drawable drawable, boolean isRtlRun) { 3211d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return drawable.getIntrinsicWidth() / 2; 3212d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3213d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3214d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3215d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean onTouchEvent(MotionEvent ev) { 3216d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final boolean result = super.onTouchEvent(ev); 3217d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3218d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne switch (ev.getActionMasked()) { 3219d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_DOWN: 3220d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDownPositionX = ev.getRawX(); 3221d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDownPositionY = ev.getRawY(); 3222d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3223d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3224d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_UP: 3225d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!offsetHasBeenChanged()) { 3226d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float deltaX = mDownPositionX - ev.getRawX(); 3227d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float deltaY = mDownPositionY - ev.getRawY(); 3228d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float distanceSquared = deltaX * deltaX + deltaY * deltaY; 3229d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3230d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ViewConfiguration viewConfiguration = ViewConfiguration.get( 3231d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getContext()); 3232d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int touchSlop = viewConfiguration.getScaledTouchSlop(); 3233d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3234d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (distanceSquared < touchSlop * touchSlop) { 3235d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) { 3236d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Tapping on the handle dismisses the displayed action popup 3237d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mActionPopupWindow.hide(); 3238d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3239d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne showWithActionPopup(); 3240d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3241d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3242d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3243d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideAfterDelay(); 3244d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3245d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3246d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_CANCEL: 3247d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideAfterDelay(); 3248d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3249d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3250d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne default: 3251d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3252d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3253d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3254d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return result; 3255d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3256d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3257d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3258d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int getCurrentCursorOffset() { 3259d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.getSelectionStart(); 3260d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3261d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3262d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3263d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void updateSelection(int offset) { 3264d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), offset); 3265d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3266d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3267d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3268d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void updatePosition(float x, float y) { 3269d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false); 3270d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3271d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3272d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3273d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void onHandleMoved() { 3274d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super.onHandleMoved(); 3275d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne removeHiderCallback(); 3276d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3277d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3278d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3279d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onDetached() { 3280d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super.onDetached(); 3281d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne removeHiderCallback(); 3282d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3283d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3284d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3285d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class SelectionStartHandleView extends HandleView { 3286d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3287d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) { 3288d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super(drawableLtr, drawableRtl); 3289d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3290d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3291d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3292d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int getHotspotX(Drawable drawable, boolean isRtlRun) { 3293d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (isRtlRun) { 3294d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return drawable.getIntrinsicWidth() / 4; 3295d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3296d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return (drawable.getIntrinsicWidth() * 3) / 4; 3297d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3298d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3299d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3300d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3301d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int getCurrentCursorOffset() { 3302d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.getSelectionStart(); 3303d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3304d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3305d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3306d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void updateSelection(int offset) { 3307d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), offset, 3308d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getSelectionEnd()); 3309d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateDrawable(); 3310d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3311d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3312d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3313d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void updatePosition(float x, float y) { 3314d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int offset = mTextView.getOffsetForPosition(x, y); 3315d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3316d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Handles can not cross and selection is at least one character 3317d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int selectionEnd = mTextView.getSelectionEnd(); 3318d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1); 3319d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3320d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionAtCursorOffset(offset, false); 3321d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3322d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3323d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public ActionPopupWindow getActionPopupWindow() { 3324d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mActionPopupWindow; 3325d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3326d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3327d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3328d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class SelectionEndHandleView extends HandleView { 3329d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3330d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) { 3331d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super(drawableLtr, drawableRtl); 3332d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3333d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3334d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3335d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne protected int getHotspotX(Drawable drawable, boolean isRtlRun) { 3336d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (isRtlRun) { 3337d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return (drawable.getIntrinsicWidth() * 3) / 4; 3338d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3339d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return drawable.getIntrinsicWidth() / 4; 3340d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3341d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3342d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3343d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3344d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int getCurrentCursorOffset() { 3345d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mTextView.getSelectionEnd(); 3346d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3347d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3348d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3349d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void updateSelection(int offset) { 3350d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Selection.setSelection((Spannable) mTextView.getText(), 3351d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getSelectionStart(), offset); 3352d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateDrawable(); 3353d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3354d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3355d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3356d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void updatePosition(float x, float y) { 3357d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int offset = mTextView.getOffsetForPosition(x, y); 3358d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3359d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Handles can not cross and selection is at least one character 3360d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int selectionStart = mTextView.getSelectionStart(); 3361d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offset <= selectionStart) { 3362d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne offset = Math.min(selectionStart + 1, mTextView.getText().length()); 3363d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3364d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3365d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne positionAtCursorOffset(offset, false); 3366d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3367d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3368d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) { 3369d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mActionPopupWindow = actionPopupWindow; 3370d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3371d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3372d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3373d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 3374d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * A CursorController instance can be used to control a cursor in the text. 3375d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 3376d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener { 3377d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 3378d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Makes the cursor controller visible on screen. 3379d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * See also {@link #hide()}. 3380d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 3381d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void show(); 3382d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3383d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 3384d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Hide the cursor controller from screen. 3385d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * See also {@link #show()}. 3386d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 3387d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void hide(); 3388d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3389d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 3390d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * Called when the view is detached from window. Perform house keeping task, such as 3391d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * stopping Runnable thread that would otherwise keep a reference on the context, thus 3392d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * preventing the activity from being recycled. 3393d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 3394d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onDetached(); 3395d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3396d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3397d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class InsertionPointCursorController implements CursorController { 3398d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private InsertionHandleView mHandle; 3399d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3400d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void show() { 3401d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getHandle().show(); 3402d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3403d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3404d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void showWithActionPopup() { 3405d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getHandle().showWithActionPopup(); 3406d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3407d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3408d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void hide() { 3409d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mHandle != null) { 3410d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mHandle.hide(); 3411d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3412d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3413d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3414d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onTouchModeChanged(boolean isInTouchMode) { 3415d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!isInTouchMode) { 3416d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hide(); 3417d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3418d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3419d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3420d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private InsertionHandleView getHandle() { 3421d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectHandleCenter == null) { 3422d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectHandleCenter = mTextView.getResources().getDrawable( 3423d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.mTextSelectHandleRes); 3424d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3425d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mHandle == null) { 3426d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mHandle = new InsertionHandleView(mSelectHandleCenter); 3427d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3428d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mHandle; 3429d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3430d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3431d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3432d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onDetached() { 3433d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ViewTreeObserver observer = mTextView.getViewTreeObserver(); 3434d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne observer.removeOnTouchModeChangeListener(this); 3435d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3436d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mHandle != null) mHandle.onDetached(); 3437d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3438d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3439d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3440d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne class SelectionModifierCursorController implements CursorController { 3441d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds 3442d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The cursor controller handles, lazily created when shown. 3443d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private SelectionStartHandleView mStartHandle; 3444d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private SelectionEndHandleView mEndHandle; 3445d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // The offsets of that last touch down event. Remembered to start selection there. 3446d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mMinTouchOffset, mMaxTouchOffset; 3447d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3448d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Double tap detection 3449d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private long mPreviousTapUpTime = 0; 3450d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private float mDownPositionX, mDownPositionY; 3451d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mGestureStayedInTapRegion; 3452d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3453d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne SelectionModifierCursorController() { 3454d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne resetTouchOffsets(); 3455d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3456d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3457d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void show() { 3458d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.isInBatchEditMode()) { 3459d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return; 3460d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3461d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne initDrawables(); 3462d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne initHandles(); 3463d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideInsertionPointCursorController(); 3464d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3465d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3466d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void initDrawables() { 3467d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectHandleLeft == null) { 3468d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectHandleLeft = mTextView.getContext().getResources().getDrawable( 3469d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.mTextSelectHandleLeftRes); 3470d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3471d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mSelectHandleRight == null) { 3472d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mSelectHandleRight = mTextView.getContext().getResources().getDrawable( 3473d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.mTextSelectHandleRightRes); 3474d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3475d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3476d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3477d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void initHandles() { 3478d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Lazy object creation has to be done before updatePosition() is called. 3479d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mStartHandle == null) { 3480d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mStartHandle = new SelectionStartHandleView(mSelectHandleLeft, mSelectHandleRight); 3481d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3482d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mEndHandle == null) { 3483d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEndHandle = new SelectionEndHandleView(mSelectHandleRight, mSelectHandleLeft); 3484d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3485d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3486d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mStartHandle.show(); 3487d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEndHandle.show(); 3488d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3489d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Make sure both left and right handles share the same ActionPopupWindow (so that 3490d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // moving any of the handles hides the action popup). 3491d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION); 3492d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow()); 3493d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3494d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hideInsertionPointCursorController(); 3495d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3496d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3497d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void hide() { 3498d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mStartHandle != null) mStartHandle.hide(); 3499d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mEndHandle != null) mEndHandle.hide(); 3500d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3501d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3502d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onTouchEvent(MotionEvent event) { 3503d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // This is done even when the View does not have focus, so that long presses can start 3504d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // selection and tap can move cursor from this tap position. 3505d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne switch (event.getActionMasked()) { 3506d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_DOWN: 3507d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float x = event.getX(); 3508d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float y = event.getY(); 3509d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3510d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Remember finger down position, to be able to start selection from there 3511d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mMinTouchOffset = mMaxTouchOffset = mTextView.getOffsetForPosition(x, y); 3512d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3513d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Double tap detection 3514d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mGestureStayedInTapRegion) { 3515d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime; 3516d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (duration <= ViewConfiguration.getDoubleTapTimeout()) { 3517d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float deltaX = x - mDownPositionX; 3518d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float deltaY = y - mDownPositionY; 3519d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float distanceSquared = deltaX * deltaX + deltaY * deltaY; 3520d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3521d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ViewConfiguration viewConfiguration = ViewConfiguration.get( 3522d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getContext()); 3523d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop(); 3524d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop; 3525d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3526d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (stayedInArea && isPositionOnText(x, y)) { 3527d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne startSelectionActionMode(); 3528d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDiscardNextActionUp = true; 3529d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3530d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3531d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3532d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3533d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDownPositionX = x; 3534d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mDownPositionY = y; 3535d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mGestureStayedInTapRegion = true; 3536d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3537d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3538d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_POINTER_DOWN: 3539d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_POINTER_UP: 3540d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Handle multi-point gestures. Keep min and max offset positions. 3541d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Only activated for devices that correctly handle multi-touch. 3542d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.getContext().getPackageManager().hasSystemFeature( 3543d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) { 3544d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne updateMinAndMaxOffsets(event); 3545d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3546d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3547d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3548d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_MOVE: 3549d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mGestureStayedInTapRegion) { 3550d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float deltaX = event.getX() - mDownPositionX; 3551d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float deltaY = event.getY() - mDownPositionY; 3552d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float distanceSquared = deltaX * deltaX + deltaY * deltaY; 3553d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3554d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ViewConfiguration viewConfiguration = ViewConfiguration.get( 3555d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.getContext()); 3556d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop(); 3557d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3558d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) { 3559d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mGestureStayedInTapRegion = false; 3560d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3561d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3562d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3563d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3564d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne case MotionEvent.ACTION_UP: 3565d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPreviousTapUpTime = SystemClock.uptimeMillis(); 3566d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne break; 3567d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3568d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3569d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3570d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 3571d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @param event 3572d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 3573d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void updateMinAndMaxOffsets(MotionEvent event) { 3574d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int pointerCount = event.getPointerCount(); 3575d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne for (int index = 0; index < pointerCount; index++) { 3576d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int offset = mTextView.getOffsetForPosition(event.getX(index), event.getY(index)); 3577d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offset < mMinTouchOffset) mMinTouchOffset = offset; 3578d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (offset > mMaxTouchOffset) mMaxTouchOffset = offset; 3579d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3580d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3581d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3582d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int getMinTouchOffset() { 3583d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mMinTouchOffset; 3584d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3585d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3586d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public int getMaxTouchOffset() { 3587d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mMaxTouchOffset; 3588d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3589d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3590d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void resetTouchOffsets() { 3591d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mMinTouchOffset = mMaxTouchOffset = -1; 3592d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3593d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3594d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne /** 3595d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne * @return true iff this controller is currently used to move the selection start. 3596d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne */ 3597d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public boolean isSelectionStartDragged() { 3598d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return mStartHandle != null && mStartHandle.isDragging(); 3599d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3600d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3601d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onTouchModeChanged(boolean isInTouchMode) { 3602d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (!isInTouchMode) { 3603d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne hide(); 3604d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3605d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3606d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3607d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3608d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void onDetached() { 3609d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ViewTreeObserver observer = mTextView.getViewTreeObserver(); 3610d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne observer.removeOnTouchModeChangeListener(this); 3611d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3612d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mStartHandle != null) mStartHandle.onDetached(); 3613d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mEndHandle != null) mEndHandle.onDetached(); 3614d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3615d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3616d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3617d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private class CorrectionHighlighter { 3618d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final Path mPath = new Path(); 3619d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 3620d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mStart, mEnd; 3621d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private long mFadingStartTime; 3622d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private RectF mTempRectF; 3623d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final static int FADE_OUT_DURATION = 400; 3624d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3625d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public CorrectionHighlighter() { 3626d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPaint.setCompatibilityScaling(mTextView.getResources().getCompatibilityInfo(). 3627d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne applicationScale); 3628d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPaint.setStyle(Paint.Style.FILL); 3629d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3630d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3631d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void highlight(CorrectionInfo info) { 3632d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mStart = info.getOffset(); 3633d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mEnd = mStart + info.getNewText().length(); 3634d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mFadingStartTime = SystemClock.uptimeMillis(); 3635d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3636d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mStart < 0 || mEnd < 0) { 3637d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne stopAnimation(); 3638d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3639d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3640d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3641d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void draw(Canvas canvas, int cursorOffsetVertical) { 3642d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (updatePath() && updatePaint()) { 3643d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (cursorOffsetVertical != 0) { 3644d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne canvas.translate(0, cursorOffsetVertical); 3645d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3646d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3647d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne canvas.drawPath(mPath, mPaint); 3648d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3649d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (cursorOffsetVertical != 0) { 3650d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne canvas.translate(0, -cursorOffsetVertical); 3651d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3652d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne invalidate(true); // TODO invalidate cursor region only 3653d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3654d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne stopAnimation(); 3655d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne invalidate(false); // TODO invalidate cursor region only 3656d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3657d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3658d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3659d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean updatePaint() { 3660d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final long duration = SystemClock.uptimeMillis() - mFadingStartTime; 3661d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (duration > FADE_OUT_DURATION) return false; 3662d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3663d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final float coef = 1.0f - (float) duration / FADE_OUT_DURATION; 3664d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int highlightColorAlpha = Color.alpha(mTextView.mHighlightColor); 3665d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int color = (mTextView.mHighlightColor & 0x00FFFFFF) + 3666d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ((int) (highlightColorAlpha * coef) << 24); 3667d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPaint.setColor(color); 3668d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 3669d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3670d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3671d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean updatePath() { 3672d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final Layout layout = mTextView.getLayout(); 3673d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (layout == null) return false; 3674d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3675d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Update in case text is edited while the animation is run 3676d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final int length = mTextView.getText().length(); 3677d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int start = Math.min(length, mStart); 3678d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int end = Math.min(length, mEnd); 3679d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3680d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPath.reset(); 3681d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne layout.getSelectionPath(start, end, mPath); 3682d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return true; 3683d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3684d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3685d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void invalidate(boolean delayed) { 3686d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTextView.getLayout() == null) return; 3687d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3688d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (mTempRectF == null) mTempRectF = new RectF(); 3689d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPath.computeBounds(mTempRectF, false); 3690d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3691d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int left = mTextView.getCompoundPaddingLeft(); 3692d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int top = mTextView.getExtendedPaddingTop() + mTextView.getVerticalOffset(true); 3693d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3694d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (delayed) { 3695d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.postInvalidateOnAnimation( 3696d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne left + (int) mTempRectF.left, top + (int) mTempRectF.top, 3697d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne left + (int) mTempRectF.right, top + (int) mTempRectF.bottom); 3698d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3699d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mTextView.postInvalidate((int) mTempRectF.left, (int) mTempRectF.top, 3700d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne (int) mTempRectF.right, (int) mTempRectF.bottom); 3701d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3702d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3703d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3704d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private void stopAnimation() { 3705d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Editor.this.mCorrectionHighlighter = null; 3706d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3707d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3708d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3709d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private static class ErrorPopup extends PopupWindow { 3710d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private boolean mAbove = false; 3711d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private final TextView mView; 3712d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mPopupInlineErrorBackgroundId = 0; 3713d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int mPopupInlineErrorAboveBackgroundId = 0; 3714d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3715d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ErrorPopup(TextView v, int width, int height) { 3716d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super(v, width, height); 3717d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mView = v; 3718d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // Make sure the TextView has a background set as it will be used the first time it is 3719d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // shown and positionned. Initialized with below background, which should have 3720d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne // dimensions identical to the above version for this to work (and is more likely). 3721d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId, 3722d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.styleable.Theme_errorMessageBackground); 3723d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mView.setBackgroundResource(mPopupInlineErrorBackgroundId); 3724d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3725d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3726d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne void fixDirection(boolean above) { 3727d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mAbove = above; 3728d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3729d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (above) { 3730d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupInlineErrorAboveBackgroundId = 3731d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne getResourceId(mPopupInlineErrorAboveBackgroundId, 3732d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.styleable.Theme_errorMessageAboveBackground); 3733d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } else { 3734d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId, 3735d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne com.android.internal.R.styleable.Theme_errorMessageBackground); 3736d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3737d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3738d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId : 3739d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne mPopupInlineErrorBackgroundId); 3740d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3741d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3742d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne private int getResourceId(int currentId, int index) { 3743d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (currentId == 0) { 3744d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne TypedArray styledAttributes = mView.getContext().obtainStyledAttributes( 3745d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne R.styleable.Theme); 3746d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne currentId = styledAttributes.getResourceId(index, 0); 3747d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne styledAttributes.recycle(); 3748d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3749d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne return currentId; 3750d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3751d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3752d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne @Override 3753d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne public void update(int x, int y, int w, int h, boolean force) { 3754d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne super.update(x, y, w, h, force); 3755d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3756d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean above = isAboveAnchor(); 3757d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne if (above != mAbove) { 3758d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne fixDirection(above); 3759d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3760d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3761d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3762d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3763d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne static class InputContentType { 3764d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int imeOptions = EditorInfo.IME_NULL; 3765d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne String privateImeOptions; 3766d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne CharSequence imeActionLabel; 3767d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int imeActionId; 3768d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Bundle extras; 3769d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne OnEditorActionListener onEditorActionListener; 3770d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean enterDown; 3771d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3772d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne 3773d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne static class InputMethodState { 3774d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne Rect mCursorRectInWindow = new Rect(); 3775d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne RectF mTmpRectF = new RectF(); 3776d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne float[] mTmpOffset = new float[2]; 3777d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne ExtractedTextRequest mExtracting; 3778d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne final ExtractedText mTmpExtracted = new ExtractedText(); 3779d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int mBatchEditNesting; 3780d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mCursorChanged; 3781d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mSelectionModeChanged; 3782d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne boolean mContentChanged; 3783d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne int mChangedStart, mChangedEnd, mChangedDelta; 3784d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne } 3785d88876a72f9ceebd2c93eb9ba1be4bcff971e754Gilles Debunne} 3786