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