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
17package com.android.server.accessibility;
18
19import android.os.Handler;
20import android.os.Message;
21import android.os.SystemClock;
22import android.util.Pools;
23import android.util.Slog;
24import android.view.KeyEvent;
25
26import com.android.server.policy.WindowManagerPolicy;
27
28/**
29 * Intercepts key events and forwards them to accessibility manager service.
30 */
31public 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