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