1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 4 import static android.os.Build.VERSION_CODES.KITKAT; 5 import static android.os.Build.VERSION_CODES.KITKAT_WATCH; 6 import static android.os.Build.VERSION_CODES.N; 7 import static android.os.Build.VERSION_CODES.O; 8 import static android.os.Build.VERSION_CODES.Q; 9 import static android.os.Build.VERSION_CODES.R; 10 import static org.robolectric.shadow.api.Shadow.invokeConstructor; 11 import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; 12 import static org.robolectric.util.ReflectionHelpers.getField; 13 import static org.robolectric.util.reflector.Reflector.reflector; 14 15 import android.annotation.SuppressLint; 16 import android.content.Context; 17 import android.graphics.Canvas; 18 import android.graphics.Paint; 19 import android.graphics.Point; 20 import android.graphics.Rect; 21 import android.graphics.RectF; 22 import android.graphics.drawable.Drawable; 23 import android.os.Build; 24 import android.os.Looper; 25 import android.os.RemoteException; 26 import android.os.SystemClock; 27 import android.text.TextUtils; 28 import android.util.AttributeSet; 29 import android.view.Choreographer; 30 import android.view.IWindowFocusObserver; 31 import android.view.IWindowId; 32 import android.view.MotionEvent; 33 import android.view.View; 34 import android.view.ViewGroup.LayoutParams; 35 import android.view.ViewParent; 36 import android.view.WindowId; 37 import android.view.WindowManager; 38 import android.view.animation.Animation; 39 import android.view.animation.Transformation; 40 import com.google.common.annotations.Beta; 41 import com.google.common.collect.ImmutableList; 42 import java.io.PrintStream; 43 import java.util.ArrayList; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Set; 47 import java.util.concurrent.CopyOnWriteArrayList; 48 import org.robolectric.RuntimeEnvironment; 49 import org.robolectric.annotation.GraphicsMode; 50 import org.robolectric.annotation.GraphicsMode.Mode; 51 import org.robolectric.annotation.Implementation; 52 import org.robolectric.annotation.Implements; 53 import org.robolectric.annotation.LooperMode; 54 import org.robolectric.annotation.RealObject; 55 import org.robolectric.annotation.ReflectorObject; 56 import org.robolectric.annotation.Resetter; 57 import org.robolectric.config.ConfigurationRegistry; 58 import org.robolectric.shadow.api.Shadow; 59 import org.robolectric.shadows.ShadowViewRootImpl.ViewRootImplReflector; 60 import org.robolectric.util.ReflectionHelpers.ClassParameter; 61 import org.robolectric.util.TimeUtils; 62 import org.robolectric.util.reflector.Accessor; 63 import org.robolectric.util.reflector.Direct; 64 import org.robolectric.util.reflector.ForType; 65 66 @Implements(View.class) 67 @SuppressLint("NewApi") 68 public class ShadowView { 69 70 @RealObject protected View realView; 71 @ReflectorObject protected _View_ viewReflector; 72 private static final List<View.OnClickListener> globalClickListeners = 73 new CopyOnWriteArrayList<>(); 74 private static final List<View.OnLongClickListener> globalLongClickListeners = 75 new CopyOnWriteArrayList<>(); 76 private View.OnClickListener onClickListener; 77 private View.OnLongClickListener onLongClickListener; 78 private View.OnFocusChangeListener onFocusChangeListener; 79 private View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener; 80 private final HashSet<View.OnAttachStateChangeListener> onAttachStateChangeListeners = 81 new HashSet<>(); 82 private final HashSet<View.OnLayoutChangeListener> onLayoutChangeListeners = new HashSet<>(); 83 private boolean wasInvalidated; 84 private View.OnTouchListener onTouchListener; 85 protected AttributeSet attributeSet; 86 public Point scrollToCoordinates = new Point(); 87 private boolean didRequestLayout; 88 private MotionEvent lastTouchEvent; 89 private int hapticFeedbackPerformed = -1; 90 private boolean onLayoutWasCalled; 91 private View.OnCreateContextMenuListener onCreateContextMenuListener; 92 private Rect globalVisibleRect; 93 private int layerType; 94 private final ArrayList<Animation> animations = new ArrayList<>(); 95 private AnimationRunner animationRunner; 96 97 /** 98 * Calls {@code performClick()} on a {@code View} after ensuring that it and its ancestors are 99 * visible and that it is enabled. 100 * 101 * @param view the view to click on 102 * @return true if {@code View.OnClickListener}s were found and fired, false otherwise. 103 * @throws RuntimeException if the preconditions are not met. 104 * @deprecated Please use Espresso for view interactions 105 */ 106 @Deprecated clickOn(View view)107 public static boolean clickOn(View view) { 108 ShadowView shadowView = Shadow.extract(view); 109 return shadowView.checkedPerformClick(); 110 } 111 112 /** 113 * Returns a textual representation of the appearance of the object. 114 * 115 * @param view the view to visualize 116 * @return Textual representation of the appearance of the object. 117 */ visualize(View view)118 public static String visualize(View view) { 119 Canvas canvas = new Canvas(); 120 view.draw(canvas); 121 if (!useRealGraphics()) { 122 ShadowCanvas shadowCanvas = Shadow.extract(canvas); 123 return shadowCanvas.getDescription(); 124 } else { 125 return ""; 126 } 127 } 128 129 /** 130 * Emits an xml-like representation of the view to System.out. 131 * 132 * @param view the view to dump. 133 * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)} 134 */ 135 @SuppressWarnings("UnusedDeclaration") 136 @Deprecated dump(View view)137 public static void dump(View view) { 138 ShadowView shadowView = Shadow.extract(view); 139 shadowView.dump(); 140 } 141 142 /** 143 * Returns the text contained within this view. 144 * 145 * @param view the view to scan for text 146 * @return Text contained within this view. 147 */ 148 @SuppressWarnings("UnusedDeclaration") innerText(View view)149 public static String innerText(View view) { 150 ShadowView shadowView = Shadow.extract(view); 151 return shadowView.innerText(); 152 } 153 getLocationInSurfaceCompat(View view)154 static int[] getLocationInSurfaceCompat(View view) { 155 int[] locationInSurface = new int[2]; 156 if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) { 157 view.getLocationInSurface(locationInSurface); 158 } else { 159 view.getLocationInWindow(locationInSurface); 160 Rect surfaceInsets = 161 reflector(ViewRootImplReflector.class, view.getViewRootImpl()) 162 .getWindowAttributes() 163 .surfaceInsets; 164 locationInSurface[0] += surfaceInsets.left; 165 locationInSurface[1] += surfaceInsets.top; 166 } 167 return locationInSurface; 168 } 169 170 // Only override up to kitkat, while this version exists after kitkat it just calls through to the 171 // __constructor__(Context, AttributeSet, int, int) variant below. 172 @Implementation(maxSdk = KITKAT) __constructor__(Context context, AttributeSet attributeSet, int defStyle)173 protected void __constructor__(Context context, AttributeSet attributeSet, int defStyle) { 174 this.attributeSet = attributeSet; 175 invokeConstructor( 176 View.class, 177 realView, 178 ClassParameter.from(Context.class, context), 179 ClassParameter.from(AttributeSet.class, attributeSet), 180 ClassParameter.from(int.class, defStyle)); 181 } 182 183 @Implementation(minSdk = KITKAT_WATCH) __constructor__( Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes)184 protected void __constructor__( 185 Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) { 186 this.attributeSet = attributeSet; 187 invokeConstructor( 188 View.class, 189 realView, 190 ClassParameter.from(Context.class, context), 191 ClassParameter.from(AttributeSet.class, attributeSet), 192 ClassParameter.from(int.class, defStyleAttr), 193 ClassParameter.from(int.class, defStyleRes)); 194 } 195 196 @Implementation setLayerType(int layerType, Paint paint)197 protected void setLayerType(int layerType, Paint paint) { 198 this.layerType = layerType; 199 reflector(_View_.class, realView).setLayerType(layerType, paint); 200 } 201 202 @Implementation setOnFocusChangeListener(View.OnFocusChangeListener l)203 protected void setOnFocusChangeListener(View.OnFocusChangeListener l) { 204 onFocusChangeListener = l; 205 reflector(_View_.class, realView).setOnFocusChangeListener(l); 206 } 207 208 @Implementation setOnClickListener(View.OnClickListener onClickListener)209 protected void setOnClickListener(View.OnClickListener onClickListener) { 210 this.onClickListener = onClickListener; 211 reflector(_View_.class, realView).setOnClickListener(onClickListener); 212 } 213 214 @Implementation setOnLongClickListener(View.OnLongClickListener onLongClickListener)215 protected void setOnLongClickListener(View.OnLongClickListener onLongClickListener) { 216 this.onLongClickListener = onLongClickListener; 217 reflector(_View_.class, realView).setOnLongClickListener(onLongClickListener); 218 } 219 220 @Implementation setOnSystemUiVisibilityChangeListener( View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener)221 protected void setOnSystemUiVisibilityChangeListener( 222 View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener) { 223 this.onSystemUiVisibilityChangeListener = onSystemUiVisibilityChangeListener; 224 reflector(_View_.class, realView) 225 .setOnSystemUiVisibilityChangeListener(onSystemUiVisibilityChangeListener); 226 } 227 228 @Implementation setOnCreateContextMenuListener( View.OnCreateContextMenuListener onCreateContextMenuListener)229 protected void setOnCreateContextMenuListener( 230 View.OnCreateContextMenuListener onCreateContextMenuListener) { 231 this.onCreateContextMenuListener = onCreateContextMenuListener; 232 reflector(_View_.class, realView).setOnCreateContextMenuListener(onCreateContextMenuListener); 233 } 234 235 @Implementation addOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener)236 protected void addOnAttachStateChangeListener( 237 View.OnAttachStateChangeListener onAttachStateChangeListener) { 238 onAttachStateChangeListeners.add(onAttachStateChangeListener); 239 reflector(_View_.class, realView).addOnAttachStateChangeListener(onAttachStateChangeListener); 240 } 241 242 @Implementation removeOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener)243 protected void removeOnAttachStateChangeListener( 244 View.OnAttachStateChangeListener onAttachStateChangeListener) { 245 onAttachStateChangeListeners.remove(onAttachStateChangeListener); 246 reflector(_View_.class, realView) 247 .removeOnAttachStateChangeListener(onAttachStateChangeListener); 248 } 249 250 @Implementation addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener)251 protected void addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener) { 252 onLayoutChangeListeners.add(onLayoutChangeListener); 253 reflector(_View_.class, realView).addOnLayoutChangeListener(onLayoutChangeListener); 254 } 255 256 @Implementation removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener)257 protected void removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener) { 258 onLayoutChangeListeners.remove(onLayoutChangeListener); 259 reflector(_View_.class, realView).removeOnLayoutChangeListener(onLayoutChangeListener); 260 } 261 262 @Implementation draw(Canvas canvas)263 protected void draw(Canvas canvas) { 264 Drawable background = realView.getBackground(); 265 if (background != null && !useRealGraphics()) { 266 Object shadowCanvas = Shadow.extract(canvas); 267 // Check that Canvas is not a Mockito mock 268 if (shadowCanvas instanceof ShadowCanvas) { 269 ((ShadowCanvas) shadowCanvas).appendDescription("background:"); 270 } 271 } 272 reflector(_View_.class, realView).draw(canvas); 273 } 274 275 @Implementation onLayout(boolean changed, int left, int top, int right, int bottom)276 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 277 onLayoutWasCalled = true; 278 reflector(_View_.class, realView).onLayout(changed, left, top, right, bottom); 279 } 280 onLayoutWasCalled()281 public boolean onLayoutWasCalled() { 282 return onLayoutWasCalled; 283 } 284 285 @Implementation requestLayout()286 protected void requestLayout() { 287 didRequestLayout = true; 288 reflector(_View_.class, realView).requestLayout(); 289 } 290 291 @Implementation performClick()292 protected boolean performClick() { 293 for (View.OnClickListener listener : globalClickListeners) { 294 listener.onClick(realView); 295 } 296 return reflector(_View_.class, realView).performClick(); 297 } 298 299 /** 300 * Registers an {@link View.OnClickListener} to the {@link ShadowView}. 301 * 302 * @param listener The {@link View.OnClickListener} to be registered. 303 */ addGlobalPerformClickListener(View.OnClickListener listener)304 public static void addGlobalPerformClickListener(View.OnClickListener listener) { 305 ShadowView.globalClickListeners.add(listener); 306 } 307 308 /** 309 * Removes an {@link View.OnClickListener} from the {@link ShadowView}. 310 * 311 * @param listener The {@link View.OnClickListener} to be removed. 312 */ removeGlobalPerformClickListener(View.OnClickListener listener)313 public static void removeGlobalPerformClickListener(View.OnClickListener listener) { 314 ShadowView.globalClickListeners.remove(listener); 315 } 316 317 @Implementation performLongClick()318 protected boolean performLongClick() { 319 for (View.OnLongClickListener listener : globalLongClickListeners) { 320 listener.onLongClick(realView); 321 } 322 return reflector(_View_.class, realView).performLongClick(); 323 } 324 325 /** 326 * Registers an {@link View.OnLongClickListener} to the {@link ShadowView}. 327 * 328 * @param listener The {@link View.OnLongClickListener} to be registered. 329 */ addGlobalPerformLongClickListener(View.OnLongClickListener listener)330 public static void addGlobalPerformLongClickListener(View.OnLongClickListener listener) { 331 ShadowView.globalLongClickListeners.add(listener); 332 } 333 334 /** 335 * Removes an {@link View.OnLongClickListener} from the {@link ShadowView}. 336 * 337 * @param listener The {@link View.OnLongClickListener} to be removed. 338 */ removeGlobalPerformLongClickListener(View.OnLongClickListener listener)339 public static void removeGlobalPerformLongClickListener(View.OnLongClickListener listener) { 340 ShadowView.globalLongClickListeners.remove(listener); 341 } 342 343 @Resetter reset()344 public static void reset() { 345 ShadowView.globalClickListeners.clear(); 346 ShadowView.globalLongClickListeners.clear(); 347 } 348 didRequestLayout()349 public boolean didRequestLayout() { 350 return didRequestLayout; 351 } 352 setDidRequestLayout(boolean didRequestLayout)353 public void setDidRequestLayout(boolean didRequestLayout) { 354 this.didRequestLayout = didRequestLayout; 355 } 356 setViewFocus(boolean hasFocus)357 public void setViewFocus(boolean hasFocus) { 358 if (onFocusChangeListener != null) { 359 onFocusChangeListener.onFocusChange(realView, hasFocus); 360 } 361 } 362 363 @Implementation invalidate()364 protected void invalidate() { 365 wasInvalidated = true; 366 reflector(_View_.class, realView).invalidate(); 367 } 368 369 @Implementation onTouchEvent(MotionEvent event)370 protected boolean onTouchEvent(MotionEvent event) { 371 lastTouchEvent = event; 372 return reflector(_View_.class, realView).onTouchEvent(event); 373 } 374 375 @Implementation setOnTouchListener(View.OnTouchListener onTouchListener)376 protected void setOnTouchListener(View.OnTouchListener onTouchListener) { 377 this.onTouchListener = onTouchListener; 378 reflector(_View_.class, realView).setOnTouchListener(onTouchListener); 379 } 380 getLastTouchEvent()381 public MotionEvent getLastTouchEvent() { 382 return lastTouchEvent; 383 } 384 385 /** 386 * Returns a string representation of this {@code View}. Unless overridden, it will be an empty 387 * string. 388 * 389 * <p>Robolectric extension. 390 * 391 * @return String representation of this view. 392 */ innerText()393 public String innerText() { 394 return ""; 395 } 396 397 /** 398 * Dumps the status of this {@code View} to {@code System.out} 399 * 400 * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)} 401 */ 402 @Deprecated dump()403 public void dump() { 404 dump(System.out, 0); 405 } 406 407 /** 408 * Dumps the status of this {@code View} to {@code System.out} at the given indentation level 409 * 410 * @param out Output stream. 411 * @param indent Indentation level. 412 * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)} 413 */ 414 @Deprecated dump(PrintStream out, int indent)415 public void dump(PrintStream out, int indent) { 416 dumpFirstPart(out, indent); 417 out.println("/>"); 418 } 419 420 @Deprecated dumpFirstPart(PrintStream out, int indent)421 protected void dumpFirstPart(PrintStream out, int indent) { 422 dumpIndent(out, indent); 423 424 out.print("<" + realView.getClass().getSimpleName()); 425 dumpAttributes(out); 426 } 427 428 @Deprecated dumpAttributes(PrintStream out)429 protected void dumpAttributes(PrintStream out) { 430 if (realView.getId() > 0) { 431 dumpAttribute( 432 out, "id", realView.getContext().getResources().getResourceName(realView.getId())); 433 } 434 435 switch (realView.getVisibility()) { 436 case View.VISIBLE: 437 break; 438 case View.INVISIBLE: 439 dumpAttribute(out, "visibility", "INVISIBLE"); 440 break; 441 case View.GONE: 442 dumpAttribute(out, "visibility", "GONE"); 443 break; 444 } 445 } 446 447 @Deprecated dumpAttribute(PrintStream out, String name, String value)448 protected void dumpAttribute(PrintStream out, String name, String value) { 449 out.print(" " + name + "=\"" + (value == null ? null : TextUtils.htmlEncode(value)) + "\""); 450 } 451 452 @Deprecated dumpIndent(PrintStream out, int indent)453 protected void dumpIndent(PrintStream out, int indent) { 454 for (int i = 0; i < indent; i++) out.print(" "); 455 } 456 457 /** 458 * @return whether or not {@link #invalidate()} has been called 459 */ wasInvalidated()460 public boolean wasInvalidated() { 461 return wasInvalidated; 462 } 463 464 /** Clears the wasInvalidated flag */ clearWasInvalidated()465 public void clearWasInvalidated() { 466 wasInvalidated = false; 467 } 468 469 /** 470 * Utility method for clicking on views exposing testing scenarios that are not possible when 471 * using the actual app. 472 * 473 * <p>If running with LooperMode PAUSED will also idle the main Looper. 474 * 475 * @throws RuntimeException if the view is disabled or if the view or any of its parents are not 476 * visible. 477 * @return Return value of the underlying click operation. 478 * @deprecated - Please use Espresso for View interactions. 479 */ 480 @Deprecated checkedPerformClick()481 public boolean checkedPerformClick() { 482 if (!realView.isShown()) { 483 throw new RuntimeException("View is not visible and cannot be clicked"); 484 } 485 if (!realView.isEnabled()) { 486 throw new RuntimeException("View is not enabled and cannot be clicked"); 487 } 488 boolean res = realView.performClick(); 489 shadowMainLooper().idleIfPaused(); 490 return res; 491 } 492 493 /** 494 * @return Touch listener, if set. 495 */ getOnTouchListener()496 public View.OnTouchListener getOnTouchListener() { 497 return onTouchListener; 498 } 499 500 /** 501 * @return Returns click listener, if set. 502 */ getOnClickListener()503 public View.OnClickListener getOnClickListener() { 504 return onClickListener; 505 } 506 507 /** 508 * @return Returns long click listener, if set. 509 */ 510 @Implementation(minSdk = R) getOnLongClickListener()511 public View.OnLongClickListener getOnLongClickListener() { 512 if (RuntimeEnvironment.getApiLevel() >= R) { 513 return reflector(_View_.class, realView).getOnLongClickListener(); 514 } else { 515 return onLongClickListener; 516 } 517 } 518 519 /** 520 * @return Returns system ui visibility change listener. 521 */ getOnSystemUiVisibilityChangeListener()522 public View.OnSystemUiVisibilityChangeListener getOnSystemUiVisibilityChangeListener() { 523 return onSystemUiVisibilityChangeListener; 524 } 525 526 /** 527 * @return Returns create ContextMenu listener, if set. 528 */ getOnCreateContextMenuListener()529 public View.OnCreateContextMenuListener getOnCreateContextMenuListener() { 530 return onCreateContextMenuListener; 531 } 532 533 /** 534 * @return Returns the attached listeners, or the empty set if none are present. 535 */ getOnAttachStateChangeListeners()536 public Set<View.OnAttachStateChangeListener> getOnAttachStateChangeListeners() { 537 return onAttachStateChangeListeners; 538 } 539 540 /** 541 * @return Returns the layout change listeners, or the empty set if none are present. 542 */ getOnLayoutChangeListeners()543 public Set<View.OnLayoutChangeListener> getOnLayoutChangeListeners() { 544 return onLayoutChangeListeners; 545 } 546 547 @Implementation post(Runnable action)548 protected boolean post(Runnable action) { 549 if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) { 550 ShadowApplication.getInstance().getForegroundThreadScheduler().post(action); 551 return true; 552 } else { 553 return reflector(_View_.class, realView).post(action); 554 } 555 } 556 557 @Implementation postDelayed(Runnable action, long delayMills)558 protected boolean postDelayed(Runnable action, long delayMills) { 559 if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) { 560 ShadowApplication.getInstance() 561 .getForegroundThreadScheduler() 562 .postDelayed(action, delayMills); 563 return true; 564 } else { 565 return reflector(_View_.class, realView).postDelayed(action, delayMills); 566 } 567 } 568 569 @Implementation postInvalidateDelayed(long delayMilliseconds)570 protected void postInvalidateDelayed(long delayMilliseconds) { 571 if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) { 572 ShadowApplication.getInstance() 573 .getForegroundThreadScheduler() 574 .postDelayed( 575 new Runnable() { 576 @Override 577 public void run() { 578 realView.invalidate(); 579 } 580 }, 581 delayMilliseconds); 582 } else { 583 reflector(_View_.class, realView).postInvalidateDelayed(delayMilliseconds); 584 } 585 } 586 587 @Implementation removeCallbacks(Runnable callback)588 protected boolean removeCallbacks(Runnable callback) { 589 if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) { 590 ShadowLegacyLooper shadowLooper = Shadow.extract(Looper.getMainLooper()); 591 shadowLooper.getScheduler().remove(callback); 592 return true; 593 } else { 594 return reflector(_View_.class, realView).removeCallbacks(callback); 595 } 596 } 597 598 @Implementation scrollTo(int x, int y)599 protected void scrollTo(int x, int y) { 600 if (useRealScrolling()) { 601 reflector(_View_.class, realView).scrollTo(x, y); 602 } else { 603 reflector(_View_.class, realView) 604 .onScrollChanged(x, y, scrollToCoordinates.x, scrollToCoordinates.y); 605 scrollToCoordinates = new Point(x, y); 606 reflector(_View_.class, realView).setMemberScrollX(x); 607 reflector(_View_.class, realView).setMemberScrollY(y); 608 } 609 } 610 611 @Implementation scrollBy(int x, int y)612 protected void scrollBy(int x, int y) { 613 if (useRealScrolling()) { 614 reflector(_View_.class, realView).scrollBy(x, y); 615 } else { 616 scrollTo(getScrollX() + x, getScrollY() + y); 617 } 618 } 619 620 @Implementation getScrollX()621 protected int getScrollX() { 622 if (useRealScrolling()) { 623 return reflector(_View_.class, realView).getScrollX(); 624 } else { 625 return scrollToCoordinates != null ? scrollToCoordinates.x : 0; 626 } 627 } 628 629 @Implementation getScrollY()630 protected int getScrollY() { 631 if (useRealScrolling()) { 632 return reflector(_View_.class, realView).getScrollY(); 633 } else { 634 return scrollToCoordinates != null ? scrollToCoordinates.y : 0; 635 } 636 } 637 638 @Implementation setScrollX(int scrollX)639 protected void setScrollX(int scrollX) { 640 if (useRealScrolling()) { 641 reflector(_View_.class, realView).setScrollX(scrollX); 642 } else { 643 scrollTo(scrollX, scrollToCoordinates.y); 644 } 645 } 646 647 @Implementation setScrollY(int scrollY)648 protected void setScrollY(int scrollY) { 649 if (useRealScrolling()) { 650 reflector(_View_.class, realView).setScrollY(scrollY); 651 } else { 652 scrollTo(scrollToCoordinates.x, scrollY); 653 } 654 } 655 656 @Implementation getLocationOnScreen(int[] outLocation)657 protected void getLocationOnScreen(int[] outLocation) { 658 reflector(_View_.class, realView).getLocationOnScreen(outLocation); 659 int[] windowLocation = getWindowLocation(); 660 outLocation[0] += windowLocation[0]; 661 outLocation[1] += windowLocation[1]; 662 } 663 664 @Implementation(minSdk = O) mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent)665 protected void mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent) { 666 reflector(_View_.class, realView).mapRectFromViewToScreenCoords(rect, clipToParent); 667 int[] windowLocation = getWindowLocation(); 668 rect.offset(windowLocation[0], windowLocation[1]); 669 } 670 671 // TODO(paulsowden): Should configure the correct frame on the ViewRootImpl instead and remove 672 // this. getWindowLocation()673 private int[] getWindowLocation() { 674 int[] location = new int[2]; 675 LayoutParams rootParams = realView.getRootView().getLayoutParams(); 676 if (rootParams instanceof WindowManager.LayoutParams) { 677 location[0] = ((WindowManager.LayoutParams) rootParams).x; 678 location[1] = ((WindowManager.LayoutParams) rootParams).y; 679 } 680 return location; 681 } 682 683 @Implementation getLayerType()684 protected int getLayerType() { 685 return this.layerType; 686 } 687 688 /** Returns a list of all animations that have been set on this view. */ getAnimations()689 public ImmutableList<Animation> getAnimations() { 690 return ImmutableList.copyOf(animations); 691 } 692 693 /** Resets the list returned by {@link #getAnimations()} to an empty list. */ clearAnimations()694 public void clearAnimations() { 695 animations.clear(); 696 } 697 698 @Implementation setAnimation(final Animation animation)699 protected void setAnimation(final Animation animation) { 700 reflector(_View_.class, realView).setAnimation(animation); 701 702 if (animation != null) { 703 animations.add(animation); 704 if (animationRunner != null) { 705 animationRunner.cancel(); 706 } 707 animationRunner = new AnimationRunner(animation); 708 animationRunner.start(); 709 } 710 } 711 712 @Implementation clearAnimation()713 protected void clearAnimation() { 714 reflector(_View_.class, realView).clearAnimation(); 715 716 if (animationRunner != null) { 717 animationRunner.cancel(); 718 animationRunner = null; 719 } 720 } 721 722 @Implementation initialAwakenScrollBars()723 protected boolean initialAwakenScrollBars() { 724 // Temporarily allow disabling initial awaken of scroll bars to aid in migration of tests to 725 // default to window's being marked visible, this will be removed once migration is complete. 726 if (Boolean.getBoolean("robolectric.disableInitialAwakenScrollBars")) { 727 return false; 728 } else { 729 return viewReflector.initialAwakenScrollBars(); 730 } 731 } 732 733 private class AnimationRunner implements Runnable { 734 private final Animation animation; 735 private final Transformation transformation = new Transformation(); 736 private long startTime; 737 private long elapsedTime; 738 private boolean canceled; 739 AnimationRunner(Animation animation)740 AnimationRunner(Animation animation) { 741 this.animation = animation; 742 } 743 start()744 private void start() { 745 startTime = animation.getStartTime(); 746 long startOffset = animation.getStartOffset(); 747 long startDelay = 748 startTime == Animation.START_ON_FIRST_FRAME 749 ? startOffset 750 : (startTime + startOffset) - SystemClock.uptimeMillis(); 751 Choreographer.getInstance() 752 .postCallbackDelayed(Choreographer.CALLBACK_ANIMATION, this, null, startDelay); 753 } 754 step()755 private boolean step() { 756 long animationTime = 757 animation.getStartTime() == Animation.START_ON_FIRST_FRAME 758 ? SystemClock.uptimeMillis() 759 : (animation.getStartTime() + animation.getStartOffset() + elapsedTime); 760 // Note in real android the parent is non-nullable, retain legacy robolectric behavior which 761 // allows detached views to animate. 762 if (!animation.isInitialized() && realView.getParent() != null) { 763 View parent = (View) realView.getParent(); 764 animation.initialize( 765 realView.getWidth(), realView.getHeight(), parent.getWidth(), parent.getHeight()); 766 } 767 boolean next = animation.getTransformation(animationTime, transformation); 768 // Note in real view implementation it doesn't check the animation equality before clearing, 769 // but in the real implementation the animation listeners are posted so it doesn't race with 770 // chained animations. 771 if (realView.getAnimation() == animation && !next) { 772 if (!animation.getFillAfter()) { 773 realView.clearAnimation(); 774 } 775 } 776 // We can't handle infinitely repeating animations in the current scheduling model, so abort 777 // after one iteration. 778 return next 779 && (animation.getRepeatCount() != Animation.INFINITE 780 || elapsedTime < animation.getDuration()); 781 } 782 783 @Override run()784 public void run() { 785 // Abort if start time has been messed with, as this simulation is only designed to handle 786 // standard situations. 787 if (!canceled && animation.getStartTime() == startTime && step()) { 788 // Start time updates for repeating animations and if START_ON_FIRST_FRAME. 789 startTime = animation.getStartTime(); 790 elapsedTime += 791 ShadowLooper.looperMode().equals(LooperMode.Mode.LEGACY) 792 ? ShadowChoreographer.getFrameInterval() / TimeUtils.NANOS_PER_MS 793 : ShadowChoreographer.getFrameDelay().toMillis(); 794 Choreographer.getInstance().postCallback(Choreographer.CALLBACK_ANIMATION, this, null); 795 } else if (animationRunner == this) { 796 animationRunner = null; 797 } 798 } 799 cancel()800 public void cancel() { 801 this.canceled = true; 802 Choreographer.getInstance() 803 .removeCallbacks(Choreographer.CALLBACK_ANIMATION, animationRunner, null); 804 } 805 } 806 807 @Implementation(minSdk = KITKAT) isAttachedToWindow()808 protected boolean isAttachedToWindow() { 809 return getAttachInfo() != null; 810 } 811 getAttachInfo()812 private Object getAttachInfo() { 813 return reflector(_View_.class, realView).getAttachInfo(); 814 } 815 816 /** Reflector interface for {@link View}'s internals. */ 817 @ForType(View.class) 818 private interface _View_ { 819 820 @Direct draw(Canvas canvas)821 void draw(Canvas canvas); 822 823 @Direct onLayout(boolean changed, int left, int top, int right, int bottom)824 void onLayout(boolean changed, int left, int top, int right, int bottom); 825 assignParent(ViewParent viewParent)826 void assignParent(ViewParent viewParent); 827 828 @Direct setOnFocusChangeListener(View.OnFocusChangeListener l)829 void setOnFocusChangeListener(View.OnFocusChangeListener l); 830 831 @Direct setLayerType(int layerType, Paint paint)832 void setLayerType(int layerType, Paint paint); 833 834 @Direct setOnClickListener(View.OnClickListener onClickListener)835 void setOnClickListener(View.OnClickListener onClickListener); 836 837 @Direct setOnLongClickListener(View.OnLongClickListener onLongClickListener)838 void setOnLongClickListener(View.OnLongClickListener onLongClickListener); 839 840 @Direct getOnLongClickListener()841 View.OnLongClickListener getOnLongClickListener(); 842 843 @Direct setOnSystemUiVisibilityChangeListener( View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener)844 void setOnSystemUiVisibilityChangeListener( 845 View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener); 846 847 @Direct setOnCreateContextMenuListener( View.OnCreateContextMenuListener onCreateContextMenuListener)848 void setOnCreateContextMenuListener( 849 View.OnCreateContextMenuListener onCreateContextMenuListener); 850 851 @Direct addOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener)852 void addOnAttachStateChangeListener( 853 View.OnAttachStateChangeListener onAttachStateChangeListener); 854 855 @Direct removeOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener)856 void removeOnAttachStateChangeListener( 857 View.OnAttachStateChangeListener onAttachStateChangeListener); 858 859 @Direct addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener)860 void addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener); 861 862 @Direct removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener)863 void removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener); 864 865 @Direct requestLayout()866 void requestLayout(); 867 868 @Direct performClick()869 boolean performClick(); 870 871 @Direct performLongClick()872 boolean performLongClick(); 873 874 @Direct invalidate()875 void invalidate(); 876 877 @Direct onTouchEvent(MotionEvent event)878 boolean onTouchEvent(MotionEvent event); 879 880 @Direct setOnTouchListener(View.OnTouchListener onTouchListener)881 void setOnTouchListener(View.OnTouchListener onTouchListener); 882 883 @Direct post(Runnable action)884 boolean post(Runnable action); 885 886 @Direct postDelayed(Runnable action, long delayMills)887 boolean postDelayed(Runnable action, long delayMills); 888 889 @Direct postInvalidateDelayed(long delayMilliseconds)890 void postInvalidateDelayed(long delayMilliseconds); 891 892 @Direct removeCallbacks(Runnable callback)893 boolean removeCallbacks(Runnable callback); 894 895 @Direct setAnimation(final Animation animation)896 void setAnimation(final Animation animation); 897 898 @Direct clearAnimation()899 void clearAnimation(); 900 901 @Direct getGlobalVisibleRect(Rect rect, Point globalOffset)902 boolean getGlobalVisibleRect(Rect rect, Point globalOffset); 903 904 @Direct getWindowId()905 WindowId getWindowId(); 906 907 @Accessor("mAttachInfo") getAttachInfo()908 Object getAttachInfo(); 909 onAttachedToWindow()910 void onAttachedToWindow(); 911 onDetachedFromWindow()912 void onDetachedFromWindow(); 913 onScrollChanged(int l, int t, int oldl, int oldt)914 void onScrollChanged(int l, int t, int oldl, int oldt); 915 916 @Direct getLocationOnScreen(int[] outLocation)917 void getLocationOnScreen(int[] outLocation); 918 919 @Direct mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent)920 void mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent); 921 922 @Direct getSourceLayoutResId()923 int getSourceLayoutResId(); 924 925 @Direct initialAwakenScrollBars()926 boolean initialAwakenScrollBars(); 927 928 @Accessor("mScrollX") setMemberScrollX(int value)929 void setMemberScrollX(int value); 930 931 @Accessor("mScrollY") setMemberScrollY(int value)932 void setMemberScrollY(int value); 933 934 @Direct scrollTo(int x, int y)935 void scrollTo(int x, int y); 936 937 @Direct scrollBy(int x, int y)938 void scrollBy(int x, int y); 939 940 @Direct getScrollX()941 int getScrollX(); 942 943 @Direct getScrollY()944 int getScrollY(); 945 946 @Direct setScrollX(int value)947 void setScrollX(int value); 948 949 @Direct setScrollY(int value)950 void setScrollY(int value); 951 } 952 callOnAttachedToWindow()953 public void callOnAttachedToWindow() { 954 reflector(_View_.class, realView).onAttachedToWindow(); 955 } 956 callOnDetachedFromWindow()957 public void callOnDetachedFromWindow() { 958 reflector(_View_.class, realView).onDetachedFromWindow(); 959 } 960 961 @Implementation(minSdk = JELLY_BEAN_MR2) getWindowId()962 protected WindowId getWindowId() { 963 return WindowIdHelper.getWindowId(this); 964 } 965 966 @Implementation performHapticFeedback(int hapticFeedbackType)967 protected boolean performHapticFeedback(int hapticFeedbackType) { 968 hapticFeedbackPerformed = hapticFeedbackType; 969 return true; 970 } 971 972 @Implementation getGlobalVisibleRect(Rect rect, Point globalOffset)973 protected boolean getGlobalVisibleRect(Rect rect, Point globalOffset) { 974 if (globalVisibleRect == null) { 975 return reflector(_View_.class, realView).getGlobalVisibleRect(rect, globalOffset); 976 } 977 978 if (!globalVisibleRect.isEmpty()) { 979 rect.set(globalVisibleRect); 980 if (globalOffset != null) { 981 rect.offset(-globalOffset.x, -globalOffset.y); 982 } 983 return true; 984 } 985 rect.setEmpty(); 986 return false; 987 } 988 setGlobalVisibleRect(Rect rect)989 public void setGlobalVisibleRect(Rect rect) { 990 if (rect != null) { 991 globalVisibleRect = new Rect(); 992 globalVisibleRect.set(rect); 993 } else { 994 globalVisibleRect = null; 995 } 996 } 997 lastHapticFeedbackPerformed()998 public int lastHapticFeedbackPerformed() { 999 return hapticFeedbackPerformed; 1000 } 1001 setMyParent(ViewParent viewParent)1002 public void setMyParent(ViewParent viewParent) { 1003 reflector(_View_.class, realView).assignParent(viewParent); 1004 } 1005 1006 @Implementation getWindowVisibleDisplayFrame(Rect outRect)1007 protected void getWindowVisibleDisplayFrame(Rect outRect) { 1008 // TODO: figure out how to simulate this logic instead 1009 // if (mAttachInfo != null) { 1010 // mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect); 1011 1012 ShadowDisplay.getDefaultDisplay().getRectSize(outRect); 1013 } 1014 1015 @Implementation(minSdk = N) getWindowDisplayFrame(Rect outRect)1016 protected void getWindowDisplayFrame(Rect outRect) { 1017 // TODO: figure out how to simulate this logic instead 1018 // if (mAttachInfo != null) { 1019 // mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect); 1020 1021 ShadowDisplay.getDefaultDisplay().getRectSize(outRect); 1022 } 1023 1024 /** 1025 * Returns the layout resource id this view was inflated from. Backwards compatible version of 1026 * {@link View#getSourceLayoutResId()}, passes through to the underlying implementation on API 1027 * levels where it is supported. 1028 */ 1029 @Implementation(minSdk = Q) getSourceLayoutResId()1030 public int getSourceLayoutResId() { 1031 if (RuntimeEnvironment.getApiLevel() >= Q) { 1032 return reflector(_View_.class, realView).getSourceLayoutResId(); 1033 } else { 1034 return ShadowResources.getAttributeSetSourceResId(attributeSet); 1035 } 1036 } 1037 1038 public static class WindowIdHelper { getWindowId(ShadowView shadowView)1039 public static WindowId getWindowId(ShadowView shadowView) { 1040 if (shadowView.isAttachedToWindow()) { 1041 Object attachInfo = shadowView.getAttachInfo(); 1042 if (getField(attachInfo, "mWindowId") == null) { 1043 IWindowId iWindowId = new MyIWindowIdStub(); 1044 reflector(_AttachInfo_.class, attachInfo).setWindowId(new WindowId(iWindowId)); 1045 reflector(_AttachInfo_.class, attachInfo).setIWindowId(iWindowId); 1046 } 1047 } 1048 1049 return reflector(_View_.class, shadowView.realView).getWindowId(); 1050 } 1051 1052 private static class MyIWindowIdStub extends IWindowId.Stub { 1053 @Override registerFocusObserver(IWindowFocusObserver iWindowFocusObserver)1054 public void registerFocusObserver(IWindowFocusObserver iWindowFocusObserver) 1055 throws RemoteException {} 1056 1057 @Override unregisterFocusObserver(IWindowFocusObserver iWindowFocusObserver)1058 public void unregisterFocusObserver(IWindowFocusObserver iWindowFocusObserver) 1059 throws RemoteException {} 1060 1061 @Override isFocused()1062 public boolean isFocused() throws RemoteException { 1063 return true; 1064 } 1065 } 1066 } 1067 1068 /** Reflector interface for android.view.View.AttachInfo's internals. */ 1069 @ForType(className = "android.view.View$AttachInfo") 1070 interface _AttachInfo_ { 1071 1072 @Accessor("mIWindowId") setIWindowId(IWindowId iWindowId)1073 void setIWindowId(IWindowId iWindowId); 1074 1075 @Accessor("mWindowId") setWindowId(WindowId windowId)1076 void setWindowId(WindowId windowId); 1077 } 1078 1079 /** 1080 * Internal API to determine if native graphics is enabled. 1081 * 1082 * <p>This is currently public because it has to be accessed from multiple packages, but it is not 1083 * recommended to depend on this API. 1084 */ 1085 @Beta useRealGraphics()1086 public static boolean useRealGraphics() { 1087 GraphicsMode.Mode graphicsMode = ConfigurationRegistry.get(GraphicsMode.Mode.class); 1088 return graphicsMode == Mode.NATIVE && RuntimeEnvironment.getApiLevel() >= O; 1089 } 1090 1091 /** 1092 * Currently the default View scrolling implementation is broken and low-fidelty. For instance, 1093 * even if a View has no children, Robolectric will still happily set the scroll position of a 1094 * View. Long-term we want to eliminate this broken behavior, but in the mean time the real 1095 * scrolling behavior is enabled when native graphics are enabled, or when a system property is 1096 * set. 1097 */ useRealScrolling()1098 static boolean useRealScrolling() { 1099 return useRealGraphics() || Boolean.getBoolean("robolectric.useRealScrolling"); 1100 } 1101 } 1102