All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowView Maven / Gradle / Ivy

The newest version!
package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
import static org.robolectric.util.ReflectionHelpers.getField;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.IWindowFocusObserver;
import android.view.IWindowId;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
import android.view.WindowId;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import com.google.common.annotations.Beta;
import com.google.common.collect.ImmutableList;
import java.io.PrintStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.GraphicsMode;
import org.robolectric.annotation.GraphicsMode.Mode;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.ReflectorObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.config.ConfigurationRegistry;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowViewRootImpl.ViewRootImplReflector;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;

@Implements(View.class)
@SuppressLint("NewApi")
public class ShadowView {

  @RealObject protected View realView;
  @ReflectorObject protected _View_ viewReflector;
  private static final List globalClickListeners =
      new CopyOnWriteArrayList<>();
  private static final List globalLongClickListeners =
      new CopyOnWriteArrayList<>();
  private View.OnClickListener onClickListener;
  private View.OnLongClickListener onLongClickListener;
  private View.OnFocusChangeListener onFocusChangeListener;
  private View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener;
  private final HashSet onAttachStateChangeListeners =
      new HashSet<>();
  private final HashSet onLayoutChangeListeners = new HashSet<>();
  private boolean wasInvalidated;
  private View.OnTouchListener onTouchListener;
  protected AttributeSet attributeSet;
  public Point scrollToCoordinates = new Point();
  private boolean didRequestLayout;
  private MotionEvent lastTouchEvent;
  private int hapticFeedbackPerformed = -1;
  private boolean onLayoutWasCalled;
  private View.OnCreateContextMenuListener onCreateContextMenuListener;
  private Rect globalVisibleRect;
  private int layerType;
  private final ArrayList animations = new ArrayList<>();
  private AnimationRunner animationRunner;

  /**
   * Calls {@code performClick()} on a {@code View} after ensuring that it and its ancestors are
   * visible and that it is enabled.
   *
   * @param view the view to click on
   * @return true if {@code View.OnClickListener}s were found and fired, false otherwise.
   * @throws RuntimeException if the preconditions are not met.
   * @deprecated Please use Espresso for view interactions
   */
  @Deprecated
  public static boolean clickOn(View view) {
    ShadowView shadowView = Shadow.extract(view);
    return shadowView.checkedPerformClick();
  }

  /**
   * Returns a textual representation of the appearance of the object.
   *
   * @param view the view to visualize
   * @return Textual representation of the appearance of the object.
   */
  public static String visualize(View view) {
    Canvas canvas = new Canvas();
    view.draw(canvas);
    if (!useRealGraphics()) {
      ShadowCanvas shadowCanvas = Shadow.extract(canvas);
      return shadowCanvas.getDescription();
    } else {
      return "";
    }
  }

  /**
   * Emits an xml-like representation of the view to System.out.
   *
   * @param view the view to dump.
   * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)}
   */
  @SuppressWarnings("UnusedDeclaration")
  @Deprecated
  public static void dump(View view) {
    ShadowView shadowView = Shadow.extract(view);
    shadowView.dump();
  }

  /**
   * Returns the text contained within this view.
   *
   * @param view the view to scan for text
   * @return Text contained within this view.
   */
  @SuppressWarnings("UnusedDeclaration")
  public static String innerText(View view) {
    ShadowView shadowView = Shadow.extract(view);
    return shadowView.innerText();
  }

