package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static org.robolectric.RuntimeEnvironment.getApiLevel;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import java.util.List;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;

@Implements(AccessibilityManager.class)
public class ShadowAccessibilityManager {
  private static AccessibilityManager sInstance;
  private static final Object sInstanceSync = new Object();

  @RealObject AccessibilityManager realAccessibilityManager;
  private boolean enabled;
  private List<AccessibilityServiceInfo> installedAccessibilityServiceList;
  private List<AccessibilityServiceInfo> enabledAccessibilityServiceList;
  private List<ServiceInfo> accessibilityServiceList;
  private boolean touchExplorationEnabled;

  private static boolean isAccessibilityButtonSupported = true;

  @Resetter
  public static void reset() {
    synchronized (sInstanceSync) {
      sInstance = null;
    }
    isAccessibilityButtonSupported = true;
  }

  @HiddenApi
  @Implementation
  public static AccessibilityManager getInstance(Context context) throws Exception {
    synchronized (sInstanceSync) {
      if (sInstance == null) {
          sInstance = createInstance(context);
      }
    }
    return sInstance;
  }

  private static AccessibilityManager createInstance(Context context) throws Exception {
    if (getApiLevel() >= KITKAT) {
      AccessibilityManager accessibilityManager = Shadow.newInstance(AccessibilityManager.class,
          new Class[]{Context.class, IAccessibilityManager.class, int.class},
          new Object[]{context, ReflectionHelpers.createNullProxy(IAccessibilityManager.class), 0});
      ReflectionHelpers.setField(accessibilityManager, "mHandler", new MyHandler(context.getMainLooper(), accessibilityManager));
      return accessibilityManager;
    } else {
      AccessibilityManager accessibilityManager = Shadow.newInstance(AccessibilityManager.class, new Class[0], new Object[0]);
      ReflectionHelpers.setField(accessibilityManager, "mHandler", new MyHandler(context.getMainLooper(), accessibilityManager));
      return accessibilityManager;
    }
  }

  @Implementation
  protected boolean addAccessibilityStateChangeListener(
      AccessibilityManager.AccessibilityStateChangeListener listener) {
    return true;
  }

  @Implementation
  protected boolean removeAccessibilityStateChangeListener(
      AccessibilityManager.AccessibilityStateChangeListener listener) {
    return true;
  }

  @Implementation
  protected List<ServiceInfo> getAccessibilityServiceList() {
    return accessibilityServiceList;
  }

  public void setAccessibilityServiceList(List<ServiceInfo> accessibilityServiceList) {
    this.accessibilityServiceList = accessibilityServiceList;
  }

  @Implementation
  protected List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
      int feedbackTypeFlags) {
    return enabledAccessibilityServiceList;
  }

  public void setEnabledAccessibilityServiceList(List<AccessibilityServiceInfo> enabledAccessibilityServiceList) {
    this.enabledAccessibilityServiceList = enabledAccessibilityServiceList;
  }

  @Implementation
  protected List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
    return installedAccessibilityServiceList;
  }

  public void setInstalledAccessibilityServiceList(List<AccessibilityServiceInfo> installedAccessibilityServiceList) {
    this.installedAccessibilityServiceList = installedAccessibilityServiceList;
  }

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

  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
    ReflectionHelpers.setField(realAccessibilityManager, "mIsEnabled", enabled);
  }

  @Implementation
  protected boolean isTouchExplorationEnabled() {
    return touchExplorationEnabled;
  }

  public void setTouchExplorationEnabled(boolean touchExplorationEnabled) {
    this.touchExplorationEnabled = touchExplorationEnabled;
  }

  /**
   * Returns {@code true} by default, or the value specified via {@link
   * #setAccessibilityButtonSupported(boolean)}
   */
  @Implementation(minSdk = O_MR1)
  protected static boolean isAccessibilityButtonSupported() {
    return isAccessibilityButtonSupported;
  }

  @HiddenApi
  @Implementation(minSdk = O)
  protected void performAccessibilityShortcut() {
    setEnabled(true);
    setTouchExplorationEnabled(true);
  }

  /**
   * Sets that the system navigation area is supported accessibility button; controls the return
   * value of {@link AccessibilityManager#isAccessibilityButtonSupported()}.
   */
  public static void setAccessibilityButtonSupported(boolean supported) {
    isAccessibilityButtonSupported = supported;
  }

  static class MyHandler extends Handler {
    private static final int DO_SET_STATE = 10;
    private final AccessibilityManager accessibilityManager;

    MyHandler(Looper mainLooper, AccessibilityManager accessibilityManager) {
      super(mainLooper);
      this.accessibilityManager = accessibilityManager;
    }

    @Override
    public void handleMessage(Message message) {
      switch (message.what) {
        case DO_SET_STATE:
          ReflectionHelpers.callInstanceMethod(accessibilityManager, "setState", ClassParameter.from(int.class, message.arg1));
          return;
        default:
          Log.w("AccessibilityManager", "Unknown message type: " + message.what);
      }
    }
  }
}

@@ -3061,32 +3061,207 @@
 turn true;%0A  %7D%0A%0A
+  @Implementation(minSdk = O)%0A  protected void addAccessibilityStateChangeListener(%0A      AccessibilityManager.AccessibilityStateChangeListener listener, Handler handler) %7B%7D%0A%0A
   @Implementatio

Add ShadowAccessibilityManager#addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler).