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 public 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.isCecControlEnabled()) {
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.isCecControlEnabled()
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.isCecControlEnabled()) {
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         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
686                 new DeviceDiscoveryCallback() {
687                     @Override
688                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
689                         for (HdmiDeviceInfo info : deviceInfos) {
690                             mService.getHdmiCecNetwork().addCecDevice(info);
691                         }
692 
693                         mSelectRequestBuffer.process();
694                         resetSelectRequestBuffer();
695 
696                         List<HotplugDetectionAction> hotplugActions
697                                 = getActions(HotplugDetectionAction.class);
698                         if (hotplugActions.isEmpty()) {
699                             addAndStartAction(
700                                     new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
701                         }
702 
703                         List<PowerStatusMonitorAction> powerStatusActions
704                                 = getActions(PowerStatusMonitorAction.class);
705                         if (powerStatusActions.isEmpty()) {
706                             addAndStartAction(
707                                     new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
708                         }
709 
710                         HdmiDeviceInfo avr = getAvrDeviceInfo();
711                         if (avr != null) {
712                             onNewAvrAdded(avr);
713                         } else {
714                             setSystemAudioMode(false);
715                         }
716                     }
717                 });
718         addAndStartAction(action);
719     }
720 
721     @ServiceThreadOnly
onNewAvrAdded(HdmiDeviceInfo avr)722     void onNewAvrAdded(HdmiDeviceInfo avr) {
723         assertRunOnServiceThread();
724         addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
725         if (!isDirectConnectAddress(avr.getPhysicalAddress())) {
726             startArcAction(false);
727         } else if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
728                 && !hasAction(SetArcTransmissionStateAction.class)) {
729             startArcAction(true);
730         }
731     }
732 
733     @ServiceThreadOnly
734     // Seq #32
changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback)735     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
736         assertRunOnServiceThread();
737         if (!mService.isCecControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
738             setSystemAudioMode(false);
739             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
740             return;
741         }
742         HdmiDeviceInfo avr = getAvrDeviceInfo();
743         if (avr == null) {
744             setSystemAudioMode(false);
745             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
746             return;
747         }
748 
749         addAndStartAction(
750                 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
751     }
752 
753     // # Seq 25
setSystemAudioMode(boolean on)754     void setSystemAudioMode(boolean on) {
755         if (!isSystemAudioControlFeatureEnabled() && on) {
756             HdmiLogger.debug("Cannot turn on system audio mode "
757                     + "because the System Audio Control feature is disabled.");
758             return;
759         }
760         HdmiLogger.debug("System Audio Mode change[old:%b new:%b]",
761                 mService.isSystemAudioActivated(), on);
762         updateAudioManagerForSystemAudio(on);
763         synchronized (mLock) {
764             if (mService.isSystemAudioActivated() != on) {
765                 mService.setSystemAudioActivated(on);
766                 mService.announceSystemAudioModeChange(on);
767             }
768             if (on && !mArcEstablished) {
769                 startArcAction(true);
770             } else if (!on) {
771                 startArcAction(false);
772             }
773         }
774     }
775 
updateAudioManagerForSystemAudio(boolean on)776     private void updateAudioManagerForSystemAudio(boolean on) {
777         int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
778         HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
779     }
780 
isSystemAudioActivated()781     boolean isSystemAudioActivated() {
782         if (!hasSystemAudioDevice()) {
783             return false;
784         }
785         return mService.isSystemAudioActivated();
786     }
787 
788     @ServiceThreadOnly
setSystemAudioControlFeatureEnabled(boolean enabled)789     void setSystemAudioControlFeatureEnabled(boolean enabled) {
790         assertRunOnServiceThread();
791         synchronized (mLock) {
792             mSystemAudioControlFeatureEnabled = enabled;
793         }
794         if (hasSystemAudioDevice()) {
795             changeSystemAudioMode(enabled, null);
796         }
797     }
798 
isSystemAudioControlFeatureEnabled()799     boolean isSystemAudioControlFeatureEnabled() {
800         synchronized (mLock) {
801             return mSystemAudioControlFeatureEnabled;
802         }
803     }
804 
805     @ServiceThreadOnly
enableArc(List<byte[]> supportedSads)806     void enableArc(List<byte[]> supportedSads) {
807         assertRunOnServiceThread();
808         HdmiLogger.debug("Set Arc Status[old:%b new:true]", mArcEstablished);
809 
810         enableAudioReturnChannel(true);
811         notifyArcStatusToAudioService(true, supportedSads);
812         mArcEstablished = true;
813     }
814 
815     @ServiceThreadOnly
disableArc()816     void disableArc() {
817         assertRunOnServiceThread();
818         HdmiLogger.debug("Set Arc Status[old:%b new:false]", mArcEstablished);
819 
820         enableAudioReturnChannel(false);
821         notifyArcStatusToAudioService(false, new ArrayList<>());
822         mArcEstablished = false;
823     }
824 
825     /**
826      * Switch hardware ARC circuit in the system.
827      */
828     @ServiceThreadOnly
enableAudioReturnChannel(boolean enabled)829     void enableAudioReturnChannel(boolean enabled) {
830         assertRunOnServiceThread();
831         HdmiDeviceInfo avr = getAvrDeviceInfo();
832         if (avr != null && avr.getPortId() != Constants.INVALID_PORT_ID) {
833             mService.enableAudioReturnChannel(avr.getPortId(), enabled);
834         }
835     }
836 
837     @ServiceThreadOnly
isConnected(int portId)838     boolean isConnected(int portId) {
839         assertRunOnServiceThread();
840         return mService.isConnected(portId);
841     }
842 
notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads)843     private void notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads) {
844         // Note that we don't set any name to ARC.
845         AudioDeviceAttributes attributes = new AudioDeviceAttributes(
846                 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_ARC, "", "",
847                 new ArrayList<AudioProfile>(), supportedSads.stream()
848                 .map(sad -> new AudioDescriptor(AudioDescriptor.STANDARD_EDID,
849                         AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE, sad))
850                 .collect(Collectors.toList()));
851         mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0);
852     }
853 
854     /**
855      * Returns true if ARC is currently established on a certain port.
856      */
857     @ServiceThreadOnly
isArcEstablished()858     boolean isArcEstablished() {
859         assertRunOnServiceThread();
860         if (mArcEstablished) {
861             for (int i = 0; i < mArcFeatureEnabled.size(); i++) {
862                 if (mArcFeatureEnabled.valueAt(i)) return true;
863             }
864         }
865         return false;
866     }
867 
868     @ServiceThreadOnly
changeArcFeatureEnabled(int portId, boolean enabled)869     void changeArcFeatureEnabled(int portId, boolean enabled) {
870         assertRunOnServiceThread();
871         if (mArcFeatureEnabled.get(portId) == enabled) {
872             return;
873         }
874         mArcFeatureEnabled.put(portId, enabled);
875         HdmiDeviceInfo avr = getAvrDeviceInfo();
876         if (avr == null || avr.getPortId() != portId) {
877             return;
878         }
879         if (enabled && !mArcEstablished) {
880             startArcAction(true);
881         } else if (!enabled && mArcEstablished) {
882             startArcAction(false);
883         }
884     }
885 
886     @ServiceThreadOnly
isArcFeatureEnabled(int portId)887     boolean isArcFeatureEnabled(int portId) {
888         assertRunOnServiceThread();
889         return mArcFeatureEnabled.get(portId);
890     }
891 
892     @ServiceThreadOnly
startArcAction(boolean enabled)893     void startArcAction(boolean enabled) {
894         startArcAction(enabled, null);
895     }
896 
897     @ServiceThreadOnly
startArcAction(boolean enabled, IHdmiControlCallback callback)898     void startArcAction(boolean enabled, IHdmiControlCallback callback) {
899         assertRunOnServiceThread();
900         HdmiDeviceInfo info = getAvrDeviceInfo();
901         if (info == null) {
902             Slog.w(TAG, "Failed to start arc action; No AVR device.");
903             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
904             return;
905         }
906         if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
907             Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
908             if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
909                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
910             }
911             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
912             return;
913         }
914         if (enabled && mService.earcBlocksArcConnection()) {
915             Slog.i(TAG,
916                     "ARC connection blocked because eARC connection is established or being "
917                             + "established.");
918             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
919             return;
920         }
921 
922         // Terminate opposite action and create an action with callback.
923         if (enabled) {
924             removeAction(RequestArcTerminationAction.class);
925             if (hasAction(RequestArcInitiationAction.class)) {
926                 RequestArcInitiationAction existingInitiationAction =
927                         getActions(RequestArcInitiationAction.class).get(0);
928                 existingInitiationAction.addCallback(callback);
929             } else {
930                 addAndStartAction(
931                         new RequestArcInitiationAction(this, info.getLogicalAddress(), callback));
932             }
933         } else {
934             removeAction(RequestArcInitiationAction.class);
935             if (hasAction(RequestArcTerminationAction.class)) {
936                 RequestArcTerminationAction existingTerminationAction =
937                         getActions(RequestArcTerminationAction.class).get(0);
938                 existingTerminationAction.addCallback(callback);
939             } else {
940                 addAndStartAction(
941                         new RequestArcTerminationAction(this, info.getLogicalAddress(), callback));
942             }
943         }
944     }
945 
isDirectConnectAddress(int physicalAddress)946     private boolean isDirectConnectAddress(int physicalAddress) {
947         return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
948     }
949 
setAudioStatus(boolean mute, int volume)950     void setAudioStatus(boolean mute, int volume) {
951         if (!isSystemAudioActivated() || mService.getHdmiCecVolumeControl()
952                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
953             return;
954         }
955         synchronized (mLock) {
956             mSystemAudioMute = mute;
957             mSystemAudioVolume = volume;
958             displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
959                     mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
960         }
961     }
962 
963     @ServiceThreadOnly
changeVolume(int curVolume, int delta, int maxVolume)964     void changeVolume(int curVolume, int delta, int maxVolume) {
965         assertRunOnServiceThread();
966         if (getAvrDeviceInfo() == null) {
967             // On initialization process, getAvrDeviceInfo() may return null and cause exception
968             return;
969         }
970         if (delta == 0 || !isSystemAudioActivated() || mService.getHdmiCecVolumeControl()
971                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
972             return;
973         }
974 
975         int targetVolume = curVolume + delta;
976         int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
977         synchronized (mLock) {
978             // If new volume is the same as current system audio volume, just ignore it.
979             // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
980             if (cecVolume == mSystemAudioVolume) {
981                 // Update tv volume with system volume value.
982                 mService.setAudioStatus(false,
983                         VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
984                 return;
985             }
986         }
987 
988         List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
989         if (actions.isEmpty()) {
990             addAndStartAction(new VolumeControlAction(this,
991                     getAvrDeviceInfo().getLogicalAddress(), delta > 0));
992         } else {
993             actions.get(0).handleVolumeChange(delta > 0);
994         }
995     }
996 
997     @ServiceThreadOnly
changeMute(boolean mute)998     void changeMute(boolean mute) {
999         assertRunOnServiceThread();
1000         if (getAvrDeviceInfo() == null || mService.getHdmiCecVolumeControl()
1001                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
1002             // On initialization process, getAvrDeviceInfo() may return null and cause exception
1003             return;
1004         }
1005         HdmiLogger.debug("[A]:Change mute:%b", mute);
1006         synchronized (mLock) {
1007             if (mSystemAudioMute == mute) {
1008                 HdmiLogger.debug("No need to change mute.");
1009                 return;
1010             }
1011         }
1012         if (!isSystemAudioActivated()) {
1013             HdmiLogger.debug("[A]:System audio is not activated.");
1014             return;
1015         }
1016 
1017         // Remove existing volume action.
1018         removeAction(VolumeControlAction.class);
1019         sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
1020                 HdmiCecKeycode.getMuteKey(mute));
1021     }
1022 
1023     @Override
1024     @ServiceThreadOnly
1025     @Constants.HandleMessageResult
handleInitiateArc(HdmiCecMessage message)1026     protected int handleInitiateArc(HdmiCecMessage message) {
1027         assertRunOnServiceThread();
1028 
1029         if (mService.earcBlocksArcConnection()) {
1030             Slog.i(TAG,
1031                     "ARC connection blocked because eARC connection is established or being "
1032                             + "established.");
1033             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1034         }
1035 
1036         if (!canStartArcUpdateAction(message.getSource(), true)) {
1037             HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo();
1038             if (avrDeviceInfo == null) {
1039                 // AVR may not have been discovered yet. Delay the message processing.
1040                 mDelayedMessageBuffer.add(message);
1041                 return Constants.HANDLED;
1042             }
1043             if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) {
1044                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
1045             }
1046             return Constants.ABORT_REFUSED;
1047         }
1048 
1049         // In case where <Initiate Arc> is started by <Request ARC Initiation>, this message is
1050         // handled in RequestArcInitiationAction as well.
1051         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1052                 message.getSource(), true);
1053         addAndStartAction(action);
1054         return Constants.HANDLED;
1055     }
1056 
canStartArcUpdateAction(int avrAddress, boolean enabled)1057     private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
1058         HdmiDeviceInfo avr = getAvrDeviceInfo();
1059         if (avr != null
1060                 && (avrAddress == avr.getLogicalAddress())
1061                 && isConnectedToArcPort(avr.getPhysicalAddress())) {
1062             if (enabled) {
1063                 return isConnected(avr.getPortId())
1064                     && isArcFeatureEnabled(avr.getPortId())
1065                     && isDirectConnectAddress(avr.getPhysicalAddress());
1066             } else {
1067                 return true;
1068             }
1069         } else {
1070             return false;
1071         }
1072     }
1073 
1074     @Override
1075     @ServiceThreadOnly
1076     @Constants.HandleMessageResult
handleTerminateArc(HdmiCecMessage message)1077     protected int handleTerminateArc(HdmiCecMessage message) {
1078         assertRunOnServiceThread();
1079         if (mService .isPowerStandbyOrTransient()) {
1080             disableArc();
1081             return Constants.HANDLED;
1082         }
1083         // Do not check ARC configuration since the AVR might have been already removed.
1084         // In case where <Terminate Arc> is started by <Request ARC Termination>, this
1085         // message is handled in RequestArcTerminationAction as well.
1086         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1087                 message.getSource(), false);
1088         addAndStartAction(action);
1089         return Constants.HANDLED;
1090     }
1091 
1092     @Override
1093     @ServiceThreadOnly
1094     @Constants.HandleMessageResult
handleSetSystemAudioMode(HdmiCecMessage message)1095     protected int handleSetSystemAudioMode(HdmiCecMessage message) {
1096         assertRunOnServiceThread();
1097         boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message);
1098         if (!isMessageForSystemAudio(message)) {
1099             if (getAvrDeviceInfo() == null) {
1100                 // AVR may not have been discovered yet. Delay the message processing.
1101                 mDelayedMessageBuffer.add(message);
1102             } else {
1103                 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
1104                 return Constants.ABORT_REFUSED;
1105             }
1106         } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) {
1107             HdmiLogger.debug("Ignoring <Set System Audio Mode> message "
1108                     + "because the System Audio Control feature is disabled: %s", message);
1109             return Constants.ABORT_REFUSED;
1110         }
1111         removeAction(SystemAudioAutoInitiationAction.class);
1112         SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
1113                 message.getSource(), systemAudioStatus, null);
1114         addAndStartAction(action);
1115         return Constants.HANDLED;
1116     }
1117 
1118     @Override
1119     @ServiceThreadOnly
1120     @Constants.HandleMessageResult
handleSystemAudioModeStatus(HdmiCecMessage message)1121     protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
1122         assertRunOnServiceThread();
1123         if (!isMessageForSystemAudio(message)) {
1124             HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
1125             // Ignore this message.
1126             return Constants.HANDLED;
1127         }
1128         boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled();
1129         boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message);
1130         // Set System Audio Mode according to TV's settings.
1131         // Handle <System Audio Mode Status> here only when
1132         // SystemAudioAutoInitiationAction timeout
1133         HdmiDeviceInfo avr = getAvrDeviceInfo();
1134         if (avr == null) {
1135             setSystemAudioMode(false);
1136         } else if (avrSystemAudioMode != tvSystemAudioMode) {
1137             addAndStartAction(new SystemAudioActionFromTv(this, avr.getLogicalAddress(),
1138                     tvSystemAudioMode, null));
1139         } else {
1140             setSystemAudioMode(tvSystemAudioMode);
1141         }
1142 
1143         return Constants.HANDLED;
1144     }
1145 
1146     // Seq #53
1147     @Override
1148     @ServiceThreadOnly
1149     @Constants.HandleMessageResult
handleRecordTvScreen(HdmiCecMessage message)1150     protected int handleRecordTvScreen(HdmiCecMessage message) {
1151         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
1152         if (!actions.isEmpty()) {
1153             // Assumes only one OneTouchRecordAction.
1154             OneTouchRecordAction action = actions.get(0);
1155             if (action.getRecorderAddress() != message.getSource()) {
1156                 announceOneTouchRecordResult(
1157                         message.getSource(),
1158                         HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
1159             }
1160             // The default behavior of <Record TV Screen> is replying <Feature Abort> with
1161             // "Cannot provide source".
1162             return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1163         }
1164 
1165         int recorderAddress = message.getSource();
1166         byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
1167         return startOneTouchRecord(recorderAddress, recordSource);
1168     }
1169 
1170     @Override
1171     @Constants.HandleMessageResult
handleTimerClearedStatus(HdmiCecMessage message)1172     protected int handleTimerClearedStatus(HdmiCecMessage message) {
1173         byte[] params = message.getParams();
1174         int timerClearedStatusData = params[0] & 0xFF;
1175         announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
1176         return Constants.HANDLED;
1177     }
1178 
1179     @Override
1180     @Constants.HandleMessageResult
handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message)1181     protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
1182         // <Set Audio Volume Level> should only be sent to the System Audio device, so we don't
1183         // handle it when System Audio Mode is enabled.
1184         if (mService.isSystemAudioActivated()) {
1185             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1186         } else {
1187             mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0);
1188             return Constants.HANDLED;
1189         }
1190     }
1191 
announceOneTouchRecordResult(int recorderAddress, int result)1192     void announceOneTouchRecordResult(int recorderAddress, int result) {
1193         mService.invokeOneTouchRecordResult(recorderAddress, result);
1194     }
1195 
announceTimerRecordingResult(int recorderAddress, int result)1196     void announceTimerRecordingResult(int recorderAddress, int result) {
1197         mService.invokeTimerRecordingResult(recorderAddress, result);
1198     }
1199 
announceClearTimerRecordingResult(int recorderAddress, int result)1200     void announceClearTimerRecordingResult(int recorderAddress, int result) {
1201         mService.invokeClearTimerRecordingResult(recorderAddress, result);
1202     }
1203 
isMessageForSystemAudio(HdmiCecMessage message)1204     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
1205         return mService.isCecControlEnabled()
1206                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1207                 && (message.getDestination() == Constants.ADDR_TV
1208                         || message.getDestination() == Constants.ADDR_BROADCAST)
1209                 && getAvrDeviceInfo() != null;
1210     }
1211 
1212     @Nullable
1213     @ServiceThreadOnly
getAvrDeviceInfo()1214     HdmiDeviceInfo getAvrDeviceInfo() {
1215         assertRunOnServiceThread();
1216         return mService.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1217     }
1218 
hasSystemAudioDevice()1219     boolean hasSystemAudioDevice() {
1220         return getSafeAvrDeviceInfo() != null;
1221     }
1222 
1223     @Nullable
getSafeAvrDeviceInfo()1224     HdmiDeviceInfo getSafeAvrDeviceInfo() {
1225         return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1226     }
1227 
1228     /**
1229      * Returns the audio output device used for System Audio Mode.
1230      */
getSystemAudioOutputDevice()1231     AudioDeviceAttributes getSystemAudioOutputDevice() {
1232         return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
1233     }
1234 
1235 
1236     @ServiceThreadOnly
handleRemoveActiveRoutingPath(int path)1237     void handleRemoveActiveRoutingPath(int path) {
1238         assertRunOnServiceThread();
1239         // Seq #23
1240         if (isTailOfActivePath(path, getActivePath())) {
1241             int newPath = mService.portIdToPath(getActivePortId());
1242             startRoutingControl(getActivePath(), newPath, null);
1243         }
1244     }
1245 
1246     /**
1247      * Launch routing control process.
1248      *
1249      * @param routingForBootup true if routing control is initiated due to One Touch Play
1250      *        or TV power on
1251      */
1252     @ServiceThreadOnly
launchRoutingControl(boolean routingForBootup)1253     void launchRoutingControl(boolean routingForBootup) {
1254         assertRunOnServiceThread();
1255         // Seq #24
1256         if (getActivePortId() != Constants.INVALID_PORT_ID
1257                 && getActivePortId() != Constants.CEC_SWITCH_HOME) {
1258             if (!routingForBootup && !isProhibitMode()) {
1259                 int newPath = mService.portIdToPath(getActivePortId());
1260                 setActivePath(newPath);
1261                 startRoutingControl(getActivePath(), newPath, null);
1262             }
1263         } else {
1264             int activePath = mService.getPhysicalAddress();
1265             setActivePath(activePath);
1266             if (!routingForBootup
1267                     && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
1268                 mService.sendCecCommand(
1269                         HdmiCecMessageBuilder.buildActiveSource(
1270                                 getDeviceInfo().getLogicalAddress(), activePath));
1271             }
1272         }
1273     }
1274 
1275     @Override
1276     @ServiceThreadOnly
onHotplug(int portId, boolean connected)1277     void onHotplug(int portId, boolean connected) {
1278         assertRunOnServiceThread();
1279 
1280         if (!connected) {
1281             mService.getHdmiCecNetwork().removeCecSwitches(portId);
1282         }
1283 
1284         // Turning System Audio Mode off when the AVR is unlugged or standby.
1285         // When the device is not unplugged but reawaken from standby, we check if the System
1286         // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly.
1287         if (getAvrDeviceInfo() != null && portId == getAvrDeviceInfo().getPortId()) {
1288             HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
1289             if (!connected) {
1290                 setSystemAudioMode(false);
1291             } else {
1292                 onNewAvrAdded(getAvrDeviceInfo());
1293             }
1294         }
1295 
1296         // Tv device will have permanent HotplugDetectionAction.
1297         List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1298         if (!hotplugActions.isEmpty()) {
1299             // Note that hotplug action is single action running on a machine.
1300             // "pollAllDevicesNow" cleans up timer and start poll action immediately.
1301             // It covers seq #40, #43.
1302             hotplugActions.get(0).pollAllDevicesNow();
1303         }
1304     }
1305 
1306     @ServiceThreadOnly
getAutoWakeup()1307     boolean getAutoWakeup() {
1308         assertRunOnServiceThread();
1309         return mService.getHdmiCecConfig().getIntValue(
1310                   HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY)
1311                     == HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED;
1312     }
1313 
1314     @Override
1315     @ServiceThreadOnly
disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)1316     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1317         assertRunOnServiceThread();
1318         mService.unregisterTvInputCallback(mTvInputCallback);
1319         // Remove any repeated working actions.
1320         // HotplugDetectionAction will be reinstated during the wake up process.
1321         // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1322         //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
1323         removeAction(DeviceDiscoveryAction.class);
1324         removeAction(HotplugDetectionAction.class);
1325         removeAction(PowerStatusMonitorAction.class);
1326         // Remove recording actions.
1327         removeAction(OneTouchRecordAction.class);
1328         removeAction(TimerRecordingAction.class);
1329         removeAction(NewDeviceAction.class);
1330         removeAction(AbsoluteVolumeAudioStatusAction.class);
1331 
1332         disableSystemAudioIfExist();
1333         disableArcIfExist();
1334 
1335         super.disableDevice(initiatedByCec, callback);
1336         clearDeviceInfoList();
1337         getActiveSource().invalidate();
1338         setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
1339         checkIfPendingActionsCleared();
1340     }
1341 
1342     @ServiceThreadOnly
disableSystemAudioIfExist()1343     private void disableSystemAudioIfExist() {
1344         assertRunOnServiceThread();
1345         if (getAvrDeviceInfo() == null) {
1346             return;
1347         }
1348 
1349         // Seq #31.
1350         removeAction(SystemAudioActionFromAvr.class);
1351         removeAction(SystemAudioActionFromTv.class);
1352         removeAction(SystemAudioAutoInitiationAction.class);
1353         removeAction(VolumeControlAction.class);
1354 
1355         if (!mService.isCecControlEnabled()) {
1356             setSystemAudioMode(false);
1357         }
1358     }
1359 
1360     @ServiceThreadOnly
forceDisableArcOnAllPins()1361     private void forceDisableArcOnAllPins() {
1362         List<HdmiPortInfo> ports = mService.getPortInfo();
1363         for (HdmiPortInfo port : ports) {
1364             if (isArcFeatureEnabled(port.getId())) {
1365                 mService.enableAudioReturnChannel(port.getId(), false);
1366             }
1367         }
1368     }
1369 
1370     @ServiceThreadOnly
disableArcIfExist()1371     private void disableArcIfExist() {
1372         assertRunOnServiceThread();
1373         HdmiDeviceInfo avr = getAvrDeviceInfo();
1374         if (avr == null) {
1375             return;
1376         }
1377 
1378         // Seq #44.
1379         removeAllRunningArcAction();
1380         if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
1381             addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1382         }
1383 
1384         // Disable ARC Pin earlier, prevent the case where AVR doesn't send <Terminate ARC> in time
1385         forceDisableArcOnAllPins();
1386     }
1387 
1388     @ServiceThreadOnly
removeAllRunningArcAction()1389     private void removeAllRunningArcAction() {
1390         // Running or pending actions make TV fail to broadcast <Standby> to connected devices
1391         removeAction(RequestArcTerminationAction.class);
1392         removeAction(RequestArcInitiationAction.class);
1393         removeAction(SetArcTransmissionStateAction.class);
1394     }
1395 
1396     @Override
1397     @ServiceThreadOnly
onStandby(boolean initiatedByCec, int standbyAction)1398     protected void onStandby(boolean initiatedByCec, int standbyAction) {
1399         assertRunOnServiceThread();
1400         // Seq #11
1401         if (!mService.isCecControlEnabled()) {
1402             return;
1403         }
1404         boolean sendStandbyOnSleep =
1405                 mService.getHdmiCecConfig().getIntValue(
1406                     HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
1407                         == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;
1408         if (!initiatedByCec && sendStandbyOnSleep) {
1409             mService.sendCecCommand(
1410                     HdmiCecMessageBuilder.buildStandby(
1411                             getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST));
1412         }
1413     }
1414 
isProhibitMode()1415     boolean isProhibitMode() {
1416         return mService.isProhibitMode();
1417     }
1418 
isPowerStandbyOrTransient()1419     boolean isPowerStandbyOrTransient() {
1420         return mService.isPowerStandbyOrTransient();
1421     }
1422 
1423     @ServiceThreadOnly
displayOsd(int messageId)1424     void displayOsd(int messageId) {
1425         assertRunOnServiceThread();
1426         mService.displayOsd(messageId);
1427     }
1428 
1429     @ServiceThreadOnly
displayOsd(int messageId, int extra)1430     void displayOsd(int messageId, int extra) {
1431         assertRunOnServiceThread();
1432         mService.displayOsd(messageId, extra);
1433     }
1434 
1435     // Seq #54 and #55
1436     @ServiceThreadOnly
1437     @Constants.HandleMessageResult
startOneTouchRecord(int recorderAddress, byte[] recordSource)1438     int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1439         assertRunOnServiceThread();
1440         if (!mService.isCecControlEnabled()) {
1441             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1442             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1443             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1444         }
1445 
1446         if (!checkRecorder(recorderAddress)) {
1447             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1448             announceOneTouchRecordResult(recorderAddress,
1449                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1450             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1451         }
1452 
1453         if (!checkRecordSource(recordSource)) {
1454             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1455             announceOneTouchRecordResult(recorderAddress,
1456                     ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
1457             return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1458         }
1459 
1460         addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1461         Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1462                 + Arrays.toString(recordSource));
1463         return Constants.HANDLED;
1464     }
1465 
1466     @ServiceThreadOnly
stopOneTouchRecord(int recorderAddress)1467     void stopOneTouchRecord(int recorderAddress) {
1468         assertRunOnServiceThread();
1469         if (!mService.isCecControlEnabled()) {
1470             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
1471             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1472             return;
1473         }
1474 
1475         if (!checkRecorder(recorderAddress)) {
1476             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1477             announceOneTouchRecordResult(recorderAddress,
1478                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1479             return;
1480         }
1481 
1482         // Remove one touch record action so that other one touch record can be started.
1483         removeAction(OneTouchRecordAction.class);
1484         mService.sendCecCommand(
1485                 HdmiCecMessageBuilder.buildRecordOff(
1486                         getDeviceInfo().getLogicalAddress(), recorderAddress));
1487         Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1488     }
1489 
checkRecorder(int recorderAddress)1490     private boolean checkRecorder(int recorderAddress) {
1491         HdmiDeviceInfo device = mService.getHdmiCecNetwork().getCecDeviceInfo(recorderAddress);
1492         return (device != null) && (HdmiUtils.isEligibleAddressForDevice(
1493                 HdmiDeviceInfo.DEVICE_RECORDER, recorderAddress));
1494     }
1495 
checkRecordSource(byte[] recordSource)1496     private boolean checkRecordSource(byte[] recordSource) {
1497         return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1498     }
1499 
1500     @ServiceThreadOnly
startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1501     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1502         assertRunOnServiceThread();
1503         if (!mService.isCecControlEnabled()) {
1504             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1505             announceTimerRecordingResult(recorderAddress,
1506                     TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
1507             return;
1508         }
1509 
1510         if (!checkRecorder(recorderAddress)) {
1511             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1512             announceTimerRecordingResult(recorderAddress,
1513                     TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
1514             return;
1515         }
1516 
1517         if (!checkTimerRecordingSource(sourceType, recordSource)) {
1518             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1519             announceTimerRecordingResult(
1520                     recorderAddress,
1521                     TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
1522             return;
1523         }
1524 
1525         addAndStartAction(
1526                 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
1527         Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
1528                 + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
1529     }
1530 
checkTimerRecordingSource(int sourceType, byte[] recordSource)1531     private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
1532         return (recordSource != null)
1533                 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
1534     }
1535 
1536     @ServiceThreadOnly
clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1537     void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1538         assertRunOnServiceThread();
1539         if (!mService.isCecControlEnabled()) {
1540             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1541             announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
1542             return;
1543         }
1544 
1545         if (!checkRecorder(recorderAddress)) {
1546             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1547             announceClearTimerRecordingResult(recorderAddress,
1548                     CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
1549             return;
1550         }
1551 
1552         if (!checkTimerRecordingSource(sourceType, recordSource)) {
1553             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1554             announceClearTimerRecordingResult(recorderAddress,
1555                     CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1556             return;
1557         }
1558 
1559         sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1560     }
1561 
sendClearTimerMessage(final int recorderAddress, int sourceType, byte[] recordSource)1562     private void sendClearTimerMessage(final int recorderAddress, int sourceType,
1563             byte[] recordSource) {
1564         HdmiCecMessage message = null;
1565         switch (sourceType) {
1566             case TIMER_RECORDING_TYPE_DIGITAL:
1567                 message =
1568                         HdmiCecMessageBuilder.buildClearDigitalTimer(
1569                                 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource);
1570                 break;
1571             case TIMER_RECORDING_TYPE_ANALOGUE:
1572                 message =
1573                         HdmiCecMessageBuilder.buildClearAnalogueTimer(
1574                                 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource);
1575                 break;
1576             case TIMER_RECORDING_TYPE_EXTERNAL:
1577                 message =
1578                         HdmiCecMessageBuilder.buildClearExternalTimer(
1579                                 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource);
1580                 break;
1581             default:
1582                 Slog.w(TAG, "Invalid source type:" + recorderAddress);
1583                 announceClearTimerRecordingResult(recorderAddress,
1584                         CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1585                 return;
1586 
1587         }
1588         mService.sendCecCommand(message, new SendMessageCallback() {
1589             @Override
1590             public void onSendCompleted(int error) {
1591                 if (error != SendMessageResult.SUCCESS) {
1592                     announceClearTimerRecordingResult(recorderAddress,
1593                             CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1594                 }
1595             }
1596         });
1597     }
1598 
1599     @Override
1600     @Constants.HandleMessageResult
handleMenuStatus(HdmiCecMessage message)1601     protected int handleMenuStatus(HdmiCecMessage message) {
1602         // Do nothing and just return true not to prevent from responding <Feature Abort>.
1603         return Constants.HANDLED;
1604     }
1605 
1606     @Constants.RcProfile
1607     @Override
getRcProfile()1608     protected int getRcProfile() {
1609         return Constants.RC_PROFILE_TV;
1610     }
1611 
1612     @Override
getRcFeatures()1613     protected List<Integer> getRcFeatures() {
1614         List<Integer> features = new ArrayList<>();
1615         @HdmiControlManager.RcProfileTv int profile = mService.getHdmiCecConfig().getIntValue(
1616                         HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV);
1617         features.add(profile);
1618         return features;
1619     }
1620 
1621     @Override
computeDeviceFeatures()1622     protected DeviceFeatures computeDeviceFeatures() {
1623         boolean hasArcPort = false;
1624         List<HdmiPortInfo> ports = mService.getPortInfo();
1625         for (HdmiPortInfo port : ports) {
1626             if (isArcFeatureEnabled(port.getId())) {
1627                 hasArcPort = true;
1628                 break;
1629             }
1630         }
1631 
1632         return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
1633                 .setRecordTvScreenSupport(FEATURE_SUPPORTED)
1634                 .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
1635                 .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)
1636                 .build();
1637     }
1638 
1639     @Override
sendStandby(int deviceId)1640     protected void sendStandby(int deviceId) {
1641         HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(deviceId);
1642         if (targetDevice == null) {
1643             return;
1644         }
1645         int targetAddress = targetDevice.getLogicalAddress();
1646         mService.sendCecCommand(
1647                 HdmiCecMessageBuilder.buildStandby(
1648                         getDeviceInfo().getLogicalAddress(), targetAddress));
1649     }
1650 
1651     @ServiceThreadOnly
processAllDelayedMessages()1652     void processAllDelayedMessages() {
1653         assertRunOnServiceThread();
1654         mDelayedMessageBuffer.processAllMessages();
1655     }
1656 
1657     @ServiceThreadOnly
processDelayedMessages(int address)1658     void processDelayedMessages(int address) {
1659         assertRunOnServiceThread();
1660         mDelayedMessageBuffer.processMessagesForDevice(address);
1661     }
1662 
1663     @ServiceThreadOnly
processDelayedActiveSource(int address)1664     void processDelayedActiveSource(int address) {
1665         assertRunOnServiceThread();
1666         mDelayedMessageBuffer.processActiveSource(address);
1667     }
1668 
1669     @Override
dump(final IndentingPrintWriter pw)1670     protected void dump(final IndentingPrintWriter pw) {
1671         super.dump(pw);
1672         pw.println("mArcEstablished: " + mArcEstablished);
1673         pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1674         pw.println("mSystemAudioMute: " + mSystemAudioMute);
1675         pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
1676         pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
1677         pw.println("mPrevPortId: " + mPrevPortId);
1678     }
1679 }
1680