  static int[] getLocationInSurfaceCompat(View view) {
    int[] locationInSurface = new int[2];
    if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
      view.getLocationInSurface(locationInSurface);
    } else {
      view.getLocationInWindow(locationInSurface);
      Rect surfaceInsets =
          reflector(ViewRootImplReflector.class, view.getViewRootImpl())
              .getWindowAttributes()
              .surfaceInsets;
      locationInSurface[0] += surfaceInsets.left;
      locationInSurface[1] += surfaceInsets.top;
    }
    return locationInSurface;
  }

  /* Note: maxSdk is R because capturing `attributeSet` is not needed any more after R. */
  @Implementation(maxSdk = R)
  protected void __constructor__(
      Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) {
    this.attributeSet = attributeSet;
    reflector(_View_.class, realView)
        .__constructor__(context, attributeSet, defStyleAttr, defStyleRes);
  }

  @Implementation
  protected void setLayerType(int layerType, Paint paint) {
    this.layerType = layerType;
    reflector(_View_.class, realView).setLayerType(layerType, paint);
  }

  @Implementation
  protected void setOnFocusChangeListener(View.OnFocusChangeListener l) {
    onFocusChangeListener = l;
    reflector(_View_.class, realView).setOnFocusChangeListener(l);
  }

  @Implementation
  protected void setOnClickListener(View.OnClickListener onClickListener) {
    this.onClickListener = onClickListener;
    reflector(_View_.class, realView).setOnClickListener(onClickListener);
  }

  @Implementation
  protected void setOnLongClickListener(View.OnLongClickListener onLongClickListener) {
    this.onLongClickListener = onLongClickListener;
    reflector(_View_.class, realView).setOnLongClickListener(onLongClickListener);
  }

  @Implementation
  protected void setOnSystemUiVisibilityChangeListener(
      View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener) {
    this.onSystemUiVisibilityChangeListener = onSystemUiVisibilityChangeListener;
    reflector(_View_.class, realView)
        .setOnSystemUiVisibilityChangeListener(onSystemUiVisibilityChangeListener);
  }

  @Implementation
  protected void setOnCreateContextMenuListener(
      View.OnCreateContextMenuListener onCreateContextMenuListener) {
    this.onCreateContextMenuListener = onCreateContextMenuListener;
    reflector(_View_.class, realView).setOnCreateContextMenuListener(onCreateContextMenuListener);
  }

  @Implementation
  protected void addOnAttachStateChangeListener(
      View.OnAttachStateChangeListener onAttachStateChangeListener) {
    onAttachStateChangeListeners.add(onAttachStateChangeListener);
    reflector(_View_.class, realView).addOnAttachStateChangeListener(onAttachStateChangeListener);
  }

  @Implementation
  protected void removeOnAttachStateChangeListener(
      View.OnAttachStateChangeListener onAttachStateChangeListener) {
    onAttachStateChangeListeners.remove(onAttachStateChangeListener);
    reflector(_View_.class, realView)
        .removeOnAttachStateChangeListener(onAttachStateChangeListener);
  }

  @Implementation
  protected void addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener) {
    onLayoutChangeListeners.add(onLayoutChangeListener);
    reflector(_View_.class, realView).addOnLayoutChangeListener(onLayoutChangeListener);
  }

  @Implementation
  protected void removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener) {
    onLayoutChangeListeners.remove(onLayoutChangeListener);
    reflector(_View_.class, realView).removeOnLayoutChangeListener(onLayoutChangeListener);
  }

  @Implementation
  protected void draw(Canvas canvas) {
    Drawable background = realView.getBackground();
    if (background != null && !useRealGraphics()) {
      Object shadowCanvas = Shadow.extract(canvas);
      // Check that Canvas is not a Mockito mock
      if (shadowCanvas instanceof ShadowCanvas) {
        ((ShadowCanvas) shadowCanvas).appendDescription("background:");
      }
    }
    reflector(_View_.class, realView).draw(canvas);
  }

  @Implementation
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    onLayoutWasCalled = true;
    reflector(_View_.class, realView).onLayout(changed, left, top, right, bottom);
  }

  public boolean onLayoutWasCalled() {
    return onLayoutWasCalled;
  }

  @Implementation
  protected void requestLayout() {
    didRequestLayout = true;
    reflector(_View_.class, realView).requestLayout();
  }

  @Implementation
  protected boolean performClick() {
    for (View.OnClickListener listener : globalClickListeners) {
      listener.onClick(realView);
    }
    return reflector(_View_.class, realView).performClick();
  }

  /**
   * Registers an {@link View.OnClickListener} to the {@link ShadowView}.
   *
   * @param listener The {@link View.OnClickListener} to be registered.
   */
  public static void addGlobalPerformClickListener(View.OnClickListener listener) {
    ShadowView.globalClickListeners.add(listener);
  }

  /**
   * Removes an {@link View.OnClickListener} from the {@link ShadowView}.
   *
   * @param listener The {@link View.OnClickListener} to be removed.
   */
  public static void removeGlobalPerformClickListener(View.OnClickListener listener) {
    ShadowView.globalClickListeners.remove(listener);
  }

  @Implementation
  protected boolean performLongClick() {
    for (View.OnLongClickListener listener : globalLongClickListeners) {
      listener.onLongClick(realView);
    }
    return reflector(_View_.class, realView).performLongClick();
  }

  /**
   * Registers an {@link View.OnLongClickListener} to the {@link ShadowView}.
   *
   * @param listener The {@link View.OnLongClickListener} to be registered.
   */
  public static void addGlobalPerformLongClickListener(View.OnLongClickListener listener) {
    ShadowView.globalLongClickListeners.add(listener);
  }

  /**
   * Removes an {@link View.OnLongClickListener} from the {@link ShadowView}.
   *
   * @param listener The {@link View.OnLongClickListener} to be removed.
   */
  public static void removeGlobalPerformLongClickListener(View.OnLongClickListener listener) {
    ShadowView.globalLongClickListeners.remove(listener);
  }

  @Resetter
  public static void reset() {
    ShadowView.globalClickListeners.clear();
    ShadowView.globalLongClickListeners.clear();
  }

  public boolean didRequestLayout() {
    return didRequestLayout;
  }

  public void setDidRequestLayout(boolean didRequestLayout) {
    this.didRequestLayout = didRequestLayout;
  }

  public void setViewFocus(boolean hasFocus) {
    if (onFocusChangeListener != null) {
      onFocusChangeListener.onFocusChange(realView, hasFocus);
    }
  }

  @Implementation
  protected void invalidate() {
    wasInvalidated = true;
    reflector(_View_.class, realView).invalidate();
  }

  @Implementation
  protected boolean onTouchEvent(MotionEvent event) {
    lastTouchEvent = event;
    return reflector(_View_.class, realView).onTouchEvent(event);
  }

  @Implementation
  protected void setOnTouchListener(View.OnTouchListener onTouchListener) {
    this.onTouchListener = onTouchListener;
    reflector(_View_.class, realView).setOnTouchListener(onTouchListener);
  }

  public MotionEvent getLastTouchEvent() {
    return lastTouchEvent;
  }

  /**
   * Returns a string representation of this {@code View}. Unless overridden, it will be an empty
   * string.
   *
   * 

Robolectric extension. * * @return String representation of this view. */ public String innerText() { return ""; } /** * Dumps the status of this {@code View} to {@code System.out} * * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)} */ @Deprecated public void dump() { dump(System.out, 0); } /** * Dumps the status of this {@code View} to {@code System.out} at the given indentation level * * @param out Output stream. * @param indent Indentation level. * @deprecated - Please use {@link androidx.test.espresso.util.HumanReadables#describe(View)} */ @Deprecated public void dump(PrintStream out, int indent) { dumpFirstPart(out, indent); out.println("/>"); } @Deprecated protected void dumpFirstPart(PrintStream out, int indent) { dumpIndent(out, indent); out.print("<" + realView.getClass().getSimpleName()); dumpAttributes(out); } @Deprecated protected void dumpAttributes(PrintStream out) { if (realView.getId() > 0) { dumpAttribute( out, "id", realView.getContext().getResources().getResourceName(realView.getId())); } switch (realView.getVisibility()) { case View.VISIBLE: break; case View.INVISIBLE: dumpAttribute(out, "visibility", "INVISIBLE"); break; case View.GONE: dumpAttribute(out, "visibility", "GONE"); break; } } @Deprecated protected void dumpAttribute(PrintStream out, String name, String value) { out.print(" " + name + "=\"" + (value == null ? null : TextUtils.htmlEncode(value)) + "\""); } @Deprecated protected void dumpIndent(PrintStream out, int indent) { for (int i = 0; i < indent; i++) out.print(" "); } /** * @return whether or not {@link #invalidate()} has been called */ public boolean wasInvalidated() { return wasInvalidated; } /** Clears the wasInvalidated flag */ public void clearWasInvalidated() { wasInvalidated = false; } /** * Utility method for clicking on views exposing testing scenarios that are not possible when * using the actual app. * *

If running with LooperMode PAUSED will also idle the main Looper. * * @throws RuntimeException if the view is disabled or if the view or any of its parents are not * visible. * @return Return value of the underlying click operation. * @deprecated - Please use Espresso for View interactions. */ @Deprecated public boolean checkedPerformClick() { if (!realView.isShown()) { throw new RuntimeException("View is not visible and cannot be clicked"); } if (!realView.isEnabled()) { throw new RuntimeException("View is not enabled and cannot be clicked"); } boolean res = realView.performClick(); shadowMainLooper().idleIfPaused(); return res; } /** * @return Touch listener, if set. */ public View.OnTouchListener getOnTouchListener() { return onTouchListener; } /** * @return Returns click listener, if set. */ public View.OnClickListener getOnClickListener() { return onClickListener; } /** * @return Returns long click listener, if set. */ @Implementation(minSdk = R) public View.OnLongClickListener getOnLongClickListener() { if (RuntimeEnvironment.getApiLevel() >= R) { return reflector(_View_.class, realView).getOnLongClickListener(); } else { return onLongClickListener; } } /** * @return Returns system ui visibility change listener. */ public View.OnSystemUiVisibilityChangeListener getOnSystemUiVisibilityChangeListener() { return onSystemUiVisibilityChangeListener; } /** * @return Returns create ContextMenu listener, if set. */ public View.OnCreateContextMenuListener getOnCreateContextMenuListener() { return onCreateContextMenuListener; } /** * @return Returns the attached listeners, or the empty set if none are present. */ public Set getOnAttachStateChangeListeners() { return onAttachStateChangeListeners; } /** * @return Returns the layout change listeners, or the empty set if none are present. */ public Set getOnLayoutChangeListeners() { return onLayoutChangeListeners; } @Implementation protected boolean post(Runnable action) { if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) { ShadowApplication.getInstance().getForegroundThreadScheduler().post(action); return true; } else { return reflector(_View_.class, realView).post(action); } } @Implementation protected boolean postDelayed(Runnable action, long delayMills) { if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) { ShadowApplication.getInstance() .getForegroundThreadScheduler() .postDelayed(action, delayMills); return true; } else { return reflector(_View_.class, realView).postDelayed(action, delayMills); } } @Implementation protected void postInvalidateDelayed(long delayMilliseconds) { if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) { ShadowApplication.getInstance() .getForegroundThreadScheduler() .postDelayed( new Runnable() { @Override public void run() { realView.invalidate(); } }, delayMilliseconds); } else { reflector(_View_.class, realView).postInvalidateDelayed(delayMilliseconds); } } @Implementation protected boolean removeCallbacks(Runnable callback) { if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) { ShadowLegacyLooper shadowLooper = Shadow.extract(Looper.getMainLooper()); shadowLooper.getScheduler().remove(callback); return true; } else { return reflector(_View_.class, realView).removeCallbacks(callback); } } @Implementation protected void scrollTo(int x, int y) { if (useRealScrolling()) { reflector(_View_.class, realView).scrollTo(x, y); } else { reflector(_View_.class, realView) .onScrollChanged(x, y, scrollToCoordinates.x, scrollToCoordinates.y); scrollToCoordinates = new Point(x, y); reflector(_View_.class, realView).setMemberScrollX(x); reflector(_View_.class, realView).setMemberScrollY(y); } } @Implementation protected void scrollBy(int x, int y) { if (useRealScrolling()) { reflector(_View_.class, realView).scrollBy(x, y); } else { scrollTo(getScrollX() + x, getScrollY() + y); } } @Implementation protected int getScrollX() { if (useRealScrolling()) { return reflector(_View_.class, realView).getScrollX(); } else { return scrollToCoordinates != null ? scrollToCoordinates.x : 0; } } @Implementation protected int getScrollY() { if (useRealScrolling()) { return reflector(_View_.class, realView).getScrollY(); } else { return scrollToCoordinates != null ? scrollToCoordinates.y : 0; } } @Implementation protected void setScrollX(int scrollX) { if (useRealScrolling()) { reflector(_View_.class, realView).setScrollX(scrollX); } else { scrollTo(scrollX, scrollToCoordinates.y); } } @Implementation protected void setScrollY(int scrollY) { if (useRealScrolling()) { reflector(_View_.class, realView).setScrollY(scrollY); } else { scrollTo(scrollToCoordinates.x, scrollY); } } @Implementation protected void getLocationOnScreen(int[] outLocation) { reflector(_View_.class, realView).getLocationOnScreen(outLocation); int[] windowLocation = getWindowLocation(); outLocation[0] += windowLocation[0]; outLocation[1] += windowLocation[1]; } @Implementation(minSdk = O) protected void mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent) { reflector(_View_.class, realView).mapRectFromViewToScreenCoords(rect, clipToParent); int[] windowLocation = getWindowLocation(); rect.offset(windowLocation[0], windowLocation[1]); } // TODO(paulsowden): Should configure the correct frame on the ViewRootImpl instead and remove // this. private int[] getWindowLocation() { int[] location = new int[2]; LayoutParams rootParams = realView.getRootView().getLayoutParams(); if (rootParams instanceof WindowManager.LayoutParams) { location[0] = ((WindowManager.LayoutParams) rootParams).x; location[1] = ((WindowManager.LayoutParams) rootParams).y; } return location; } @Implementation protected int getLayerType() { return this.layerType; } /** Returns a list of all animations that have been set on this view. */ public ImmutableList getAnimations() { return ImmutableList.copyOf(animations); } /** Resets the list returned by {@link #getAnimations()} to an empty list. */ public void clearAnimations() { animations.clear(); } @Implementation protected void setAnimation(final Animation animation) { reflector(_View_.class, realView).setAnimation(animation); if (animation != null) { animations.add(animation); if (animationRunner != null) { animationRunner.cancel(); } animationRunner = new AnimationRunner(animation); animationRunner.start(); } } @Implementation protected void clearAnimation() { reflector(_View_.class, realView).clearAnimation(); if (animationRunner != null) { animationRunner.cancel(); animationRunner = null; } } @Implementation protected boolean initialAwakenScrollBars() { // Temporarily allow disabling initial awaken of scroll bars to aid in migration of tests to // default to window's being marked visible, this will be removed once migration is complete. if (Boolean.getBoolean("robolectric.disableInitialAwakenScrollBars")) { return false; } else { return viewReflector.initialAwakenScrollBars(); } } private class AnimationRunner implements Runnable { private final Animation animation; private final Transformation transformation = new Transformation(); private long startTime; private long elapsedTime; private boolean canceled; AnimationRunner(Animation animation) { this.animation = animation; } private void start() { startTime = animation.getStartTime(); long startOffset = animation.getStartOffset(); long startDelay = startTime == Animation.START_ON_FIRST_FRAME ? startOffset : (startTime + startOffset) - SystemClock.uptimeMillis(); Choreographer.getInstance() .postCallbackDelayed(Choreographer.CALLBACK_ANIMATION, this, null, startDelay); } private boolean step() { long animationTime = animation.getStartTime() == Animation.START_ON_FIRST_FRAME ? SystemClock.uptimeMillis() : (animation.getStartTime() + animation.getStartOffset() + elapsedTime); // Note in real android the parent is non-nullable, retain legacy robolectric behavior which // allows detached views to animate. if (!animation.isInitialized() && realView.getParent() != null) { View parent = (View) realView.getParent(); animation.initialize( realView.getWidth(), realView.getHeight(), parent.getWidth(), parent.getHeight()); } boolean next = animation.getTransformation(animationTime, transformation); // Note in real view implementation it doesn't check the animation equality before clearing, // but in the real implementation the animation listeners are posted so it doesn't race with // chained animations. if (realView.getAnimation() == animation && !next) { if (!animation.getFillAfter()) { realView.clearAnimation(); } } // We can't handle infinitely repeating animations in the current scheduling model, so abort // after one iteration. return next && (animation.getRepeatCount() != Animation.INFINITE || elapsedTime < animation.getDuration()); } @Override public void run() { // Abort if start time has been messed with, as this simulation is only designed to handle // standard situations. if (!canceled && animation.getStartTime() == startTime && step()) { // Start time updates for repeating animations and if START_ON_FIRST_FRAME. startTime = animation.getStartTime(); elapsedTime += ShadowLooper.looperMode().equals(LooperMode.Mode.LEGACY) ? Duration.ofNanos(ShadowChoreographer.getFrameInterval()).toMillis() : ShadowChoreographer.getFrameDelay().toMillis(); Choreographer.getInstance().postCallback(Choreographer.CALLBACK_ANIMATION, this, null); } else if (animationRunner == this) { animationRunner = null; } } public void cancel() { this.canceled = true; Choreographer.getInstance() .removeCallbacks(Choreographer.CALLBACK_ANIMATION, animationRunner, null); } } @Implementation protected boolean isAttachedToWindow() { return getAttachInfo() != null; } private Object getAttachInfo() { return reflector(_View_.class, realView).getAttachInfo(); } /** Reflector interface for {@link View}'s internals. */ @ForType(View.class) private interface _View_ { @Direct void draw(Canvas canvas); @Direct void onLayout(boolean changed, int left, int top, int right, int bottom); void assignParent(ViewParent viewParent); @Direct void setOnFocusChangeListener(View.OnFocusChangeListener l); @Direct void setLayerType(int layerType, Paint paint); @Direct void setOnClickListener(View.OnClickListener onClickListener); @Direct void setOnLongClickListener(View.OnLongClickListener onLongClickListener); @Direct View.OnLongClickListener getOnLongClickListener(); @Direct void setOnSystemUiVisibilityChangeListener( View.OnSystemUiVisibilityChangeListener onSystemUiVisibilityChangeListener); @Direct void setOnCreateContextMenuListener( View.OnCreateContextMenuListener onCreateContextMenuListener); @Direct void addOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener); @Direct void removeOnAttachStateChangeListener( View.OnAttachStateChangeListener onAttachStateChangeListener); @Direct void addOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener); @Direct void removeOnLayoutChangeListener(View.OnLayoutChangeListener onLayoutChangeListener); @Direct void requestLayout(); @Direct boolean performClick(); @Direct boolean performLongClick(); @Direct void invalidate(); @Direct boolean onTouchEvent(MotionEvent event); @Direct void setOnTouchListener(View.OnTouchListener onTouchListener); @Direct boolean post(Runnable action); @Direct boolean postDelayed(Runnable action, long delayMills); @Direct void postInvalidateDelayed(long delayMilliseconds); @Direct boolean removeCallbacks(Runnable callback); @Direct void setAnimation(final Animation animation); @Direct void clearAnimation(); @Direct boolean getGlobalVisibleRect(Rect rect, Point globalOffset); @Direct WindowId getWindowId(); @Accessor("mAttachInfo") Object getAttachInfo(); void onAttachedToWindow(); void onDetachedFromWindow(); void onScrollChanged(int l, int t, int oldl, int oldt); @Direct void getLocationOnScreen(int[] outLocation); @Direct void mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent); @Direct int getSourceLayoutResId(); @Direct boolean initialAwakenScrollBars(); @Accessor("mScrollX") void setMemberScrollX(int value); @Accessor("mScrollY") void setMemberScrollY(int value); @Direct void scrollTo(int x, int y); @Direct void scrollBy(int x, int y); @Direct int getScrollX(); @Direct int getScrollY(); @Direct void setScrollX(int value); @Direct void setScrollY(int value); @Direct void __constructor__(Context context, AttributeSet attributeSet, int defStyle); @Direct void __constructor__( Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes); } public void callOnAttachedToWindow() { reflector(_View_.class, realView).onAttachedToWindow(); } public void callOnDetachedFromWindow() { reflector(_View_.class, realView).onDetachedFromWindow(); } @Implementation protected WindowId getWindowId() { return WindowIdHelper.getWindowId(this); } @Implementation protected boolean performHapticFeedback(int hapticFeedbackType) { hapticFeedbackPerformed = hapticFeedbackType; return true; } @Implementation protected boolean getGlobalVisibleRect(Rect rect, Point globalOffset) { if (globalVisibleRect == null) { return reflector(_View_.class, realView).getGlobalVisibleRect(rect, globalOffset); } if (!globalVisibleRect.isEmpty()) { rect.set(globalVisibleRect); if (globalOffset != null) { rect.offset(-globalOffset.x, -globalOffset.y); } return true; } rect.setEmpty(); return false; } public void setGlobalVisibleRect(Rect rect) { if (rect != null) { globalVisibleRect = new Rect(); globalVisibleRect.set(rect); } else { globalVisibleRect = null; } } public int lastHapticFeedbackPerformed() { return hapticFeedbackPerformed; } public void setMyParent(ViewParent viewParent) { reflector(_View_.class, realView).assignParent(viewParent); } @Implementation protected void getWindowVisibleDisplayFrame(Rect outRect) { // TODO: figure out how to simulate this logic instead // if (mAttachInfo != null) { // mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect); ShadowDisplay.getDefaultDisplay().getRectSize(outRect); } @Implementation(minSdk = N) protected void getWindowDisplayFrame(Rect outRect) { // TODO: figure out how to simulate this logic instead // if (mAttachInfo != null) { // mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect); ShadowDisplay.getDefaultDisplay().getRectSize(outRect); } /** * Returns the layout resource id this view was inflated from. Backwards compatible version of * {@link View#getSourceLayoutResId()}, passes through to the underlying implementation on API * levels where it is supported. */ @Implementation(minSdk = Q) public int getSourceLayoutResId() { if (RuntimeEnvironment.getApiLevel() >= Q) { return reflector(_View_.class, realView).getSourceLayoutResId(); } else { return ShadowResources.getAttributeSetSourceResId(attributeSet); } } public static class WindowIdHelper { public static WindowId getWindowId(ShadowView shadowView) { if (shadowView.isAttachedToWindow()) { Object attachInfo = shadowView.getAttachInfo(); if (getField(attachInfo, "mWindowId") == null) { IWindowId iWindowId = new MyIWindowIdStub(); reflector(_AttachInfo_.class, attachInfo).setWindowId(new WindowId(iWindowId)); reflector(_AttachInfo_.class, attachInfo).setIWindowId(iWindowId); } } return reflector(_View_.class, shadowView.realView).getWindowId(); } private static class MyIWindowIdStub extends IWindowId.Stub { @Override public void registerFocusObserver(IWindowFocusObserver iWindowFocusObserver) throws RemoteException {} @Override public void unregisterFocusObserver(IWindowFocusObserver iWindowFocusObserver) throws RemoteException {} @Override public boolean isFocused() throws RemoteException { return true; } } } /** Reflector interface for android.view.View.AttachInfo's internals. */ @ForType(className = "android.view.View$AttachInfo") interface _AttachInfo_ { @Accessor("mIWindowId") void setIWindowId(IWindowId iWindowId); @Accessor("mWindowId") void setWindowId(WindowId windowId); } /** * Internal API to determine if native graphics is enabled. * *

This is currently public because it has to be accessed from multiple packages, but it is not * recommended to depend on this API. */ @Beta public static boolean useRealGraphics() { GraphicsMode.Mode graphicsMode = ConfigurationRegistry.get(GraphicsMode.Mode.class); return graphicsMode == Mode.NATIVE && RuntimeEnvironment.getApiLevel() >= O; } /** * Currently the default View scrolling implementation is broken and low-fidelity. For instance, * even if a View has no children, Robolectric will still happily set the scroll position of a * View. Long-term we want to eliminate this broken behavior, but in the mean time the real * scrolling behavior is enabled when native graphics are enabled, or when a system property is * set. */ static boolean useRealScrolling() { return useRealGraphics() || Boolean.parseBoolean(System.getProperty("robolectric.useRealScrolling", "true")); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy