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