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