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