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