Home | History | Annotate | Download | only in policy
      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.server.policy;
     18 
     19 import android.accessibilityservice.AccessibilityService;
     20 import android.accessibilityservice.AccessibilityServiceInfo;
     21 import android.annotation.Nullable;
     22 import android.app.ActivityManager;
     23 import android.content.ComponentName;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.pm.ServiceInfo;
     27 import android.media.AudioManager;
     28 import android.media.Ringtone;
     29 import android.media.RingtoneManager;
     30 import android.os.Handler;
     31 import android.os.Message;
     32 import android.os.RemoteException;
     33 import android.os.ServiceManager;
     34 import android.os.UserManager;
     35 import android.provider.Settings;
     36 import android.speech.tts.TextToSpeech;
     37 import android.util.Log;
     38 import android.util.MathUtils;
     39 import android.view.IWindowManager;
     40 import android.view.MotionEvent;
     41 import android.view.WindowManager;
     42 import android.view.WindowManagerGlobal;
     43 import android.view.WindowManagerInternal;
     44 import android.view.accessibility.AccessibilityManager;
     45 import android.view.accessibility.IAccessibilityManager;
     46 
     47 import com.android.internal.R;
     48 import com.android.server.LocalServices;
     49 
     50 import java.util.ArrayList;
     51 import java.util.Iterator;
     52 import java.util.List;
     53 
     54 public class EnableAccessibilityController {
     55     private static final String TAG = "EnableAccessibilityController";
     56 
     57     private static final int SPEAK_WARNING_DELAY_MILLIS = 2000;
     58     private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000;
     59 
     60     public static final int MESSAGE_SPEAK_WARNING = 1;
     61     public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2;
     62     public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3;
     63 
     64     private final Handler mHandler = new Handler() {
     65         @Override
     66         public void handleMessage(Message message) {
     67             switch (message.what) {
     68                 case MESSAGE_SPEAK_WARNING: {
     69                     String text = mContext.getString(R.string.continue_to_enable_accessibility);
     70                     mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
     71                 } break;
     72                 case MESSAGE_SPEAK_ENABLE_CANCELED: {
     73                     String text = mContext.getString(R.string.enable_accessibility_canceled);
     74                     mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
     75                 } break;
     76                 case MESSAGE_ENABLE_ACCESSIBILITY: {
     77                     enableAccessibility();
     78                     mTone.play();
     79                     mTts.speak(mContext.getString(R.string.accessibility_enabled),
     80                             TextToSpeech.QUEUE_FLUSH, null);
     81                 } break;
     82             }
     83         }
     84     };
     85 
     86     private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
     87             .Stub.asInterface(ServiceManager.getService("accessibility"));
     88 
     89 
     90     private final Context mContext;
     91     private final Runnable mOnAccessibilityEnabledCallback;
     92     private final UserManager mUserManager;
     93     private final TextToSpeech mTts;
     94     private final Ringtone mTone;
     95 
     96     private final float mTouchSlop;
     97 
     98     private boolean mDestroyed;
     99     private boolean mCanceled;
    100 
    101     private float mFirstPointerDownX;
    102     private float mFirstPointerDownY;
    103     private float mSecondPointerDownX;
    104     private float mSecondPointerDownY;
    105 
    106     public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) {
    107         mContext = context;
    108         mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback;
    109         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
    110         mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
    111             @Override
    112             public void onInit(int status) {
    113                 if (mDestroyed) {
    114                     mTts.shutdown();
    115                 }
    116             }
    117         });
    118         mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
    119         mTone.setStreamType(AudioManager.STREAM_MUSIC);
    120         mTouchSlop = context.getResources().getDimensionPixelSize(
    121                 R.dimen.accessibility_touch_slop);
    122     }
    123 
    124     public static boolean canEnableAccessibilityViaGesture(Context context) {
    125         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
    126         // Accessibility is enabled and there is an enabled speaking
    127         // accessibility service, then we have nothing to do.
    128         if (accessibilityManager.isEnabled()
    129                 && !accessibilityManager.getEnabledAccessibilityServiceList(
    130                         AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
    131             return false;
    132         }
    133         // If the global gesture is enabled and there is a speaking service
    134         // installed we are good to go, otherwise there is nothing to do.
    135         return Settings.Global.getInt(context.getContentResolver(),
    136                 Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
    137                 && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
    138     }
    139 
    140     public static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
    141             Context context) {
    142         List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>();
    143         services.addAll(AccessibilityManager.getInstance(context)
    144                 .getInstalledAccessibilityServiceList());
    145         Iterator<AccessibilityServiceInfo> iterator = services.iterator();
    146         while (iterator.hasNext()) {
    147             AccessibilityServiceInfo service = iterator.next();
    148             if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
    149                 iterator.remove();
    150             }
    151         }
    152         return services;
    153     }
    154 
    155     public void onDestroy() {
    156         mDestroyed = true;
    157     }
    158 
    159     public boolean onInterceptTouchEvent(MotionEvent event) {
    160         if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
    161                 && event.getPointerCount() == 2) {
    162             mFirstPointerDownX = event.getX(0);
    163             mFirstPointerDownY = event.getY(0);
    164             mSecondPointerDownX = event.getX(1);
    165             mSecondPointerDownY = event.getY(1);
    166             mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
    167                     SPEAK_WARNING_DELAY_MILLIS);
    168             mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
    169                    ENABLE_ACCESSIBILITY_DELAY_MILLIS);
    170             return true;
    171         }
    172         return false;
    173     }
    174 
    175     public boolean onTouchEvent(MotionEvent event) {
    176         final int pointerCount = event.getPointerCount();
    177         final int action = event.getActionMasked();
    178         if (mCanceled) {
    179             if (action == MotionEvent.ACTION_UP) {
    180                 mCanceled = false;
    181             }
    182             return true;
    183         }
    184         switch (action) {
    185             case MotionEvent.ACTION_POINTER_DOWN: {
    186                 if (pointerCount > 2) {
    187                     cancel();
    188                 }
    189             } break;
    190             case MotionEvent.ACTION_MOVE: {
    191                 final float firstPointerMove = MathUtils.dist(event.getX(0),
    192                         event.getY(0), mFirstPointerDownX, mFirstPointerDownY);
    193                 if (Math.abs(firstPointerMove) > mTouchSlop) {
    194                     cancel();
    195                 }
    196                 final float secondPointerMove = MathUtils.dist(event.getX(1),
    197                         event.getY(1), mSecondPointerDownX, mSecondPointerDownY);
    198                 if (Math.abs(secondPointerMove) > mTouchSlop) {
    199                     cancel();
    200                 }
    201             } break;
    202             case MotionEvent.ACTION_POINTER_UP:
    203             case MotionEvent.ACTION_CANCEL: {
    204                 cancel();
    205             } break;
    206         }
    207         return true;
    208     }
    209 
    210     private void cancel() {
    211         mCanceled = true;
    212         if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
    213             mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
    214         } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
    215             mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
    216         }
    217         mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
    218     }
    219 
    220     private void enableAccessibility() {
    221         if (enableAccessibility(mContext)) {
    222             mOnAccessibilityEnabledCallback.run();
    223         }
    224     }
    225 
    226     public static boolean enableAccessibility(Context context) {
    227         final IAccessibilityManager accessibilityManager = IAccessibilityManager
    228                 .Stub.asInterface(ServiceManager.getService("accessibility"));
    229         final WindowManagerInternal windowManager = LocalServices.getService(
    230                 WindowManagerInternal.class);
    231         final UserManager userManager = (UserManager) context.getSystemService(
    232                 Context.USER_SERVICE);
    233         ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
    234         if (componentName == null) {
    235             return false;
    236         }
    237 
    238         boolean keyguardLocked = windowManager.isKeyguardLocked();
    239         final boolean hasMoreThanOneUser = userManager.getUsers().size() > 1;
    240         try {
    241             if (!keyguardLocked || !hasMoreThanOneUser) {
    242                 final int userId = ActivityManager.getCurrentUser();
    243                 accessibilityManager.enableAccessibilityService(componentName, userId);
    244             } else if (keyguardLocked) {
    245                 accessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
    246                         componentName, true /* enableTouchExploration */);
    247             }
    248         } catch (RemoteException e) {
    249             Log.e(TAG, "cannot enable accessibilty: " + e);
    250         }
    251 
    252         return true;
    253     }
    254 
    255     public static void disableAccessibility(Context context) {
    256         final IAccessibilityManager accessibilityManager = IAccessibilityManager
    257                 .Stub.asInterface(ServiceManager.getService("accessibility"));
    258         ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
    259         if (componentName == null) {
    260             return;
    261         }
    262 
    263         final int userId = ActivityManager.getCurrentUser();
    264         try {
    265             accessibilityManager.disableAccessibilityService(componentName, userId);
    266         } catch (RemoteException e) {
    267             Log.e(TAG, "cannot disable accessibility " + e);
    268         }
    269     }
    270 
    271     public static boolean isAccessibilityEnabled(Context context) {
    272         final AccessibilityManager accessibilityManager =
    273                 context.getSystemService(AccessibilityManager.class);
    274         List enabledServices = accessibilityManager.getEnabledAccessibilityServiceList(
    275                 AccessibilityServiceInfo.FEEDBACK_SPOKEN);
    276         return enabledServices != null && !enabledServices.isEmpty();
    277     }
    278 
    279     @Nullable
    280     public static ComponentName getInstalledSpeakingAccessibilityServiceComponent(
    281             Context context) {
    282         List<AccessibilityServiceInfo> services =
    283                 getInstalledSpeakingAccessibilityServices(context);
    284         if (services.isEmpty()) {
    285             return null;
    286         }
    287 
    288         ServiceInfo serviceInfo = services.get(0).getResolveInfo().serviceInfo;
    289         return new ComponentName(serviceInfo.packageName, serviceInfo.name);
    290     }
    291 }
    292