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