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