1 /*
2  * Copyright (C) 2018 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 package com.android.server.hdmi;
17 
18 import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
19 import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
20 
21 import static com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
22 import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
23 import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
24 
25 import android.annotation.Nullable;
26 import android.content.ActivityNotFoundException;
27 import android.content.Intent;
28 import android.hardware.hdmi.DeviceFeatures;
29 import android.hardware.hdmi.HdmiControlManager;
30 import android.hardware.hdmi.HdmiDeviceInfo;
31 import android.hardware.hdmi.HdmiPortInfo;
32 import android.hardware.hdmi.IHdmiControlCallback;
33 import android.media.AudioDeviceInfo;
34 import android.media.AudioFormat;
35 import android.media.AudioManager;
36 import android.media.AudioSystem;
37 import android.media.tv.TvContract;
38 import android.media.tv.TvInputInfo;
39 import android.media.tv.TvInputManager.TvInputCallback;
40 import android.os.SystemProperties;
41 import android.sysprop.HdmiProperties;
42 import android.util.Slog;
43 
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.util.IndentingPrintWriter;
47 import com.android.server.hdmi.Constants.AudioCodec;
48 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
49 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
50 import com.android.server.hdmi.HdmiUtils.CodecSad;
51 import com.android.server.hdmi.HdmiUtils.DeviceConfig;
52 
53 import org.xmlpull.v1.XmlPullParserException;
54 
55 import java.io.File;
56 import java.io.FileInputStream;
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.stream.Collectors;
64 
65 /**
66  * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
67  * system.
68  */
69 public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
70 
71     private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
72 
73     private static final boolean WAKE_ON_HOTPLUG = false;
74     private static final int MAX_CHANNELS = 8;
75     private static final HashMap<Integer, List<Integer>> AUDIO_CODECS_MAP =
76             mapAudioCodecWithAudioFormat();
77 
78     // Whether the System Audio Control feature is enabled or not. True by default.
79     @GuardedBy("mLock")
80     private boolean mSystemAudioControlFeatureEnabled;
81 
82     /**
83      * Indicates if the TV that the current device is connected to supports System Audio Mode or not
84      *
85      * <p>If the current device has no information on this, keep mTvSystemAudioModeSupport null
86      *
87      * <p>The boolean will be reset to null every time when the current device goes to standby
88      * or loses its physical address.
89      */
90     private Boolean mTvSystemAudioModeSupport = null;
91 
92     // Whether ARC is available or not. "true" means that ARC is established between TV and
93     // AVR as audio receiver.
94     @ServiceThreadOnly private boolean mArcEstablished = false;
95 
96     // If the current device uses TvInput for ARC. We assume all other inputs also use TvInput
97     // when ARC is using TvInput.
98     private boolean mArcIntentUsed = HdmiProperties.arc_port().orElse("0").contains("tvinput");
99 
100     // Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to
101     // accept input switching request from HDMI devices.
102     @GuardedBy("mLock")
103     private final HashMap<Integer, String> mPortIdToTvInputs = new HashMap<>();
104 
105     // A map from TV input id to HDMI device info.
106     @GuardedBy("mLock")
107     private final HashMap<String, HdmiDeviceInfo> mTvInputsToDeviceInfo = new HashMap<>();
108 
109     // Message buffer used to buffer selected messages to process later. <Active Source>
110     // from a source device, for instance, needs to be buffered if the device is not
111     // discovered yet. The buffered commands are taken out and when they are ready to
112     // handle.
113     private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
114 
HdmiCecLocalDeviceAudioSystem(HdmiControlService service)115     protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
116         super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
117         mRoutingControlFeatureEnabled = mService.getHdmiCecConfig().getIntValue(
118                 HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL)
119                     == HdmiControlManager.ROUTING_CONTROL_ENABLED;
120         mSystemAudioControlFeatureEnabled = mService.getHdmiCecConfig().getIntValue(
121                 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL)
122                     == HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED;
123         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
124     }
125 
126     private static final String SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH = "/vendor/etc/sadConfig.xml";
127 
128     private final TvInputCallback mTvInputCallback = new TvInputCallback() {
129         @Override
130         public void onInputAdded(String inputId) {
131             addOrUpdateTvInput(inputId);
132         }
133 
134         @Override
135         public void onInputRemoved(String inputId) {
136             removeTvInput(inputId);
137         }
138 
139         @Override
140         public void onInputUpdated(String inputId) {
141             addOrUpdateTvInput(inputId);
142         }
143     };
144 
145     @ServiceThreadOnly
addOrUpdateTvInput(String inputId)146     private void addOrUpdateTvInput(String inputId) {
147         assertRunOnServiceThread();
148         synchronized (mLock) {
149             TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
150             if (tvInfo == null) {
151                 return;
152             }
153             HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
154             if (info == null) {
155                 return;
156             }
157             mPortIdToTvInputs.put(info.getPortId(), inputId);
158             mTvInputsToDeviceInfo.put(inputId, info);
159             if (info.isCecDevice()) {
160                 processDelayedActiveSource(info.getLogicalAddress());
161             }
162         }
163     }
164 
165     @ServiceThreadOnly
removeTvInput(String inputId)166     private void removeTvInput(String inputId) {
167         assertRunOnServiceThread();
168         synchronized (mLock) {
169             if (mTvInputsToDeviceInfo.get(inputId) == null) {
170                 return;
171             }
172             int portId = mTvInputsToDeviceInfo.get(inputId).getPortId();
173             mPortIdToTvInputs.remove(portId);
174             mTvInputsToDeviceInfo.remove(inputId);
175         }
176     }
177 
178     @Override
179     @ServiceThreadOnly
isInputReady(int portId)180     protected boolean isInputReady(int portId) {
181         assertRunOnServiceThread();
182         String tvInputId = mPortIdToTvInputs.get(portId);
183         HdmiDeviceInfo info = mTvInputsToDeviceInfo.get(tvInputId);
184         return info != null;
185     }
186 
187     @Override
computeDeviceFeatures()188     protected DeviceFeatures computeDeviceFeatures() {
189         boolean arcSupport = SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true);
190 
191         return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
192                 .setArcRxSupport(arcSupport ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
193                 .build();
194     }
195 
196     @Override
197     @ServiceThreadOnly
onHotplug(int portId, boolean connected)198     void onHotplug(int portId, boolean connected) {
199         assertRunOnServiceThread();
200         if (WAKE_ON_HOTPLUG && connected) {
201             mService.wakeUp();
202         }
203         if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
204             mCecMessageCache.flushAll();
205             if (!connected) {
206                 if (isSystemAudioActivated()) {
207                     mTvSystemAudioModeSupport = null;
208                     checkSupportAndSetSystemAudioMode(false);
209                 }
210                 if (isArcEnabled()) {
211                     setArcStatus(false);
212                 }
213             }
214         } else if (!connected && mPortIdToTvInputs.get(portId) != null) {
215             String tvInputId = mPortIdToTvInputs.get(portId);
216             HdmiDeviceInfo info = mTvInputsToDeviceInfo.get(tvInputId);
217             if (info == null) {
218                 return;
219             }
220             // Update with TIF on the device removal. TIF callback will update
221             // mPortIdToTvInputs and mPortIdToTvInputs.
222             mService.getHdmiCecNetwork().removeCecDevice(this, info.getLogicalAddress());
223         }
224     }
225 
226     @Override
227     @ServiceThreadOnly
disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)228     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
229         terminateAudioReturnChannel();
230 
231         super.disableDevice(initiatedByCec, callback);
232         assertRunOnServiceThread();
233         mService.unregisterTvInputCallback(mTvInputCallback);
234     }
235 
236     @Override
237     @ServiceThreadOnly
onStandby(boolean initiatedByCec, int standbyAction)238     protected void onStandby(boolean initiatedByCec, int standbyAction) {
239         assertRunOnServiceThread();
240         // Invalidate the internal active source record when goes to standby
241         // This set will also update mIsActiveSource
242         mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS,
243                 "HdmiCecLocalDeviceAudioSystem#onStandby()");
244         mTvSystemAudioModeSupport = null;
245         // Record the last state of System Audio Control before going to standby
246         synchronized (mLock) {
247             mService.writeStringSystemProperty(
248                     Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL,
249                     isSystemAudioActivated() ? "true" : "false");
250         }
251         terminateSystemAudioMode();
252     }
253 
254     @Override
255     @ServiceThreadOnly
onAddressAllocated(int logicalAddress, int reason)256     protected void onAddressAllocated(int logicalAddress, int reason) {
257         assertRunOnServiceThread();
258         if (reason == mService.INITIATED_BY_ENABLE_CEC) {
259             mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
260                     getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST,
261                     "HdmiCecLocalDeviceAudioSystem#onAddressAllocated()");
262         }
263         mService.sendCecCommand(
264                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
265                         getDeviceInfo().getLogicalAddress(),
266                         mService.getPhysicalAddress(),
267                         mDeviceType));
268         mService.sendCecCommand(
269                 HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
270                         getDeviceInfo().getLogicalAddress(), mService.getVendorId()));
271         mService.registerTvInputCallback(mTvInputCallback);
272         // Some TVs, for example Mi TV, need ARC on before turning System Audio Mode on
273         // to request Short Audio Descriptor. Since ARC and SAM are independent,
274         // we can turn on ARC anyways when audio system device just boots up.
275         initArcOnFromAvr();
276 
277         // This prevents turning on of System Audio Mode during a quiescent boot. If the quiescent
278         // boot is exited just after this check, this code will be executed only at the next
279         // wake-up.
280         if (!mService.isScreenOff()) {
281             int systemAudioControlOnPowerOnProp =
282                     SystemProperties.getInt(
283                             PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON,
284                             ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON);
285             boolean lastSystemAudioControlStatus =
286                     SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
287             systemAudioControlOnPowerOn(
288                     systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
289         }
290         mService.getHdmiCecNetwork().clearDeviceList();
291         launchDeviceDiscovery();
292         startQueuedActions();
293     }
294 
295     @Override
findKeyReceiverAddress()296     protected int findKeyReceiverAddress() {
297         if (getActiveSource().isValid()) {
298             return getActiveSource().logicalAddress;
299         }
300         return Constants.ADDR_INVALID;
301     }
302 
303     @VisibleForTesting
systemAudioControlOnPowerOn( int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus)304     protected void systemAudioControlOnPowerOn(
305             int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
306         if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
307                 || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
308                 && lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) {
309             addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
310         }
311     }
312 
313     @Override
314     @ServiceThreadOnly
getPreferredAddress()315     protected int getPreferredAddress() {
316         assertRunOnServiceThread();
317         return SystemProperties.getInt(
318             Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
319     }
320 
321     @Override
322     @ServiceThreadOnly
setPreferredAddress(int addr)323     protected void setPreferredAddress(int addr) {
324         assertRunOnServiceThread();
325         mService.writeStringSystemProperty(
326                 Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr));
327     }
328 
329     @ServiceThreadOnly
processDelayedActiveSource(int address)330     void processDelayedActiveSource(int address) {
331         assertRunOnServiceThread();
332         mDelayedMessageBuffer.processActiveSource(address);
333     }
334 
335     @Override
336     @ServiceThreadOnly
337     @Constants.HandleMessageResult
handleActiveSource(HdmiCecMessage message)338     protected int handleActiveSource(HdmiCecMessage message) {
339         assertRunOnServiceThread();
340         int logicalAddress = message.getSource();
341         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
342         if (HdmiUtils.getLocalPortFromPhysicalAddress(
343             physicalAddress, mService.getPhysicalAddress())
344                 == HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) {
345             return super.handleActiveSource(message);
346         }
347         // If the new Active Source is under the current device, check if the device info and the TV
348         // input is ready to switch to the new Active Source. If not ready, buffer the cec command
349         // to handle later when the device is ready.
350         HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress);
351         if (info == null) {
352             HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
353             mDelayedMessageBuffer.add(message);
354         } else if (!isInputReady(info.getPortId())){
355             HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
356             mDelayedMessageBuffer.add(message);
357         } else {
358             mDelayedMessageBuffer.removeActiveSource();
359             return super.handleActiveSource(message);
360         }
361         return Constants.HANDLED;
362     }
363 
364     @Override
365     @ServiceThreadOnly
366     @Constants.HandleMessageResult
handleInitiateArc(HdmiCecMessage message)367     protected int handleInitiateArc(HdmiCecMessage message) {
368         assertRunOnServiceThread();
369         // TODO(amyjojo): implement initiate arc handler
370         HdmiLogger.debug(TAG + "Stub handleInitiateArc");
371         return Constants.HANDLED;
372     }
373 
374     @Override
375     @ServiceThreadOnly
376     @Constants.HandleMessageResult
handleReportArcInitiate(HdmiCecMessage message)377     protected int handleReportArcInitiate(HdmiCecMessage message) {
378         assertRunOnServiceThread();
379         /*
380          * Ideally, we should have got this response before the {@link ArcInitiationActionFromAvr}
381          * has timed out. Even if the response is late, {@link ArcInitiationActionFromAvr
382          * #handleInitiateArcTimeout()} would not have disabled ARC. So nothing needs to be done
383          * here.
384          */
385         return Constants.HANDLED;
386     }
387 
388     @Override
389     @ServiceThreadOnly
390     @Constants.HandleMessageResult
handleReportArcTermination(HdmiCecMessage message)391     protected int handleReportArcTermination(HdmiCecMessage message) {
392         assertRunOnServiceThread();
393         processArcTermination();
394         return Constants.HANDLED;
395     }
396 
397     @Override
398     @ServiceThreadOnly
399     @Constants.HandleMessageResult
handleGiveAudioStatus(HdmiCecMessage message)400     protected int handleGiveAudioStatus(HdmiCecMessage message) {
401         assertRunOnServiceThread();
402         if (isSystemAudioControlFeatureEnabled() && mService.getHdmiCecVolumeControl()
403                 == HdmiControlManager.VOLUME_CONTROL_ENABLED) {
404             reportAudioStatus(message.getSource());
405             return Constants.HANDLED;
406         }
407         return Constants.ABORT_REFUSED;
408     }
409 
410     @Override
411     @ServiceThreadOnly
412     @Constants.HandleMessageResult
handleGiveSystemAudioModeStatus(HdmiCecMessage message)413     protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
414         assertRunOnServiceThread();
415         // If the audio system is initiating the system audio mode on and TV asks the sam status at
416         // the same time, respond with true. Since we know TV supports sam in this situation.
417         // If the query comes from STB, we should respond with the current sam status and the STB
418         // should listen to the <Set System Audio Mode> broadcasting.
419         boolean isSystemAudioModeOnOrTurningOn = isSystemAudioActivated();
420         if (!isSystemAudioModeOnOrTurningOn
421                 && message.getSource() == Constants.ADDR_TV
422                 && hasAction(SystemAudioInitiationActionFromAvr.class)) {
423             isSystemAudioModeOnOrTurningOn = true;
424         }
425         mService.sendCecCommand(
426                 HdmiCecMessageBuilder.buildReportSystemAudioMode(
427                         getDeviceInfo().getLogicalAddress(),
428                         message.getSource(),
429                         isSystemAudioModeOnOrTurningOn));
430         return Constants.HANDLED;
431     }
432 
433     @Override
434     @ServiceThreadOnly
435     @Constants.HandleMessageResult
handleRequestArcInitiate(HdmiCecMessage message)436     protected int handleRequestArcInitiate(HdmiCecMessage message) {
437         assertRunOnServiceThread();
438         removeAction(ArcInitiationActionFromAvr.class);
439         if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
440             return Constants.ABORT_UNRECOGNIZED_OPCODE;
441         } else if (!isDirectConnectToTv()) {
442             HdmiLogger.debug("AVR device is not directly connected with TV");
443             return Constants.ABORT_NOT_IN_CORRECT_MODE;
444         } else {
445             addAndStartAction(new ArcInitiationActionFromAvr(this));
446             return Constants.HANDLED;
447         }
448     }
449 
450     @Override
451     @ServiceThreadOnly
452     @Constants.HandleMessageResult
handleRequestArcTermination(HdmiCecMessage message)453     protected int handleRequestArcTermination(HdmiCecMessage message) {
454         assertRunOnServiceThread();
455         if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
456             return Constants.ABORT_UNRECOGNIZED_OPCODE;
457         } else if (!isArcEnabled()) {
458             HdmiLogger.debug("ARC is not established between TV and AVR device");
459             return Constants.ABORT_NOT_IN_CORRECT_MODE;
460         } else {
461             if (!getActions(ArcTerminationActionFromAvr.class).isEmpty()
462                     && !getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.isEmpty()) {
463                 IHdmiControlCallback callback =
464                         getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0);
465                 removeAction(ArcTerminationActionFromAvr.class);
466                 addAndStartAction(new ArcTerminationActionFromAvr(this, callback));
467             } else {
468                 removeAction(ArcTerminationActionFromAvr.class);
469                 addAndStartAction(new ArcTerminationActionFromAvr(this));
470             }
471             return Constants.HANDLED;
472         }
473     }
474 
475     @ServiceThreadOnly
476     @Constants.HandleMessageResult
handleRequestShortAudioDescriptor(HdmiCecMessage message)477     protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) {
478         assertRunOnServiceThread();
479         HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor");
480         if (!isSystemAudioControlFeatureEnabled()) {
481             return Constants.ABORT_REFUSED;
482         }
483         if (!isSystemAudioActivated()) {
484             return Constants.ABORT_NOT_IN_CORRECT_MODE;
485         }
486 
487         List<DeviceConfig> config = null;
488         File file = new File(SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH);
489         if (file.exists()) {
490             try {
491                 InputStream in = new FileInputStream(file);
492                 config = HdmiUtils.ShortAudioDescriptorXmlParser.parse(in);
493                 in.close();
494             } catch (IOException e) {
495                 Slog.e(TAG, "Error reading file: " + file, e);
496             } catch (XmlPullParserException e) {
497                 Slog.e(TAG, "Unable to parse file: " + file, e);
498             }
499         }
500 
501         @AudioCodec int[] audioCodecs = parseAudioCodecs(message.getParams());
502         byte[] sadBytes;
503         if (config != null && config.size() > 0) {
504             sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioCodecs);
505         } else {
506             AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo();
507             if (deviceInfo == null) {
508                 return Constants.ABORT_UNABLE_TO_DETERMINE;
509             }
510 
511             sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioCodecs);
512         }
513 
514         if (sadBytes.length == 0) {
515             return Constants.ABORT_INVALID_OPERAND;
516         } else {
517             mService.sendCecCommand(
518                     HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
519                             getDeviceInfo().getLogicalAddress(), message.getSource(), sadBytes));
520             return Constants.HANDLED;
521         }
522     }
523 
524     @VisibleForTesting
getSupportedShortAudioDescriptors( AudioDeviceInfo deviceInfo, @AudioCodec int[] audioCodecs)525     byte[] getSupportedShortAudioDescriptors(
526             AudioDeviceInfo deviceInfo, @AudioCodec int[] audioCodecs) {
527         ArrayList<byte[]> sads = new ArrayList<>(audioCodecs.length);
528         for (@AudioCodec int audioCodec : audioCodecs) {
529             byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioCodec);
530             if (sad != null) {
531                 if (sad.length == 3) {
532 
533                     sads.add(sad);
534                 } else {
535                     HdmiLogger.warning(
536                             "Dropping Short Audio Descriptor with length %d for requested codec %x",
537                             sad.length, audioCodec);
538                 }
539             }
540         }
541         return getShortAudioDescriptorBytes(sads);
542     }
543 
getSupportedShortAudioDescriptorsFromConfig( List<DeviceConfig> deviceConfig, @AudioCodec int[] audioCodecs)544     private byte[] getSupportedShortAudioDescriptorsFromConfig(
545             List<DeviceConfig> deviceConfig, @AudioCodec int[] audioCodecs) {
546         DeviceConfig deviceConfigToUse = null;
547         String audioDeviceName = SystemProperties.get(
548                 Constants.PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT,
549                 "VX_AUDIO_DEVICE_IN_HDMI_ARC");
550         for (DeviceConfig device : deviceConfig) {
551             if (device.name.equals(audioDeviceName)) {
552                 deviceConfigToUse = device;
553                 break;
554             }
555         }
556         if (deviceConfigToUse == null) {
557             Slog.w(TAG, "sadConfig.xml does not have required device info for " + audioDeviceName);
558             return new byte[0];
559         }
560         HashMap<Integer, byte[]> map = new HashMap<>();
561         ArrayList<byte[]> sads = new ArrayList<>(audioCodecs.length);
562         for (CodecSad codecSad : deviceConfigToUse.supportedCodecs) {
563             map.put(codecSad.audioCodec, codecSad.sad);
564         }
565         for (int i = 0; i < audioCodecs.length; i++) {
566             if (map.containsKey(audioCodecs[i])) {
567                 byte[] sad = map.get(audioCodecs[i]);
568                 if (sad != null && sad.length == 3) {
569                     sads.add(sad);
570                 }
571             }
572         }
573         return getShortAudioDescriptorBytes(sads);
574     }
575 
getShortAudioDescriptorBytes(ArrayList<byte[]> sads)576     private byte[] getShortAudioDescriptorBytes(ArrayList<byte[]> sads) {
577         // Short Audio Descriptors are always 3 bytes long.
578         byte[] bytes = new byte[sads.size() * 3];
579         int index = 0;
580         for (byte[] sad : sads) {
581             System.arraycopy(sad, 0, bytes, index, 3);
582             index += 3;
583         }
584         return bytes;
585     }
586 
587     /**
588      * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the
589      * audioCodec is not supported.
590      */
591     @Nullable
592     @VisibleForTesting
getSupportedShortAudioDescriptor( AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec)593     byte[] getSupportedShortAudioDescriptor(
594             AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec) {
595         byte[] shortAudioDescriptor = new byte[3];
596 
597         int[] deviceSupportedAudioFormats = deviceInfo.getEncodings();
598         // Return null when audioCodec or device does not support any audio formats.
599         if (!AUDIO_CODECS_MAP.containsKey(audioCodec) || deviceSupportedAudioFormats.length == 0) {
600             return null;
601         }
602         List<Integer> audioCodecSupportedAudioFormats = AUDIO_CODECS_MAP.get(audioCodec);
603 
604         for (int supportedAudioFormat : deviceSupportedAudioFormats) {
605             if (audioCodecSupportedAudioFormats.contains(supportedAudioFormat)) {
606                 // Initialise the first two bytes of short audio descriptor.
607                 shortAudioDescriptor[0] = getFirstByteOfSAD(deviceInfo, audioCodec);
608                 shortAudioDescriptor[1] = getSecondByteOfSAD(deviceInfo);
609                 switch (audioCodec) {
610                     case Constants.AUDIO_CODEC_NONE: {
611                         return null;
612                     }
613                     case Constants.AUDIO_CODEC_LPCM: {
614                         if (supportedAudioFormat == AudioFormat.ENCODING_PCM_16BIT) {
615                             shortAudioDescriptor[2] = (byte) 0x01;
616                         } else if (supportedAudioFormat
617                                 == AudioFormat.ENCODING_PCM_24BIT_PACKED) {
618                             shortAudioDescriptor[2] = (byte) 0x04;
619                         } else {
620                             // Since no bit is reserved for these audio formats in LPCM codec.
621                             shortAudioDescriptor[2] = (byte) 0x00;
622                         }
623                         return shortAudioDescriptor;
624                     }
625                     case Constants.AUDIO_CODEC_DD:
626                     case Constants.AUDIO_CODEC_MPEG1:
627                     case Constants.AUDIO_CODEC_MP3:
628                     case Constants.AUDIO_CODEC_MPEG2:
629                     case Constants.AUDIO_CODEC_AAC:
630                     case Constants.AUDIO_CODEC_DTS: {
631                         shortAudioDescriptor[2] = getThirdSadByteForCodecs2Through8(deviceInfo);
632                         return shortAudioDescriptor;
633                     }
634                     case Constants.AUDIO_CODEC_DDP:
635                     case Constants.AUDIO_CODEC_DTSHD:
636                     case Constants.AUDIO_CODEC_TRUEHD: {
637                         // Default value is 0x0 unless defined by Audio Codec Vendor.
638                         shortAudioDescriptor[2] = (byte) 0x00;
639                         return shortAudioDescriptor;
640                     }
641                     case Constants.AUDIO_CODEC_ATRAC:
642                     case Constants.AUDIO_CODEC_ONEBITAUDIO:
643                     case Constants.AUDIO_CODEC_DST:
644                     case Constants.AUDIO_CODEC_WMAPRO:
645                         // Not supported.
646                     default: {
647                         return null;
648                     }
649                 }
650             }
651         }
652         return null;
653     }
654 
mapAudioCodecWithAudioFormat()655     private static HashMap<Integer, List<Integer>> mapAudioCodecWithAudioFormat() {
656         // Mapping the values of @AudioCodec audio codecs with @AudioFormat audio formats.
657         HashMap<Integer, List<Integer>> audioCodecsMap = new HashMap<Integer, List<Integer>>();
658 
659         audioCodecsMap.put(Constants.AUDIO_CODEC_NONE, List.of(AudioFormat.ENCODING_DEFAULT));
660         audioCodecsMap.put(
661                 Constants.AUDIO_CODEC_LPCM,
662                 List.of(
663                         AudioFormat.ENCODING_PCM_8BIT,
664                         AudioFormat.ENCODING_PCM_16BIT,
665                         AudioFormat.ENCODING_PCM_FLOAT,
666                         AudioFormat.ENCODING_PCM_24BIT_PACKED,
667                         AudioFormat.ENCODING_PCM_32BIT));
668         audioCodecsMap.put(Constants.AUDIO_CODEC_DD, List.of(AudioFormat.ENCODING_AC3));
669         audioCodecsMap.put(Constants.AUDIO_CODEC_MPEG1, List.of(AudioFormat.ENCODING_AAC_HE_V1));
670         audioCodecsMap.put(Constants.AUDIO_CODEC_MPEG2, List.of(AudioFormat.ENCODING_AAC_HE_V2));
671         audioCodecsMap.put(Constants.AUDIO_CODEC_MP3, List.of(AudioFormat.ENCODING_MP3));
672         audioCodecsMap.put(Constants.AUDIO_CODEC_AAC, List.of(AudioFormat.ENCODING_AAC_LC));
673         audioCodecsMap.put(Constants.AUDIO_CODEC_DTS, List.of(AudioFormat.ENCODING_DTS));
674         audioCodecsMap.put(
675                 Constants.AUDIO_CODEC_DDP,
676                 List.of(AudioFormat.ENCODING_E_AC3, AudioFormat.ENCODING_E_AC3_JOC));
677         audioCodecsMap.put(Constants.AUDIO_CODEC_DTSHD, List.of(AudioFormat.ENCODING_DTS_HD));
678         audioCodecsMap.put(
679                 Constants.AUDIO_CODEC_TRUEHD,
680                 List.of(AudioFormat.ENCODING_DOLBY_TRUEHD, AudioFormat.ENCODING_DOLBY_MAT));
681 
682         return audioCodecsMap;
683     }
684 
getFirstByteOfSAD(AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec)685     private byte getFirstByteOfSAD(AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec) {
686         byte firstByte = 0;
687         int maxNumberOfChannels = getMaxNumberOfChannels(deviceInfo);
688 
689         // Fill bits 0-2 of the first byte.
690         firstByte |= (maxNumberOfChannels - 1);
691 
692         // Fill bits 3-6 of the first byte.
693         firstByte |= (audioCodec << 3);
694 
695         return firstByte;
696     }
697 
getSecondByteOfSAD(AudioDeviceInfo deviceInfo)698     private byte getSecondByteOfSAD(AudioDeviceInfo deviceInfo) {
699         ArrayList<Integer> samplingRates =
700                 new ArrayList<Integer>(Arrays.asList(32, 44, 48, 88, 96, 176, 192));
701 
702         // samplingRatesdevicesupports is guaranteed to be not null
703         int[] samplingRatesDeviceSupports = deviceInfo.getSampleRates();
704         if (samplingRatesDeviceSupports.length == 0) {
705             Slog.e(TAG, "Device supports arbitrary rates");
706             // Since device supports arbitrary rates, we will return 0x7f since bit 7 is reserved.
707             return (byte) 0x7f;
708         }
709         byte secondByte = 0;
710         for (int supportedSampleRate : samplingRatesDeviceSupports) {
711             if (samplingRates.contains(supportedSampleRate)) {
712                 int index = samplingRates.indexOf(supportedSampleRate);
713                 // Setting the bit of a sample rate which is being supported.
714                 secondByte |= (1 << index);
715             }
716         }
717 
718         return secondByte;
719     }
720 
721     /**
722      * Empty array from deviceInfo.getChannelCounts() implies device supports arbitrary channel
723      * counts and hence we assume max channels are supported by the device.
724      */
getMaxNumberOfChannels(AudioDeviceInfo deviceInfo)725     private int getMaxNumberOfChannels(AudioDeviceInfo deviceInfo) {
726         int maxNumberOfChannels = MAX_CHANNELS;
727         int[] channelCounts = deviceInfo.getChannelCounts();
728         if (channelCounts.length != 0) {
729             maxNumberOfChannels = channelCounts[channelCounts.length - 1];
730             maxNumberOfChannels =
731                     (maxNumberOfChannels > MAX_CHANNELS ? MAX_CHANNELS : maxNumberOfChannels);
732         }
733         return maxNumberOfChannels;
734     }
735 
getThirdSadByteForCodecs2Through8(AudioDeviceInfo deviceInfo)736     private byte getThirdSadByteForCodecs2Through8(AudioDeviceInfo deviceInfo) {
737         /*
738          * Here, we are assuming that max bit rate is closely equals to the max sampling rate the
739          * device supports.
740          */
741         int maxSamplingRate = 0;
742         int[] samplingRatesDeviceSupports = deviceInfo.getSampleRates();
743         if (samplingRatesDeviceSupports.length == 0) {
744             maxSamplingRate = 192;
745         } else {
746             for (int sampleRate : samplingRatesDeviceSupports) {
747                 if (maxSamplingRate < sampleRate) {
748                     maxSamplingRate = sampleRate;
749                 }
750             }
751         }
752 
753         return (byte) (maxSamplingRate / 8);
754     }
755 
756     @Nullable
getSystemAudioDeviceInfo()757     private AudioDeviceInfo getSystemAudioDeviceInfo() {
758         AudioManager audioManager = mService.getContext().getSystemService(AudioManager.class);
759         if (audioManager == null) {
760             HdmiLogger.error(
761                     "Error getting system audio device because AudioManager not available.");
762             return null;
763         }
764         AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
765         HdmiLogger.debug("Found %d audio input devices", devices.length);
766         for (AudioDeviceInfo device : devices) {
767             HdmiLogger.debug("%s at port %s", device.getProductName(), device.getPort());
768             HdmiLogger.debug("Supported encodings are %s",
769                     Arrays.stream(device.getEncodings()).mapToObj(
770                             AudioFormat::toLogFriendlyEncoding
771                     ).collect(Collectors.joining(", ")));
772             if (device.getType() == AudioDeviceInfo.TYPE_HDMI_ARC) {
773                 return device;
774             }
775         }
776         return null;
777     }
778 
779     @AudioCodec
parseAudioCodecs(byte[] params)780     private int[] parseAudioCodecs(byte[] params) {
781         @AudioCodec int[] audioCodecs = new int[params.length];
782         for (int i = 0; i < params.length; i++) {
783             byte val = params[i];
784             audioCodecs[i] =
785                     val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
786         }
787         return audioCodecs;
788     }
789 
790     @Override
791     @ServiceThreadOnly
792     @Constants.HandleMessageResult
handleSystemAudioModeRequest(HdmiCecMessage message)793     protected int handleSystemAudioModeRequest(HdmiCecMessage message) {
794         assertRunOnServiceThread();
795         boolean systemAudioStatusOn = message.getParams().length != 0;
796         // Check if the request comes from a non-TV device.
797         // Need to check if TV supports System Audio Control
798         // if non-TV device tries to turn on the feature
799         if (message.getSource() != Constants.ADDR_TV) {
800             if (systemAudioStatusOn) {
801                 return handleSystemAudioModeOnFromNonTvDevice(message);
802             }
803         } else {
804             // If TV request the feature on
805             // cache TV supporting System Audio Control
806             // until Audio System loses its physical address.
807             setTvSystemAudioModeSupport(true);
808         }
809         // If TV or Audio System does not support the feature,
810         // will send abort command.
811         if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
812             return Constants.ABORT_REFUSED;
813         }
814 
815         mService.sendCecCommand(
816                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
817                         getDeviceInfo().getLogicalAddress(),
818                         Constants.ADDR_BROADCAST,
819                         systemAudioStatusOn));
820 
821         if (systemAudioStatusOn) {
822             // If TV sends out SAM Request with a path of a non-CEC device, which should not show
823             // up in the CEC device list and not under the current AVR device, the AVR would switch
824             // to ARC.
825             int sourcePhysicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
826             if (HdmiUtils.getLocalPortFromPhysicalAddress(
827                     sourcePhysicalAddress, getDeviceInfo().getPhysicalAddress())
828                             != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) {
829                 return Constants.HANDLED;
830             }
831             HdmiDeviceInfo safeDeviceInfoByPath =
832                     mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress);
833             if (safeDeviceInfoByPath == null) {
834                 switchInputOnReceivingNewActivePath(sourcePhysicalAddress);
835             }
836         }
837         return Constants.HANDLED;
838     }
839 
840     @Override
841     @ServiceThreadOnly
842     @Constants.HandleMessageResult
handleSetSystemAudioMode(HdmiCecMessage message)843     protected int handleSetSystemAudioMode(HdmiCecMessage message) {
844         assertRunOnServiceThread();
845         if (!checkSupportAndSetSystemAudioMode(
846                 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
847             return Constants.ABORT_REFUSED;
848         }
849         return Constants.HANDLED;
850     }
851 
852     @Override
853     @ServiceThreadOnly
854     @Constants.HandleMessageResult
handleSystemAudioModeStatus(HdmiCecMessage message)855     protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
856         assertRunOnServiceThread();
857         if (!checkSupportAndSetSystemAudioMode(
858                 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
859             return Constants.ABORT_REFUSED;
860         }
861         return Constants.HANDLED;
862     }
863 
864     @ServiceThreadOnly
setArcStatus(boolean enabled)865     void setArcStatus(boolean enabled) {
866         assertRunOnServiceThread();
867 
868         HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
869         // 1. Enable/disable ARC circuit.
870         enableAudioReturnChannel(enabled);
871         // 2. Notify arc status to audio service.
872         notifyArcStatusToAudioService(enabled);
873         // 3. Update arc status;
874         mArcEstablished = enabled;
875     }
876 
processArcTermination()877     void processArcTermination() {
878         setArcStatus(false);
879         // Switch away from ARC input when ARC is terminated.
880         if (getLocalActivePort() == Constants.CEC_SWITCH_ARC) {
881             routeToInputFromPortId(getRoutingPort());
882         }
883     }
884 
885     /** Switch hardware ARC circuit in the system. */
886     @ServiceThreadOnly
enableAudioReturnChannel(boolean enabled)887     private void enableAudioReturnChannel(boolean enabled) {
888         assertRunOnServiceThread();
889         mService.enableAudioReturnChannel(
890                 Integer.parseInt(HdmiProperties.arc_port().orElse("0")),
891                 enabled);
892     }
893 
notifyArcStatusToAudioService(boolean enabled)894     private void notifyArcStatusToAudioService(boolean enabled) {
895         // Note that we don't set any name to ARC.
896         mService.getAudioManager()
897             .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI_ARC, enabled ? 1 : 0, "", "");
898     }
899 
reportAudioStatus(int source)900     void reportAudioStatus(int source) {
901         assertRunOnServiceThread();
902         if (mService.getHdmiCecVolumeControl()
903                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
904             return;
905         }
906 
907         int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
908         boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
909         int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
910         int minVolume = mService.getAudioManager().getStreamMinVolume(AudioManager.STREAM_MUSIC);
911         int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
912         HdmiLogger.debug("Reporting volume %d (%d-%d) as CEC volume %d", volume,
913                 minVolume, maxVolume, scaledVolume);
914 
915         mService.sendCecCommand(
916                 HdmiCecMessageBuilder.buildReportAudioStatus(
917                         getDeviceInfo().getLogicalAddress(), source, scaledVolume, mute));
918     }
919 
920     /**
921      * Method to check if device support System Audio Control. If so, wake up device if necessary.
922      *
923      * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode
924      * @param newSystemAudioMode turning feature on or off. True is on. False is off.
925      * @return true or false.
926      *
927      * <p>False when device does not support the feature. Otherwise returns true.
928      */
checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode)929     protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) {
930         if (!isSystemAudioControlFeatureEnabled()) {
931             HdmiLogger.debug(
932                     "Cannot turn "
933                             + (newSystemAudioMode ? "on" : "off")
934                             + "system audio mode "
935                             + "because the System Audio Control feature is disabled.");
936             return false;
937         }
938         HdmiLogger.debug(
939                 "System Audio Mode change[old:%b new:%b]",
940                 isSystemAudioActivated(), newSystemAudioMode);
941         // Wake up device if System Audio Control is turned on
942         if (newSystemAudioMode) {
943             mService.wakeUp();
944         }
945         setSystemAudioMode(newSystemAudioMode);
946         return true;
947     }
948 
949     /**
950      * Real work to turn on or off System Audio Mode.
951      *
952      * Use {@link #checkSupportAndSetSystemAudioMode(boolean)}
953      * if trying to turn on or off the feature.
954      */
setSystemAudioMode(boolean newSystemAudioMode)955     private void setSystemAudioMode(boolean newSystemAudioMode) {
956         int targetPhysicalAddress = getActiveSource().physicalAddress;
957         int port = mService.pathToPortId(targetPhysicalAddress);
958         if (newSystemAudioMode && port >= 0) {
959             switchToAudioInput();
960         }
961         // Mute device when feature is turned off and unmute device when feature is turned on.
962         // CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING is false when device never needs to be muted.
963         boolean systemAudioModeMutingEnabled = mService.getHdmiCecConfig().getIntValue(
964                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING)
965                         == HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_ENABLED;
966         boolean currentMuteStatus =
967                 mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
968         if (currentMuteStatus == newSystemAudioMode) {
969             if (systemAudioModeMutingEnabled || newSystemAudioMode) {
970                 mService.getAudioManager()
971                         .adjustStreamVolume(
972                                 AudioManager.STREAM_MUSIC,
973                                 newSystemAudioMode
974                                         ? AudioManager.ADJUST_UNMUTE
975                                         : AudioManager.ADJUST_MUTE,
976                                 0);
977             }
978         }
979         updateAudioManagerForSystemAudio(newSystemAudioMode);
980         synchronized (mLock) {
981             if (isSystemAudioActivated() != newSystemAudioMode) {
982                 mService.setSystemAudioActivated(newSystemAudioMode);
983                 mService.announceSystemAudioModeChange(newSystemAudioMode);
984             }
985         }
986         // Since ARC is independent from System Audio Mode control, when the TV requests
987         // System Audio Mode off, it does not need to terminate ARC at the same time.
988         // When the current audio device is using ARC as a TV input and disables muting,
989         // it needs to automatically switch to the previous active input source when System
990         // Audio Mode is off even without terminating the ARC. This can stop the current
991         // audio device from playing audio when system audio mode is off.
992         if (mArcIntentUsed
993                 && !systemAudioModeMutingEnabled
994                 && !newSystemAudioMode
995                 && getLocalActivePort() == Constants.CEC_SWITCH_ARC) {
996             routeToInputFromPortId(getRoutingPort());
997         }
998         // Init arc whenever System Audio Mode is on
999         // Since some TVs don't request ARC on with System Audio Mode on request
1000         if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
1001                 && isDirectConnectToTv() && mService.isSystemAudioActivated()) {
1002             if (!hasAction(ArcInitiationActionFromAvr.class)) {
1003                 addAndStartAction(new ArcInitiationActionFromAvr(this));
1004             }
1005         }
1006     }
1007 
switchToAudioInput()1008     protected void switchToAudioInput() {
1009     }
1010 
isDirectConnectToTv()1011     protected boolean isDirectConnectToTv() {
1012         int myPhysicalAddress = mService.getPhysicalAddress();
1013         return (myPhysicalAddress & Constants.ROUTING_PATH_TOP_MASK) == myPhysicalAddress;
1014     }
1015 
updateAudioManagerForSystemAudio(boolean on)1016     private void updateAudioManagerForSystemAudio(boolean on) {
1017         int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
1018         HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
1019     }
1020 
onSystemAudioControlFeatureSupportChanged(boolean enabled)1021     void onSystemAudioControlFeatureSupportChanged(boolean enabled) {
1022         setSystemAudioControlFeatureEnabled(enabled);
1023         if (enabled) {
1024             addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
1025         }
1026     }
1027 
1028     @ServiceThreadOnly
setSystemAudioControlFeatureEnabled(boolean enabled)1029     void setSystemAudioControlFeatureEnabled(boolean enabled) {
1030         assertRunOnServiceThread();
1031         synchronized (mLock) {
1032             mSystemAudioControlFeatureEnabled = enabled;
1033         }
1034     }
1035 
1036     @ServiceThreadOnly
setRoutingControlFeatureEnabled(boolean enabled)1037     void setRoutingControlFeatureEnabled(boolean enabled) {
1038         assertRunOnServiceThread();
1039         synchronized (mLock) {
1040             mRoutingControlFeatureEnabled = enabled;
1041         }
1042     }
1043 
1044     @ServiceThreadOnly
doManualPortSwitching(int portId, IHdmiControlCallback callback)1045     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
1046         assertRunOnServiceThread();
1047         if (!mService.isValidPortId(portId)) {
1048             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
1049             return;
1050         }
1051         if (portId == getLocalActivePort()) {
1052             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1053             return;
1054         }
1055         if (!mService.isCecControlEnabled()) {
1056             setRoutingPort(portId);
1057             setLocalActivePort(portId);
1058             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
1059             return;
1060         }
1061         int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME
1062                 ? mService.portIdToPath(getRoutingPort())
1063                 : getDeviceInfo().getPhysicalAddress();
1064         int newPath = mService.portIdToPath(portId);
1065         if (oldPath == newPath) {
1066             return;
1067         }
1068         setRoutingPort(portId);
1069         setLocalActivePort(portId);
1070         HdmiCecMessage routingChange =
1071                 HdmiCecMessageBuilder.buildRoutingChange(
1072                         getDeviceInfo().getLogicalAddress(), oldPath, newPath);
1073         mService.sendCecCommand(routingChange);
1074     }
1075 
isSystemAudioControlFeatureEnabled()1076     boolean isSystemAudioControlFeatureEnabled() {
1077         synchronized (mLock) {
1078             return mSystemAudioControlFeatureEnabled;
1079         }
1080     }
1081 
isSystemAudioActivated()1082     protected boolean isSystemAudioActivated() {
1083         return mService.isSystemAudioActivated();
1084     }
1085 
terminateSystemAudioMode()1086     protected void terminateSystemAudioMode() {
1087         // remove pending initiation actions
1088         removeAction(SystemAudioInitiationActionFromAvr.class);
1089         if (!isSystemAudioActivated()) {
1090             return;
1091         }
1092 
1093         if (checkSupportAndSetSystemAudioMode(false)) {
1094             // send <Set System Audio Mode> [“Off”]
1095             mService.sendCecCommand(
1096                     HdmiCecMessageBuilder.buildSetSystemAudioMode(
1097                             getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, false));
1098         }
1099     }
1100 
terminateAudioReturnChannel()1101     private void terminateAudioReturnChannel() {
1102         // remove pending initiation actions
1103         removeAction(ArcInitiationActionFromAvr.class);
1104         if (!isArcEnabled()
1105                 || !mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
1106             return;
1107         }
1108         addAndStartAction(new ArcTerminationActionFromAvr(this));
1109     }
1110 
1111     /** Reports if System Audio Mode is supported by the connected TV */
1112     interface TvSystemAudioModeSupportedCallback {
1113 
1114         /** {@code supported} is true if the TV is connected and supports System Audio Mode. */
onResult(boolean supported)1115         void onResult(boolean supported);
1116     }
1117 
1118     /**
1119      * Queries the connected TV to detect if System Audio Mode is supported by the TV.
1120      *
1121      * <p>This query may take up to 2 seconds to complete.
1122      *
1123      * <p>The result of the query may be cached until Audio device type is put in standby or loses
1124      * its physical address.
1125      */
queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback)1126     void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
1127         if (mTvSystemAudioModeSupport == null) {
1128             addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
1129         } else {
1130             callback.onResult(mTvSystemAudioModeSupport);
1131         }
1132     }
1133 
1134     /**
1135      * Handler of System Audio Mode Request on from non TV device
1136      */
1137     @Constants.HandleMessageResult
handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message)1138     int handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
1139         if (!isSystemAudioControlFeatureEnabled()) {
1140             HdmiLogger.debug(
1141                     "Cannot turn on" + "system audio mode "
1142                             + "because the System Audio Control feature is disabled.");
1143             return Constants.ABORT_REFUSED;
1144         }
1145         // Wake up device
1146         mService.wakeUp();
1147         // If Audio device is the active source or is on the active path,
1148         // enable system audio mode without querying TV's support on sam.
1149         // This is per HDMI spec 1.4b CEC 13.15.4.2.
1150         if (mService.pathToPortId(getActiveSource().physicalAddress)
1151                 != Constants.INVALID_PORT_ID) {
1152             setSystemAudioMode(true);
1153             mService.sendCecCommand(
1154                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
1155                     getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, true));
1156             return Constants.HANDLED;
1157         }
1158         // Check if TV supports System Audio Control.
1159         // Handle broadcasting setSystemAudioMode on or aborting message on callback.
1160         queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
1161             public void onResult(boolean supported) {
1162                 if (supported) {
1163                     setSystemAudioMode(true);
1164                     mService.sendCecCommand(
1165                             HdmiCecMessageBuilder.buildSetSystemAudioMode(
1166                                     getDeviceInfo().getLogicalAddress(),
1167                                     Constants.ADDR_BROADCAST,
1168                                     true));
1169                 } else {
1170                     mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1171                 }
1172             }
1173         });
1174         return Constants.HANDLED;
1175     }
1176 
setTvSystemAudioModeSupport(boolean supported)1177     void setTvSystemAudioModeSupport(boolean supported) {
1178         mTvSystemAudioModeSupport = supported;
1179     }
1180 
1181     @VisibleForTesting
isArcEnabled()1182     protected boolean isArcEnabled() {
1183         synchronized (mLock) {
1184             return mArcEstablished;
1185         }
1186     }
1187 
initArcOnFromAvr()1188     private void initArcOnFromAvr() {
1189         removeAction(ArcTerminationActionFromAvr.class);
1190         if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
1191                 && isDirectConnectToTv() && !isArcEnabled()) {
1192             removeAction(ArcInitiationActionFromAvr.class);
1193             addAndStartAction(new ArcInitiationActionFromAvr(this));
1194         }
1195     }
1196 
1197     @Override
switchInputOnReceivingNewActivePath(int physicalAddress)1198     protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
1199         int port = mService.pathToPortId(physicalAddress);
1200         if (isSystemAudioActivated() && port < 0) {
1201             // If system audio mode is on and the new active source is not under the current device,
1202             // Will switch to ARC input.
1203             routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
1204         } else if (mIsSwitchDevice && port >= 0) {
1205             // If current device is a switch and the new active source is under it,
1206             // will switch to the corresponding active path.
1207             routeToInputFromPortId(port);
1208         }
1209     }
1210 
routeToInputFromPortId(int portId)1211     protected void routeToInputFromPortId(int portId) {
1212         if (!isRoutingControlFeatureEnabled()) {
1213             HdmiLogger.debug("Routing Control Feature is not enabled.");
1214             return;
1215         }
1216         if (mArcIntentUsed) {
1217             routeToTvInputFromPortId(portId);
1218         } else {
1219             // TODO(): implement input switching for devices not using TvInput.
1220         }
1221     }
1222 
routeToTvInputFromPortId(int portId)1223     protected void routeToTvInputFromPortId(int portId) {
1224         if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) {
1225             HdmiLogger.debug("Invalid port number for Tv Input switching.");
1226             return;
1227         }
1228         // Wake up if the current device if ready to route.
1229         mService.wakeUp();
1230         if ((getLocalActivePort() == portId) && (portId != Constants.CEC_SWITCH_ARC)) {
1231             HdmiLogger.debug("Not switching to the same port " + portId + " except for arc");
1232             return;
1233         }
1234         // Switch to HOME if the current active port is not HOME yet
1235         if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
1236             switchToHomeTvInput();
1237         } else if (portId == Constants.CEC_SWITCH_ARC) {
1238             switchToTvInput(HdmiProperties.arc_port().orElse("0"));
1239             setLocalActivePort(portId);
1240             return;
1241         } else {
1242             String uri = mPortIdToTvInputs.get(portId);
1243             if (uri != null) {
1244                 switchToTvInput(uri);
1245             } else {
1246                 HdmiLogger.debug("Port number does not match any Tv Input.");
1247                 return;
1248             }
1249         }
1250 
1251         setLocalActivePort(portId);
1252         setRoutingPort(portId);
1253     }
1254 
1255     // For device to switch to specific TvInput with corresponding URI.
switchToTvInput(String uri)1256     private void switchToTvInput(String uri) {
1257         try {
1258             mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
1259                     TvContract.buildChannelUriForPassthroughInput(uri))
1260                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
1261         } catch (ActivityNotFoundException e) {
1262             Slog.e(TAG, "Can't find activity to switch to " + uri, e);
1263         }
1264     }
1265 
1266     // For device using TvInput to switch to Home.
switchToHomeTvInput()1267     private void switchToHomeTvInput() {
1268         try {
1269             Intent activityIntent = new Intent(Intent.ACTION_MAIN)
1270                     .addCategory(Intent.CATEGORY_HOME)
1271                     .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
1272                     | Intent.FLAG_ACTIVITY_SINGLE_TOP
1273                     | Intent.FLAG_ACTIVITY_NEW_TASK
1274                     | Intent.FLAG_ACTIVITY_NO_ANIMATION);
1275             mService.getContext().startActivity(activityIntent);
1276         } catch (ActivityNotFoundException e) {
1277             Slog.e(TAG, "Can't find activity to switch to HOME", e);
1278         }
1279     }
1280 
1281     @Override
handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message)1282     protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
1283         int port = mService.pathToPortId(physicalAddress);
1284         // Routing change or information sent from switches under the current device can be ignored.
1285         if (port > 0) {
1286             return;
1287         }
1288         // When other switches route to some other devices not under the current device,
1289         // check system audio mode status and do ARC switch if needed.
1290         if (port < 0 && isSystemAudioActivated()) {
1291             handleRoutingChangeAndInformationForSystemAudio();
1292             return;
1293         }
1294         // When other switches route to the current device
1295         // and the current device is also a switch.
1296         if (port == 0) {
1297             handleRoutingChangeAndInformationForSwitch(message);
1298         }
1299     }
1300 
1301     // Handle the system audio(ARC) part of the logic on receiving routing change or information.
handleRoutingChangeAndInformationForSystemAudio()1302     private void handleRoutingChangeAndInformationForSystemAudio() {
1303         routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
1304     }
1305 
1306     // Handle the routing control part of the logic on receiving routing change or information.
handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message)1307     private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
1308         if (getRoutingPort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
1309             routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
1310             mService.setAndBroadcastActiveSourceFromOneDeviceType(
1311                     message.getSource(), mService.getPhysicalAddress(),
1312                     "HdmiCecLocalDeviceAudioSystem#handleRoutingChangeAndInformationForSwitch()");
1313             return;
1314         }
1315 
1316         int routingInformationPath = mService.portIdToPath(getRoutingPort());
1317         // If current device is already the leaf of the whole HDMI system, will do nothing.
1318         if (routingInformationPath == mService.getPhysicalAddress()) {
1319             HdmiLogger.debug("Current device can't assign valid physical address"
1320                     + "to devices under it any more. "
1321                     + "It's physical address is "
1322                     + routingInformationPath);
1323             return;
1324         }
1325         // Otherwise will switch to the current active port and broadcast routing information.
1326         mService.sendCecCommand(
1327                 HdmiCecMessageBuilder.buildRoutingInformation(
1328                         getDeviceInfo().getLogicalAddress(), routingInformationPath));
1329         routeToInputFromPortId(getRoutingPort());
1330     }
1331 
1332     @ServiceThreadOnly
launchDeviceDiscovery()1333     private void launchDeviceDiscovery() {
1334         assertRunOnServiceThread();
1335         if (mService.isDeviceDiscoveryHandledByPlayback()) {
1336             return;
1337         }
1338         if (hasAction(DeviceDiscoveryAction.class)) {
1339             Slog.i(TAG, "Device Discovery Action is in progress. Restarting.");
1340             removeAction(DeviceDiscoveryAction.class);
1341         }
1342         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
1343                 new DeviceDiscoveryCallback() {
1344                     @Override
1345                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
1346                         for (HdmiDeviceInfo info : deviceInfos) {
1347                             mService.getHdmiCecNetwork().addCecDevice(info);
1348                         }
1349                     }
1350                 });
1351         addAndStartAction(action);
1352     }
1353 
1354     @Override
dump(IndentingPrintWriter pw)1355     protected void dump(IndentingPrintWriter pw) {
1356         pw.println("HdmiCecLocalDeviceAudioSystem:");
1357         pw.increaseIndent();
1358         pw.println("isRoutingFeatureEnabled " + isRoutingControlFeatureEnabled());
1359         pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
1360         pw.println("mTvSystemAudioModeSupport: " + mTvSystemAudioModeSupport);
1361         pw.println("mArcEstablished: " + mArcEstablished);
1362         pw.println("mArcIntentUsed: " + mArcIntentUsed);
1363         pw.println("mRoutingPort: " + getRoutingPort());
1364         pw.println("mLocalActivePort: " + getLocalActivePort());
1365         HdmiUtils.dumpMap(pw, "mPortIdToTvInputs:", mPortIdToTvInputs);
1366         HdmiUtils.dumpMap(pw, "mTvInputsToDeviceInfo:", mTvInputsToDeviceInfo);
1367         pw.decreaseIndent();
1368         super.dump(pw);
1369     }
1370 }
1371