Home | History | Annotate | Download | only in hdmi
      1 /*
      2  * Copyright (C) 2014 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 package com.android.server.hdmi;
     17 
     18 import static com.android.server.hdmi.HdmiConfig.IRT_MS;
     19 
     20 import android.util.Slog;
     21 import android.view.KeyEvent;
     22 
     23 /**
     24  * Feature action that transmits remote control key command (User Control Press/
     25  * User Control Release) to CEC bus.
     26  *
     27  * <p>This action is created when a new key event is passed to CEC service. It optionally
     28  * does key repeat (a.k.a. press-and-hold) operation until it receives a key release event.
     29  * If another key press event is received before the key in use is released, CEC service
     30  * does not create a new action but recycles the current one by updating the key used
     31  * for press-and-hold operation.
     32  *
     33  * <p>Package-private, accessed by {@link HdmiControlService} only.
     34  */
     35 final class SendKeyAction extends HdmiCecFeatureAction {
     36     private static final String TAG = "SendKeyAction";
     37 
     38     // If the first key press lasts this much amount of time without any other key event
     39     // coming down, we trigger the press-and-hold operation. Set to the value slightly
     40     // shorter than the threshold(500ms) between two successive key press events
     41     // as specified in the standard for the operation.
     42     private static final int AWAIT_LONGPRESS_MS = 400;
     43 
     44     // Amount of time this action waits for a new release key input event. When timed out,
     45     // the action sends out UCR and finishes its lifecycle. Used to deal with missing key release
     46     // event, which can lead the device on the receiving end to generating unintended key repeats.
     47     private static final int AWAIT_RELEASE_KEY_MS = 1000;
     48 
     49     // State in which the long press is being checked at the beginning. The state is set in
     50     // {@link #start()} and lasts for {@link #AWAIT_LONGPRESS_MS}.
     51     private static final int STATE_CHECKING_LONGPRESS = 1;
     52 
     53     // State in which the action is handling incoming keys. Persists throughout the process
     54     // till it is set back to {@code STATE_NONE} at the end when a release key event for
     55     // the last key is processed.
     56     private static final int STATE_PROCESSING_KEYCODE = 2;
     57 
     58     // Logical address of the device to which the UCP/UCP commands are sent.
     59     private final int mTargetAddress;
     60 
     61     // The key code of the last key press event the action is passed via processKeyEvent.
     62     private int mLastKeycode;
     63 
     64     // The time stamp when the last CEC key command was sent. Used to determine the press-and-hold
     65     // operation.
     66     private long mLastSendKeyTime;
     67 
     68     /**
     69      * Constructor.
     70      *
     71      * @param source {@link HdmiCecLocalDevice} instance
     72      * @param targetAddress logical address of the device to send the keys to
     73      * @param keycode remote control key code as defined in {@link KeyEvent}
     74      */
     75     SendKeyAction(HdmiCecLocalDevice source, int targetAddress, int keycode) {
     76         super(source);
     77         mTargetAddress = targetAddress;
     78         mLastKeycode = keycode;
     79     }
     80 
     81     @Override
     82     public boolean start() {
     83         sendKeyDown(mLastKeycode);
     84         mLastSendKeyTime = getCurrentTime();
     85         // finish action for non-repeatable key.
     86         if (!HdmiCecKeycode.isRepeatableKey(mLastKeycode)) {
     87             sendKeyUp();
     88             finish();
     89             return true;
     90         }
     91         mState = STATE_CHECKING_LONGPRESS;
     92         addTimer(mState, AWAIT_LONGPRESS_MS);
     93         return true;
     94     }
     95 
     96     private long getCurrentTime() {
     97         return System.currentTimeMillis();
     98     }
     99 
    100     /**
    101      * Called when a key event should be handled for the action.
    102      *
    103      * @param keycode key code of {@link KeyEvent} object
    104      * @param isPressed true if the key event is of {@link KeyEvent#ACTION_DOWN}
    105      */
    106     void processKeyEvent(int keycode, boolean isPressed) {
    107         if (mState != STATE_CHECKING_LONGPRESS && mState != STATE_PROCESSING_KEYCODE) {
    108             Slog.w(TAG, "Not in a valid state");
    109             return;
    110         }
    111         if (isPressed) {
    112             // A new key press event that comes in with a key code different from the last
    113             // one becomes a new key code to be used for press-and-hold operation.
    114             if (keycode != mLastKeycode) {
    115                 sendKeyDown(keycode);
    116                 mLastSendKeyTime = getCurrentTime();
    117                 if (!HdmiCecKeycode.isRepeatableKey(keycode)) {
    118                     sendKeyUp();
    119                     finish();
    120                     return;
    121                 }
    122             } else {
    123                 // Press-and-hold key transmission takes place if Android key inputs are
    124                 // repeatedly coming in and more than IRT_MS has passed since the last
    125                 // press-and-hold key transmission.
    126                 if (getCurrentTime() - mLastSendKeyTime >= IRT_MS) {
    127                     sendKeyDown(keycode);
    128                     mLastSendKeyTime = getCurrentTime();
    129                 }
    130             }
    131             mActionTimer.clearTimerMessage();
    132             addTimer(mState, AWAIT_RELEASE_KEY_MS);
    133             mLastKeycode = keycode;
    134         } else {
    135             // Key release event indicates that the action shall be finished. Send UCR
    136             // command and terminate the action. Other release events are ignored.
    137             if (keycode == mLastKeycode) {
    138                 sendKeyUp();
    139                 finish();
    140             }
    141         }
    142     }
    143 
    144     private void sendKeyDown(int keycode) {
    145         byte[] cecKeycodeAndParams = HdmiCecKeycode.androidKeyToCecKey(keycode);
    146         if (cecKeycodeAndParams == null) {
    147             return;
    148         }
    149         sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(),
    150                 mTargetAddress, cecKeycodeAndParams));
    151     }
    152 
    153     private void sendKeyUp() {
    154         sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
    155                 mTargetAddress));
    156     }
    157 
    158     @Override
    159     public boolean processCommand(HdmiCecMessage cmd) {
    160         // Send key action doesn't need any incoming CEC command, hence does not consume it.
    161         return false;
    162     }
    163 
    164     @Override
    165     public void handleTimerEvent(int state) {
    166         switch (mState) {
    167             case STATE_CHECKING_LONGPRESS:
    168                 // The first key press lasts long enough to start press-and-hold.
    169                 mActionTimer.clearTimerMessage();
    170                 mState = STATE_PROCESSING_KEYCODE;
    171                 sendKeyDown(mLastKeycode);
    172                 mLastSendKeyTime = getCurrentTime();
    173                 addTimer(mState, AWAIT_RELEASE_KEY_MS);
    174                 break;
    175             case STATE_PROCESSING_KEYCODE:
    176                 // Timeout on waiting for the release key event. Send UCR and quit the action.
    177                 sendKeyUp();
    178                 finish();
    179                 break;
    180             default:
    181                 Slog.w(TAG, "Not in a valid state");
    182                 break;
    183         }
    184     }
    185 }
    186