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