TextViewActions.java revision 46faad60230ade76b6a4944a2b9fae274698ab91
1/* 2 * Copyright (C) 2015 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.widget.espresso; 18 19import static android.support.test.espresso.action.ViewActions.actionWithAssertions; 20import android.graphics.Rect; 21import android.support.test.espresso.PerformException; 22import android.support.test.espresso.ViewAction; 23import android.support.test.espresso.action.CoordinatesProvider; 24import android.support.test.espresso.action.GeneralClickAction; 25import android.support.test.espresso.action.Press; 26import android.support.test.espresso.action.Tap; 27import android.support.test.espresso.util.HumanReadables; 28import android.text.Layout; 29import android.view.View; 30import android.widget.Editor; 31import android.widget.TextView; 32 33/** 34 * A collection of actions on a {@link android.widget.TextView}. 35 */ 36public final class TextViewActions { 37 38 private TextViewActions() {} 39 40 /** 41 * Returns an action that clicks on text at an index on the TextView.<br> 42 * <br> 43 * View constraints: 44 * <ul> 45 * <li>must be a TextView displayed on screen 46 * <ul> 47 * 48 * @param index The index of the TextView's text to click on. 49 */ 50 public static ViewAction clickOnTextAtIndex(int index) { 51 return actionWithAssertions( 52 new GeneralClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER)); 53 } 54 55 /** 56 * Returns an action that clicks by mouse on text at an index on the TextView.<br> 57 * <br> 58 * View constraints: 59 * <ul> 60 * <li>must be a TextView displayed on screen 61 * <ul> 62 * 63 * @param index The index of the TextView's text to click on. 64 */ 65 public static ViewAction mouseClickOnTextAtIndex(int index) { 66 return actionWithAssertions( 67 new MouseClickAction(Tap.SINGLE, new TextCoordinates(index))); 68 } 69 70 /** 71 * Returns an action that double-clicks on text at an index on the TextView.<br> 72 * <br> 73 * View constraints: 74 * <ul> 75 * <li>must be a TextView displayed on screen 76 * <ul> 77 * 78 * @param index The index of the TextView's text to double-click on. 79 */ 80 public static ViewAction doubleClickOnTextAtIndex(int index) { 81 return actionWithAssertions( 82 new GeneralClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER)); 83 } 84 85 /** 86 * Returns an action that double-clicks by mouse on text at an index on the TextView.<br> 87 * <br> 88 * View constraints: 89 * <ul> 90 * <li>must be a TextView displayed on screen 91 * <ul> 92 * 93 * @param index The index of the TextView's text to double-click on. 94 */ 95 public static ViewAction mouseDoubleClickOnTextAtIndex(int index) { 96 return actionWithAssertions( 97 new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index))); 98 } 99 100 /** 101 * Returns an action that long presses on text at an index on the TextView.<br> 102 * <br> 103 * View constraints: 104 * <ul> 105 * <li>must be a TextView displayed on screen 106 * <ul> 107 * 108 * @param index The index of the TextView's text to long press on. 109 */ 110 public static ViewAction longPressOnTextAtIndex(int index) { 111 return actionWithAssertions( 112 new GeneralClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER)); 113 } 114 115 /** 116 * Returns an action that long click by mouse on text at an index on the TextView.<br> 117 * <br> 118 * View constraints: 119 * <ul> 120 * <li>must be a TextView displayed on screen 121 * <ul> 122 * 123 * @param index The index of the TextView's text to long click on. 124 */ 125 public static ViewAction mouseLongClickOnTextAtIndex(int index) { 126 return actionWithAssertions( 127 new MouseClickAction(Tap.LONG, new TextCoordinates(index))); 128 } 129 130 /** 131 * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br> 132 * <br> 133 * View constraints: 134 * <ul> 135 * <li>must be a TextView displayed on screen 136 * <ul> 137 * 138 * @param index The index of the TextView's text to triple-click on. 139 */ 140 public static ViewAction mouseTripleClickOnTextAtIndex(int index) { 141 return actionWithAssertions( 142 new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index))); 143 } 144 145 /** 146 * Returns an action that long presses then drags on text from startIndex to endIndex on the 147 * TextView.<br> 148 * <br> 149 * View constraints: 150 * <ul> 151 * <li>must be a TextView displayed on screen 152 * <ul> 153 * 154 * @param startIndex The index of the TextView's text to start a drag from 155 * @param endIndex The index of the TextView's text to end the drag at 156 */ 157 public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) { 158 return actionWithAssertions( 159 new DragAction( 160 DragAction.Drag.LONG_PRESS, 161 new TextCoordinates(startIndex), 162 new TextCoordinates(endIndex), 163 Press.FINGER, 164 TextView.class)); 165 } 166 167 /** 168 * Returns an action that double taps then drags on text from startIndex to endIndex on the 169 * TextView.<br> 170 * <br> 171 * View constraints: 172 * <ul> 173 * <li>must be a TextView displayed on screen 174 * <ul> 175 * 176 * @param startIndex The index of the TextView's text to start a drag from 177 * @param endIndex The index of the TextView's text to end the drag at 178 */ 179 public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) { 180 return actionWithAssertions( 181 new DragAction( 182 DragAction.Drag.DOUBLE_TAP, 183 new TextCoordinates(startIndex), 184 new TextCoordinates(endIndex), 185 Press.FINGER, 186 TextView.class)); 187 } 188 189 /** 190 * Returns an action that click then drags by mouse on text from startIndex to endIndex on the 191 * TextView.<br> 192 * <br> 193 * View constraints: 194 * <ul> 195 * <li>must be a TextView displayed on screen 196 * <ul> 197 * 198 * @param startIndex The index of the TextView's text to start a drag from 199 * @param endIndex The index of the TextView's text to end the drag at 200 */ 201 public static ViewAction mouseDragOnText(int startIndex, int endIndex) { 202 return actionWithAssertions( 203 new DragAction( 204 DragAction.Drag.MOUSE_DOWN, 205 new TextCoordinates(startIndex), 206 new TextCoordinates(endIndex), 207 Press.PINPOINT, 208 TextView.class)); 209 } 210 211 /** 212 * Returns an action that double click then drags by mouse on text from startIndex to endIndex 213 * on the TextView.<br> 214 * <br> 215 * View constraints: 216 * <ul> 217 * <li>must be a TextView displayed on screen 218 * <ul> 219 * 220 * @param startIndex The index of the TextView's text to start a drag from 221 * @param endIndex The index of the TextView's text to end the drag at 222 */ 223 public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) { 224 return actionWithAssertions( 225 new DragAction( 226 DragAction.Drag.MOUSE_DOUBLE_CLICK, 227 new TextCoordinates(startIndex), 228 new TextCoordinates(endIndex), 229 Press.PINPOINT, 230 TextView.class)); 231 } 232 233 /** 234 * Returns an action that long click then drags by mouse on text from startIndex to endIndex 235 * on the TextView.<br> 236 * <br> 237 * View constraints: 238 * <ul> 239 * <li>must be a TextView displayed on screen 240 * <ul> 241 * 242 * @param startIndex The index of the TextView's text to start a drag from 243 * @param endIndex The index of the TextView's text to end the drag at 244 */ 245 public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) { 246 return actionWithAssertions( 247 new DragAction( 248 DragAction.Drag.MOUSE_LONG_CLICK, 249 new TextCoordinates(startIndex), 250 new TextCoordinates(endIndex), 251 Press.PINPOINT, 252 TextView.class)); 253 } 254 255 /** 256 * Returns an action that triple click then drags by mouse on text from startIndex to endIndex 257 * on the TextView.<br> 258 * <br> 259 * View constraints: 260 * <ul> 261 * <li>must be a TextView displayed on screen 262 * <ul> 263 * 264 * @param startIndex The index of the TextView's text to start a drag from 265 * @param endIndex The index of the TextView's text to end the drag at 266 */ 267 public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) { 268 return actionWithAssertions( 269 new DragAction( 270 DragAction.Drag.MOUSE_TRIPLE_CLICK, 271 new TextCoordinates(startIndex), 272 new TextCoordinates(endIndex), 273 Press.PINPOINT, 274 TextView.class)); 275 } 276 277 public enum Handle { 278 SELECTION_START, 279 SELECTION_END, 280 INSERTION 281 }; 282 283 /** 284 * Returns an action that tap then drags on the handle from the current position to endIndex on 285 * the TextView.<br> 286 * <br> 287 * View constraints: 288 * <ul> 289 * <li>must be a TextView's drag-handle displayed on screen 290 * <ul> 291 * 292 * @param textView TextView the handle is on 293 * @param handleType Type of the handle 294 * @param endIndex The index of the TextView's text to end the drag at 295 */ 296 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex) { 297 final int currentOffset = handleType == Handle.SELECTION_START ? 298 textView.getSelectionStart() : textView.getSelectionEnd(); 299 return actionWithAssertions( 300 new DragAction( 301 DragAction.Drag.TAP, 302 new HandleCoordinates(textView, handleType, currentOffset), 303 new HandleCoordinates(textView, handleType, endIndex), 304 Press.FINGER, 305 Editor.HandleView.class)); 306 } 307 308 /** 309 * A provider of the x, y coordinates of the handle that points the specified text index in a 310 * text view. 311 */ 312 private static final class HandleCoordinates implements CoordinatesProvider { 313 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS. 314 private final static float LINE_SLOP_MULTIPLIER = 0.6f; 315 private final TextView mTextView; 316 private final Handle mHandleType; 317 private final int mIndex; 318 private final String mActionDescription; 319 320 public HandleCoordinates(TextView textView, Handle handleType, int index) { 321 mTextView = textView; 322 mHandleType = handleType; 323 mIndex = index; 324 mActionDescription = "Could not locate " + handleType.toString() 325 + " handle that points text index: " + index; 326 } 327 328 @Override 329 public float[] calculateCoordinates(View view) { 330 try { 331 return locateHandlePointsTextIndex(view); 332 } catch (StringIndexOutOfBoundsException e) { 333 throw new PerformException.Builder() 334 .withActionDescription(mActionDescription) 335 .withViewDescription(HumanReadables.describe(view)) 336 .withCause(e) 337 .build(); 338 } 339 } 340 341 private float[] locateHandlePointsTextIndex(View view) { 342 final int currentOffset = mHandleType == Handle.SELECTION_START ? 343 mTextView.getSelectionStart() : mTextView.getSelectionEnd(); 344 345 final Layout layout = mTextView.getLayout(); 346 final int currentLine = layout.getLineForOffset(currentOffset); 347 final int targetLine = layout.getLineForOffset(mIndex); 348 349 final float[] currentCoordinates = 350 (new TextCoordinates(currentOffset)).calculateCoordinates(mTextView); 351 final float[] targetCoordinates = 352 (new TextCoordinates(mIndex)).calculateCoordinates(mTextView); 353 final Rect bounds = new Rect(); 354 view.getBoundsOnScreen(bounds); 355 final Rect visibleDisplayBounds = new Rect(); 356 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds); 357 visibleDisplayBounds.right -= 1; 358 visibleDisplayBounds.bottom -= 1; 359 if (!visibleDisplayBounds.intersect(bounds)) { 360 throw new PerformException.Builder() 361 .withActionDescription(mActionDescription 362 + " The handle is entirely out of the visible display frame of" 363 + "the TextView's window.") 364 .withViewDescription(HumanReadables.describe(view)) 365 .build(); 366 } 367 final float dragPointX = Math.max(Math.min(bounds.centerX(), 368 visibleDisplayBounds.right), visibleDisplayBounds.left); 369 final float diffX = dragPointX - currentCoordinates[0]; 370 final float verticalOffset = bounds.height() * 0.7f; 371 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset, 372 visibleDisplayBounds.bottom), visibleDisplayBounds.top); 373 float diffY = dragPointY - currentCoordinates[1]; 374 if (currentLine > targetLine) { 375 diffY -= mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER; 376 } else if (currentLine < targetLine) { 377 diffY += mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER; 378 } 379 return new float[] {targetCoordinates[0] + diffX, targetCoordinates[1] + diffY}; 380 } 381 } 382 383 /** 384 * A provider of the x, y coordinates of the text at the specified index in a text view. 385 */ 386 private static final class TextCoordinates implements CoordinatesProvider { 387 388 private final int mIndex; 389 private final String mActionDescription; 390 391 public TextCoordinates(int index) { 392 mIndex = index; 393 mActionDescription = "Could not locate text at index: " + mIndex; 394 } 395 396 @Override 397 public float[] calculateCoordinates(View view) { 398 try { 399 return locateTextAtIndex((TextView) view, mIndex); 400 } catch (ClassCastException e) { 401 throw new PerformException.Builder() 402 .withActionDescription(mActionDescription) 403 .withViewDescription(HumanReadables.describe(view)) 404 .withCause(e) 405 .build(); 406 } catch (StringIndexOutOfBoundsException e) { 407 throw new PerformException.Builder() 408 .withActionDescription(mActionDescription) 409 .withViewDescription(HumanReadables.describe(view)) 410 .withCause(e) 411 .build(); 412 } 413 } 414 415 /** 416 * @throws StringIndexOutOfBoundsException 417 */ 418 private float[] locateTextAtIndex(TextView textView, int index) { 419 if (index < 0 || index > textView.getText().length()) { 420 throw new StringIndexOutOfBoundsException(index); 421 } 422 final int[] xy = new int[2]; 423 textView.getLocationOnScreen(xy); 424 final Layout layout = textView.getLayout(); 425 final int line = layout.getLineForOffset(index); 426 final float x = textView.getTotalPaddingLeft() - textView.getScrollX() 427 + layout.getPrimaryHorizontal(index); 428 final float y = textView.getTotalPaddingTop() - textView.getScrollY() 429 + layout.getLineTop(line); 430 return new float[]{x + xy[0], y + xy[1]}; 431 } 432 } 433} 434