/* * 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 android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION; import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE; import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE; import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; import android.util.Slog; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; import java.util.Arrays; /** * Feature action that performs timer recording. */ public class TimerRecordingAction extends HdmiCecFeatureAction { private static final String TAG = "TimerRecordingAction"; // Timer out for waiting 120s. private static final int TIMER_STATUS_TIMEOUT_MS = 120000; // State that waits for once sending private static final int STATE_WAITING_FOR_TIMER_STATUS = 1; private final int mRecorderAddress; private final int mSourceType; private final byte[] mRecordSource; TimerRecordingAction(HdmiCecLocalDevice source, int recorderAddress, int sourceType, byte[] recordSource) { super(source); mRecorderAddress = recorderAddress; mSourceType = sourceType; mRecordSource = recordSource; } @Override boolean start() { sendTimerMessage(); return true; } private void sendTimerMessage() { HdmiCecMessage message = null; switch (mSourceType) { case TIMER_RECORDING_TYPE_DIGITAL: message = HdmiCecMessageBuilder.buildSetDigitalTimer(getSourceAddress(), mRecorderAddress, mRecordSource); break; case TIMER_RECORDING_TYPE_ANALOGUE: message = HdmiCecMessageBuilder.buildSetAnalogueTimer(getSourceAddress(), mRecorderAddress, mRecordSource); break; case TIMER_RECORDING_TYPE_EXTERNAL: message = HdmiCecMessageBuilder.buildSetExternalTimer(getSourceAddress(), mRecorderAddress, mRecordSource); break; default: tv().announceTimerRecordingResult(mRecorderAddress, TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); finish(); return; } sendCommand(message, new SendMessageCallback() { @Override public void onSendCompleted(int error) { if (error != Constants.SEND_RESULT_SUCCESS) { tv().announceTimerRecordingResult(mRecorderAddress, TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); finish(); return; } mState = STATE_WAITING_FOR_TIMER_STATUS; addTimer(mState, TIMER_STATUS_TIMEOUT_MS); } }); } @Override boolean processCommand(HdmiCecMessage cmd) { if (mState != STATE_WAITING_FOR_TIMER_STATUS || cmd.getSource() != mRecorderAddress) { return false; } switch (cmd.getOpcode()) { case Constants.MESSAGE_TIMER_STATUS: return handleTimerStatus(cmd); case Constants.MESSAGE_FEATURE_ABORT: return handleFeatureAbort(cmd); } return false; } private boolean handleTimerStatus(HdmiCecMessage cmd) { byte[] timerStatusData = cmd.getParams(); // [Timer Status Data] should be one or three bytes. if (timerStatusData.length == 1 || timerStatusData.length == 3) { tv().announceTimerRecordingResult(mRecorderAddress, bytesToInt(timerStatusData)); Slog.i(TAG, "Received [Timer Status Data]:" + Arrays.toString(timerStatusData)); } else { Slog.w(TAG, "Invalid [Timer Status Data]:" + Arrays.toString(timerStatusData)); } // Unlike one touch record, finish timer record when is received. finish(); return true; } private boolean handleFeatureAbort(HdmiCecMessage cmd) { byte[] params = cmd.getParams(); int messageType = params[0] & 0xFF; switch (messageType) { case Constants.MESSAGE_SET_DIGITAL_TIMER: // fall through case Constants.MESSAGE_SET_ANALOG_TIMER: // fall through case Constants.MESSAGE_SET_EXTERNAL_TIMER: // fall through break; default: return false; } int reason = params[1] & 0xFF; Slog.i(TAG, "[Feature Abort] for " + messageType + " reason:" + reason); tv().announceTimerRecordingResult(mRecorderAddress, TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); finish(); return true; } // Convert byte array to int. private static int bytesToInt(byte[] data) { if (data.length > 4) { throw new IllegalArgumentException("Invalid data size:" + Arrays.toString(data)); } int result = 0; for (int i = 0; i < data.length; ++i) { int shift = (3 - i) * 8; result |= ((data[i] & 0xFF) << shift); } return result; } @Override void handleTimerEvent(int state) { if (mState != state) { Slog.w(TAG, "Timeout in invalid state:[Expected:" + mState + ", Actual:" + state + "]"); return; } tv().announceTimerRecordingResult(mRecorderAddress, TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); finish(); } }