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