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