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

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

package org.robolectric.shadows;

import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;

import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.internal.ShadowExtractor;
import org.robolectric.util.ReflectionHelpers;

import java.util.HashMap;
import java.util.Map;

/**
 * Shadow of {@link android.view.accessibility.AccessibilityEvent}.
 */
@Implements(AccessibilityEvent.class)
public class ShadowAccessibilityEvent extends ShadowAccessibilityRecord {
  // Map of obtained instances of the class along with stack traces of how they were obtained
  private static final Map obtainedInstances =
      new HashMap<>();

  private static final SparseArray orderedInstances = new SparseArray<>();

  private static int sAllocationCount = 0;
  private int eventType;
  private CharSequence contentDescription;
  private CharSequence packageName;
  private CharSequence className;
  private boolean enabled;

  @RealObject
  private AccessibilityEvent realAccessibilityEvent;

  public void __constructor__() {
    ReflectionHelpers.setStaticField(AccessibilityEvent.class, "CREATOR", ShadowAccessibilityEvent.CREATOR);
  }

  public static final Parcelable.Creator CREATOR =
      new Parcelable.Creator() {

    @Override
    public AccessibilityEvent createFromParcel(Parcel source) {
      return obtain(orderedInstances.get(source.readInt()).mEvent);
    }

    @Override
    public AccessibilityEvent[] newArray(int size) {
      return new AccessibilityEvent[size];
    }};

  @Implementation
  public static AccessibilityEvent obtain(AccessibilityEvent event) {
    ShadowAccessibilityEvent shadowEvent =
        ((ShadowAccessibilityEvent) ShadowExtractor.extract(event));
    AccessibilityEvent obtainedInstance = shadowEvent.getClone();

    sAllocationCount++;
    StrictEqualityEventWrapper wrapper = new StrictEqualityEventWrapper(obtainedInstance);
    obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
    orderedInstances.put(sAllocationCount, wrapper);
    return obtainedInstance;
  }

  @Implementation
  public static AccessibilityEvent obtain(int eventType) {
    // We explicitly avoid allocating the AccessibilityEvent from the actual pool by using
    // the private constructor. Not doing so affects test suites which use both shadow and
    // non-shadow objects.
    final AccessibilityEvent obtainedInstance =
        ReflectionHelpers.callConstructor(AccessibilityEvent.class);
    final ShadowAccessibilityEvent shadowObtained =
        ((ShadowAccessibilityEvent) ShadowExtractor.extract(obtainedInstance));

    sAllocationCount++;
    StrictEqualityEventWrapper wrapper = new StrictEqualityEventWrapper(obtainedInstance);
    obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
    orderedInstances.put(sAllocationCount, wrapper);
    shadowObtained.eventType = eventType;
    return obtainedInstance;
  }

  @Implementation
  public static AccessibilityEvent obtain() {
    return obtain(0);
  }

  /**
   * Check for leaked objects that were {@code obtain}ed but never {@code recycle}d.
   * @param printUnrecycledEventsToSystemErr - if true, stack traces of calls to {@code obtain}
   * that lack matching calls to {@code recycle} are dumped to System.err.
   * @return {@code true} if there are unrecycled events
   */
  public static boolean areThereUnrecycledEvents(boolean printUnrecycledEventsToSystemErr) {
    if (printUnrecycledEventsToSystemErr) {
      for (final StrictEqualityEventWrapper wrapper : obtainedInstances.keySet()) {
        final ShadowAccessibilityEvent shadow =
            ((ShadowAccessibilityEvent) ShadowExtractor.extract(wrapper.mEvent));

        System.err.println(String.format(
            "Leaked AccessibilityEvent type: %d. Stack trace of allocation:",
            shadow.getEventType()));
        for (final StackTraceElement stackTraceElement : obtainedInstances.get(wrapper)) {
          System.err.println(stackTraceElement.toString());
        }
      }
    }

    return (obtainedInstances.size() != 0);
  }

  /**
   * Clear list of obtained instance objects. {@code areThereUnrecycledNodes} will always
   * return false if called immediately afterwards.
   */
  public static void resetObtainedInstances() {
    obtainedInstances.clear();
    orderedInstances.clear();
  }

