1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.KITKAT; 4 import static android.os.Build.VERSION_CODES.O; 5 import static android.os.Build.VERSION_CODES.O_MR1; 6 import static org.robolectric.RuntimeEnvironment.getApiLevel; 7 8 import android.accessibilityservice.AccessibilityServiceInfo; 9 import android.content.Context; 10 import android.content.pm.ServiceInfo; 11 import android.os.Handler; 12 import android.os.Looper; 13 import android.os.Message; 14 import android.util.Log; 15 import android.view.accessibility.AccessibilityManager; 16 import android.view.accessibility.IAccessibilityManager; 17 import java.util.List; 18 import org.robolectric.annotation.HiddenApi; 19 import org.robolectric.annotation.Implementation; 20 import org.robolectric.annotation.Implements; 21 import org.robolectric.annotation.RealObject; 22 import org.robolectric.annotation.Resetter; 23 import org.robolectric.shadow.api.Shadow; 24 import org.robolectric.util.ReflectionHelpers; 25 import org.robolectric.util.ReflectionHelpers.ClassParameter; 26 27 @Implements(AccessibilityManager.class) 28 public class ShadowAccessibilityManager { 29 private static AccessibilityManager sInstance; 30 private static final Object sInstanceSync = new Object(); 31 32 @RealObject AccessibilityManager realAccessibilityManager; 33 private boolean enabled; 34 private List<AccessibilityServiceInfo> installedAccessibilityServiceList; 35 private List<AccessibilityServiceInfo> enabledAccessibilityServiceList; 36 private List<ServiceInfo> accessibilityServiceList; 37 private boolean touchExplorationEnabled; 38 39 private static boolean isAccessibilityButtonSupported = true; 40 41 @Resetter 42 public static void reset() { 43 synchronized (sInstanceSync) { 44 sInstance = null; 45 } 46 isAccessibilityButtonSupported = true; 47 } 48 49 @HiddenApi 50 @Implementation 51 public static AccessibilityManager getInstance(Context context) throws Exception { 52 synchronized (sInstanceSync) { 53 if (sInstance == null) { 54 sInstance = createInstance(context); 55 } 56 } 57 return sInstance; 58 } 59 60 private static AccessibilityManager createInstance(Context context) throws Exception { 61 if (getApiLevel() >= KITKAT) { 62 AccessibilityManager accessibilityManager = Shadow.newInstance(AccessibilityManager.class, 63 new Class[]{Context.class, IAccessibilityManager.class, int.class}, 64 new Object[]{context, ReflectionHelpers.createNullProxy(IAccessibilityManager.class), 0}); 65 ReflectionHelpers.setField(accessibilityManager, "mHandler", new MyHandler(context.getMainLooper(), accessibilityManager)); 66 return accessibilityManager; 67 } else { 68 AccessibilityManager accessibilityManager = Shadow.newInstance(AccessibilityManager.class, new Class[0], new Object[0]); 69 ReflectionHelpers.setField(accessibilityManager, "mHandler", new MyHandler(context.getMainLooper(), accessibilityManager)); 70 return accessibilityManager; 71 } 72 } 73 74 @Implementation 75 protected boolean addAccessibilityStateChangeListener( 76 AccessibilityManager.AccessibilityStateChangeListener listener) { 77 return true; 78 } 79 80 @Implementation 81 protected boolean removeAccessibilityStateChangeListener( 82 AccessibilityManager.AccessibilityStateChangeListener listener) { 83 return true; 84 } 85 86 @Implementation 87 protected List<ServiceInfo> getAccessibilityServiceList() { 88 return accessibilityServiceList; 89 } 90 91 public void setAccessibilityServiceList(List<ServiceInfo> accessibilityServiceList) { 92 this.accessibilityServiceList = accessibilityServiceList; 93 } 94 95 @Implementation 96 protected List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( 97 int feedbackTypeFlags) { 98 return enabledAccessibilityServiceList; 99 } 100 101 public void setEnabledAccessibilityServiceList(List<AccessibilityServiceInfo> enabledAccessibilityServiceList) { 102 this.enabledAccessibilityServiceList = enabledAccessibilityServiceList; 103 } 104 105 @Implementation 106 protected List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { 107 return installedAccessibilityServiceList; 108 } 109 110 public void setInstalledAccessibilityServiceList(List<AccessibilityServiceInfo> installedAccessibilityServiceList) { 111 this.installedAccessibilityServiceList = installedAccessibilityServiceList; 112 } 113 114 @Implementation 115 protected boolean isEnabled() { 116 return enabled; 117 } 118 119 public void setEnabled(boolean enabled) { 120 this.enabled = enabled; 121 ReflectionHelpers.setField(realAccessibilityManager, "mIsEnabled", enabled); 122 } 123 124 @Implementation 125 protected boolean isTouchExplorationEnabled() { 126 return touchExplorationEnabled; 127 } 128 129 public void setTouchExplorationEnabled(boolean touchExplorationEnabled) { 130 this.touchExplorationEnabled = touchExplorationEnabled; 131 } 132 133 /** 134 * Returns {@code true} by default, or the value specified via {@link 135 * #setAccessibilityButtonSupported(boolean)} 136 */ 137 @Implementation(minSdk = O_MR1) 138 protected static boolean isAccessibilityButtonSupported() { 139 return isAccessibilityButtonSupported; 140 } 141 142 @HiddenApi 143 @Implementation(minSdk = O) 144 protected void performAccessibilityShortcut() { 145 setEnabled(true); 146 setTouchExplorationEnabled(true); 147 } 148 149 /** 150 * Sets that the system navigation area is supported accessibility button; controls the return 151 * value of {@link AccessibilityManager#isAccessibilityButtonSupported()}. 152 */ 153 public static void setAccessibilityButtonSupported(boolean supported) { 154 isAccessibilityButtonSupported = supported; 155 } 156 157 static class MyHandler extends Handler { 158 private static final int DO_SET_STATE = 10; 159 private final AccessibilityManager accessibilityManager; 160 161 MyHandler(Looper mainLooper, AccessibilityManager accessibilityManager) { 162 super(mainLooper); 163 this.accessibilityManager = accessibilityManager; 164 } 165 166 @Override 167 public void handleMessage(Message message) { 168 switch (message.what) { 169 case DO_SET_STATE: 170 ReflectionHelpers.callInstanceMethod(accessibilityManager, "setState", ClassParameter.from(int.class, message.arg1)); 171 return; 172 default: 173 Log.w("AccessibilityManager", "Unknown message type: " + message.what); 174 } 175 } 176 } 177 } 178