1 /* 2 * Copyright (C) 2021 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 android.hardware.hdmi.HdmiControlManager; 20 import android.util.Slog; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import java.util.ArrayList; 25 import java.util.List; 26 import java.util.Objects; 27 28 /** 29 * Feature action that queries the Short Audio Descriptor (SAD) of another device. This action is 30 * initiated from the Android system working as TV device to get the SAD of the connected audio 31 * system device. 32 * <p> 33 * Package-private 34 */ 35 final class RequestSadAction extends HdmiCecFeatureAction { 36 private static final String TAG = "RequestSadAction"; 37 38 // State in which the action is waiting for <Report Short Audio Descriptor>. 39 private static final int STATE_WAITING_FOR_REPORT_SAD = 1; 40 private static final int MAX_SAD_PER_REQUEST = 4; 41 @VisibleForTesting 42 public static final int RETRY_COUNTER_MAX = 3; 43 private final int mTargetAddress; 44 private final RequestSadCallback mCallback; 45 private final List<Integer> mCecCodecsToQuery = new ArrayList<>(); 46 // List of all valid SADs reported by the target device. Not parsed nor deduplicated. 47 private final List<byte[]> mSupportedSads = new ArrayList<>(); 48 private int mQueriedSadCount = 0; // Number of SADs queries that has already been completed 49 private int mTimeoutRetry = 0; // Number of times we have already retried on time-out 50 51 /** 52 * Constructor. 53 * 54 * @param source an instance of {@link HdmiCecLocalDevice}. 55 * @param targetAddress the logical address the SAD is directed at. 56 */ RequestSadAction(HdmiCecLocalDevice source, int targetAddress, RequestSadCallback callback)57 RequestSadAction(HdmiCecLocalDevice source, int targetAddress, RequestSadCallback callback) { 58 super(source); 59 mTargetAddress = targetAddress; 60 mCallback = Objects.requireNonNull(callback); 61 HdmiCecConfig hdmiCecConfig = localDevice().mService.getHdmiCecConfig(); 62 if (hdmiCecConfig.getIntValue( 63 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM) 64 == HdmiControlManager.QUERY_SAD_ENABLED) { 65 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_LPCM); 66 } 67 if (hdmiCecConfig.getIntValue( 68 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD) 69 == HdmiControlManager.QUERY_SAD_ENABLED) { 70 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DD); 71 } 72 if (hdmiCecConfig.getIntValue( 73 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1) 74 == HdmiControlManager.QUERY_SAD_ENABLED) { 75 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_MPEG1); 76 } 77 if (hdmiCecConfig.getIntValue( 78 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3) 79 == HdmiControlManager.QUERY_SAD_ENABLED) { 80 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_MP3); 81 } 82 if (hdmiCecConfig.getIntValue( 83 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2) 84 == HdmiControlManager.QUERY_SAD_ENABLED) { 85 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_MPEG2); 86 } 87 if (hdmiCecConfig.getIntValue( 88 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC) 89 == HdmiControlManager.QUERY_SAD_ENABLED) { 90 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_AAC); 91 } 92 if (hdmiCecConfig.getIntValue( 93 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS) 94 == HdmiControlManager.QUERY_SAD_ENABLED) { 95 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DTS); 96 } 97 if (hdmiCecConfig.getIntValue( 98 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC) 99 == HdmiControlManager.QUERY_SAD_ENABLED) { 100 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_ATRAC); 101 } 102 if (hdmiCecConfig.getIntValue( 103 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO) 104 == HdmiControlManager.QUERY_SAD_ENABLED) { 105 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_ONEBITAUDIO); 106 } 107 if (hdmiCecConfig.getIntValue( 108 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP) 109 == HdmiControlManager.QUERY_SAD_ENABLED) { 110 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DDP); 111 } 112 if (hdmiCecConfig.getIntValue( 113 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD) 114 == HdmiControlManager.QUERY_SAD_ENABLED) { 115 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DTSHD); 116 } 117 if (hdmiCecConfig.getIntValue( 118 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD) 119 == HdmiControlManager.QUERY_SAD_ENABLED) { 120 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_TRUEHD); 121 } 122 if (hdmiCecConfig.getIntValue( 123 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST) 124 == HdmiControlManager.QUERY_SAD_ENABLED) { 125 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_DST); 126 } 127 if (hdmiCecConfig.getIntValue( 128 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO) 129 == HdmiControlManager.QUERY_SAD_ENABLED) { 130 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_WMAPRO); 131 } 132 if (hdmiCecConfig.getIntValue( 133 HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX) 134 == HdmiControlManager.QUERY_SAD_ENABLED) { 135 mCecCodecsToQuery.add(Constants.AUDIO_CODEC_MAX); 136 } 137 } 138 139 @Override start()140 boolean start() { 141 querySad(); 142 return true; 143 } 144 querySad()145 private void querySad() { 146 if (mQueriedSadCount >= mCecCodecsToQuery.size()) { 147 wrapUpAndFinish(); 148 return; 149 } 150 int[] codecsToQuery = mCecCodecsToQuery.subList(mQueriedSadCount, 151 Math.min(mCecCodecsToQuery.size(), mQueriedSadCount + MAX_SAD_PER_REQUEST)) 152 .stream().mapToInt(i -> i).toArray(); 153 sendCommand(HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(getSourceAddress(), 154 mTargetAddress, codecsToQuery)); 155 mState = STATE_WAITING_FOR_REPORT_SAD; 156 addTimer(mState, HdmiConfig.TIMEOUT_MS); 157 } 158 159 @Override processCommand(HdmiCecMessage cmd)160 boolean processCommand(HdmiCecMessage cmd) { 161 if (mState != STATE_WAITING_FOR_REPORT_SAD 162 || mTargetAddress != cmd.getSource()) { 163 return false; 164 } 165 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR) { 166 if (cmd.getParams() == null || cmd.getParams().length == 0 167 || cmd.getParams().length % 3 != 0) { 168 // Invalid message. Wait for time-out and query again. 169 return true; 170 } 171 for (int i = 0; i < cmd.getParams().length - 2; i += 3) { 172 if (isValidCodec(cmd.getParams()[i])) { 173 byte[] sad = new byte[]{cmd.getParams()[i], cmd.getParams()[i + 1], 174 cmd.getParams()[i + 2]}; 175 updateResult(sad); 176 } else { 177 // Don't include invalid codecs in the result. Don't query again. 178 Slog.w(TAG, "Dropped invalid codec " + cmd.getParams()[i] + "."); 179 } 180 } 181 mQueriedSadCount += MAX_SAD_PER_REQUEST; 182 mTimeoutRetry = 0; 183 querySad(); 184 return true; 185 } 186 if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT 187 && (cmd.getParams()[0] & 0xFF) 188 == Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR) { 189 if ((cmd.getParams()[1] & 0xFF) == Constants.ABORT_UNRECOGNIZED_OPCODE) { 190 // SAD feature is not supported 191 wrapUpAndFinish(); 192 return true; 193 } 194 if ((cmd.getParams()[1] & 0xFF) == Constants.ABORT_INVALID_OPERAND) { 195 // Queried SADs are not supported 196 mQueriedSadCount += MAX_SAD_PER_REQUEST; 197 mTimeoutRetry = 0; 198 querySad(); 199 return true; 200 } 201 } 202 return false; 203 } 204 isValidCodec(byte codec)205 private boolean isValidCodec(byte codec) { 206 // Bit 7 needs to be 0. 207 if ((codec & (1 << 7)) != 0) { 208 return false; 209 } 210 // Bit [6, 3] is the audio format code. 211 int audioFormatCode = (codec & Constants.AUDIO_FORMAT_MASK) >> 3; 212 return Constants.AUDIO_CODEC_NONE < audioFormatCode 213 && audioFormatCode <= Constants.AUDIO_CODEC_MAX; 214 } 215 updateResult(byte[] sad)216 private void updateResult(byte[] sad) { 217 mSupportedSads.add(sad); 218 } 219 220 @Override handleTimerEvent(int state)221 void handleTimerEvent(int state) { 222 if (mState != state) { 223 return; 224 } 225 if (state == STATE_WAITING_FOR_REPORT_SAD) { 226 if (++mTimeoutRetry <= RETRY_COUNTER_MAX) { 227 querySad(); 228 return; 229 } 230 // Don't query any other SADs if one of the SAD queries ran into the maximum amount of 231 // retries. 232 wrapUpAndFinish(); 233 } 234 } 235 wrapUpAndFinish()236 private void wrapUpAndFinish() { 237 mCallback.onRequestSadDone(mSupportedSads); 238 finish(); 239 } 240 241 /** 242 * Interface used to report result of SAD request. 243 */ 244 interface RequestSadCallback { 245 /** 246 * Called when SAD request is done. 247 * 248 * @param sads a list of all supported SADs. It can be an empty list. 249 */ onRequestSadDone(List<byte[]> supportedSads)250 void onRequestSadDone(List<byte[]> supportedSads); 251 } 252 } 253