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