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