Home | History | Annotate | Download | only in accessibility
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.accessibility;
     18 
     19 import android.os.Handler;
     20 import android.os.Message;
     21 import android.os.SystemClock;
     22 import android.util.Pools;
     23 import android.util.Slog;
     24 import android.view.KeyEvent;
     25 
     26 import com.android.server.policy.WindowManagerPolicy;
     27 
     28 /**
     29  * Intercepts key events and forwards them to accessibility manager service.
     30  */
     31 public class KeyboardInterceptor extends BaseEventStreamTransformation implements Handler.Callback {
     32     private static final int MESSAGE_PROCESS_QUEUED_EVENTS = 1;
     33     private static final String LOG_TAG = "KeyboardInterceptor";
     34 
     35     private final AccessibilityManagerService mAms;
     36     private final WindowManagerPolicy mPolicy;
     37     private final Handler mHandler;
     38 
     39     private KeyEventHolder mEventQueueStart;
     40     private KeyEventHolder mEventQueueEnd;
     41 
     42     /**
     43      * @param service The service to notify of key events
     44      * @param policy The policy to check for keys that may affect a11y
     45      */
     46     public KeyboardInterceptor(AccessibilityManagerService service, WindowManagerPolicy policy) {
     47         mAms = service;
     48         mPolicy = policy;
     49         mHandler = new Handler(this);
     50     }
     51 
     52     /**
     53      * @param service The service to notify of key events
     54      * @param policy The policy to check for keys that may affect a11y
     55      * @param handler The handler to use. Only used for testing.
     56      */
     57     public KeyboardInterceptor(AccessibilityManagerService service, WindowManagerPolicy policy,
     58             Handler handler) {
     59         // Can't combine the constructors without making at least mHandler non-final.
     60         mAms = service;
     61         mPolicy = policy;
     62         mHandler = handler;
     63     }
     64 
     65     @Override
     66     public void onKeyEvent(KeyEvent event, int policyFlags) {
     67         /*
     68          * Certain keys have system-level behavior that affects accessibility services.
     69          * Let that behavior settle before handling the keys
     70          */
     71         long eventDelay = getEventDelay(event, policyFlags);
     72         if (eventDelay < 0) {
     73             return;
     74         }
     75         if ((eventDelay > 0) || (mEventQueueStart != null))  {
     76             addEventToQueue(event, policyFlags, eventDelay);
     77             return;
     78         }
     79 
     80         mAms.notifyKeyEvent(event, policyFlags);
     81     }
     82 
     83     @Override
     84     public boolean handleMessage(Message msg) {
     85         if (msg.what != MESSAGE_PROCESS_QUEUED_EVENTS) {
     86             Slog.e(LOG_TAG, "Unexpected message type");
     87             return false;
     88         }
     89         processQueuedEvents();
     90         if (mEventQueueStart != null) {
     91             scheduleProcessQueuedEvents();
     92         }
     93         return true;
     94     }
     95 
     96     private void addEventToQueue(KeyEvent event, int policyFlags, long delay) {
     97         long dispatchTime = SystemClock.uptimeMillis() + delay;
     98         if (mEventQueueStart == null) {
     99             mEventQueueEnd = mEventQueueStart =
    100                     KeyEventHolder.obtain(event, policyFlags, dispatchTime);
    101             scheduleProcessQueuedEvents();
    102             return;
    103         }
    104         final KeyEventHolder holder = KeyEventHolder.obtain(event, policyFlags, dispatchTime);
    105         holder.next = mEventQueueStart;
    106         mEventQueueStart.previous = holder;
    107         mEventQueueStart = holder;
    108     }
    109 
    110     private void scheduleProcessQueuedEvents() {
    111         if (!mHandler.sendEmptyMessageAtTime(
    112                 MESSAGE_PROCESS_QUEUED_EVENTS, mEventQueueEnd.dispatchTime)) {
    113             Slog.e(LOG_TAG, "Failed to schedule key event");
    114         };
    115     }
    116 
    117     private void processQueuedEvents() {
    118         final long currentTime = SystemClock.uptimeMillis();
    119         while ((mEventQueueEnd != null) && (mEventQueueEnd.dispatchTime <= currentTime)) {
    120             final long eventDelay = getEventDelay(mEventQueueEnd.event, mEventQueueEnd.policyFlags);
    121             if (eventDelay > 0) {
    122                 // Reschedule the event
    123                 mEventQueueEnd.dispatchTime = currentTime + eventDelay;
    124                 return;
    125             }
    126             // We'll either send or drop the event
    127             if (eventDelay == 0) {
    128                 mAms.notifyKeyEvent(mEventQueueEnd.event, mEventQueueEnd.policyFlags);
    129             }
    130             final KeyEventHolder eventToBeRecycled = mEventQueueEnd;
    131             mEventQueueEnd = mEventQueueEnd.previous;
    132             if (mEventQueueEnd != null) {
    133                 mEventQueueEnd.next = null;
    134             }
    135             eventToBeRecycled.recycle();
    136             if (mEventQueueEnd == null) {
    137                 mEventQueueStart = null;
    138             }
    139         }
    140     }
    141 
    142     private long getEventDelay(KeyEvent event, int policyFlags) {
    143         int keyCode = event.getKeyCode();
    144         if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || (keyCode == KeyEvent.KEYCODE_VOLUME_UP)) {
    145             return mPolicy.interceptKeyBeforeDispatching(null, event, policyFlags);
    146         }
    147         return 0;
    148     }
    149 
    150     private static class KeyEventHolder {
    151         private static final int MAX_POOL_SIZE = 32;
    152         private static final Pools.SimplePool<KeyEventHolder> sPool =
    153                 new Pools.SimplePool<>(MAX_POOL_SIZE);
    154 
    155         public int policyFlags;
    156         public long dispatchTime;
    157         public KeyEvent event;
    158         public KeyEventHolder next;
    159         public KeyEventHolder previous;
    160 
    161         public static KeyEventHolder obtain(KeyEvent event, int policyFlags, long dispatchTime) {
    162             KeyEventHolder holder = sPool.acquire();
    163             if (holder == null) {
    164                 holder = new KeyEventHolder();
    165             }
    166             holder.event = KeyEvent.obtain(event);
    167             holder.policyFlags = policyFlags;
    168             holder.dispatchTime = dispatchTime;
    169             return holder;
    170         }
    171 
    172         public void recycle() {
    173             event.recycle();
    174             event = null;
    175             policyFlags = 0;
    176             dispatchTime = 0;
    177             next = null;
    178             previous = null;
    179             sPool.release(this);
    180         }
    181     }
    182 }
    183