• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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