| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.accessibility; |
| |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.SystemClock; |
| import android.util.Pools; |
| import android.util.Slog; |
| import android.view.KeyEvent; |
| |
| import com.android.server.policy.WindowManagerPolicy; |
| |
| /** |
| * Intercepts key events and forwards them to accessibility manager service. |
| */ |
| public class KeyboardInterceptor extends BaseEventStreamTransformation implements Handler.Callback { |
| private static final int MESSAGE_PROCESS_QUEUED_EVENTS = 1; |
| private static final String LOG_TAG = "KeyboardInterceptor"; |
| |
| private final AccessibilityManagerService mAms; |
| private final WindowManagerPolicy mPolicy; |
| private final Handler mHandler; |
| |
| private KeyEventHolder mEventQueueStart; |
| private KeyEventHolder mEventQueueEnd; |
| |
| /** |
| * @param service The service to notify of key events |
| * @param policy The policy to check for keys that may affect a11y |
| */ |
| public KeyboardInterceptor(AccessibilityManagerService service, WindowManagerPolicy policy) { |
| mAms = service; |
| mPolicy = policy; |
| mHandler = new Handler(this); |
| } |
| |
| /** |
| * @param service The service to notify of key events |
| * @param policy The policy to check for keys that may affect a11y |
| * @param handler The handler to use. Only used for testing. |
| */ |
| public KeyboardInterceptor(AccessibilityManagerService service, WindowManagerPolicy policy, |
| Handler handler) { |
| // Can't combine the constructors without making at least mHandler non-final. |
| mAms = service; |
| mPolicy = policy; |
| mHandler = handler; |
| } |
| |
| @Override |
| public void onKeyEvent(KeyEvent event, int policyFlags) { |
| /* |
| * Certain keys have system-level behavior that affects accessibility services. |
| * Let that behavior settle before handling the keys |
| */ |
| long eventDelay = getEventDelay(event, policyFlags); |
| if (eventDelay < 0) { |
| return; |
| } |
| if ((eventDelay > 0) || (mEventQueueStart != null)) { |
| addEventToQueue(event, policyFlags, eventDelay); |
| return; |
| } |
| |
| mAms.notifyKeyEvent(event, policyFlags); |
| } |
| |
| @Override |
| public boolean handleMessage(Message msg) { |
| if (msg.what != MESSAGE_PROCESS_QUEUED_EVENTS) { |
| Slog.e(LOG_TAG, "Unexpected message type"); |
| return false; |
| } |
| processQueuedEvents(); |
| if (mEventQueueStart != null) { |
| scheduleProcessQueuedEvents(); |
| } |
| return true; |
| } |
| |
| private void addEventToQueue(KeyEvent event, int policyFlags, long delay) { |
| long dispatchTime = SystemClock.uptimeMillis() + delay; |
| if (mEventQueueStart == null) { |
| mEventQueueEnd = mEventQueueStart = |
| KeyEventHolder.obtain(event, policyFlags, dispatchTime); |
| scheduleProcessQueuedEvents(); |
| return; |
| } |
| final KeyEventHolder holder = KeyEventHolder.obtain(event, policyFlags, dispatchTime); |
| holder.next = mEventQueueStart; |
| mEventQueueStart.previous = holder; |
| mEventQueueStart = holder; |
| } |
| |
| private void scheduleProcessQueuedEvents() { |
| if (!mHandler.sendEmptyMessageAtTime( |
| MESSAGE_PROCESS_QUEUED_EVENTS, mEventQueueEnd.dispatchTime)) { |
| Slog.e(LOG_TAG, "Failed to schedule key event"); |
| }; |
| } |
| |
| private void processQueuedEvents() { |
| final long currentTime = SystemClock.uptimeMillis(); |
| while ((mEventQueueEnd != null) && (mEventQueueEnd.dispatchTime <= currentTime)) { |
| final long eventDelay = getEventDelay(mEventQueueEnd.event, mEventQueueEnd.policyFlags); |
| if (eventDelay > 0) { |
| // Reschedule the event |
| mEventQueueEnd.dispatchTime = currentTime + eventDelay; |
| return; |
| } |
| // We'll either send or drop the event |
| if (eventDelay == 0) { |
| mAms.notifyKeyEvent(mEventQueueEnd.event, mEventQueueEnd.policyFlags); |
| } |
| final KeyEventHolder eventToBeRecycled = mEventQueueEnd; |
| mEventQueueEnd = mEventQueueEnd.previous; |
| if (mEventQueueEnd != null) { |
| mEventQueueEnd.next = null; |
| } |
| eventToBeRecycled.recycle(); |
| if (mEventQueueEnd == null) { |
| mEventQueueStart = null; |
| } |
| } |
| } |
| |
| private long getEventDelay(KeyEvent event, int policyFlags) { |
| int keyCode = event.getKeyCode(); |
| if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || (keyCode == KeyEvent.KEYCODE_VOLUME_UP)) { |
| return mPolicy.interceptKeyBeforeDispatching(null, event, policyFlags); |
| } |
| return 0; |
| } |
| |
| private static class KeyEventHolder { |
| private static final int MAX_POOL_SIZE = 32; |
| private static final Pools.SimplePool<KeyEventHolder> sPool = |
| new Pools.SimplePool<>(MAX_POOL_SIZE); |
| |
| public int policyFlags; |
| public long dispatchTime; |
| public KeyEvent event; |
| public KeyEventHolder next; |
| public KeyEventHolder previous; |
| |
| public static KeyEventHolder obtain(KeyEvent event, int policyFlags, long dispatchTime) { |
| KeyEventHolder holder = sPool.acquire(); |
| if (holder == null) { |
| holder = new KeyEventHolder(); |
| } |
| holder.event = KeyEvent.obtain(event); |
| holder.policyFlags = policyFlags; |
| holder.dispatchTime = dispatchTime; |
| return holder; |
| } |
| |
| public void recycle() { |
| event.recycle(); |
| event = null; |
| policyFlags = 0; |
| dispatchTime = 0; |
| next = null; |
| previous = null; |
| sPool.release(this); |
| } |
| } |
| } |