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 17package com.android.server.hdmi; 18 19import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION; 20import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE; 21import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE; 22import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; 23import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; 24 25import android.util.Slog; 26 27import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 28 29import java.util.Arrays; 30 31/** 32 * Feature action that performs timer recording. 33 */ 34public class TimerRecordingAction extends HdmiCecFeatureAction { 35 private static final String TAG = "TimerRecordingAction"; 36 37 // Timer out for waiting <Timer Status> 120s. 38 private static final int TIMER_STATUS_TIMEOUT_MS = 120000; 39 40 // State that waits for <Timer Status> once sending <Set XXX Timer> 41 private static final int STATE_WAITING_FOR_TIMER_STATUS = 1; 42 43 private final int mRecorderAddress; 44 private final int mSourceType; 45 private final byte[] mRecordSource; 46 47 TimerRecordingAction(HdmiCecLocalDevice source, int recorderAddress, int sourceType, 48 byte[] recordSource) { 49 super(source); 50 mRecorderAddress = recorderAddress; 51 mSourceType = sourceType; 52 mRecordSource = recordSource; 53 } 54 55 @Override 56 boolean start() { 57 sendTimerMessage(); 58 return true; 59 } 60 61 private void sendTimerMessage() { 62 HdmiCecMessage message = null; 63 switch (mSourceType) { 64 case TIMER_RECORDING_TYPE_DIGITAL: 65 message = HdmiCecMessageBuilder.buildSetDigitalTimer(getSourceAddress(), 66 mRecorderAddress, mRecordSource); 67 break; 68 case TIMER_RECORDING_TYPE_ANALOGUE: 69 message = HdmiCecMessageBuilder.buildSetAnalogueTimer(getSourceAddress(), 70 mRecorderAddress, mRecordSource); 71 break; 72 case TIMER_RECORDING_TYPE_EXTERNAL: 73 message = HdmiCecMessageBuilder.buildSetExternalTimer(getSourceAddress(), 74 mRecorderAddress, mRecordSource); 75 break; 76 default: 77 tv().announceTimerRecordingResult(mRecorderAddress, 78 TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); 79 finish(); 80 return; 81 } 82 sendCommand(message, new SendMessageCallback() { 83 @Override 84 public void onSendCompleted(int error) { 85 if (error != Constants.SEND_RESULT_SUCCESS) { 86 tv().announceTimerRecordingResult(mRecorderAddress, 87 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); 88 finish(); 89 return; 90 } 91 mState = STATE_WAITING_FOR_TIMER_STATUS; 92 addTimer(mState, TIMER_STATUS_TIMEOUT_MS); 93 } 94 }); 95 } 96 97 @Override 98 boolean processCommand(HdmiCecMessage cmd) { 99 if (mState != STATE_WAITING_FOR_TIMER_STATUS 100 || cmd.getSource() != mRecorderAddress) { 101 return false; 102 } 103 104 switch (cmd.getOpcode()) { 105 case Constants.MESSAGE_TIMER_STATUS: 106 return handleTimerStatus(cmd); 107 case Constants.MESSAGE_FEATURE_ABORT: 108 return handleFeatureAbort(cmd); 109 } 110 return false; 111 } 112 113 private boolean handleTimerStatus(HdmiCecMessage cmd) { 114 byte[] timerStatusData = cmd.getParams(); 115 // [Timer Status Data] should be one or three bytes. 116 if (timerStatusData.length == 1 || timerStatusData.length == 3) { 117 tv().announceTimerRecordingResult(mRecorderAddress, bytesToInt(timerStatusData)); 118 Slog.i(TAG, "Received [Timer Status Data]:" + Arrays.toString(timerStatusData)); 119 } else { 120 Slog.w(TAG, "Invalid [Timer Status Data]:" + Arrays.toString(timerStatusData)); 121 } 122 123 // Unlike one touch record, finish timer record when <Timer Status> is received. 124 finish(); 125 return true; 126 } 127 128 private boolean handleFeatureAbort(HdmiCecMessage cmd) { 129 byte[] params = cmd.getParams(); 130 int messageType = params[0] & 0xFF; 131 switch (messageType) { 132 case Constants.MESSAGE_SET_DIGITAL_TIMER: // fall through 133 case Constants.MESSAGE_SET_ANALOG_TIMER: // fall through 134 case Constants.MESSAGE_SET_EXTERNAL_TIMER: // fall through 135 break; 136 default: 137 return false; 138 } 139 int reason = params[1] & 0xFF; 140 Slog.i(TAG, "[Feature Abort] for " + messageType + " reason:" + reason); 141 tv().announceTimerRecordingResult(mRecorderAddress, 142 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); 143 finish(); 144 return true; 145 } 146 147 // Convert byte array to int. 148 private static int bytesToInt(byte[] data) { 149 if (data.length > 4) { 150 throw new IllegalArgumentException("Invalid data size:" + Arrays.toString(data)); 151 } 152 int result = 0; 153 for (int i = 0; i < data.length; ++i) { 154 int shift = (3 - i) * 8; 155 result |= ((data[i] & 0xFF) << shift); 156 } 157 return result; 158 } 159 160 @Override 161 void handleTimerEvent(int state) { 162 if (mState != state) { 163 Slog.w(TAG, "Timeout in invalid state:[Expected:" + mState + ", Actual:" + state + "]"); 164 return; 165 } 166 167 tv().announceTimerRecordingResult(mRecorderAddress, 168 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); 169 finish(); 170 } 171} 172