19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/*
29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2006 The Android Open Source Project
39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License.
69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at
79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and
149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License.
159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.text.method;
189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
1967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brownimport android.graphics.Rect;
20b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.text.Layout;
21b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.text.Selection;
22b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.text.Spannable;
23cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnanimport android.view.InputDevice;
249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.view.KeyEvent;
259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.view.MotionEvent;
26b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.view.View;
27b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.widget.TextView;
289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
2967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown/**
3067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown * A movement method that provides cursor movement and selection.
3167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown * Supports displaying the context menu on DPad Center.
3267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown */
3367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brownpublic class ArrowKeyMovementMethod extends BaseMovementMethod implements MovementMethod {
3467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    private static boolean isSelecting(Spannable buffer) {
35497a92cc5ba2176b8a8484b0a7da040eac0e887bJeff Brown        return ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SHIFT_ON) == 1) ||
36b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne                (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
37b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne    }
38b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne
39287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne    private static int getCurrentLineTop(Spannable buffer, Layout layout) {
4067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer)));
41b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne    }
42b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne
43287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne    private static int getPageHeight(TextView widget) {
4467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        // This calculation does not take into account the view transformations that
4567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        // may have been applied to the child or its containers.  In case of scaling or
4667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        // rotation, the calculated page height may be incorrect.
4767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final Rect rect = new Rect();
4867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        return widget.getGlobalVisibleRect(rect) ? rect.height() : 0;
4967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    }
509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
5167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
5267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
5367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            int movementMetaState, KeyEvent event) {
5467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        switch (keyCode) {
5567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            case KeyEvent.KEYCODE_DPAD_CENTER:
5667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
5767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                    if (event.getAction() == KeyEvent.ACTION_DOWN
5867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                            && event.getRepeatCount() == 0
5967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                            && MetaKeyKeyListener.getMetaState(buffer,
6014f10e5d5f51bc54ca2a45ee62d3cfb6debd3af0Raph Levien                                        MetaKeyKeyListener.META_SELECTING, event) != 0) {
6167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                        return widget.showContextMenu();
6267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                    }
6367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                }
6467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                break;
6567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        }
6667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
6767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    }
6867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown
6967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
7067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean left(TextView widget, Spannable buffer) {
7167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final Layout layout = widget.getLayout();
7267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        if (isSelecting(buffer)) {
7367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.extendLeft(buffer, layout);
749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else {
7567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.moveLeft(buffer, layout);
769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
7967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
8067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean right(TextView widget, Spannable buffer) {
8167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final Layout layout = widget.getLayout();
8267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        if (isSelecting(buffer)) {
8367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.extendRight(buffer, layout);
8467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        } else {
8567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.moveRight(buffer, layout);
8667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        }
8767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    }
889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
8967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
9067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean up(TextView widget, Spannable buffer) {
9167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final Layout layout = widget.getLayout();
9267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        if (isSelecting(buffer)) {
9367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.extendUp(buffer, layout);
949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else {
9567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.moveUp(buffer, layout);
969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
9967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
10067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean down(TextView widget, Spannable buffer) {
10167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final Layout layout = widget.getLayout();
10267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        if (isSelecting(buffer)) {
10367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.extendDown(buffer, layout);
10467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        } else {
10567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.moveDown(buffer, layout);
10667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        }
10767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    }
1089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
10967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
11067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean pageUp(TextView widget, Spannable buffer) {
11167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final Layout layout = widget.getLayout();
11267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final boolean selecting = isSelecting(buffer);
11367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final int targetY = getCurrentLineTop(buffer, layout) - getPageHeight(widget);
11467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        boolean handled = false;
11567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        for (;;) {
11667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            final int previousSelectionEnd = Selection.getSelectionEnd(buffer);
11767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            if (selecting) {
11867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                Selection.extendUp(buffer, layout);
1199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } else {
12067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                Selection.moveUp(buffer, layout);
1219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
12267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) {
12367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                break;
12467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            }
12567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            handled = true;
12667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            if (getCurrentLineTop(buffer, layout) <= targetY) {
12767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                break;
1289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
1299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
13067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        return handled;
1319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
13367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
13467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean pageDown(TextView widget, Spannable buffer) {
13567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final Layout layout = widget.getLayout();
13667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final boolean selecting = isSelecting(buffer);
13767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final int targetY = getCurrentLineTop(buffer, layout) + getPageHeight(widget);
13867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        boolean handled = false;
13967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        for (;;) {
14067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            final int previousSelectionEnd = Selection.getSelectionEnd(buffer);
14167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            if (selecting) {
14267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                Selection.extendDown(buffer, layout);
1439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            } else {
14467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                Selection.moveDown(buffer, layout);
1459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
14667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) {
14767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                break;
14867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            }
14967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            handled = true;
15067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            if (getCurrentLineTop(buffer, layout) >= targetY) {
15167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown                break;
1529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
1539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
15467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        return handled;
1559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
15767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
15867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean top(TextView widget, Spannable buffer) {
15967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        if (isSelecting(buffer)) {
16067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            Selection.extendSelection(buffer, 0);
16167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        } else {
16267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            Selection.setSelection(buffer, 0);
1639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
16467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        return true;
1659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
16767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
16867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean bottom(TextView widget, Spannable buffer) {
16967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        if (isSelecting(buffer)) {
17067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            Selection.extendSelection(buffer, buffer.length());
17167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        } else {
17267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            Selection.setSelection(buffer, buffer.length());
1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
17467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        return true;
17567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    }
1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
17767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
17867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean lineStart(TextView widget, Spannable buffer) {
17967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final Layout layout = widget.getLayout();
18067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        if (isSelecting(buffer)) {
18167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.extendToLeftEdge(buffer, layout);
18267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        } else {
18367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.moveToLeftEdge(buffer, layout);
1849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
1859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
18767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
18867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean lineEnd(TextView widget, Spannable buffer) {
18967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        final Layout layout = widget.getLayout();
19067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        if (isSelecting(buffer)) {
19167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.extendToRightEdge(buffer, layout);
19267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        } else {
19367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown            return Selection.moveToRightEdge(buffer, layout);
19467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        }
1959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
197e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey    /** {@hide} */
198e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey    @Override
199e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey    protected boolean leftWord(TextView widget, Spannable buffer) {
200287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne        final int selectionEnd = widget.getSelectionEnd();
2019d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne        final WordIterator wordIterator = widget.getWordIterator();
2029d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne        wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
2039d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne        return Selection.moveToPreceding(buffer, wordIterator, isSelecting(buffer));
204e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey    }
205e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey
206e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey    /** {@hide} */
207e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey    @Override
208e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey    protected boolean rightWord(TextView widget, Spannable buffer) {
209287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne        final int selectionEnd = widget.getSelectionEnd();
2109d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne        final WordIterator wordIterator = widget.getWordIterator();
2119d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne        wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
2129d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne        return Selection.moveToFollowing(buffer, wordIterator, isSelecting(buffer));
213e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey    }
214e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey
21567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
21667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean home(TextView widget, Spannable buffer) {
21767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        return lineStart(widget, buffer);
2189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
219ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett
22067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
22167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    protected boolean end(TextView widget, Spannable buffer) {
22267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown        return lineEnd(widget, buffer);
2239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
224ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett
225cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan    private static boolean isTouchSelecting(boolean isMouse, Spannable buffer) {
226cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan        return isMouse ? Touch.isActivelySelecting(buffer) : isSelecting(buffer);
227cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan    }
228cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan
22967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
230b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
231e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey        int initialScrollX = -1;
232e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey        int initialScrollY = -1;
2330eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne        final int action = event.getAction();
234cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan        final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
2350eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne
2360eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne        if (action == MotionEvent.ACTION_UP) {
2371c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn            initialScrollX = Touch.getInitialScrollX(widget, buffer);
2381c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn            initialScrollY = Touch.getInitialScrollY(widget, buffer);
2391c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn        }
240ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett
2419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        boolean handled = Touch.onTouchEvent(widget, buffer, event);
2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
243b2a3dd88a53cc8c6d19f6dc8ec4f3d6c4abd9b54The Android Open Source Project        if (widget.isFocused() && !widget.didTouchFocusSelect()) {
2440eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne            if (action == MotionEvent.ACTION_DOWN) {
245cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                // Capture the mouse pointer down location to ensure selection starts
246cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                // right under the mouse (and is not influenced by cursor location).
247cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                // The code below needs to run for mouse events.
248cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                // For touch events, the code should run only when selection is active.
249cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                if (isMouse || isTouchSelecting(isMouse, buffer)) {
250cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                    int offset = widget.getOffsetForPosition(event.getX(), event.getY());
251cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                    buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
252cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                    // Disallow intercepting of the touch events, so that
253cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                    // users can scroll and select at the same time.
254cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                    // without this, users would get booted out of select
255cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                    // mode once the view detected it needed to scroll.
256cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                    widget.getParent().requestDisallowInterceptTouchEvent(true);
257cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                }
258cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan            } else if (action == MotionEvent.ACTION_MOVE) {
259e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey
260cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                // Cursor can be active at any location in the text while mouse pointer can start
261cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                // selection from a totally different location. Use LAST_TAP_DOWN span to ensure
262cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                // text selection will start from mouse pointer location.
263cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                if (isMouse && Touch.isSelectionStarted(buffer)) {
264cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                    int offset = buffer.getSpanStart(LAST_TAP_DOWN);
265cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                    Selection.setSelection(buffer, offset);
266cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                }
267ab9289320f598509cf358523ba173d69178a55eaMaryam Garrett
268cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                if (isTouchSelecting(isMouse, buffer) && handled) {
26939f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett                    // Before selecting, make sure we've moved out of the "slop".
27039f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett                    // handled will be true, if we're in select mode AND we're
27139f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett                    // OUT of the slop
27239f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett
27339f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett                    // Turn long press off while we're selecting. User needs to
274b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne                    // re-tap on the selection to enable long press
27539f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett                    widget.cancelLongPress();
27639f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett
27739f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett                    // Update selection as we're moving the selection area.
27839f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett
27939f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett                    // Get the current touch position
2803bca69b09fb51116b5eb18fb91cb991c1450e384Gilles Debunne                    int offset = widget.getOffsetForPosition(event.getX(), event.getY());
281b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne
282b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne                    Selection.extendSelection(buffer, offset);
28339f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett                    return true;
284ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett                }
2850eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne            } else if (action == MotionEvent.ACTION_UP) {
2861c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn                // If we have scrolled, then the up shouldn't move the cursor,
2871c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn                // but we do need to make sure the cursor is still visible at
2881c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn                // the current scroll offset to avoid the scroll jumping later
2891c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn                // to show it.
2901c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn                if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
291b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne                    (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
2921c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn                    widget.moveCursorToVisibleOffset();
2931c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn                    return true;
2941c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn                }
295ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett
2963bca69b09fb51116b5eb18fb91cb991c1450e384Gilles Debunne                int offset = widget.getOffsetForPosition(event.getX(), event.getY());
297cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan                if (isTouchSelecting(isMouse, buffer)) {
29862c4ad3b6ba162540c3fb57fcacb375ccfa53454Maryam Garrett                    buffer.removeSpan(LAST_TAP_DOWN);
299b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne                    Selection.extendSelection(buffer, offset);
3009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
3019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
3029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
3039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                MetaKeyKeyListener.resetLockedMeta(buffer);
3049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
3059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                return true;
3069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
3079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
3089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        return handled;
3099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
3109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
31167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
3129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public boolean canSelectArbitrarily() {
3139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        return true;
3149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
3159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
31667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
3179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void initialize(TextView widget, Spannable text) {
3189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        Selection.setSelection(text, 0);
3199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
3209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
32167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown    @Override
3229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public void onTakeFocus(TextView view, Spannable text, int dir) {
3239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
3242703a42d16af0e62da1bba02b6c935d98debf936Gilles Debunne            if (view.getLayout() == null) {
3252703a42d16af0e62da1bba02b6c935d98debf936Gilles Debunne                // This shouldn't be null, but do something sensible if it is.
3269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                Selection.setSelection(text, text.length());
3279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
3289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        } else {
3299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            Selection.setSelection(text, text.length());
3309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
3319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
3329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
3339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static MovementMethod getInstance() {
334b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne        if (sInstance == null) {
3359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            sInstance = new ArrowKeyMovementMethod();
336b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne        }
3379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
3389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        return sInstance;
3399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
3409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
341ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett    private static final Object LAST_TAP_DOWN = new Object();
3429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static ArrowKeyMovementMethod sInstance;
3439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project}
344