content: /*
   * 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);
          }
      }
  }
  