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