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