Home | History | Annotate | Download | only in impl
      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 
     17 package com.android.internal.policy.impl;
     18 
     19 import android.accessibilityservice.AccessibilityServiceInfo;
     20 import android.app.ActivityManager;
     21 import android.content.ComponentName;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.pm.ServiceInfo;
     25 import android.media.AudioManager;
     26 import android.media.Ringtone;
     27 import android.media.RingtoneManager;
     28 import android.os.Handler;
     29 import android.os.Message;
     30 import android.os.RemoteException;
     31 import android.os.ServiceManager;
     32 import android.os.UserManager;
     33 import android.provider.Settings;
     34 import android.speech.tts.TextToSpeech;
     35 import android.util.MathUtils;
     36 import android.view.IWindowManager;
     37 import android.view.MotionEvent;
     38 import android.view.accessibility.AccessibilityManager;
     39 import android.view.accessibility.IAccessibilityManager;
     40 
     41 import com.android.internal.R;
     42 
     43 import java.util.ArrayList;
     44 import java.util.Iterator;
     45 import java.util.List;
     46 
     47 public 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 Runnable mOnAccessibilityEnabledCallback;
     87     private final UserManager mUserManager;
     88     private final TextToSpeech mTts;
     89     private final Ringtone mTone;
     90 
     91     private final float mTouchSlop;
     92 
     93     private boolean mDestroyed;
     94     private boolean mCanceled;
     95 
     96     private float mFirstPointerDownX;
     97     private float mFirstPointerDownY;
     98     private float mSecondPointerDownX;
     99     private float mSecondPointerDownY;
    100 
    101     public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) {
    102         mContext = context;
    103         mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback;
    104         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
    105         mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
    106             @Override
    107             public void onInit(int status) {
    108                 if (mDestroyed) {
    109                     mTts.shutdown();
    110                 }
    111             }
    112         });
    113         mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
    114         mTone.setStreamType(AudioManager.STREAM_MUSIC);
    115         mTouchSlop = context.getResources().getDimensionPixelSize(
    116                 R.dimen.accessibility_touch_slop);
    117     }
    118 
    119     public static boolean canEnableAccessibilityViaGesture(Context context) {
    120         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
    121         // Accessibility is enabled and there is an enabled speaking
    122         // accessibility service, then we have nothing to do.
    123         if (accessibilityManager.isEnabled()
    124                 && !accessibilityManager.getEnabledAccessibilityServiceList(
    125                         AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
    126             return false;
    127         }
    128         // If the global gesture is enabled and there is a speaking service
    129         // installed we are good to go, otherwise there is nothing to do.
    130         return Settings.Global.getInt(context.getContentResolver(),
    131                 Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
    132                 && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
    133     }
    134 
    135     private static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
    136             Context context) {
    137         List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>();
    138         services.addAll(AccessibilityManager.getInstance(context)
    139                 .getInstalledAccessibilityServiceList());
    140         Iterator<AccessibilityServiceInfo> iterator = services.iterator();
    141         while (iterator.hasNext()) {
    142             AccessibilityServiceInfo service = iterator.next();
    143             if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
    144                 iterator.remove();
    145             }
    146         }
    147         return services;
    148     }
    149 
    150     public void onDestroy() {
    151         mDestroyed = true;
    152     }
    153 
    154     public boolean onInterceptTouchEvent(MotionEvent event) {
    155         if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
    156                 && event.getPointerCount() == 2) {
    157             mFirstPointerDownX = event.getX(0);
    158             mFirstPointerDownY = event.getY(0);
    159             mSecondPointerDownX = event.getX(1);
    160             mSecondPointerDownY = event.getY(1);
    161             mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
    162                     SPEAK_WARNING_DELAY_MILLIS);
    163             mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
    164                    ENABLE_ACCESSIBILITY_DELAY_MILLIS);
    165             return true;
    166         }
    167         return false;
    168     }
    169 
    170     public boolean onTouchEvent(MotionEvent event) {
    171         final int pointerCount = event.getPointerCount();
    172         final int action = event.getActionMasked();
    173         if (mCanceled) {
    174             if (action == MotionEvent.ACTION_UP) {
    175                 mCanceled = false;
    176             }
    177             return true;
    178         }
    179         switch (action) {
    180             case MotionEvent.ACTION_POINTER_DOWN: {
    181                 if (pointerCount > 2) {
    182                     cancel();
    183                 }
    184             } break;
    185             case MotionEvent.ACTION_MOVE: {
    186                 final float firstPointerMove = MathUtils.dist(event.getX(0),
    187                         event.getY(0), mFirstPointerDownX, mFirstPointerDownY);
    188                 if (Math.abs(firstPointerMove) > mTouchSlop) {
    189                     cancel();
    190                 }
    191                 final float secondPointerMove = MathUtils.dist(event.getX(1),
    192                         event.getY(1), mSecondPointerDownX, mSecondPointerDownY);
    193                 if (Math.abs(secondPointerMove) > mTouchSlop) {
    194                     cancel();
    195                 }
    196             } break;
    197             case MotionEvent.ACTION_POINTER_UP:
    198             case MotionEvent.ACTION_CANCEL: {
    199                 cancel();
    200             } break;
    201         }
    202         return true;
    203     }
    204 
    205     private void cancel() {
    206         mCanceled = true;
    207         if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
    208             mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
    209         } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
    210             mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
    211         }
    212         mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
    213     }
    214 
    215     private void enableAccessibility() {
    216         List<AccessibilityServiceInfo> services = getInstalledSpeakingAccessibilityServices(
    217                 mContext);
    218         if (services.isEmpty()) {
    219             return;
    220         }
    221         boolean keyguardLocked = false;
    222         try {
    223             keyguardLocked = mWindowManager.isKeyguardLocked();
    224         } catch (RemoteException re) {
    225             /* ignore */
    226         }
    227 
    228         final boolean hasMoreThanOneUser = mUserManager.getUsers().size() > 1;
    229 
    230         AccessibilityServiceInfo service = services.get(0);
    231         boolean enableTouchExploration = (service.flags
    232                 & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
    233         // Try to find a service supporting explore by touch.
    234         if (!enableTouchExploration) {
    235             final int serviceCount = services.size();
    236             for (int i = 1; i < serviceCount; i++) {
    237                 AccessibilityServiceInfo candidate = services.get(i);
    238                 if ((candidate.flags & AccessibilityServiceInfo
    239                         .FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0) {
    240                     enableTouchExploration = true;
    241                     service = candidate;
    242                     break;
    243                 }
    244             }
    245         }
    246 
    247         ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
    248         ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
    249         if (!keyguardLocked || !hasMoreThanOneUser) {
    250             final int userId = ActivityManager.getCurrentUser();
    251             String enabledServiceString = componentName.flattenToString();
    252             ContentResolver resolver = mContext.getContentResolver();
    253             // Enable one speaking accessibility service.
    254             Settings.Secure.putStringForUser(resolver,
    255                     Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
    256                     enabledServiceString, userId);
    257             // Allow the services we just enabled to toggle touch exploration.
    258             Settings.Secure.putStringForUser(resolver,
    259                     Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
    260                     enabledServiceString, userId);
    261             // Enable touch exploration.
    262             if (enableTouchExploration) {
    263                 Settings.Secure.putIntForUser(resolver, Settings.Secure.TOUCH_EXPLORATION_ENABLED,
    264                         1, userId);
    265             }
    266             // Enable accessibility script injection (AndroidVox) for web content.
    267             Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
    268                     1, userId);
    269             // Turn on accessibility mode last.
    270             Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_ENABLED,
    271                     1, userId);
    272         } else if (keyguardLocked) {
    273             try {
    274                 mAccessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
    275                         componentName, enableTouchExploration);
    276             } catch (RemoteException re) {
    277                 /* ignore */
    278             }
    279         }
    280 
    281         mOnAccessibilityEnabledCallback.run();
    282     }
    283 }
    284