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