  @Implementation
  public void recycle() {
    final StrictEqualityEventWrapper wrapper =
        new StrictEqualityEventWrapper(realAccessibilityEvent);
    if (!obtainedInstances.containsKey(wrapper)) {
      throw new IllegalStateException();
    }

    obtainedInstances.remove(wrapper);
    int keyOfWrapper = -1;
    for (int i = 0; i < orderedInstances.size(); i++) {
      int key = orderedInstances.keyAt(i);
      if (orderedInstances.get(key).equals(wrapper)) {
        keyOfWrapper = key;
        break;
      }
    }
    orderedInstances.remove(keyOfWrapper);
    setParcelableData(null);
  }

  @Implementation
  public void setEventType(int type) {
    eventType = type;
  }

  @Implementation
  public int getEventType() {
    return eventType;
  }

  @Implementation
  public void setContentDescription(CharSequence description) {
    contentDescription = description;
  }

  @Implementation
  public CharSequence getContentDescription() {
    return contentDescription;
  }

  @Implementation
  public void setPackageName(CharSequence name) {
    packageName = name;
  }

  @Implementation
  public CharSequence getPackageName() {
    return packageName;
  }

  @Implementation
  public void setClassName(CharSequence name) {
    className = name;
  }

  @Implementation
  public CharSequence getClassName() {
    return className;
  }

  @Implementation
  public void setEnabled(boolean value) {
    enabled = value;
  }

  @Implementation
  public boolean isEnabled() {
    return enabled;
  }

  @Override
  @Implementation
  public boolean equals(Object object) {
    if (!(object instanceof AccessibilityEvent)) {
      return false;
    }

    final AccessibilityEvent event = (AccessibilityEvent) object;
    final ShadowAccessibilityEvent otherShadow =
        (ShadowAccessibilityEvent) ShadowExtractor.extract(event);

    boolean areEqual = (eventType == otherShadow.eventType);
    areEqual &= (enabled == otherShadow.enabled);
    areEqual &= TextUtils.equals(contentDescription, otherShadow.contentDescription);
    areEqual &= TextUtils.equals(packageName, otherShadow.packageName);
    areEqual &= TextUtils.equals(className, otherShadow.className);
    boolean parcelableDataEqual = false;
    if (getParcelableData() == null && otherShadow.getParcelableData() == null){
      parcelableDataEqual = true;
    } else if (getParcelableData().equals(otherShadow.getParcelableData())) {
      parcelableDataEqual = true;
    }
    areEqual &= parcelableDataEqual;

    return areEqual;
  }

  @Override
  public int hashCode() {
    // This is 0 for a reason. If you change it, you will break the obtained instances map in
    // a manner that is remarkably difficult to debug. Having a dynamic hash code keeps this
    // object from being located in the map if it was mutated after being obtained.
    return 0;
  }

  /**
   * @return A shallow copy.
   */
  private AccessibilityEvent getClone() {
    // We explicitly avoid allocating the AccessibilityEvent from the actual pool by using
    // the private constructor. Not doing so affects test suites which use both shadow and
    // non-shadow objects.
    final AccessibilityEvent newEvent = ReflectionHelpers.callConstructor(AccessibilityEvent.class);
    final ShadowAccessibilityEvent newShadow =
        (ShadowAccessibilityEvent) ShadowExtractor.extract(newEvent);

    newShadow.eventType = eventType;
    newShadow.contentDescription = contentDescription;
    newShadow.packageName = packageName;
    newShadow.className = className;
    newShadow.enabled = enabled;
    newShadow.setParcelableData(getParcelableData());

    return newEvent;
  }

  /**
   * Private class to keep different events straight in the mObtainedInstances map.
   */
  private static class StrictEqualityEventWrapper {
    public final AccessibilityEvent mEvent;

    public StrictEqualityEventWrapper(AccessibilityEvent event) {
      mEvent = event;
    }

    @Override
    public boolean equals(Object object) {
      if (object == null) {
        return false;
      }

      final StrictEqualityEventWrapper wrapper = (StrictEqualityEventWrapper) object;
      return mEvent == wrapper.mEvent;
    }

    @Override
    public int hashCode() {
      return mEvent.hashCode();
    }
  }

  @Implementation
  public int describeContents() {
    return 0;
  }

  @Implementation
  public void writeToParcel(Parcel dest, int flags) {
    StrictEqualityEventWrapper wrapper = new StrictEqualityEventWrapper(realAccessibilityEvent);
    int keyOfWrapper = -1;
    for (int i = 0; i < orderedInstances.size(); i++) {
      if (orderedInstances.valueAt(i).equals(wrapper)) {
        keyOfWrapper = orderedInstances.keyAt(i);
        break;
      }
    }
    dest.writeInt(keyOfWrapper);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy