1/* 2 * Copyright (C) 2012 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.internal.policy.impl; 18 19import android.accessibilityservice.AccessibilityServiceInfo; 20import android.app.ActivityManager; 21import android.content.ComponentName; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.pm.ServiceInfo; 25import android.media.AudioManager; 26import android.media.Ringtone; 27import android.media.RingtoneManager; 28import android.os.Handler; 29import android.os.Message; 30import android.os.RemoteException; 31import android.os.ServiceManager; 32import android.os.UserManager; 33import android.provider.Settings; 34import android.speech.tts.TextToSpeech; 35import android.util.MathUtils; 36import android.view.IWindowManager; 37import android.view.MotionEvent; 38import android.view.accessibility.AccessibilityManager; 39import android.view.accessibility.IAccessibilityManager; 40 41import com.android.internal.R; 42 43import java.util.ArrayList; 44import java.util.Iterator; 45import java.util.List; 46 47public class EnableAccessibilityController { 48 49 private static final int SPEAK_WARNING_DELAY_MILLIS = 2000; 50 private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000; 51 52 public static final int MESSAGE_SPEAK_WARNING = 1; 53 public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2; 54 public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3; 55 56 private final Handler mHandler = new Handler() { 57 @Override 58 public void handleMessage(Message message) { 59 switch (message.what) { 60 case MESSAGE_SPEAK_WARNING: { 61 String text = mContext.getString(R.string.continue_to_enable_accessibility); 62 mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null); 63 } break; 64 case MESSAGE_SPEAK_ENABLE_CANCELED: { 65 String text = mContext.getString(R.string.enable_accessibility_canceled); 66 mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null); 67 } break; 68 case MESSAGE_ENABLE_ACCESSIBILITY: { 69 enableAccessibility(); 70 mTone.play(); 71 mTts.speak(mContext.getString(R.string.accessibility_enabled), 72 TextToSpeech.QUEUE_FLUSH, null); 73 } break; 74 } 75 } 76 }; 77 78 private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface( 79 ServiceManager.getService("window")); 80 81 private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager 82 .Stub.asInterface(ServiceManager.getService("accessibility")); 83 84 85 private final Context mContext; 86 private final UserManager mUserManager; 87 private final TextToSpeech mTts; 88 private final Ringtone mTone; 89 90 private final float mTouchSlop; 91 92 private boolean mDestroyed; 93 private boolean mCanceled; 94 95 private float mFirstPointerDownX; 96 private float mFirstPointerDownY; 97 private float mSecondPointerDownX; 98 private float mSecondPointerDownY; 99 100 public EnableAccessibilityController(Context context) { 101 mContext = context; 102 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 103 mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() { 104 @Override 105 public void onInit(int status) { 106 if (mDestroyed) { 107 mTts.shutdown(); 108 } 109 } 110 }); 111 mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI); 112 mTone.setStreamType(AudioManager.STREAM_MUSIC); 113 mTouchSlop = context.getResources().getDimensionPixelSize( 114 R.dimen.accessibility_touch_slop); 115 } 116 117 public static boolean canEnableAccessibilityViaGesture(Context context) { 118 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context); 119 // Accessibility is enabled and there is an enabled speaking 120 // accessibility service, then we have nothing to do. 121 if (accessibilityManager.isEnabled() 122 && !accessibilityManager.getEnabledAccessibilityServiceList( 123 AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) { 124 return false; 125 } 126 // If the global gesture is enabled and there is a speaking service 127 // installed we are good to go, otherwise there is nothing to do. 128 return Settings.Global.getInt(context.getContentResolver(), 129 Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1 130 && !getInstalledSpeakingAccessibilityServices(context).isEmpty(); 131 } 132 133 private static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices( 134 Context context) { 135 List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>(); 136 services.addAll(AccessibilityManager.getInstance(context) 137 .getInstalledAccessibilityServiceList()); 138 Iterator<AccessibilityServiceInfo> iterator = services.iterator(); 139 while (iterator.hasNext()) { 140 AccessibilityServiceInfo service = iterator.next(); 141 if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) { 142 iterator.remove(); 143 } 144 } 145 return services; 146 } 147 148 public void onDestroy() { 149 mDestroyed = true; 150 } 151 152 public boolean onInterceptTouchEvent(MotionEvent event) { 153 if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN 154 && event.getPointerCount() == 2) { 155 mFirstPointerDownX = event.getX(0); 156 mFirstPointerDownY = event.getY(0); 157 mSecondPointerDownX = event.getX(1); 158 mSecondPointerDownY = event.getY(1); 159 mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING, 160 SPEAK_WARNING_DELAY_MILLIS); 161 mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY, 162 ENABLE_ACCESSIBILITY_DELAY_MILLIS); 163 return true; 164 } 165 return false; 166 } 167 168 public boolean onTouchEvent(MotionEvent event) { 169 final int pointerCount = event.getPointerCount(); 170 final int action = event.getActionMasked(); 171 if (mCanceled) { 172 if (action == MotionEvent.ACTION_UP) { 173 mCanceled = false; 174 } 175 return true; 176 } 177 switch (action) { 178 case MotionEvent.ACTION_POINTER_DOWN: { 179 if (pointerCount > 2) { 180 cancel(); 181 } 182 } break; 183 case MotionEvent.ACTION_MOVE: { 184 final float firstPointerMove = MathUtils.dist(event.getX(0), 185 event.getY(0), mFirstPointerDownX, mFirstPointerDownY); 186 if (Math.abs(firstPointerMove) > mTouchSlop) { 187 cancel(); 188 } 189 final float secondPointerMove = MathUtils.dist(event.getX(1), 190 event.getY(1), mSecondPointerDownX, mSecondPointerDownY); 191 if (Math.abs(secondPointerMove) > mTouchSlop) { 192 cancel(); 193 } 194 } break; 195 case MotionEvent.ACTION_POINTER_UP: 196 case MotionEvent.ACTION_CANCEL: { 197 cancel(); 198 } break; 199 } 200 return true; 201 } 202 203 private void cancel() { 204 mCanceled = true; 205 if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) { 206 mHandler.removeMessages(MESSAGE_SPEAK_WARNING); 207 } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) { 208 mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED); 209 } 210 mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY); 211 } 212 213 private void enableAccessibility() { 214 List<AccessibilityServiceInfo> services = getInstalledSpeakingAccessibilityServices( 215 mContext); 216 if (services.isEmpty()) { 217 return; 218 } 219 boolean keyguardLocked = false; 220 try { 221 keyguardLocked = mWindowManager.isKeyguardLocked(); 222 } catch (RemoteException re) { 223 /* ignore */ 224 } 225 226 final boolean hasMoreThanOneUser = mUserManager.getUsers().size() > 1; 227 228 AccessibilityServiceInfo service = services.get(0); 229 boolean enableTouchExploration = (service.flags 230 & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; 231 // Try to find a service supporting explore by touch. 232 if (!enableTouchExploration) { 233 final int serviceCount = services.size(); 234 for (int i = 1; i < serviceCount; i++) { 235 AccessibilityServiceInfo candidate = services.get(i); 236 if ((candidate.flags & AccessibilityServiceInfo 237 .FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0) { 238 enableTouchExploration = true; 239 service = candidate; 240 break; 241 } 242 } 243 } 244 245 ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo; 246 ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name); 247 if (!keyguardLocked || !hasMoreThanOneUser) { 248 final int userId = ActivityManager.getCurrentUser(); 249 String enabledServiceString = componentName.flattenToString(); 250 ContentResolver resolver = mContext.getContentResolver(); 251 // Enable one speaking accessibility service. 252 Settings.Secure.putStringForUser(resolver, 253 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 254 enabledServiceString, userId); 255 // Allow the services we just enabled to toggle touch exploration. 256 Settings.Secure.putStringForUser(resolver, 257 Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, 258 enabledServiceString, userId); 259 // Enable touch exploration. 260 if (enableTouchExploration) { 261 Settings.Secure.putIntForUser(resolver, Settings.Secure.TOUCH_EXPLORATION_ENABLED, 262 1, userId); 263 } 264 // Enable accessibility script injection (AndroidVox) for web content. 265 Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 266 1, userId); 267 // Turn on accessibility mode last. 268 Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_ENABLED, 269 1, userId); 270 } else if (keyguardLocked) { 271 try { 272 mAccessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved( 273 componentName, enableTouchExploration); 274 } catch (RemoteException re) { 275 /* ignore */ 276 } 277 } 278 } 279} 280