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