LinkMovementMethod.java revision d24b8183b93e781080b2c16c487e60d51c12da31
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.text.method;
18
19import android.content.Intent;
20import android.net.Uri;
21import android.view.KeyEvent;
22import android.view.MotionEvent;
23import android.text.*;
24import android.text.style.*;
25import android.view.View;
26import android.widget.TextView;
27
28public class
29LinkMovementMethod
30extends ScrollingMovementMethod
31{
32    private static final int CLICK = 1;
33    private static final int UP = 2;
34    private static final int DOWN = 3;
35
36    @Override
37    public boolean onKeyDown(TextView widget, Spannable buffer,
38                             int keyCode, KeyEvent event) {
39        switch (keyCode) {
40        case KeyEvent.KEYCODE_DPAD_CENTER:
41        case KeyEvent.KEYCODE_ENTER:
42            if (event.getRepeatCount() == 0) {
43                if (action(CLICK, widget, buffer)) {
44                    return true;
45                }
46            }
47        }
48
49        return super.onKeyDown(widget, buffer, keyCode, event);
50    }
51
52    @Override
53    protected boolean up(TextView widget, Spannable buffer) {
54        if (action(UP, widget, buffer)) {
55            return true;
56        }
57
58        return super.up(widget, buffer);
59    }
60
61    @Override
62    protected boolean down(TextView widget, Spannable buffer) {
63        if (action(DOWN, widget, buffer)) {
64            return true;
65        }
66
67        return super.down(widget, buffer);
68    }
69
70    @Override
71    protected boolean left(TextView widget, Spannable buffer) {
72        if (action(UP, widget, buffer)) {
73            return true;
74        }
75
76        return super.left(widget, buffer);
77    }
78
79    @Override
80    protected boolean right(TextView widget, Spannable buffer) {
81        if (action(DOWN, widget, buffer)) {
82            return true;
83        }
84
85        return super.right(widget, buffer);
86    }
87
88    private boolean action(int what, TextView widget, Spannable buffer) {
89        boolean handled = false;
90
91        Layout layout = widget.getLayout();
92
93        int padding = widget.getTotalPaddingTop() +
94                      widget.getTotalPaddingBottom();
95        int areatop = widget.getScrollY();
96        int areabot = areatop + widget.getHeight() - padding;
97
98        int linetop = layout.getLineForVertical(areatop);
99        int linebot = layout.getLineForVertical(areabot);
100
101        int first = layout.getLineStart(linetop);
102        int last = layout.getLineEnd(linebot);
103
104        ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);
105
106        int a = Selection.getSelectionStart(buffer);
107        int b = Selection.getSelectionEnd(buffer);
108
109        int selStart = Math.min(a, b);
110        int selEnd = Math.max(a, b);
111
112        if (selStart < 0) {
113            if (buffer.getSpanStart(FROM_BELOW) >= 0) {
114                selStart = selEnd = buffer.length();
115            }
116        }
117
118        if (selStart > last)
119            selStart = selEnd = Integer.MAX_VALUE;
120        if (selEnd < first)
121            selStart = selEnd = -1;
122
123        switch (what) {
124        case CLICK:
125            if (selStart == selEnd) {
126                return false;
127            }
128
129            ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class);
130
131            if (link.length != 1)
132                return false;
133
134            link[0].onClick(widget);
135            break;
136
137        case UP:
138            int beststart, bestend;
139
140            beststart = -1;
141            bestend = -1;
142
143            for (int i = 0; i < candidates.length; i++) {
144                int end = buffer.getSpanEnd(candidates[i]);
145
146                if (end < selEnd || selStart == selEnd) {
147                    if (end > bestend) {
148                        beststart = buffer.getSpanStart(candidates[i]);
149                        bestend = end;
150                    }
151                }
152            }
153
154            if (beststart >= 0) {
155                Selection.setSelection(buffer, bestend, beststart);
156                return true;
157            }
158
159            break;
160
161        case DOWN:
162            beststart = Integer.MAX_VALUE;
163            bestend = Integer.MAX_VALUE;
164
165            for (int i = 0; i < candidates.length; i++) {
166                int start = buffer.getSpanStart(candidates[i]);
167
168                if (start > selStart || selStart == selEnd) {
169                    if (start < beststart) {
170                        beststart = start;
171                        bestend = buffer.getSpanEnd(candidates[i]);
172                    }
173                }
174            }
175
176            if (bestend < Integer.MAX_VALUE) {
177                Selection.setSelection(buffer, beststart, bestend);
178                return true;
179            }
180
181            break;
182        }
183
184        return false;
185    }
186
187    public boolean onKeyUp(TextView widget, Spannable buffer,
188                           int keyCode, KeyEvent event) {
189        return false;
190    }
191
192    @Override
193    public boolean onTouchEvent(TextView widget, Spannable buffer,
194                                MotionEvent event) {
195        int action = event.getAction();
196
197        if (action == MotionEvent.ACTION_UP ||
198            action == MotionEvent.ACTION_DOWN) {
199            int x = (int) event.getX();
200            int y = (int) event.getY();
201
202            x -= widget.getTotalPaddingLeft();
203            y -= widget.getTotalPaddingTop();
204
205            x += widget.getScrollX();
206            y += widget.getScrollY();
207
208            Layout layout = widget.getLayout();
209            int line = layout.getLineForVertical(y);
210            int off = layout.getOffsetForHorizontal(line, x);
211
212            ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
213
214            if (link.length != 0) {
215                if (action == MotionEvent.ACTION_UP) {
216                    link[0].onClick(widget);
217                } else if (action == MotionEvent.ACTION_DOWN) {
218                    Selection.setSelection(buffer,
219                                           buffer.getSpanStart(link[0]),
220                                           buffer.getSpanEnd(link[0]));
221                }
222
223                return true;
224            } else {
225                Selection.removeSelection(buffer);
226            }
227        }
228
229        return super.onTouchEvent(widget, buffer, event);
230    }
231
232    public void initialize(TextView widget, Spannable text) {
233        Selection.removeSelection(text);
234        text.removeSpan(FROM_BELOW);
235    }
236
237    public void onTakeFocus(TextView view, Spannable text, int dir) {
238        Selection.removeSelection(text);
239
240        if ((dir & View.FOCUS_BACKWARD) != 0) {
241            text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);
242        } else {
243            text.removeSpan(FROM_BELOW);
244        }
245    }
246
247    public static MovementMethod getInstance() {
248        if (sInstance == null)
249            sInstance = new LinkMovementMethod();
250
251        return sInstance;
252    }
253
254    private static LinkMovementMethod sInstance;
255    private static Object FROM_BELOW = new NoCopySpan.Concrete();
256}
257