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