• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             addAndStartAction(new SystemAudioInitiationActionFromAvr(this), true);
321         }
322     }
323 
324     @Override
325     @ServiceThreadOnly
getPreferredAddress()326     protected int getPreferredAddress() {
327         assertRunOnServiceThread();
328         return SystemProperties.getInt(
329             Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
330     }
331 
332     @Override
333     @ServiceThreadOnly
setPreferredAddress(int addr)334     protected void setPreferredAddress(int addr) {
335         assertRunOnServiceThread();
336         mService.writeStringSystemProperty(
337                 Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr));
338     }
339 
340     @ServiceThreadOnly
processDelayedActiveSource(int address)341     void processDelayedActiveSource(int address) {
342         assertRunOnServiceThread();
343         mDelayedMessageBuffer.processActiveSource(address);
344     }
345 
346     @Override
347     @ServiceThreadOnly
348     @Constants.HandleMessageResult
handleActiveSource(HdmiCecMessage message)349     protected int handleActiveSource(HdmiCecMessage message) {
350         assertRunOnServiceThread();
351         int logicalAddress = message.getSource();
352         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
353         if (HdmiUtils.getLocalPortFromPhysicalAddress(
354             physicalAddress, mService.getPhysicalAddress())
355                 == HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) {
356             return super.handleActiveSource(message);
357         }
358         // If the new Active Source is under the current device, check if the device info and the TV
359         // input is ready to switch to the new Active Source. If not ready, buffer the cec command
360         // to handle later when the device is ready.
361         HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress);
362         if (info == null) {
363             HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
364             mDelayedMessageBuffer.add(message);
365         } else if (!isInputReady(info.getPortId())){
366             HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
367             mDelayedMessageBuffer.add(message);
368         } else {
369             mDelayedMessageBuffer.removeActiveSource();
370             return super.handleActiveSource(message);
371         }
372         return Constants.HANDLED;
373     }
374 
375     @Override
376     @ServiceThreadOnly
377     @Constants.HandleMessageResult
handleInitiateArc(HdmiCecMessage message)378     protected int handleInitiateArc(HdmiCecMessage message) {
379         assertRunOnServiceThread();
380         // TODO(amyjojo): implement initiate arc handler
381         HdmiLogger.debug(TAG + "Stub handleInitiateArc");
382         return Constants.HANDLED;
383     }
384 
385     @Override
386     @ServiceThreadOnly
387     @Constants.HandleMessageResult
handleReportArcInitiate(HdmiCecMessage message)388     protected int handleReportArcInitiate(HdmiCecMessage message) {
389         assertRunOnServiceThread();
390         /*
391          * Ideally, we should have got this response before the {@link ArcInitiationActionFromAvr}
392          * has timed out. Even if the response is late, {@link ArcInitiationActionFromAvr
393          * #handleInitiateArcTimeout()} would not have disabled ARC. So nothing needs to be done
394          * here.
395          */
396         return Constants.HANDLED;
397     }
398 
399     @Override
400     @ServiceThreadOnly
401     @Constants.HandleMessageResult
handleReportArcTermination(HdmiCecMessage message)402     protected int handleReportArcTermination(HdmiCecMessage message) {
403         assertRunOnServiceThread();
404         processArcTermination();
405         return Constants.HANDLED;
406     }
407 
408     @Override
409     @ServiceThreadOnly
410     @Constants.HandleMessageResult
handleGiveAudioStatus(HdmiCecMessage message)411     protected int handleGiveAudioStatus(HdmiCecMessage message) {
412         assertRunOnServiceThread();
413         if (isSystemAudioControlFeatureEnabled() && mService.getHdmiCecVolumeControl()
414                 == HdmiControlManager.VOLUME_CONTROL_ENABLED) {
415             reportAudioStatus(message.getSource());
416             return Constants.HANDLED;
417         }
418         return Constants.ABORT_REFUSED;
419     }
420 
421     @Override
422     @ServiceThreadOnly
423     @Constants.HandleMessageResult
handleGiveSystemAudioModeStatus(HdmiCecMessage message)424     protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
425         assertRunOnServiceThread();
426         // If the audio system is initiating the system audio mode on and TV asks the sam status at
427         // the same time, respond with true. Since we know TV supports sam in this situation.
428         // If the query comes from STB, we should respond with the current sam status and the STB
429         // should listen to the <Set System Audio Mode> broadcasting.
430         boolean isSystemAudioModeOnOrTurningOn = isSystemAudioActivated();
431         if (!isSystemAudioModeOnOrTurningOn
432                 && message.getSource() == Constants.ADDR_TV
433                 && hasAction(SystemAudioInitiationActionFromAvr.class)) {
434             isSystemAudioModeOnOrTurningOn = true;
435         }
436         mService.sendCecCommand(
437                 HdmiCecMessageBuilder.buildReportSystemAudioMode(
438                         getDeviceInfo().getLogicalAddress(),
439                         message.getSource(),
440                         isSystemAudioModeOnOrTurningOn));
441         return Constants.HANDLED;
442     }
443 
444     @Override
445     @ServiceThreadOnly
446     @Constants.HandleMessageResult
handleRequestArcInitiate(HdmiCecMessage message)447     protected int handleRequestArcInitiate(HdmiCecMessage message) {
448         assertRunOnServiceThread();
449         removeAction(ArcInitiationActionFromAvr.class);
450         if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
451             return Constants.ABORT_UNRECOGNIZED_OPCODE;
452         } else if (!isDirectConnectToTv()) {
453             HdmiLogger.debug("AVR device is not directly connected with TV");
454             return Constants.ABORT_NOT_IN_CORRECT_MODE;
455         } else {
456             // Action has been removed if it existed, do not attempt to remove again before start.
457             addAndStartAction(new ArcInitiationActionFromAvr(this));
458             return Constants.HANDLED;
459         }
460     }
461 
462     @Override
463     @ServiceThreadOnly
464     @Constants.HandleMessageResult
handleRequestArcTermination(HdmiCecMessage message)465     protected int handleRequestArcTermination(HdmiCecMessage message) {
466         assertRunOnServiceThread();
467         if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
468             return Constants.ABORT_UNRECOGNIZED_OPCODE;
469         } else if (!isArcEnabled()) {
470             HdmiLogger.debug("ARC is not established between TV and AVR device");
471             return Constants.ABORT_NOT_IN_CORRECT_MODE;
472         } else {
473             if (!getActions(ArcTerminationActionFromAvr.class).isEmpty()
474                     && !getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.isEmpty()) {
475                 IHdmiControlCallback callback =
476                         getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0);
477                 addAndStartAction(new ArcTerminationActionFromAvr(this, callback), true);
478             } else {
479                 addAndStartAction(new ArcTerminationActionFromAvr(this), true);
480             }
481             return Constants.HANDLED;
482         }
483     }
484 
485     @ServiceThreadOnly
486     @Constants.HandleMessageResult
handleRequestShortAudioDescriptor(HdmiCecMessage message)487     protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) {
488         assertRunOnServiceThread();
489         HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor");
490         if (!isSystemAudioControlFeatureEnabled()) {
491             return Constants.ABORT_REFUSED;
492         }
493         if (!isSystemAudioActivated()) {
494             return Constants.ABORT_NOT_IN_CORRECT_MODE;
495         }
496 
497         List<DeviceConfig> config = null;
498         File file = new File(SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH);
499         if (file.exists()) {
500             try {
501                 InputStream in = new FileInputStream(file);
502                 config = HdmiUtils.ShortAudioDescriptorXmlParser.parse(in);
503                 in.close();
504             } catch (IOException e) {
505                 Slog.e(TAG, "Error reading file: " + file, e);
506             } catch (XmlPullParserException e) {
507                 Slog.e(TAG, "Unable to parse file: " + file, e);
508             }
509         }
510 
511         @AudioCodec int[] audioCodecs = parseAudioCodecs(message.getParams());
512         byte[] sadBytes;
513         if (config != null && config.size() > 0) {
514             sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioCodecs);
515         } else {
516             AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo();
517             if (deviceInfo == null) {
518                 return Constants.ABORT_UNABLE_TO_DETERMINE;
519             }
520 
521             sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioCodecs);
522         }
523 
524         if (sadBytes.length == 0) {
525             return Constants.ABORT_INVALID_OPERAND;
526         } else {
527             mService.sendCecCommand(
528                     HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
529                             getDeviceInfo().getLogicalAddress(), message.getSource(), sadBytes));
530             return Constants.HANDLED;
531         }
532     }
533 
534     @VisibleForTesting
getSupportedShortAudioDescriptors( AudioDeviceInfo deviceInfo, @AudioCodec int[] audioCodecs)535     byte[] getSupportedShortAudioDescriptors(
536             AudioDeviceInfo deviceInfo, @AudioCodec int[] audioCodecs) {
537         ArrayList<byte[]> sads = new ArrayList<>(audioCodecs.length);
538         for (@AudioCodec int audioCodec : audioCodecs) {
539             byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioCodec);
540             if (sad != null) {
541                 if (sad.length == 3) {
542 
543                     sads.add(sad);
544                 } else {
545                     HdmiLogger.warning(
546                             "Dropping Short Audio Descriptor with length %d for requested codec %x",
547                             sad.length, audioCodec);
548                 }
549             }
550         }
551         return getShortAudioDescriptorBytes(sads);
552     }
553 
getSupportedShortAudioDescriptorsFromConfig( List<DeviceConfig> deviceConfig, @AudioCodec int[] audioCodecs)554     private byte[] getSupportedShortAudioDescriptorsFromConfig(
555             List<DeviceConfig> deviceConfig, @AudioCodec int[] audioCodecs) {
556         DeviceConfig deviceConfigToUse = null;
557         String audioDeviceName = SystemProperties.get(
558                 Constants.PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT,
559                 "VX_AUDIO_DEVICE_IN_HDMI_ARC");
560         for (DeviceConfig device : deviceConfig) {
561             if (device.name.equals(audioDeviceName)) {
562                 deviceConfigToUse = device;
563                 break;
564             }
565         }
566         if (deviceConfigToUse == null) {
567             Slog.w(TAG, "sadConfig.xml does not have required device info for " + audioDeviceName);
568             return new byte[0];
569         }
570         HashMap<Integer, byte[]> map = new HashMap<>();
571         ArrayList<byte[]> sads = new ArrayList<>(audioCodecs.length);
572         for (CodecSad codecSad : deviceConfigToUse.supportedCodecs) {
573             map.put(codecSad.audioCodec, codecSad.sad);
574         }
575         for (int i = 0; i < audioCodecs.length; i++) {
576             if (map.containsKey(audioCodecs[i])) {
577                 byte[] sad = map.get(audioCodecs[i]);
578                 if (sad != null && sad.length == 3) {
579                     sads.add(sad);
580                 }
581             }
582         }
583         return getShortAudioDescriptorBytes(sads);
584     }
585 
getShortAudioDescriptorBytes(ArrayList<byte[]> sads)586     private byte[] getShortAudioDescriptorBytes(ArrayList<byte[]> sads) {
587         // Short Audio Descriptors are always 3 bytes long.
588         byte[] bytes = new byte[sads.size() * 3];
589         int index = 0;
590         for (byte[] sad : sads) {
591             System.arraycopy(sad, 0, bytes, index, 3);
592             index += 3;
593         }
594         return bytes;
595     }
596 
597     /**
598      * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the
599      * audioCodec is not supported.
600      */
601     @Nullable
602     @VisibleForTesting
getSupportedShortAudioDescriptor( AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec)603     byte[] getSupportedShortAudioDescriptor(
604             AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec) {
605         byte[] shortAudioDescriptor = new byte[3];
606 
607         int[] deviceSupportedAudioFormats = deviceInfo.getEncodings();
608         // Return null when audioCodec or device does not support any audio formats.
609         if (!AUDIO_CODECS_MAP.containsKey(audioCodec) || deviceSupportedAudioFormats.length == 0) {
610             return null;
611         }
612         List<Integer> audioCodecSupportedAudioFormats = AUDIO_CODECS_MAP.get(audioCodec);
613 
614         for (int supportedAudioFormat : deviceSupportedAudioFormats) {
615             if (audioCodecSupportedAudioFormats.contains(supportedAudioFormat)) {
616                 // Initialise the first two bytes of short audio descriptor.
617                 shortAudioDescriptor[0] = getFirstByteOfSAD(deviceInfo, audioCodec);
618                 shortAudioDescriptor[1] = getSecondByteOfSAD(deviceInfo);
619                 switch (audioCodec) {
620                     case Constants.AUDIO_CODEC_NONE: {
621                         return null;
622                     }
623                     case Constants.AUDIO_CODEC_LPCM: {
624                         if (supportedAudioFormat == AudioFormat.ENCODING_PCM_16BIT) {
625                             shortAudioDescriptor[2] = (byte) 0x01;
626                         } else if (supportedAudioFormat
627                                 == AudioFormat.ENCODING_PCM_24BIT_PACKED) {
628                             shortAudioDescriptor[2] = (byte) 0x04;
629                         } else {
630                             // Since no bit is reserved for these audio formats in LPCM codec.
631                             shortAudioDescriptor[2] = (byte) 0x00;
632                         }
633                         return shortAudioDescriptor;
634                     }
635                     case Constants.AUDIO_CODEC_DD:
636                     case Constants.AUDIO_CODEC_MPEG1:
637                     case Constants.AUDIO_CODEC_MP3:
638                     case Constants.AUDIO_CODEC_MPEG2:
639                     case Constants.AUDIO_CODEC_AAC:
640                     case Constants.AUDIO_CODEC_DTS: {
641                         shortAudioDescriptor[2] = getThirdSadByteForCodecs2Through8(deviceInfo);
642                         return shortAudioDescriptor;
643                     }
644                     case Constants.AUDIO_CODEC_DDP:
645                     case Constants.AUDIO_CODEC_DTSHD:
646                     case Constants.AUDIO_CODEC_TRUEHD: {
647                         // Default value is 0x0 unless defined by Audio Codec Vendor.
648                         shortAudioDescriptor[2] = (byte) 0x00;
649                         return shortAudioDescriptor;
650                     }
651                     case Constants.AUDIO_CODEC_ATRAC:
652                     case Constants.AUDIO_CODEC_ONEBITAUDIO:
653                     case Constants.AUDIO_CODEC_DST:
654                     case Constants.AUDIO_CODEC_WMAPRO:
655                         // Not supported.
656                     default: {
657                         return null;
658                     }
659                 }
660             }
661         }
662         return null;
663     }
664 
mapAudioCodecWithAudioFormat()665     private static HashMap<Integer, List<Integer>> mapAudioCodecWithAudioFormat() {
666         // Mapping the values of @AudioCodec audio codecs with @AudioFormat audio formats.
667         HashMap<Integer, List<Integer>> audioCodecsMap = new HashMap<Integer, List<Integer>>();
668 
669         audioCodecsMap.put(Constants.AUDIO_CODEC_NONE, List.of(AudioFormat.ENCODING_DEFAULT));
670         audioCodecsMap.put(
671                 Constants.AUDIO_CODEC_LPCM,
672                 List.of(
673                         AudioFormat.ENCODING_PCM_8BIT,
674                         AudioFormat.ENCODING_PCM_16BIT,
675                         AudioFormat.ENCODING_PCM_FLOAT,
676                         AudioFormat.ENCODING_PCM_24BIT_PACKED,
677                         AudioFormat.ENCODING_PCM_32BIT));
678         audioCodecsMap.put(Constants.AUDIO_CODEC_DD, List.of(AudioFormat.ENCODING_AC3));
679         audioCodecsMap.put(Constants.AUDIO_CODEC_MPEG1, List.of(AudioFormat.ENCODING_AAC_HE_V1));
680         audioCodecsMap.put(Constants.AUDIO_CODEC_MPEG2, List.of(AudioFormat.ENCODING_AAC_HE_V2));
681         audioCodecsMap.put(Constants.AUDIO_CODEC_MP3, List.of(AudioFormat.ENCODING_MP3));
682         audioCodecsMap.put(Constants.AUDIO_CODEC_AAC, List.of(AudioFormat.ENCODING_AAC_LC));
683         audioCodecsMap.put(Constants.AUDIO_CODEC_DTS, List.of(AudioFormat.ENCODING_DTS));
684         audioCodecsMap.put(
685                 Constants.AUDIO_CODEC_DDP,
686                 List.of(AudioFormat.ENCODING_E_AC3, AudioFormat.ENCODING_E_AC3_JOC));
687         audioCodecsMap.put(Constants.AUDIO_CODEC_DTSHD, List.of(AudioFormat.ENCODING_DTS_HD));
688         audioCodecsMap.put(
689                 Constants.AUDIO_CODEC_TRUEHD,
690                 List.of(AudioFormat.ENCODING_DOLBY_TRUEHD, AudioFormat.ENCODING_DOLBY_MAT));
691 
692         return audioCodecsMap;
693     }
694 
getFirstByteOfSAD(AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec)695     private byte getFirstByteOfSAD(AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec) {
696         byte firstByte = 0;
697         int maxNumberOfChannels = getMaxNumberOfChannels(deviceInfo);
698 
699         // Fill bits 0-2 of the first byte.
700         firstByte |= (maxNumberOfChannels - 1);
701 
702         // Fill bits 3-6 of the first byte.
703         firstByte |= (audioCodec << 3);
704 
705         return firstByte;
706     }
707 
getSecondByteOfSAD(AudioDeviceInfo deviceInfo)708     private byte getSecondByteOfSAD(AudioDeviceInfo deviceInfo) {
709         ArrayList<Integer> samplingRates =
710                 new ArrayList<Integer>(Arrays.asList(32, 44, 48, 88, 96, 176, 192));
711 
712         // samplingRatesdevicesupports is guaranteed to be not null
713         int[] samplingRatesDeviceSupports = deviceInfo.getSampleRates();
714         if (samplingRatesDeviceSupports.length == 0) {
715             Slog.e(TAG, "Device supports arbitrary rates");
716             // Since device supports arbitrary rates, we will return 0x7f since bit 7 is reserved.
717             return (byte) 0x7f;
718         }
719         byte secondByte = 0;
720         for (int supportedSampleRate : samplingRatesDeviceSupports) {
721             if (samplingRates.contains(supportedSampleRate)) {
722                 int index = samplingRates.indexOf(supportedSampleRate);
723                 // Setting the bit of a sample rate which is being supported.
724                 secondByte |= (1 << index);
725             }
726         }
727 
728         return secondByte;
729     }
730 
731     /**
732      * Empty array from deviceInfo.getChannelCounts() implies device supports arbitrary channel
733      * counts and hence we assume max channels are supported by the device.
734      */
getMaxNumberOfChannels(AudioDeviceInfo deviceInfo)735     private int getMaxNumberOfChannels(AudioDeviceInfo deviceInfo) {
736         int maxNumberOfChannels = MAX_CHANNELS;
737         int[] channelCounts = deviceInfo.getChannelCounts();
738         if (channelCounts.length != 0) {
739             maxNumberOfChannels = channelCounts[channelCounts.length - 1];
740             maxNumberOfChannels =
741                     (maxNumberOfChannels > MAX_CHANNELS ? MAX_CHANNELS : maxNumberOfChannels);
742         }
743         return maxNumberOfChannels;
744     }
745 
getThirdSadByteForCodecs2Through8(AudioDeviceInfo deviceInfo)746     private byte getThirdSadByteForCodecs2Through8(AudioDeviceInfo deviceInfo) {
747         /*
748          * Here, we are assuming that max bit rate is closely equals to the max sampling rate the
749          * device supports.
750          */
751         int maxSamplingRate = 0;
752         int[] samplingRatesDeviceSupports = deviceInfo.getSampleRates();
753         if (samplingRatesDeviceSupports.length == 0) {
754             maxSamplingRate = 192;
755         } else {
756             for (int sampleRate : samplingRatesDeviceSupports) {
757                 if (maxSamplingRate < sampleRate) {
758                     maxSamplingRate = sampleRate;
759                 }
760             }
761         }
762 
763         return (byte) (maxSamplingRate / 8);
764     }
765 
766     @Nullable
getSystemAudioDeviceInfo()767     private AudioDeviceInfo getSystemAudioDeviceInfo() {
768         AudioManager audioManager = mService.getContext().getSystemService(AudioManager.class);
769         if (audioManager == null) {
770             HdmiLogger.error(
771                     "Error getting system audio device because AudioManager not available.");
772             return null;
773         }
774         AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
775         HdmiLogger.debug("Found %d audio input devices", devices.length);
776         for (AudioDeviceInfo device : devices) {
777             HdmiLogger.debug("%s at port %s", device.getProductName(), device.getPort());
778             HdmiLogger.debug("Supported encodings are %s",
779                     Arrays.stream(device.getEncodings()).mapToObj(
780                             AudioFormat::toLogFriendlyEncoding
781                     ).collect(Collectors.joining(", ")));
782             if (device.getType() == AudioDeviceInfo.TYPE_HDMI_ARC) {
783                 return device;
784             }
785         }
786         return null;
787     }
788 
789     @AudioCodec
parseAudioCodecs(byte[] params)790     private int[] parseAudioCodecs(byte[] params) {
791         @AudioCodec int[] audioCodecs = new int[params.length];
792         for (int i = 0; i < params.length; i++) {
793             byte val = params[i];
794             audioCodecs[i] =
795                     val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
796         }
797         return audioCodecs;
798     }
799 
800     @Override
801     @ServiceThreadOnly
802     @Constants.HandleMessageResult
handleSystemAudioModeRequest(HdmiCecMessage message)803     protected int handleSystemAudioModeRequest(HdmiCecMessage message) {
804         assertRunOnServiceThread();
805         boolean systemAudioStatusOn = message.getParams().length != 0;
806         // Check if the request comes from a non-TV device.
807         // Need to check if TV supports System Audio Control
808         // if non-TV device tries to turn on the feature
809         if (message.getSource() != Constants.ADDR_TV) {
810             if (systemAudioStatusOn) {
811                 return handleSystemAudioModeOnFromNonTvDevice(message);
812             }
813         } else {
814             // If TV request the feature on
815             // cache TV supporting System Audio Control
816             // until Audio System loses its physical address.
817             setTvSystemAudioModeSupport(true);
818         }
819         // If TV or Audio System does not support the feature,
820         // will send abort command.
821         if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
822             return Constants.ABORT_REFUSED;
823         }
824 
825         mService.sendCecCommand(
826                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
827                         getDeviceInfo().getLogicalAddress(),
828                         Constants.ADDR_BROADCAST,
829                         systemAudioStatusOn));
830 
831         if (systemAudioStatusOn) {
832             // If TV sends out SAM Request with a path of a non-CEC device, which should not show
833             // up in the CEC device list and not under the current AVR device, the AVR would switch
834             // to ARC.
835             int sourcePhysicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
836             if (HdmiUtils.getLocalPortFromPhysicalAddress(
837                     sourcePhysicalAddress, getDeviceInfo().getPhysicalAddress())
838                             != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) {
839                 return Constants.HANDLED;
840             }
841             HdmiDeviceInfo safeDeviceInfoByPath =
842                     mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress);
843             if (safeDeviceInfoByPath == null) {
844                 switchInputOnReceivingNewActivePath(sourcePhysicalAddress);
845             }
846         }
847         return Constants.HANDLED;
848     }
849 
850     @Override
851     @ServiceThreadOnly
852     @Constants.HandleMessageResult
handleSetSystemAudioMode(HdmiCecMessage message)853     protected int handleSetSystemAudioMode(HdmiCecMessage message) {
854         assertRunOnServiceThread();
855         if (!checkSupportAndSetSystemAudioMode(
856                 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
857             return Constants.ABORT_REFUSED;
858         }
859         return Constants.HANDLED;
860     }
861 
862     @Override
863     @ServiceThreadOnly
864     @Constants.HandleMessageResult
handleSystemAudioModeStatus(HdmiCecMessage message)865     protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
866         assertRunOnServiceThread();
867         if (!checkSupportAndSetSystemAudioMode(
868                 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
869             return Constants.ABORT_REFUSED;
870         }
871         return Constants.HANDLED;
872     }
873 
874     @ServiceThreadOnly
setArcStatus(boolean enabled)875     void setArcStatus(boolean enabled) {
876         assertRunOnServiceThread();
877 
878         HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
879         // 1. Enable/disable ARC circuit.
880         enableAudioReturnChannel(enabled);
881         // 2. Notify arc status to audio service.
882         notifyArcStatusToAudioService(enabled);
883         // 3. Update arc status;
884         mArcEstablished = enabled;
885     }
886 
processArcTermination()887     void processArcTermination() {
888         setArcStatus(false);
889         // Switch away from ARC input when ARC is terminated.
890         if (getLocalActivePort() == Constants.CEC_SWITCH_ARC) {
891             routeToInputFromPortId(getRoutingPort());
892         }
893     }
894 
895     /** Switch hardware ARC circuit in the system. */
896     @ServiceThreadOnly
enableAudioReturnChannel(boolean enabled)897     private void enableAudioReturnChannel(boolean enabled) {
898         assertRunOnServiceThread();
899         mService.enableAudioReturnChannel(
900                 Integer.parseInt(HdmiProperties.arc_port().orElse("0")),
901                 enabled);
902     }
903 
notifyArcStatusToAudioService(boolean enabled)904     private void notifyArcStatusToAudioService(boolean enabled) {
905         // Note that we don't set any name to ARC.
906         mService.getAudioManager()
907             .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI_ARC, enabled ? 1 : 0, "", "");
908     }
909 
reportAudioStatus(int source)910     void reportAudioStatus(int source) {
911         assertRunOnServiceThread();
912         if (mService.getHdmiCecVolumeControl()
913                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
914             return;
915         }
916 
917         int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
918         boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
919         int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
920         int minVolume = mService.getAudioManager().getStreamMinVolume(AudioManager.STREAM_MUSIC);
921         int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
922         HdmiLogger.debug("Reporting volume %d (%d-%d) as CEC volume %d", volume,
923                 minVolume, maxVolume, scaledVolume);
924 
925         mService.sendCecCommand(
926                 HdmiCecMessageBuilder.buildReportAudioStatus(
927                         getDeviceInfo().getLogicalAddress(), source, scaledVolume, mute));
928     }
929 
930     /**
931      * Method to check if device support System Audio Control. If so, wake up device if necessary.
932      *
933      * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode
934      * @param newSystemAudioMode turning feature on or off. True is on. False is off.
935      * @return true or false.
936      *
937      * <p>False when device does not support the feature. Otherwise returns true.
938      */
checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode)939     protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) {
940         if (!isSystemAudioControlFeatureEnabled()) {
941             HdmiLogger.debug(
942                     "Cannot turn "
943                             + (newSystemAudioMode ? "on" : "off")
944                             + "system audio mode "
945                             + "because the System Audio Control feature is disabled.");
946             return false;
947         }
948         HdmiLogger.debug(
949                 "System Audio Mode change[old:%b new:%b]",
950                 isSystemAudioActivated(), newSystemAudioMode);
951         // Wake up device if System Audio Control is turned on
952         if (newSystemAudioMode) {
953             mService.wakeUp();
954         }
955         setSystemAudioMode(newSystemAudioMode);
956         return true;
957     }
958 
959     /**
960      * Real work to turn on or off System Audio Mode.
961      *
962      * Use {@link #checkSupportAndSetSystemAudioMode(boolean)}
963      * if trying to turn on or off the feature.
964      */
setSystemAudioMode(boolean newSystemAudioMode)965     private void setSystemAudioMode(boolean newSystemAudioMode) {
966         int targetPhysicalAddress = getActiveSource().physicalAddress;
967         int port = mService.pathToPortId(targetPhysicalAddress);
968         if (newSystemAudioMode && port >= 0) {
969             switchToAudioInput();
970         }
971         // Mute device when feature is turned off and unmute device when feature is turned on.
972         // CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING is false when device never needs to be muted.
973         boolean systemAudioModeMutingEnabled = mService.getHdmiCecConfig().getIntValue(
974                     HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING)
975                         == HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_ENABLED;
976         boolean currentMuteStatus =
977                 mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
978         if (currentMuteStatus == newSystemAudioMode) {
979             if (systemAudioModeMutingEnabled || newSystemAudioMode) {
980                 mService.getAudioManager()
981                         .adjustStreamVolume(
982                                 AudioManager.STREAM_MUSIC,
983                                 newSystemAudioMode
984                                         ? AudioManager.ADJUST_UNMUTE
985                                         : AudioManager.ADJUST_MUTE,
986                                 0);
987             }
988         }
989         updateAudioManagerForSystemAudio(newSystemAudioMode);
990         synchronized (mLock) {
991             if (isSystemAudioActivated() != newSystemAudioMode) {
992                 mService.setSystemAudioActivated(newSystemAudioMode);
993                 mService.announceSystemAudioModeChange(newSystemAudioMode);
994             }
995         }
996         // Since ARC is independent from System Audio Mode control, when the TV requests
997         // System Audio Mode off, it does not need to terminate ARC at the same time.
998         // When the current audio device is using ARC as a TV input and disables muting,
999         // it needs to automatically switch to the previous active input source when System
1000         // Audio Mode is off even without terminating the ARC. This can stop the current
1001         // audio device from playing audio when system audio mode is off.
1002         if (mArcIntentUsed
1003                 && !systemAudioModeMutingEnabled
1004                 && !newSystemAudioMode
1005                 && getLocalActivePort() == Constants.CEC_SWITCH_ARC) {
1006             routeToInputFromPortId(getRoutingPort());
1007         }
1008         // Init arc whenever System Audio Mode is on
1009         // Since some TVs don't request ARC on with System Audio Mode on request
1010         if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
1011                 && isDirectConnectToTv() && mService.isSystemAudioActivated()) {
1012             if (!hasAction(ArcInitiationActionFromAvr.class)) {
1013                 addAndStartAction(new ArcInitiationActionFromAvr(this));
1014             }
1015         }
1016     }
1017 
switchToAudioInput()1018     protected void switchToAudioInput() {
1019     }
1020 
isDirectConnectToTv()1021     protected boolean isDirectConnectToTv() {
1022         int myPhysicalAddress = mService.getPhysicalAddress();
1023         return (myPhysicalAddress & Constants.ROUTING_PATH_TOP_MASK) == myPhysicalAddress;
1024     }
1025 
updateAudioManagerForSystemAudio(boolean on)1026     private void updateAudioManagerForSystemAudio(boolean on) {
1027         int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
1028         HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
1029     }
1030 
onSystemAudioControlFeatureSupportChanged(boolean enabled)1031     void onSystemAudioControlFeatureSupportChanged(boolean enabled) {
1032         setSystemAudioControlFeatureEnabled(enabled);
1033         if (enabled) {
1034             addAndStartAction(new SystemAudioInitiationActionFromAvr(this), true);
1035         }
1036     }
1037 
1038     @ServiceThreadOnly
setSystemAudioControlFeatureEnabled(boolean enabled)1039     void setSystemAudioControlFeatureEnabled(boolean enabled) {
1040         assertRunOnServiceThread();
1041         synchronized (mLock) {
1042             mSystemAudioControlFeatureEnabled = enabled;
1043         }
1044     }
1045 
1046     @ServiceThreadOnly
setRoutingControlFeatureEnabled(boolean enabled)1047     void setRoutingControlFeatureEnabled(boolean enabled) {
1048         assertRunOnServiceThread();
1049         synchronized (mLock) {
1050             mRoutingControlFeatureEnabled = enabled;
1051         }
1052     }
1053 
1054     @ServiceThreadOnly
doManualPortSwitching(int portId, IHdmiControlCallback callback)1055     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
1056         assertRunOnServiceThread();
1057         if (!mService.isValidPortId(portId)) {
1058             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
1059             return;
1060         }
1061         if (portId == getLocalActivePort()) {
1062             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1063             return;
1064         }
1065         if (!mService.isCecControlEnabled()) {
1066             setRoutingPort(portId);
1067             setLocalActivePort(portId);
1068             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
1069             return;
1070         }
1071         int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME
1072                 ? mService.portIdToPath(getRoutingPort())
1073                 : getDeviceInfo().getPhysicalAddress();
1074         int newPath = mService.portIdToPath(portId);
1075         if (oldPath == newPath) {
1076             return;
1077         }
1078         setRoutingPort(portId);
1079         setLocalActivePort(portId);
1080         HdmiCecMessage routingChange =
1081                 HdmiCecMessageBuilder.buildRoutingChange(
1082                         getDeviceInfo().getLogicalAddress(), oldPath, newPath);
1083         mService.sendCecCommand(routingChange);
1084     }
1085 
isSystemAudioControlFeatureEnabled()1086     boolean isSystemAudioControlFeatureEnabled() {
1087         synchronized (mLock) {
1088             return mSystemAudioControlFeatureEnabled;
1089         }
1090     }
1091 
isSystemAudioActivated()1092     protected boolean isSystemAudioActivated() {
1093         return mService.isSystemAudioActivated();
1094     }
1095 
terminateSystemAudioMode()1096     protected void terminateSystemAudioMode() {
1097         terminateSystemAudioMode(null);
1098     }
1099 
1100     // Since this method is not just called during the standby process, the callback should be
1101     // generalized in the future.
terminateSystemAudioMode(StandbyCompletedCallback callback)1102     protected void terminateSystemAudioMode(StandbyCompletedCallback callback) {
1103         // remove pending initiation actions
1104         removeAction(SystemAudioInitiationActionFromAvr.class);
1105         if (!isSystemAudioActivated()) {
1106             invokeStandbyCompletedCallback(callback);
1107             return;
1108         }
1109 
1110         if (checkSupportAndSetSystemAudioMode(false)) {
1111             // send <Set System Audio Mode> [“Off”]
1112             mService.sendCecCommand(
1113                     HdmiCecMessageBuilder.buildSetSystemAudioMode(
1114                             getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, false),
1115                     new SendMessageCallback() {
1116                         @Override
1117                         public void onSendCompleted(int error) {
1118                             invokeStandbyCompletedCallback(callback);
1119                         }
1120                     });
1121         }
1122     }
1123 
terminateAudioReturnChannel()1124     private void terminateAudioReturnChannel() {
1125         // remove pending initiation actions
1126         removeAction(ArcInitiationActionFromAvr.class);
1127         if (!isArcEnabled()
1128                 || !mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
1129             return;
1130         }
1131         addAndStartAction(new ArcTerminationActionFromAvr(this));
1132     }
1133 
1134     /** Reports if System Audio Mode is supported by the connected TV */
1135     interface TvSystemAudioModeSupportedCallback {
1136 
1137         /** {@code supported} is true if the TV is connected and supports System Audio Mode. */
onResult(boolean supported)1138         void onResult(boolean supported);
1139     }
1140 
1141     /**
1142      * Queries the connected TV to detect if System Audio Mode is supported by the TV.
1143      *
1144      * <p>This query may take up to 2 seconds to complete.
1145      *
1146      * <p>The result of the query may be cached until Audio device type is put in standby or loses
1147      * its physical address.
1148      */
queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback)1149     void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
1150         if (mTvSystemAudioModeSupport == null) {
1151             addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
1152         } else {
1153             callback.onResult(mTvSystemAudioModeSupport);
1154         }
1155     }
1156 
1157     /**
1158      * Handler of System Audio Mode Request on from non TV device
1159      */
1160     @Constants.HandleMessageResult
handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message)1161     int handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
1162         if (!isSystemAudioControlFeatureEnabled()) {
1163             HdmiLogger.debug(
1164                     "Cannot turn on" + "system audio mode "
1165                             + "because the System Audio Control feature is disabled.");
1166             return Constants.ABORT_REFUSED;
1167         }
1168         // Wake up device
1169         mService.wakeUp();
1170         // If Audio device is the active source or is on the active path,
1171         // enable system audio mode without querying TV's support on sam.
1172         // This is per HDMI spec 1.4b CEC 13.15.4.2.
1173         if (mService.pathToPortId(getActiveSource().physicalAddress)
1174                 != Constants.INVALID_PORT_ID) {
1175             setSystemAudioMode(true);
1176             mService.sendCecCommand(
1177                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
1178                     getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, true));
1179             return Constants.HANDLED;
1180         }
1181         // Check if TV supports System Audio Control.
1182         // Handle broadcasting setSystemAudioMode on or aborting message on callback.
1183         queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
1184             public void onResult(boolean supported) {
1185                 if (supported) {
1186                     setSystemAudioMode(true);
1187                     mService.sendCecCommand(
1188                             HdmiCecMessageBuilder.buildSetSystemAudioMode(
1189                                     getDeviceInfo().getLogicalAddress(),
1190                                     Constants.ADDR_BROADCAST,
1191                                     true));
1192                 } else {
1193                     mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1194                 }
1195             }
1196         });
1197         return Constants.HANDLED;
1198     }
1199 
setTvSystemAudioModeSupport(boolean supported)1200     void setTvSystemAudioModeSupport(boolean supported) {
1201         mTvSystemAudioModeSupport = supported;
1202     }
1203 
1204     @VisibleForTesting
isArcEnabled()1205     protected boolean isArcEnabled() {
1206         synchronized (mLock) {
1207             return mArcEstablished;
1208         }
1209     }
1210 
initArcOnFromAvr()1211     private void initArcOnFromAvr() {
1212         removeAction(ArcTerminationActionFromAvr.class);
1213         if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
1214                 && isDirectConnectToTv() && !isArcEnabled()) {
1215             addAndStartAction(new ArcInitiationActionFromAvr(this), true);
1216         }
1217     }
1218 
1219     @Override
switchInputOnReceivingNewActivePath(int physicalAddress)1220     protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
1221         int port = mService.pathToPortId(physicalAddress);
1222         if (isSystemAudioActivated() && port < 0) {
1223             // If system audio mode is on and the new active source is not under the current device,
1224             // Will switch to ARC input.
1225             routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
1226         } else if (mIsSwitchDevice && port >= 0) {
1227             // If current device is a switch and the new active source is under it,
1228             // will switch to the corresponding active path.
1229             routeToInputFromPortId(port);
1230         }
1231     }
1232 
routeToInputFromPortId(int portId)1233     protected void routeToInputFromPortId(int portId) {
1234         if (!isRoutingControlFeatureEnabled()) {
1235             HdmiLogger.debug("Routing Control Feature is not enabled.");
1236             return;
1237         }
1238         if (mArcIntentUsed) {
1239             routeToTvInputFromPortId(portId);
1240         } else {
1241             // TODO(): implement input switching for devices not using TvInput.
1242         }
1243     }
1244 
routeToTvInputFromPortId(int portId)1245     protected void routeToTvInputFromPortId(int portId) {
1246         if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) {
1247             HdmiLogger.debug("Invalid port number for Tv Input switching.");
1248             return;
1249         }
1250         // Wake up if the current device if ready to route.
1251         mService.wakeUp();
1252         if ((getLocalActivePort() == portId) && (portId != Constants.CEC_SWITCH_ARC)) {
1253             HdmiLogger.debug("Not switching to the same port " + portId + " except for arc");
1254             return;
1255         }
1256         // Switch to HOME if the current active port is not HOME yet
1257         if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
1258             switchToHomeTvInput();
1259         } else if (portId == Constants.CEC_SWITCH_ARC) {
1260             switchToTvInput(HdmiProperties.arc_port().orElse("0"));
1261             setLocalActivePort(portId);
1262             return;
1263         } else {
1264             String uri = mPortIdToTvInputs.get(portId);
1265             if (uri != null) {
1266                 switchToTvInput(uri);
1267             } else {
1268                 HdmiLogger.debug("Port number does not match any Tv Input.");
1269                 return;
1270             }
1271         }
1272 
1273         setLocalActivePort(portId);
1274         setRoutingPort(portId);
1275     }
1276 
1277     // For device to switch to specific TvInput with corresponding URI.
switchToTvInput(String uri)1278     private void switchToTvInput(String uri) {
1279         try {
1280             mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
1281                     TvContract.buildChannelUriForPassthroughInput(uri))
1282                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
1283         } catch (ActivityNotFoundException e) {
1284             Slog.e(TAG, "Can't find activity to switch to " + uri, e);
1285         }
1286     }
1287 
1288     // For device using TvInput to switch to Home.
switchToHomeTvInput()1289     private void switchToHomeTvInput() {
1290         try {
1291             Intent activityIntent = new Intent(Intent.ACTION_MAIN)
1292                     .addCategory(Intent.CATEGORY_HOME)
1293                     .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
1294                     | Intent.FLAG_ACTIVITY_SINGLE_TOP
1295                     | Intent.FLAG_ACTIVITY_NEW_TASK
1296                     | Intent.FLAG_ACTIVITY_NO_ANIMATION);
1297             mService.getContext().startActivity(activityIntent);
1298         } catch (ActivityNotFoundException e) {
1299             Slog.e(TAG, "Can't find activity to switch to HOME", e);
1300         }
1301     }
1302 
1303     @Override
handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message)1304     protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
1305         int port = mService.pathToPortId(physicalAddress);
1306         // Routing change or information sent from switches under the current device can be ignored.
1307         if (port > 0) {
1308             return;
1309         }
1310         // When other switches route to some other devices not under the current device,
1311         // check system audio mode status and do ARC switch if needed.
1312         if (port < 0 && isSystemAudioActivated()) {
1313             handleRoutingChangeAndInformationForSystemAudio();
1314             return;
1315         }
1316         // When other switches route to the current device
1317         // and the current device is also a switch.
1318         if (port == 0) {
1319             handleRoutingChangeAndInformationForSwitch(message);
1320         }
1321     }
1322 
1323     // Handle the system audio(ARC) part of the logic on receiving routing change or information.
handleRoutingChangeAndInformationForSystemAudio()1324     private void handleRoutingChangeAndInformationForSystemAudio() {
1325         routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
1326     }
1327 
1328     // Handle the routing control part of the logic on receiving routing change or information.
handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message)1329     private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
1330         if (getRoutingPort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
1331             routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
1332             mService.setAndBroadcastActiveSourceFromOneDeviceType(
1333                     message.getSource(), mService.getPhysicalAddress(),
1334                     "HdmiCecLocalDeviceAudioSystem#handleRoutingChangeAndInformationForSwitch()");
1335             return;
1336         }
1337 
1338         int routingInformationPath = mService.portIdToPath(getRoutingPort());
1339         // If current device is already the leaf of the whole HDMI system, will do nothing.
1340         if (routingInformationPath == mService.getPhysicalAddress()) {
1341             HdmiLogger.debug("Current device can't assign valid physical address"
1342                     + "to devices under it any more. "
1343                     + "It's physical address is "
1344                     + routingInformationPath);
1345             return;
1346         }
1347         // Otherwise will switch to the current active port and broadcast routing information.
1348         mService.sendCecCommand(
1349                 HdmiCecMessageBuilder.buildRoutingInformation(
1350                         getDeviceInfo().getLogicalAddress(), routingInformationPath));
1351         routeToInputFromPortId(getRoutingPort());
1352     }
1353 
1354     @ServiceThreadOnly
launchDeviceDiscovery()1355     private void launchDeviceDiscovery() {
1356         assertRunOnServiceThread();
1357         if (mService.isDeviceDiscoveryHandledByPlayback()) {
1358             return;
1359         }
1360         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
1361                 new DeviceDiscoveryCallback() {
1362                     @Override
1363                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
1364                         for (HdmiDeviceInfo info : deviceInfos) {
1365                             mService.getHdmiCecNetwork().addCecDevice(info);
1366                         }
1367                     }
1368                 });
1369         addAndStartAction(action, true);
1370     }
1371 
1372     @Override
dump(IndentingPrintWriter pw)1373     protected void dump(IndentingPrintWriter pw) {
1374         pw.println("HdmiCecLocalDeviceAudioSystem:");
1375         pw.increaseIndent();
1376         pw.println("isRoutingFeatureEnabled " + isRoutingControlFeatureEnabled());
1377         pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
1378         pw.println("mTvSystemAudioModeSupport: " + mTvSystemAudioModeSupport);
1379         pw.println("mArcEstablished: " + mArcEstablished);
1380         pw.println("mArcIntentUsed: " + mArcIntentUsed);
1381         pw.println("mRoutingPort: " + getRoutingPort());
1382         pw.println("mLocalActivePort: " + getLocalActivePort());
1383         HdmiUtils.dumpMap(pw, "mPortIdToTvInputs:", mPortIdToTvInputs);
1384         HdmiUtils.dumpMap(pw, "mTvInputsToDeviceInfo:", mTvInputsToDeviceInfo);
1385         pw.decreaseIndent();
1386         super.dump(pw);
1387     }
1388 }
1389