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