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