• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 
17 package com.android.server.hdmi;
18 
19 import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
20 import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
21 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE;
22 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION;
23 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE;
24 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED;
25 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
26 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN;
27 import static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT;
28 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED;
29 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
30 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE;
31 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
32 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
33 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
34 
35 import android.annotation.Nullable;
36 import android.hardware.hdmi.DeviceFeatures;
37 import android.hardware.hdmi.HdmiControlManager;
38 import android.hardware.hdmi.HdmiDeviceInfo;
39 import android.hardware.hdmi.HdmiPortInfo;
40 import android.hardware.hdmi.HdmiRecordSources;
41 import android.hardware.hdmi.HdmiTimerRecordSources;
42 import android.hardware.hdmi.IHdmiControlCallback;
43 import android.hardware.tv.cec.V1_0.SendMessageResult;
44 import android.media.AudioDescriptor;
45 import android.media.AudioDeviceAttributes;
46 import android.media.AudioDeviceInfo;
47 import android.media.AudioProfile;
48 import android.media.tv.TvInputInfo;
49 import android.media.tv.TvInputManager.TvInputCallback;
50 import android.util.Slog;
51 import android.util.SparseBooleanArray;
52 
53 import com.android.internal.annotations.GuardedBy;
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.internal.util.IndentingPrintWriter;
56 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
57 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
58 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
59 
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.stream.Collectors;
65 
66 /**
67  * Represent a logical device of type TV residing in Android system.
68  */
69 final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
70     private static final String TAG = "HdmiCecLocalDeviceTv";
71 
72     // Whether ARC is available or not. "true" means that ARC is established between TV and
73     // AVR as audio receiver.
74     @ServiceThreadOnly
75     private boolean mArcEstablished = false;
76 
77     // Stores whether ARC feature is enabled per port.
78     // True by default for all the ARC-enabled ports.
79     private final SparseBooleanArray mArcFeatureEnabled = new SparseBooleanArray();
80 
81     // Whether the System Audio Control feature is enabled or not. True by default.
82     @GuardedBy("mLock")
83     private boolean mSystemAudioControlFeatureEnabled;
84 
85     // The previous port id (input) before switching to the new one. This is remembered in order to
86     // be able to switch to it upon receiving <Inactive Source> from currently active source.
87     // This remains valid only when the active source was switched via one touch play operation
88     // (either by TV or source device). Manual port switching invalidates this value to
89     // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
90     @GuardedBy("mLock")
91     private int mPrevPortId;
92 
93     @GuardedBy("mLock")
94     private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
95 
96     @GuardedBy("mLock")
97     private boolean mSystemAudioMute = false;
98 
99     // If true, do not do routing control/send active source for internal source.
100     // Set to true when the device was woken up by <Text/Image View On>.
101     private boolean mSkipRoutingControl;
102 
103     // Message buffer used to buffer selected messages to process later. <Active Source>
104     // from a source device, for instance, needs to be buffered if the device is not
105     // discovered yet. The buffered commands are taken out and when they are ready to
106     // handle.
107     private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
108 
109     // Defines the callback invoked when TV input framework is updated with input status.
110     // We are interested in the notification for HDMI input addition event, in order to
111     // process any CEC commands that arrived before the input is added.
112     private final TvInputCallback mTvInputCallback = new TvInputCallback() {
113         @Override
114         public void onInputAdded(String inputId) {
115             TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
116             if (tvInfo == null) return;
117             HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
118             if (info == null) return;
119             addTvInput(inputId, info.getId());
120             if (info.isCecDevice()) {
121                 processDelayedActiveSource(info.getLogicalAddress());
122             }
123         }
124 
125         @Override
126         public void onInputRemoved(String inputId) {
127             removeTvInput(inputId);
128         }
129     };
130 
131     // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to
132     // accept input switching request from HDMI devices. Requests for which the corresponding
133     // input ID is not yet registered by TV input framework need to be buffered for delayed
134     // processing.
135     private final HashMap<String, Integer> mTvInputs = new HashMap<>();
136 
137     @ServiceThreadOnly
addTvInput(String inputId, int deviceId)138     private void addTvInput(String inputId, int deviceId) {
139         assertRunOnServiceThread();
140         mTvInputs.put(inputId, deviceId);
141     }
142 
143     @ServiceThreadOnly
removeTvInput(String inputId)144     private void removeTvInput(String inputId) {
145         assertRunOnServiceThread();
146         mTvInputs.remove(inputId);
147     }
148 
149     @Override
150     @ServiceThreadOnly
isInputReady(int deviceId)151     protected boolean isInputReady(int deviceId) {
152         assertRunOnServiceThread();
153         return mTvInputs.containsValue(deviceId);
154     }
155 
156     private SelectRequestBuffer mSelectRequestBuffer;
157 
HdmiCecLocalDeviceTv(HdmiControlService service)158     HdmiCecLocalDeviceTv(HdmiControlService service) {
159         super(service, HdmiDeviceInfo.DEVICE_TV);
160         mPrevPortId = Constants.INVALID_PORT_ID;
161         mSystemAudioControlFeatureEnabled = service.getHdmiCecConfig().getIntValue(
162                 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL)
163                     == HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED;
164         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
165     }
166 
167     @Override
168     @ServiceThreadOnly
onAddressAllocated(int logicalAddress, int reason)169     protected void onAddressAllocated(int logicalAddress, int reason) {
170         assertRunOnServiceThread();
171         List<HdmiPortInfo> ports = mService.getPortInfo();
172         for (HdmiPortInfo port : ports) {
173             mArcFeatureEnabled.put(port.getId(), port.isArcSupported());
174         }
175         mService.registerTvInputCallback(mTvInputCallback);
176         mService.sendCecCommand(
177                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
178                         getDeviceInfo().getLogicalAddress(),
179                         mService.getPhysicalAddress(),
180                         mDeviceType));
181         mService.sendCecCommand(
182                 HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
183                         getDeviceInfo().getLogicalAddress(), mService.getVendorId()));
184         mService.getHdmiCecNetwork().addCecSwitch(
185                 mService.getHdmiCecNetwork().getPhysicalAddress());  // TV is a CEC switch too.
186         mTvInputs.clear();
187         mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
188         launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
189                 reason != HdmiControlService.INITIATED_BY_BOOT_UP);
190         resetSelectRequestBuffer();
191         launchDeviceDiscovery();
192         startQueuedActions();
193         if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
194             mService.sendCecCommand(
195                     HdmiCecMessageBuilder.buildRequestActiveSource(
196                             getDeviceInfo().getLogicalAddress()));
197         }
198     }
199 
200     @ServiceThreadOnly
setSelectRequestBuffer(SelectRequestBuffer requestBuffer)201     public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) {
202         assertRunOnServiceThread();
203         mSelectRequestBuffer = requestBuffer;
204     }
205 
206     @ServiceThreadOnly
resetSelectRequestBuffer()207     private void resetSelectRequestBuffer() {
208         assertRunOnServiceThread();
209         setSelectRequestBuffer(SelectRequestBuffer.EMPTY_BUFFER);
210     }
211 
212     @Override
getPreferredAddress()213     protected int getPreferredAddress() {
214         return Constants.ADDR_TV;
215     }
216 
217     @Override
setPreferredAddress(int addr)218     protected void setPreferredAddress(int addr) {
219         Slog.w(TAG, "Preferred addres will not be stored for TV");
220     }
221 
222     @Override
223     @ServiceThreadOnly
224     @VisibleForTesting
225     @Constants.HandleMessageResult
dispatchMessage(HdmiCecMessage message)226     protected int dispatchMessage(HdmiCecMessage message) {
227         assertRunOnServiceThread();
228         if (mService.isPowerStandby() && !mService.isWakeUpMessageReceived()
229                 && mStandbyHandler.handleCommand(message)) {
230             return Constants.HANDLED;
231         }
232         return super.onMessage(message);
233     }
234 
235     /**
236      * Performs the action 'device select', or 'one touch play' initiated by TV.
237      *
238      * @param id id of HDMI device to select
239      * @param callback callback object to report the result with
240      */
241     @ServiceThreadOnly
deviceSelect(int id, IHdmiControlCallback callback)242     void deviceSelect(int id, IHdmiControlCallback callback) {
243         assertRunOnServiceThread();
244         HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id);
245         if (targetDevice == null) {
246             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
247             return;
248         }
249         int targetAddress = targetDevice.getLogicalAddress();
250         if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
251             return;
252         }
253         if (targetAddress == Constants.ADDR_INTERNAL) {
254             handleSelectInternalSource();
255             // Switching to internal source is always successful even when CEC control is disabled.
256             setActiveSource(targetAddress, mService.getPhysicalAddress(),
257                     "HdmiCecLocalDeviceTv#deviceSelect()");
258             setActivePath(mService.getPhysicalAddress());
259             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
260             return;
261         }
262         if (!mService.isControlEnabled()) {
263             setActiveSource(targetDevice, "HdmiCecLocalDeviceTv#deviceSelect()");
264             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
265             return;
266         }
267         removeAction(DeviceSelectActionFromTv.class);
268         addAndStartAction(new DeviceSelectActionFromTv(this, targetDevice, callback));
269     }
270 
271     @ServiceThreadOnly
handleSelectInternalSource()272     private void handleSelectInternalSource() {
273         assertRunOnServiceThread();
274         // Seq #18
275         if (mService.isControlEnabled()
276                 && getActiveSource().logicalAddress != getDeviceInfo().getLogicalAddress()) {
277             updateActiveSource(
278                     getDeviceInfo().getLogicalAddress(),
279                     mService.getPhysicalAddress(),
280                     "HdmiCecLocalDeviceTv#handleSelectInternalSource()");
281             if (mSkipRoutingControl) {
282                 mSkipRoutingControl = false;
283                 return;
284             }
285             HdmiCecMessage activeSource =
286                     HdmiCecMessageBuilder.buildActiveSource(
287                             getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress());
288             mService.sendCecCommand(activeSource);
289         }
290     }
291 
292     @ServiceThreadOnly
updateActiveSource(int logicalAddress, int physicalAddress, String caller)293     void updateActiveSource(int logicalAddress, int physicalAddress, String caller) {
294         assertRunOnServiceThread();
295         updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress), caller);
296     }
297 
298     @ServiceThreadOnly
updateActiveSource(ActiveSource newActive, String caller)299     void updateActiveSource(ActiveSource newActive, String caller) {
300         assertRunOnServiceThread();
301         // Seq #14
302         if (getActiveSource().equals(newActive)) {
303             return;
304         }
305         setActiveSource(newActive, caller);
306         int logicalAddress = newActive.logicalAddress;
307         if (mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress) != null
308                 && logicalAddress != getDeviceInfo().getLogicalAddress()) {
309             if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
310                 setPrevPortId(getActivePortId());
311             }
312             // TODO: Show the OSD banner related to the new active source device.
313         } else {
314             // TODO: If displayed, remove the OSD banner related to the previous
315             //       active source device.
316         }
317     }
318 
319     /**
320      * Returns the previous port id kept to handle input switching on <Inactive Source>.
321      */
getPrevPortId()322     int getPrevPortId() {
323         synchronized (mLock) {
324             return mPrevPortId;
325         }
326     }
327 
328     /**
329      * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
330      * taken for <Inactive Source>.
331      */
setPrevPortId(int portId)332     void setPrevPortId(int portId) {
333         synchronized (mLock) {
334             mPrevPortId = portId;
335         }
336     }
337 
338     @ServiceThreadOnly
updateActiveInput(int path, boolean notifyInputChange)339     void updateActiveInput(int path, boolean notifyInputChange) {
340         assertRunOnServiceThread();
341         // Seq #15
342         setActivePath(path);
343         // TODO: Handle PAP/PIP case.
344         // Show OSD port change banner
345         if (notifyInputChange) {
346             ActiveSource activeSource = getActiveSource();
347             HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(
348                     activeSource.logicalAddress);
349             if (info == null) {
350                 info = mService.getDeviceInfoByPort(getActivePortId());
351                 if (info == null) {
352                     // No CEC/MHL device is present at the port. Attempt to switch to
353                     // the hardware port itself for non-CEC devices that may be connected.
354                     info = HdmiDeviceInfo.hardwarePort(path, getActivePortId());
355                 }
356             }
357             mService.invokeInputChangeListener(info);
358         }
359     }
360 
361     @ServiceThreadOnly
doManualPortSwitching(int portId, IHdmiControlCallback callback)362     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
363         assertRunOnServiceThread();
364         // Seq #20
365         if (!mService.isValidPortId(portId)) {
366             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
367             return;
368         }
369         if (portId == getActivePortId()) {
370             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
371             return;
372         }
373         getActiveSource().invalidate();
374         if (!mService.isControlEnabled()) {
375             setActivePortId(portId);
376             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
377             return;
378         }
379         int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
380                 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
381         setActivePath(oldPath);
382         if (mSkipRoutingControl) {
383             mSkipRoutingControl = false;
384             return;
385         }
386         int newPath = mService.portIdToPath(portId);
387         startRoutingControl(oldPath, newPath, callback);
388     }
389 
390     @ServiceThreadOnly
startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback)391     void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) {
392         assertRunOnServiceThread();
393         if (oldPath == newPath) {
394             return;
395         }
396         HdmiCecMessage routingChange =
397                 HdmiCecMessageBuilder.buildRoutingChange(
398                         getDeviceInfo().getLogicalAddress(), oldPath, newPath);
399         mService.sendCecCommand(routingChange);
400         removeAction(RoutingControlAction.class);
401         addAndStartAction(
402                 new RoutingControlAction(this, newPath, callback));
403     }
404 
405     @ServiceThreadOnly
getPowerStatus()406     int getPowerStatus() {
407         assertRunOnServiceThread();
408         return mService.getPowerStatus();
409     }
410 
411     @Override
findKeyReceiverAddress()412     protected int findKeyReceiverAddress() {
413         if (getActiveSource().isValid()) {
414             return getActiveSource().logicalAddress;
415         }
416         HdmiDeviceInfo info = mService.getHdmiCecNetwork().getDeviceInfoByPath(getActivePath());
417         if (info != null) {
418             return info.getLogicalAddress();
419         }
420         return Constants.ADDR_INVALID;
421     }
422 
423     @Override
findAudioReceiverAddress()424     protected int findAudioReceiverAddress() {
425         return Constants.ADDR_AUDIO_SYSTEM;
426     }
427 
428     @Override
429     @ServiceThreadOnly
430     @Constants.HandleMessageResult
handleActiveSource(HdmiCecMessage message)431     protected int handleActiveSource(HdmiCecMessage message) {
432         assertRunOnServiceThread();
433         int logicalAddress = message.getSource();
434         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
435         HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress);
436         if (info == null) {
437             if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
438                 HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
439                 mDelayedMessageBuffer.add(message);
440             }
441         } else if (isInputReady(info.getId())
442                 || info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
443             mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress,
444                     HdmiControlManager.POWER_STATUS_ON);
445             ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
446             ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
447         } else {
448             HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
449             mDelayedMessageBuffer.add(message);
450         }
451         return Constants.HANDLED;
452     }
453 
454     @Override
455     @ServiceThreadOnly
456     @Constants.HandleMessageResult
handleInactiveSource(HdmiCecMessage message)457     protected int handleInactiveSource(HdmiCecMessage message) {
458         assertRunOnServiceThread();
459         // Seq #10
460 
461         // Ignore <Inactive Source> from non-active source device.
462         if (getActiveSource().logicalAddress != message.getSource()) {
463             return Constants.HANDLED;
464         }
465         if (isProhibitMode()) {
466             return Constants.HANDLED;
467         }
468         int portId = getPrevPortId();
469         if (portId != Constants.INVALID_PORT_ID) {
470             // TODO: Do this only if TV is not showing multiview like PIP/PAP.
471 
472             HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo(
473                     message.getSource());
474             if (inactiveSource == null) {
475                 return Constants.HANDLED;
476             }
477             if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
478                 return Constants.HANDLED;
479             }
480             // TODO: Switch the TV freeze mode off
481 
482             doManualPortSwitching(portId, null);
483             setPrevPortId(Constants.INVALID_PORT_ID);
484         } else {
485             // No HDMI port to switch to was found. Notify the input change listers to
486             // switch to the lastly shown internal input.
487             getActiveSource().invalidate();
488             setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
489             mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
490         }
491         return Constants.HANDLED;
492     }
493 
494     @Override
495     @ServiceThreadOnly
496     @Constants.HandleMessageResult
handleRequestActiveSource(HdmiCecMessage message)497     protected int handleRequestActiveSource(HdmiCecMessage message) {
498         assertRunOnServiceThread();
499         // Seq #19
500         if (getDeviceInfo().getLogicalAddress() == getActiveSource().logicalAddress) {
501             mService.sendCecCommand(
502                     HdmiCecMessageBuilder.buildActiveSource(
503                             getDeviceInfo().getLogicalAddress(), getActivePath()));
504         }
505         return Constants.HANDLED;
506     }
507 
508     @Override
509     @ServiceThreadOnly
510     @Constants.HandleMessageResult
handleGetMenuLanguage(HdmiCecMessage message)511     protected int handleGetMenuLanguage(HdmiCecMessage message) {
512         assertRunOnServiceThread();
513         if (!broadcastMenuLanguage(mService.getLanguage())) {
514             Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
515         }
516         return Constants.HANDLED;
517     }
518 
519     @ServiceThreadOnly
broadcastMenuLanguage(String language)520     boolean broadcastMenuLanguage(String language) {
521         assertRunOnServiceThread();
522         HdmiCecMessage command =
523                 HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
524                         getDeviceInfo().getLogicalAddress(), language);
525         if (command != null) {
526             mService.sendCecCommand(command);
527             return true;
528         }
529         return false;
530     }
531 
532     @Override
533     @Constants.HandleMessageResult
handleReportPhysicalAddress(HdmiCecMessage message)534     protected int handleReportPhysicalAddress(HdmiCecMessage message) {
535         super.handleReportPhysicalAddress(message);
536         int path = HdmiUtils.twoBytesToInt(message.getParams());
537         int address = message.getSource();
538         int type = message.getParams()[2];
539 
540         if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) {
541             handleNewDeviceAtTheTailOfActivePath(path);
542         }
543         startNewDeviceAction(ActiveSource.of(address, path), type);
544         return Constants.HANDLED;
545     }
546 
547     @Override
548     @Constants.HandleMessageResult
handleTimerStatus(HdmiCecMessage message)549     protected int handleTimerStatus(HdmiCecMessage message) {
550         // Do nothing.
551         return Constants.HANDLED;
552     }
553 
554     @Override
555     @Constants.HandleMessageResult
handleRecordStatus(HdmiCecMessage message)556     protected int handleRecordStatus(HdmiCecMessage message) {
557         // Do nothing.
558         return Constants.HANDLED;
559     }
560 
startNewDeviceAction(ActiveSource activeSource, int deviceType)561     void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
562         for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
563             // If there is new device action which has the same logical address and path
564             // ignore new request.
565             // NewDeviceAction is created whenever it receives <Report Physical Address>.
566             // And there is a chance starting NewDeviceAction for the same source.
567             // Usually, new device sends <Report Physical Address> when it's plugged
568             // in. However, TV can detect a new device from HotPlugDetectionAction,
569             // which sends <Give Physical Address> to the source for newly detected
570             // device.
571             if (action.isActionOf(activeSource)) {
572                 return;
573             }
574         }
575 
576         addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
577                 activeSource.physicalAddress, deviceType));
578     }
579 
handleNewDeviceAtTheTailOfActivePath(int path)580     private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
581         // Seq #22
582         if (isTailOfActivePath(path, getActivePath())) {
583             int newPath = mService.portIdToPath(getActivePortId());
584             setActivePath(newPath);
585             startRoutingControl(getActivePath(), newPath, null);
586             return true;
587         }
588         return false;
589     }
590 
591     /**
592      * Whether the given path is located in the tail of current active path.
593      *
594      * @param path to be tested
595      * @param activePath current active path
596      * @return true if the given path is located in the tail of current active path; otherwise,
597      *         false
598      */
isTailOfActivePath(int path, int activePath)599     static boolean isTailOfActivePath(int path, int activePath) {
600         // If active routing path is internal source, return false.
601         if (activePath == 0) {
602             return false;
603         }
604         for (int i = 12; i >= 0; i -= 4) {
605             int curActivePath = (activePath >> i) & 0xF;
606             if (curActivePath == 0) {
607                 return true;
608             } else {
609                 int curPath = (path >> i) & 0xF;
610                 if (curPath != curActivePath) {
611                     return false;
612                 }
613             }
614         }
615         return false;
616     }
617 
618     @Override
619     @ServiceThreadOnly
620     @Constants.HandleMessageResult
handleRoutingChange(HdmiCecMessage message)621     protected int handleRoutingChange(HdmiCecMessage message) {
622         assertRunOnServiceThread();
623         // Seq #21
624         byte[] params = message.getParams();
625         int currentPath = HdmiUtils.twoBytesToInt(params);
626         if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
627             getActiveSource().invalidate();
628             removeAction(RoutingControlAction.class);
629             int newPath = HdmiUtils.twoBytesToInt(params, 2);
630             addAndStartAction(new RoutingControlAction(this, newPath, null));
631         }
632         return Constants.HANDLED;
633     }
634 
635     @Override
636     @ServiceThreadOnly
637     @Constants.HandleMessageResult
handleReportAudioStatus(HdmiCecMessage message)638     protected int handleReportAudioStatus(HdmiCecMessage message) {
639         assertRunOnServiceThread();
640         if (mService.getHdmiCecVolumeControl()
641                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
642             return Constants.ABORT_REFUSED;
643         }
644 
645         boolean mute = HdmiUtils.isAudioStatusMute(message);
646         int volume = HdmiUtils.getAudioStatusVolume(message);
647         setAudioStatus(mute, volume);
648         return Constants.HANDLED;
649     }
650 
651     @Override
652     @ServiceThreadOnly
653     @Constants.HandleMessageResult
handleTextViewOn(HdmiCecMessage message)654     protected int handleTextViewOn(HdmiCecMessage message) {
655         assertRunOnServiceThread();
656 
657         // Note that if the device is in sleep mode, the <Text View On> (and <Image View On>)
658         // command won't be handled here in most cases. A dedicated microcontroller should be in
659         // charge while the Android system is in sleep mode, and the command doesn't need to be
660         // passed up to this service.
661         // The only situations where the command reaches this handler are
662         // 1. if sleep mode is implemented in such a way that Android system is not really put to
663         // standby mode but only the display is set to blank. Then the command leads to
664         // turning on the display by the invocation of PowerManager.wakeUp().
665         // 2. if the device is in dream mode, not sleep mode. Then this command leads to
666         // waking up the device from dream mode by the invocation of PowerManager.wakeUp().
667         if (getAutoWakeup()) {
668             mService.wakeUp();
669         }
670         return Constants.HANDLED;
671     }
672 
673     @Override
674     @ServiceThreadOnly
675     @Constants.HandleMessageResult
handleImageViewOn(HdmiCecMessage message)676     protected int handleImageViewOn(HdmiCecMessage message) {
677         assertRunOnServiceThread();
678         // Currently, it's the same as <Text View On>.
679         return handleTextViewOn(message);
680     }
681 
682     @ServiceThreadOnly
launchDeviceDiscovery()683     private void launchDeviceDiscovery() {
684         assertRunOnServiceThread();
685         clearDeviceInfoList();
686         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
687                 new DeviceDiscoveryCallback() {
688                     @Override
689                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
690                         for (HdmiDeviceInfo info : deviceInfos) {
691                             mService.getHdmiCecNetwork().addCecDevice(info);
692                         }
693 
694                         // Since we removed all devices when it starts and
695                         // device discovery action does not poll local devices,
696                         // we should put device info of local device manually here
697                         for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
698                             mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
699                         }
700 
701                         mSelectRequestBuffer.process();
702                         resetSelectRequestBuffer();
703 
704                         List<HotplugDetectionAction> hotplugActions
705                                 = getActions(HotplugDetectionAction.class);
706                         if (hotplugActions.isEmpty()) {
707                             addAndStartAction(
708                                     new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
709                         }
710 
711                         List<PowerStatusMonitorAction> powerStatusActions
712                                 = getActions(PowerStatusMonitorAction.class);
713                         if (powerStatusActions.isEmpty()) {
714                             addAndStartAction(
715                                     new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
716                         }
717 
718                         HdmiDeviceInfo avr = getAvrDeviceInfo();
719                         if (avr != null) {
720                             onNewAvrAdded(avr);
721                         } else {
722                             setSystemAudioMode(false);
723                         }
724                     }
725                 });
726         addAndStartAction(action);
727     }
728 
729     @ServiceThreadOnly
onNewAvrAdded(HdmiDeviceInfo avr)730     void onNewAvrAdded(HdmiDeviceInfo avr) {
731         assertRunOnServiceThread();
732         addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
733         if (!isDirectConnectAddress(avr.getPhysicalAddress())) {
734             startArcAction(false);
735         } else if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
736                 && !hasAction(SetArcTransmissionStateAction.class)) {
737             startArcAction(true);
738         }
739     }
740 
741     @ServiceThreadOnly
742     // Seq #32
changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback)743     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
744         assertRunOnServiceThread();
745         if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
746             setSystemAudioMode(false);
747             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
748             return;
749         }
750         HdmiDeviceInfo avr = getAvrDeviceInfo();
751         if (avr == null) {
752             setSystemAudioMode(false);
753             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
754             return;
755         }
756 
757         addAndStartAction(
758                 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
759     }
760 
761     // # Seq 25
setSystemAudioMode(boolean on)762     void setSystemAudioMode(boolean on) {
763         if (!isSystemAudioControlFeatureEnabled() && on) {
764             HdmiLogger.debug("Cannot turn on system audio mode "
765                     + "because the System Audio Control feature is disabled.");
766             return;
767         }
768         HdmiLogger.debug("System Audio Mode change[old:%b new:%b]",
769                 mService.isSystemAudioActivated(), on);
770         updateAudioManagerForSystemAudio(on);
771         synchronized (mLock) {
772             if (mService.isSystemAudioActivated() != on) {
773                 mService.setSystemAudioActivated(on);
774                 mService.announceSystemAudioModeChange(on);
775             }
776             if (on && !mArcEstablished) {
777                 startArcAction(true);
778             } else if (!on) {
779                 startArcAction(false);
780             }
781         }
782     }
783 
updateAudioManagerForSystemAudio(boolean on)784     private void updateAudioManagerForSystemAudio(boolean on) {
785         int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
786         HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
787     }
788 
isSystemAudioActivated()789     boolean isSystemAudioActivated() {
790         if (!hasSystemAudioDevice()) {
791             return false;
792         }
793         return mService.isSystemAudioActivated();
794     }
795 
796     @ServiceThreadOnly
setSystemAudioControlFeatureEnabled(boolean enabled)797     void setSystemAudioControlFeatureEnabled(boolean enabled) {
798         assertRunOnServiceThread();
799         synchronized (mLock) {
800             mSystemAudioControlFeatureEnabled = enabled;
801         }
802         if (hasSystemAudioDevice()) {
803             changeSystemAudioMode(enabled, null);
804         }
805     }
806 
isSystemAudioControlFeatureEnabled()807     boolean isSystemAudioControlFeatureEnabled() {
808         synchronized (mLock) {
809             return mSystemAudioControlFeatureEnabled;
810         }
811     }
812 
813     @ServiceThreadOnly
enableArc(List<byte[]> supportedSads)814     void enableArc(List<byte[]> supportedSads) {
815         assertRunOnServiceThread();
816         HdmiLogger.debug("Set Arc Status[old:%b new:true]", mArcEstablished);
817 
818         enableAudioReturnChannel(true);
819         notifyArcStatusToAudioService(true, supportedSads);
820         mArcEstablished = true;
821     }
822 
823     @ServiceThreadOnly
disableArc()824     void disableArc() {
825         assertRunOnServiceThread();
826         HdmiLogger.debug("Set Arc Status[old:%b new:false]", mArcEstablished);
827 
828         enableAudioReturnChannel(false);
829         notifyArcStatusToAudioService(false, new ArrayList<>());
830         mArcEstablished = false;
831     }
832 
833     /**
834      * Switch hardware ARC circuit in the system.
835      */
836     @ServiceThreadOnly
enableAudioReturnChannel(boolean enabled)837     void enableAudioReturnChannel(boolean enabled) {
838         assertRunOnServiceThread();
839         HdmiDeviceInfo avr = getAvrDeviceInfo();
840         if (avr != null) {
841             mService.enableAudioReturnChannel(avr.getPortId(), enabled);
842         }
843     }
844 
845     @ServiceThreadOnly
isConnected(int portId)846     boolean isConnected(int portId) {
847         assertRunOnServiceThread();
848         return mService.isConnected(portId);
849     }
850 
notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads)851     private void notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads) {
852         // Note that we don't set any name to ARC.
853         AudioDeviceAttributes attributes = new AudioDeviceAttributes(
854                 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_ARC, "", "",
855                 new ArrayList<AudioProfile>(), supportedSads.stream()
856                 .map(sad -> new AudioDescriptor(AudioDescriptor.STANDARD_EDID,
857                         AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE, sad))
858                 .collect(Collectors.toList()));
859         mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0);
860     }
861 
862     /**
863      * Returns true if ARC is currently established on a certain port.
864      */
865     @ServiceThreadOnly
isArcEstablished()866     boolean isArcEstablished() {
867         assertRunOnServiceThread();
868         if (mArcEstablished) {
869             for (int i = 0; i < mArcFeatureEnabled.size(); i++) {
870                 if (mArcFeatureEnabled.valueAt(i)) return true;
871             }
872         }
873         return false;
874     }
875 
876     @ServiceThreadOnly
changeArcFeatureEnabled(int portId, boolean enabled)877     void changeArcFeatureEnabled(int portId, boolean enabled) {
878         assertRunOnServiceThread();
879         if (mArcFeatureEnabled.get(portId) == enabled) {
880             return;
881         }
882         mArcFeatureEnabled.put(portId, enabled);
883         HdmiDeviceInfo avr = getAvrDeviceInfo();
884         if (avr == null || avr.getPortId() != portId) {
885             return;
886         }
887         if (enabled && !mArcEstablished) {
888             startArcAction(true);
889         } else if (!enabled && mArcEstablished) {
890             startArcAction(false);
891         }
892     }
893 
894     @ServiceThreadOnly
isArcFeatureEnabled(int portId)895     boolean isArcFeatureEnabled(int portId) {
896         assertRunOnServiceThread();
897         return mArcFeatureEnabled.get(portId);
898     }
899 
900     @ServiceThreadOnly
startArcAction(boolean enabled)901     void startArcAction(boolean enabled) {
902         assertRunOnServiceThread();
903         HdmiDeviceInfo info = getAvrDeviceInfo();
904         if (info == null) {
905             Slog.w(TAG, "Failed to start arc action; No AVR device.");
906             return;
907         }
908         if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
909             Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
910             if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
911                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
912             }
913             return;
914         }
915 
916         // Terminate opposite action and start action if not exist.
917         if (enabled) {
918             removeAction(RequestArcTerminationAction.class);
919             if (!hasAction(RequestArcInitiationAction.class)) {
920                 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
921             }
922         } else {
923             removeAction(RequestArcInitiationAction.class);
924             if (!hasAction(RequestArcTerminationAction.class)) {
925                 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
926             }
927         }
928     }
929 
isDirectConnectAddress(int physicalAddress)930     private boolean isDirectConnectAddress(int physicalAddress) {
931         return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
932     }
933 
setAudioStatus(boolean mute, int volume)934     void setAudioStatus(boolean mute, int volume) {
935         if (!isSystemAudioActivated() || mService.getHdmiCecVolumeControl()
936                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
937             return;
938         }
939         synchronized (mLock) {
940             mSystemAudioMute = mute;
941             mSystemAudioVolume = volume;
942             displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
943                     mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
944         }
945     }
946 
947     @ServiceThreadOnly
changeVolume(int curVolume, int delta, int maxVolume)948     void changeVolume(int curVolume, int delta, int maxVolume) {
949         assertRunOnServiceThread();
950         if (getAvrDeviceInfo() == null) {
951             // On initialization process, getAvrDeviceInfo() may return null and cause exception
952             return;
953         }
954         if (delta == 0 || !isSystemAudioActivated() || mService.getHdmiCecVolumeControl()
955                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
956             return;
957         }
958 
959         int targetVolume = curVolume + delta;
960         int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
961         synchronized (mLock) {
962             // If new volume is the same as current system audio volume, just ignore it.
963             // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
964             if (cecVolume == mSystemAudioVolume) {
965                 // Update tv volume with system volume value.
966                 mService.setAudioStatus(false,
967                         VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
968                 return;
969             }
970         }
971 
972         List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
973         if (actions.isEmpty()) {
974             addAndStartAction(new VolumeControlAction(this,
975                     getAvrDeviceInfo().getLogicalAddress(), delta > 0));
976         } else {
977             actions.get(0).handleVolumeChange(delta > 0);
978         }
979     }
980 
981     @ServiceThreadOnly
changeMute(boolean mute)982     void changeMute(boolean mute) {
983         assertRunOnServiceThread();
984         if (getAvrDeviceInfo() == null || mService.getHdmiCecVolumeControl()
985                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
986             // On initialization process, getAvrDeviceInfo() may return null and cause exception
987             return;
988         }
989         HdmiLogger.debug("[A]:Change mute:%b", mute);
990         synchronized (mLock) {
991             if (mSystemAudioMute == mute) {
992                 HdmiLogger.debug("No need to change mute.");
993                 return;
994             }
995         }
996         if (!isSystemAudioActivated()) {
997             HdmiLogger.debug("[A]:System audio is not activated.");
998             return;
999         }
1000 
1001         // Remove existing volume action.
1002         removeAction(VolumeControlAction.class);
1003         sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
1004                 HdmiCecKeycode.getMuteKey(mute));
1005     }
1006 
1007     @Override
1008     @ServiceThreadOnly
1009     @Constants.HandleMessageResult
handleInitiateArc(HdmiCecMessage message)1010     protected int handleInitiateArc(HdmiCecMessage message) {
1011         assertRunOnServiceThread();
1012 
1013         if (!canStartArcUpdateAction(message.getSource(), true)) {
1014             HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo();
1015             if (avrDeviceInfo == null) {
1016                 // AVR may not have been discovered yet. Delay the message processing.
1017                 mDelayedMessageBuffer.add(message);
1018                 return Constants.HANDLED;
1019             }
1020             if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) {
1021                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
1022             }
1023             return Constants.ABORT_REFUSED;
1024         }
1025 
1026         // In case where <Initiate Arc> is started by <Request ARC Initiation>
1027         // need to clean up RequestArcInitiationAction.
1028         removeAction(RequestArcInitiationAction.class);
1029         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1030                 message.getSource(), true);
1031         addAndStartAction(action);
1032         return Constants.HANDLED;
1033     }
1034 
canStartArcUpdateAction(int avrAddress, boolean enabled)1035     private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
1036         HdmiDeviceInfo avr = getAvrDeviceInfo();
1037         if (avr != null
1038                 && (avrAddress == avr.getLogicalAddress())
1039                 && isConnectedToArcPort(avr.getPhysicalAddress())) {
1040             if (enabled) {
1041                 return isConnected(avr.getPortId())
1042                     && isArcFeatureEnabled(avr.getPortId())
1043                     && isDirectConnectAddress(avr.getPhysicalAddress());
1044             } else {
1045                 return true;
1046             }
1047         } else {
1048             return false;
1049         }
1050     }
1051 
1052     @Override
1053     @ServiceThreadOnly
1054     @Constants.HandleMessageResult
handleTerminateArc(HdmiCecMessage message)1055     protected int handleTerminateArc(HdmiCecMessage message) {
1056         assertRunOnServiceThread();
1057         if (mService .isPowerStandbyOrTransient()) {
1058             disableArc();
1059             return Constants.HANDLED;
1060         }
1061         // Do not check ARC configuration since the AVR might have been already removed.
1062         // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
1063         // <Request ARC Termination>.
1064         removeAction(RequestArcTerminationAction.class);
1065         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1066                 message.getSource(), false);
1067         addAndStartAction(action);
1068         return Constants.HANDLED;
1069     }
1070 
1071     @Override
1072     @ServiceThreadOnly
1073     @Constants.HandleMessageResult
handleSetSystemAudioMode(HdmiCecMessage message)1074     protected int handleSetSystemAudioMode(HdmiCecMessage message) {
1075         assertRunOnServiceThread();
1076         boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message);
1077         if (!isMessageForSystemAudio(message)) {
1078             if (getAvrDeviceInfo() == null) {
1079                 // AVR may not have been discovered yet. Delay the message processing.
1080                 mDelayedMessageBuffer.add(message);
1081             } else {
1082                 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
1083                 return Constants.ABORT_REFUSED;
1084             }
1085         } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) {
1086             HdmiLogger.debug("Ignoring <Set System Audio Mode> message "
1087                     + "because the System Audio Control feature is disabled: %s", message);
1088             return Constants.ABORT_REFUSED;
1089         }
1090         removeAction(SystemAudioAutoInitiationAction.class);
1091         SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
1092                 message.getSource(), systemAudioStatus, null);
1093         addAndStartAction(action);
1094         return Constants.HANDLED;
1095     }
1096 
1097     @Override
1098     @ServiceThreadOnly
1099     @Constants.HandleMessageResult
handleSystemAudioModeStatus(HdmiCecMessage message)1100     protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
1101         assertRunOnServiceThread();
1102         if (!isMessageForSystemAudio(message)) {
1103             HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
1104             // Ignore this message.
1105             return Constants.HANDLED;
1106         }
1107         boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled();
1108         boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message);
1109         // Set System Audio Mode according to TV's settings.
1110         // Handle <System Audio Mode Status> here only when
1111         // SystemAudioAutoInitiationAction timeout
1112         HdmiDeviceInfo avr = getAvrDeviceInfo();
1113         if (avr == null) {
1114             setSystemAudioMode(false);
1115         } else if (avrSystemAudioMode != tvSystemAudioMode) {
1116             addAndStartAction(new SystemAudioActionFromTv(this, avr.getLogicalAddress(),
1117                     tvSystemAudioMode, null));
1118         } else {
1119             setSystemAudioMode(tvSystemAudioMode);
1120         }
1121 
1122         return Constants.HANDLED;
1123     }
1124 
1125     // Seq #53
1126     @Override
1127     @ServiceThreadOnly
1128     @Constants.HandleMessageResult
handleRecordTvScreen(HdmiCecMessage message)1129     protected int handleRecordTvScreen(HdmiCecMessage message) {
1130         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
1131         if (!actions.isEmpty()) {
1132             // Assumes only one OneTouchRecordAction.
1133             OneTouchRecordAction action = actions.get(0);
1134             if (action.getRecorderAddress() != message.getSource()) {
1135                 announceOneTouchRecordResult(
1136                         message.getSource(),
1137                         HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
1138             }
1139             // The default behavior of <Record TV Screen> is replying <Feature Abort> with
1140             // "Cannot provide source".
1141             return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1142         }
1143 
1144         int recorderAddress = message.getSource();
1145         byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
1146         return startOneTouchRecord(recorderAddress, recordSource);
1147     }
1148 
1149     @Override
1150     @Constants.HandleMessageResult
handleTimerClearedStatus(HdmiCecMessage message)1151     protected int handleTimerClearedStatus(HdmiCecMessage message) {
1152         byte[] params = message.getParams();
1153         int timerClearedStatusData = params[0] & 0xFF;
1154         announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
1155         return Constants.HANDLED;
1156     }
1157 
1158     @Override
1159     @Constants.HandleMessageResult
handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message)1160     protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
1161         // <Set Audio Volume Level> should only be sent to the System Audio device, so we don't
1162         // handle it when System Audio Mode is enabled.
1163         if (mService.isSystemAudioActivated()) {
1164             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1165         } else {
1166             mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0);
1167             return Constants.HANDLED;
1168         }
1169     }
1170 
announceOneTouchRecordResult(int recorderAddress, int result)1171     void announceOneTouchRecordResult(int recorderAddress, int result) {
1172         mService.invokeOneTouchRecordResult(recorderAddress, result);
1173     }
1174 
announceTimerRecordingResult(int recorderAddress, int result)1175     void announceTimerRecordingResult(int recorderAddress, int result) {
1176         mService.invokeTimerRecordingResult(recorderAddress, result);
1177     }
1178 
announceClearTimerRecordingResult(int recorderAddress, int result)1179     void announceClearTimerRecordingResult(int recorderAddress, int result) {
1180         mService.invokeClearTimerRecordingResult(recorderAddress, result);
1181     }
1182 
isMessageForSystemAudio(HdmiCecMessage message)1183     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
1184         return mService.isControlEnabled()
1185                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1186                 && (message.getDestination() == Constants.ADDR_TV
1187                         || message.getDestination() == Constants.ADDR_BROADCAST)
1188                 && getAvrDeviceInfo() != null;
1189     }
1190 
1191     @Nullable
1192     @ServiceThreadOnly
getAvrDeviceInfo()1193     HdmiDeviceInfo getAvrDeviceInfo() {
1194         assertRunOnServiceThread();
1195         return mService.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1196     }
1197 
hasSystemAudioDevice()1198     boolean hasSystemAudioDevice() {
1199         return getSafeAvrDeviceInfo() != null;
1200     }
1201 
1202     @Nullable
getSafeAvrDeviceInfo()1203     HdmiDeviceInfo getSafeAvrDeviceInfo() {
1204         return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1205     }
1206 
1207     /**
1208      * Returns the audio output device used for System Audio Mode.
1209      */
getSystemAudioOutputDevice()1210     AudioDeviceAttributes getSystemAudioOutputDevice() {
1211         return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
1212     }
1213 
1214 
1215     @ServiceThreadOnly
handleRemoveActiveRoutingPath(int path)1216     void handleRemoveActiveRoutingPath(int path) {
1217         assertRunOnServiceThread();
1218         // Seq #23
1219         if (isTailOfActivePath(path, getActivePath())) {
1220             int newPath = mService.portIdToPath(getActivePortId());
1221             startRoutingControl(getActivePath(), newPath, null);
1222         }
1223     }
1224 
1225     /**
1226      * Launch routing control process.
1227      *
1228      * @param routingForBootup true if routing control is initiated due to One Touch Play
1229      *        or TV power on
1230      */
1231     @ServiceThreadOnly
launchRoutingControl(boolean routingForBootup)1232     void launchRoutingControl(boolean routingForBootup) {
1233         assertRunOnServiceThread();
1234         // Seq #24
1235         if (getActivePortId() != Constants.INVALID_PORT_ID) {
1236             if (!routingForBootup && !isProhibitMode()) {
1237                 int newPath = mService.portIdToPath(getActivePortId());
1238                 setActivePath(newPath);
1239                 startRoutingControl(getActivePath(), newPath, null);
1240             }
1241         } else {
1242             int activePath = mService.getPhysicalAddress();
1243             setActivePath(activePath);
1244             if (!routingForBootup
1245                     && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
1246                 mService.sendCecCommand(
1247                         HdmiCecMessageBuilder.buildActiveSource(
1248                                 getDeviceInfo().getLogicalAddress(), activePath));
1249             }
1250         }
1251     }
1252 
1253     @Override
1254     @ServiceThreadOnly
onHotplug(int portId, boolean connected)1255     void onHotplug(int portId, boolean connected) {
1256         assertRunOnServiceThread();
1257 
1258         if (!connected) {
1259             mService.getHdmiCecNetwork().removeCecSwitches(portId);
1260         }
1261 
1262         // Turning System Audio Mode off when the AVR is unlugged or standby.
1263         // When the device is not unplugged but reawaken from standby, we check if the System
1264         // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly.
1265         if (getAvrDeviceInfo() != null && portId == getAvrDeviceInfo().getPortId()) {
1266             HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
1267             if (!connected) {
1268                 setSystemAudioMode(false);
1269             } else {
1270                 onNewAvrAdded(getAvrDeviceInfo());
1271             }
1272         }
1273 
1274         // Tv device will have permanent HotplugDetectionAction.
1275         List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1276         if (!hotplugActions.isEmpty()) {
1277             // Note that hotplug action is single action running on a machine.
1278             // "pollAllDevicesNow" cleans up timer and start poll action immediately.
1279             // It covers seq #40, #43.
1280             hotplugActions.get(0).pollAllDevicesNow();
1281         }
1282     }
1283 
1284     @ServiceThreadOnly
getAutoWakeup()1285     boolean getAutoWakeup() {
1286         assertRunOnServiceThread();
1287         return mService.getHdmiCecConfig().getIntValue(
1288                   HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY)
1289                     == HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED;
1290     }
1291 
1292     @Override
1293     @ServiceThreadOnly
disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)1294     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1295         assertRunOnServiceThread();
1296         mService.unregisterTvInputCallback(mTvInputCallback);
1297         // Remove any repeated working actions.
1298         // HotplugDetectionAction will be reinstated during the wake up process.
1299         // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1300         //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
1301         removeAction(DeviceDiscoveryAction.class);
1302         removeAction(HotplugDetectionAction.class);
1303         removeAction(PowerStatusMonitorAction.class);
1304         // Remove recording actions.
1305         removeAction(OneTouchRecordAction.class);
1306         removeAction(TimerRecordingAction.class);
1307         removeAction(NewDeviceAction.class);
1308         removeAction(AbsoluteVolumeAudioStatusAction.class);
1309 
1310         disableSystemAudioIfExist();
1311         disableArcIfExist();
1312 
1313         super.disableDevice(initiatedByCec, callback);
1314         clearDeviceInfoList();
1315         getActiveSource().invalidate();
1316         setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
1317         checkIfPendingActionsCleared();
1318     }
1319 
1320     @ServiceThreadOnly
disableSystemAudioIfExist()1321     private void disableSystemAudioIfExist() {
1322         assertRunOnServiceThread();
1323         if (getAvrDeviceInfo() == null) {
1324             return;
1325         }
1326 
1327         // Seq #31.
1328         removeAction(SystemAudioActionFromAvr.class);
1329         removeAction(SystemAudioActionFromTv.class);
1330         removeAction(SystemAudioAutoInitiationAction.class);
1331         removeAction(VolumeControlAction.class);
1332 
1333         if (!mService.isControlEnabled()) {
1334             setSystemAudioMode(false);
1335         }
1336     }
1337 
1338     @ServiceThreadOnly
disableArcIfExist()1339     private void disableArcIfExist() {
1340         assertRunOnServiceThread();
1341         HdmiDeviceInfo avr = getAvrDeviceInfo();
1342         if (avr == null) {
1343             return;
1344         }
1345         disableArc();
1346 
1347         // Seq #44.
1348         removeAllRunningArcAction();
1349         if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
1350             addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1351         }
1352     }
1353 
1354     @ServiceThreadOnly
removeAllRunningArcAction()1355     private void removeAllRunningArcAction() {
1356         // Running or pending actions make TV fail to broadcast <Standby> to connected devices
1357         removeAction(RequestArcTerminationAction.class);
1358         removeAction(RequestArcInitiationAction.class);
1359         removeAction(SetArcTransmissionStateAction.class);
1360     }
1361 
1362     @Override
1363     @ServiceThreadOnly
onStandby(boolean initiatedByCec, int standbyAction)1364     protected void onStandby(boolean initiatedByCec, int standbyAction) {
1365         assertRunOnServiceThread();
1366         // Seq #11
1367         if (!mService.isControlEnabled()) {
1368             return;
1369         }
1370         boolean sendStandbyOnSleep =
1371                 mService.getHdmiCecConfig().getIntValue(
1372                     HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
1373                         == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;
1374         if (!initiatedByCec && sendStandbyOnSleep) {
1375             mService.sendCecCommand(
1376                     HdmiCecMessageBuilder.buildStandby(
1377                             getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST));
1378         }
1379     }
1380 
isProhibitMode()1381     boolean isProhibitMode() {
1382         return mService.isProhibitMode();
1383     }
1384 
isPowerStandbyOrTransient()1385     boolean isPowerStandbyOrTransient() {
1386         return mService.isPowerStandbyOrTransient();
1387     }
1388 
1389     @ServiceThreadOnly
displayOsd(int messageId)1390     void displayOsd(int messageId) {
1391         assertRunOnServiceThread();
1392         mService.displayOsd(messageId);
1393     }
1394 
1395     @ServiceThreadOnly
displayOsd(int messageId, int extra)1396     void displayOsd(int messageId, int extra) {
1397         assertRunOnServiceThread();
1398         mService.displayOsd(messageId, extra);
1399     }
1400 
1401     // Seq #54 and #55
1402     @ServiceThreadOnly
1403     @Constants.HandleMessageResult
startOneTouchRecord(int recorderAddress, byte[] recordSource)1404     int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1405         assertRunOnServiceThread();
1406         if (!mService.isControlEnabled()) {
1407             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1408             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1409             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1410         }
1411 
1412         if (!checkRecorder(recorderAddress)) {
1413             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1414             announceOneTouchRecordResult(recorderAddress,
1415                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1416             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1417         }
1418 
1419         if (!checkRecordSource(recordSource)) {
1420             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1421             announceOneTouchRecordResult(recorderAddress,
1422                     ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
1423             return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1424         }
1425 
1426         addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1427         Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1428                 + Arrays.toString(recordSource));
1429         return Constants.HANDLED;
1430     }
1431 
1432     @ServiceThreadOnly
stopOneTouchRecord(int recorderAddress)1433     void stopOneTouchRecord(int recorderAddress) {
1434         assertRunOnServiceThread();
1435         if (!mService.isControlEnabled()) {
1436             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
1437             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1438             return;
1439         }
1440 
1441         if (!checkRecorder(recorderAddress)) {
1442             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1443             announceOneTouchRecordResult(recorderAddress,
1444                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1445             return;
1446         }
1447 
1448         // Remove one touch record action so that other one touch record can be started.
1449         removeAction(OneTouchRecordAction.class);
1450         mService.sendCecCommand(
1451                 HdmiCecMessageBuilder.buildRecordOff(
1452                         getDeviceInfo().getLogicalAddress(), recorderAddress));
1453         Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1454     }
1455 
checkRecorder(int recorderAddress)1456     private boolean checkRecorder(int recorderAddress) {
1457         HdmiDeviceInfo device = mService.getHdmiCecNetwork().getCecDeviceInfo(recorderAddress);
1458         return (device != null) && (HdmiUtils.isEligibleAddressForDevice(
1459                 HdmiDeviceInfo.DEVICE_RECORDER, recorderAddress));
1460     }
1461 
checkRecordSource(byte[] recordSource)1462     private boolean checkRecordSource(byte[] recordSource) {
1463         return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1464     }
1465 
1466     @ServiceThreadOnly
startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1467     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1468         assertRunOnServiceThread();
1469         if (!mService.isControlEnabled()) {
1470             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1471             announceTimerRecordingResult(recorderAddress,
1472                     TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
1473             return;
1474         }
1475 
1476         if (!checkRecorder(recorderAddress)) {
1477             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1478             announceTimerRecordingResult(recorderAddress,
1479                     TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
1480             return;
1481         }
1482 
1483         if (!checkTimerRecordingSource(sourceType, recordSource)) {
1484             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1485             announceTimerRecordingResult(
1486                     recorderAddress,
1487                     TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
1488             return;
1489         }
1490 
1491         addAndStartAction(
1492                 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
1493         Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
1494                 + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
1495     }
1496 
checkTimerRecordingSource(int sourceType, byte[] recordSource)1497     private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
1498         return (recordSource != null)
1499                 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
1500     }
1501 
1502     @ServiceThreadOnly
clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1503     void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1504         assertRunOnServiceThread();
1505         if (!mService.isControlEnabled()) {
1506             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1507             announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
1508             return;
1509         }
1510 
1511         if (!checkRecorder(recorderAddress)) {
1512             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1513             announceClearTimerRecordingResult(recorderAddress,
1514                     CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
1515             return;
1516         }
1517 
1518         if (!checkTimerRecordingSource(sourceType, recordSource)) {
1519             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1520             announceClearTimerRecordingResult(recorderAddress,
1521                     CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1522             return;
1523         }
1524 
1525         sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1526     }
1527 
sendClearTimerMessage(final int recorderAddress, int sourceType, byte[] recordSource)1528     private void sendClearTimerMessage(final int recorderAddress, int sourceType,
1529             byte[] recordSource) {
1530         HdmiCecMessage message = null;
1531         switch (sourceType) {
1532             case TIMER_RECORDING_TYPE_DIGITAL:
1533                 message =
1534                         HdmiCecMessageBuilder.buildClearDigitalTimer(
1535                                 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource);
1536                 break;
1537             case TIMER_RECORDING_TYPE_ANALOGUE:
1538                 message =
1539                         HdmiCecMessageBuilder.buildClearAnalogueTimer(
1540                                 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource);
1541                 break;
1542             case TIMER_RECORDING_TYPE_EXTERNAL:
1543                 message =
1544                         HdmiCecMessageBuilder.buildClearExternalTimer(
1545                                 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource);
1546                 break;
1547             default:
1548                 Slog.w(TAG, "Invalid source type:" + recorderAddress);
1549                 announceClearTimerRecordingResult(recorderAddress,
1550                         CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1551                 return;
1552 
1553         }
1554         mService.sendCecCommand(message, new SendMessageCallback() {
1555             @Override
1556             public void onSendCompleted(int error) {
1557                 if (error != SendMessageResult.SUCCESS) {
1558                     announceClearTimerRecordingResult(recorderAddress,
1559                             CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1560                 }
1561             }
1562         });
1563     }
1564 
1565     @Override
1566     @Constants.HandleMessageResult
handleMenuStatus(HdmiCecMessage message)1567     protected int handleMenuStatus(HdmiCecMessage message) {
1568         // Do nothing and just return true not to prevent from responding <Feature Abort>.
1569         return Constants.HANDLED;
1570     }
1571 
1572     @Constants.RcProfile
1573     @Override
getRcProfile()1574     protected int getRcProfile() {
1575         return Constants.RC_PROFILE_TV;
1576     }
1577 
1578     @Override
getRcFeatures()1579     protected List<Integer> getRcFeatures() {
1580         List<Integer> features = new ArrayList<>();
1581         @HdmiControlManager.RcProfileTv int profile = mService.getHdmiCecConfig().getIntValue(
1582                         HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV);
1583         features.add(profile);
1584         return features;
1585     }
1586 
1587     @Override
computeDeviceFeatures()1588     protected DeviceFeatures computeDeviceFeatures() {
1589         boolean hasArcPort = false;
1590         List<HdmiPortInfo> ports = mService.getPortInfo();
1591         for (HdmiPortInfo port : ports) {
1592             if (isArcFeatureEnabled(port.getId())) {
1593                 hasArcPort = true;
1594                 break;
1595             }
1596         }
1597 
1598         return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
1599                 .setRecordTvScreenSupport(FEATURE_SUPPORTED)
1600                 .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
1601                 .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)
1602                 .build();
1603     }
1604 
1605     @Override
sendStandby(int deviceId)1606     protected void sendStandby(int deviceId) {
1607         HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(deviceId);
1608         if (targetDevice == null) {
1609             return;
1610         }
1611         int targetAddress = targetDevice.getLogicalAddress();
1612         mService.sendCecCommand(
1613                 HdmiCecMessageBuilder.buildStandby(
1614                         getDeviceInfo().getLogicalAddress(), targetAddress));
1615     }
1616 
1617     @ServiceThreadOnly
processAllDelayedMessages()1618     void processAllDelayedMessages() {
1619         assertRunOnServiceThread();
1620         mDelayedMessageBuffer.processAllMessages();
1621     }
1622 
1623     @ServiceThreadOnly
processDelayedMessages(int address)1624     void processDelayedMessages(int address) {
1625         assertRunOnServiceThread();
1626         mDelayedMessageBuffer.processMessagesForDevice(address);
1627     }
1628 
1629     @ServiceThreadOnly
processDelayedActiveSource(int address)1630     void processDelayedActiveSource(int address) {
1631         assertRunOnServiceThread();
1632         mDelayedMessageBuffer.processActiveSource(address);
1633     }
1634 
1635     @Override
dump(final IndentingPrintWriter pw)1636     protected void dump(final IndentingPrintWriter pw) {
1637         super.dump(pw);
1638         pw.println("mArcEstablished: " + mArcEstablished);
1639         pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1640         pw.println("mSystemAudioMute: " + mSystemAudioMute);
1641         pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
1642         pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
1643         pw.println("mPrevPortId: " + mPrevPortId);
1644     }
1645 }
1646