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