/* * Copyright (C) 2014 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.hdmi; import static com.android.server.hdmi.HdmiConfig.IRT_MS; import android.util.Slog; import android.view.KeyEvent; /** * Feature action that transmits remote control key command (User Control Press/ * User Control Release) to CEC bus. * *

This action is created when a new key event is passed to CEC service. It optionally * does key repeat (a.k.a. press-and-hold) operation until it receives a key release event. * If another key press event is received before the key in use is released, CEC service * does not create a new action but recycles the current one by updating the key used * for press-and-hold operation. * *

Package-private, accessed by {@link HdmiControlService} only. */ final class SendKeyAction extends HdmiCecFeatureAction { private static final String TAG = "SendKeyAction"; // If the first key press lasts this much amount of time without any other key event // coming down, we trigger the press-and-hold operation. Set to the value slightly // shorter than the threshold(500ms) between two successive key press events // as specified in the standard for the operation. private static final int AWAIT_LONGPRESS_MS = 400; // Amount of time this action waits for a new release key input event. When timed out, // the action sends out UCR and finishes its lifecycle. Used to deal with missing key release // event, which can lead the device on the receiving end to generating unintended key repeats. private static final int AWAIT_RELEASE_KEY_MS = 1000; // State in which the long press is being checked at the beginning. The state is set in // {@link #start()} and lasts for {@link #AWAIT_LONGPRESS_MS}. private static final int STATE_CHECKING_LONGPRESS = 1; // State in which the action is handling incoming keys. Persists throughout the process // till it is set back to {@code STATE_NONE} at the end when a release key event for // the last key is processed. private static final int STATE_PROCESSING_KEYCODE = 2; // Logical address of the device to which the UCP/UCP commands are sent. private final int mTargetAddress; // The key code of the last key press event the action is passed via processKeyEvent. private int mLastKeycode; // The time stamp when the last CEC key command was sent. Used to determine the press-and-hold // operation. private long mLastSendKeyTime; /** * Constructor. * * @param source {@link HdmiCecLocalDevice} instance * @param targetAddress logical address of the device to send the keys to * @param keycode remote control key code as defined in {@link KeyEvent} */ SendKeyAction(HdmiCecLocalDevice source, int targetAddress, int keycode) { super(source); mTargetAddress = targetAddress; mLastKeycode = keycode; } @Override public boolean start() { sendKeyDown(mLastKeycode); mLastSendKeyTime = getCurrentTime(); // finish action for non-repeatable key. if (!HdmiCecKeycode.isRepeatableKey(mLastKeycode)) { sendKeyUp(); finish(); return true; } mState = STATE_CHECKING_LONGPRESS; addTimer(mState, AWAIT_LONGPRESS_MS); return true; } private long getCurrentTime() { return System.currentTimeMillis(); } /** * Called when a key event should be handled for the action. * * @param keycode key code of {@link KeyEvent} object * @param isPressed true if the key event is of {@link KeyEvent#ACTION_DOWN} */ void processKeyEvent(int keycode, boolean isPressed) { if (mState != STATE_CHECKING_LONGPRESS && mState != STATE_PROCESSING_KEYCODE) { Slog.w(TAG, "Not in a valid state"); return; } if (isPressed) { // A new key press event that comes in with a key code different from the last // one becomes a new key code to be used for press-and-hold operation. if (keycode != mLastKeycode) { sendKeyDown(keycode); mLastSendKeyTime = getCurrentTime(); if (!HdmiCecKeycode.isRepeatableKey(keycode)) { sendKeyUp(); finish(); return; } } else { // Press-and-hold key transmission takes place if Android key inputs are // repeatedly coming in and more than IRT_MS has passed since the last // press-and-hold key transmission. if (getCurrentTime() - mLastSendKeyTime >= IRT_MS) { sendKeyDown(keycode); mLastSendKeyTime = getCurrentTime(); } } mActionTimer.clearTimerMessage(); addTimer(mState, AWAIT_RELEASE_KEY_MS); mLastKeycode = keycode; } else { // Key release event indicates that the action shall be finished. Send UCR // command and terminate the action. Other release events are ignored. if (keycode == mLastKeycode) { sendKeyUp(); finish(); } } } private void sendKeyDown(int keycode) { byte[] cecKeycodeAndParams = HdmiCecKeycode.androidKeyToCecKey(keycode); if (cecKeycodeAndParams == null) { return; } sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mTargetAddress, cecKeycodeAndParams)); } private void sendKeyUp() { sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(), mTargetAddress)); } @Override public boolean processCommand(HdmiCecMessage cmd) { // Send key action doesn't need any incoming CEC command, hence does not consume it. return false; } @Override public void handleTimerEvent(int state) { switch (mState) { case STATE_CHECKING_LONGPRESS: // The first key press lasts long enough to start press-and-hold. mActionTimer.clearTimerMessage(); mState = STATE_PROCESSING_KEYCODE; sendKeyDown(mLastKeycode); mLastSendKeyTime = getCurrentTime(); addTimer(mState, AWAIT_RELEASE_KEY_MS); break; case STATE_PROCESSING_KEYCODE: // Timeout on waiting for the release key event. Send UCR and quit the action. sendKeyUp(); finish(); break; default: Slog.w(TAG, "Not in a valid state"); break; } } }