2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.hdmi;
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;
33 import android.annotation.Nullable;
34 import android.content.Context;
35 import android.hardware.hdmi.HdmiControlManager;
36 import android.hardware.hdmi.HdmiDeviceInfo;
37 import android.hardware.hdmi.HdmiPortInfo;
38 import android.hardware.hdmi.HdmiRecordSources;
39 import android.hardware.hdmi.HdmiTimerRecordSources;
40 import android.hardware.hdmi.IHdmiControlCallback;
41 import android.media.AudioManager;
42 import android.media.AudioSystem;
43 import android.media.tv.TvInputInfo;
44 import android.media.tv.TvInputManager;
45 import android.media.tv.TvInputManager.TvInputCallback;
46 import android.os.RemoteException;
47 import android.os.SystemProperties;
48 import android.provider.Settings.Global;
49 import android.util.ArraySet;
50 import android.util.Slog;
51 import android.util.SparseArray;
53 import com.android.internal.annotations.GuardedBy;
54 import com.android.internal.util.IndentingPrintWriter;
55 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
56 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
57 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
58 import com.android.server.SystemService;
60 import java.io.UnsupportedEncodingException;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collection;
64 import java.util.Collections;
65 import java.util.Iterator;
66 import java.util.List;
67 import java.util.HashMap;
70 * Represent a logical device of type TV residing in Android system.
72 final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
73 private static final String TAG = "HdmiCecLocalDeviceTv";
75 // Whether ARC is available or not. "true" means that ARC is established between TV and
76 // AVR as audio receiver.
78 private boolean mArcEstablished = false;
80 // Whether ARC feature is enabled or not. The default value is true.
81 // TODO: once adding system setting for it, read the value to it.
82 private boolean mArcFeatureEnabled = true;
84 // Whether System audio mode is activated or not.
85 // This becomes true only when all system audio sequences are finished.
87 private boolean mSystemAudioActivated = false;
89 // The previous port id (input) before switching to the new one. This is remembered in order to
90 // be able to switch to it upon receiving <Inactive Source> from currently active source.
91 // This remains valid only when the active source was switched via one touch play operation
92 // (either by TV or source device). Manual port switching invalidates this value to
93 // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
95 private int mPrevPortId;
98 private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
101 private boolean mSystemAudioMute = false;
103 // Copy of mDeviceInfos to guarantee thread-safety.
105 private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
106 // All external cec input(source) devices. Does not include system audio device.
108 private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
110 // Map-like container of all cec devices including local ones.
111 // device id is used as key of container.
112 // This is not thread-safe. For external purpose use mSafeDeviceInfos.
113 private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
115 // If true, TV going to standby mode puts other devices also to standby.
116 private boolean mAutoDeviceOff;
118 // If true, TV wakes itself up when receiving <Text/Image View On>.
119 private boolean mAutoWakeup;
121 // List of the logical address of local CEC devices. Unmodifiable, thread-safe.
122 private List<Integer> mLocalDeviceAddresses;
124 private final HdmiCecStandbyModeHandler mStandbyHandler;
126 // If true, do not do routing control/send active source for internal source.
127 // Set to true when the device was woken up by <Text/Image View On>.
128 private boolean mSkipRoutingControl;
130 // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
131 // other CEC devices since they might not have logical address.
132 private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
134 // Message buffer used to buffer selected messages to process later. <Active Source>
135 // from a source device, for instance, needs to be buffered if the device is not
136 // discovered yet. The buffered commands are taken out and when they are ready to
138 private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
140 // Defines the callback invoked when TV input framework is updated with input status.
141 // We are interested in the notification for HDMI input addition event, in order to
142 // process any CEC commands that arrived before the input is added.
143 private final TvInputCallback mTvInputCallback = new TvInputCallback() {
145 public void onInputAdded(String inputId) {
146 TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
147 HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
148 if (info == null) return;
149 addTvInput(inputId, info.getId());
150 if (info.isCecDevice()) {
151 processDelayedActiveSource(info.getLogicalAddress());
156 public void onInputRemoved(String inputId) {
157 removeTvInput(inputId);
161 // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to
162 // accept input switching request from HDMI devices. Requests for which the corresponding
163 // input ID is not yet registered by TV input framework need to be buffered for delayed
165 private final HashMap<String, Integer> mTvInputs = new HashMap<>();
168 private void addTvInput(String inputId, int deviceId) {
169 assertRunOnServiceThread();
170 mTvInputs.put(inputId, deviceId);
174 private void removeTvInput(String inputId) {
175 assertRunOnServiceThread();
176 mTvInputs.remove(inputId);
181 protected boolean isInputReady(int deviceId) {
182 assertRunOnServiceThread();
183 return mTvInputs.containsValue(deviceId);
186 HdmiCecLocalDeviceTv(HdmiControlService service) {
187 super(service, HdmiDeviceInfo.DEVICE_TV);
188 mPrevPortId = Constants.INVALID_PORT_ID;
189 mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
191 mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true);
192 mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
197 protected void onAddressAllocated(int logicalAddress, int reason) {
198 assertRunOnServiceThread();
199 mService.registerTvInputCallback(mTvInputCallback);
200 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
201 mAddress, mService.getPhysicalAddress(), mDeviceType));
202 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
203 mAddress, mService.getVendorId()));
204 mCecSwitches.add(mService.getPhysicalAddress()); // TV is a CEC switch too.
206 mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
207 launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
208 reason != HdmiControlService.INITIATED_BY_BOOT_UP);
209 mLocalDeviceAddresses = initLocalDeviceAddresses();
210 launchDeviceDiscovery();
211 startQueuedActions();
216 private List<Integer> initLocalDeviceAddresses() {
217 assertRunOnServiceThread();
218 List<Integer> addresses = new ArrayList<>();
219 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
220 addresses.add(device.getDeviceInfo().getLogicalAddress());
222 return Collections.unmodifiableList(addresses);
226 protected int getPreferredAddress() {
227 return Constants.ADDR_TV;
231 protected void setPreferredAddress(int addr) {
232 Slog.w(TAG, "Preferred addres will not be stored for TV");
237 boolean dispatchMessage(HdmiCecMessage message) {
238 assertRunOnServiceThread();
239 if (mService.isPowerStandby() && mStandbyHandler.handleCommand(message)) {
242 return super.onMessage(message);
246 * Performs the action 'device select', or 'one touch play' initiated by TV.
248 * @param id id of HDMI device to select
249 * @param callback callback object to report the result with
252 void deviceSelect(int id, IHdmiControlCallback callback) {
253 assertRunOnServiceThread();
254 HdmiDeviceInfo targetDevice = mDeviceInfos.get(id);
255 if (targetDevice == null) {
256 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
259 int targetAddress = targetDevice.getLogicalAddress();
260 ActiveSource active = getActiveSource();
261 if (active.isValid() && targetAddress == active.logicalAddress) {
262 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
265 if (targetAddress == Constants.ADDR_INTERNAL) {
266 handleSelectInternalSource();
267 // Switching to internal source is always successful even when CEC control is disabled.
268 setActiveSource(targetAddress, mService.getPhysicalAddress());
269 setActivePath(mService.getPhysicalAddress());
270 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
273 if (!mService.isControlEnabled()) {
274 setActiveSource(targetDevice);
275 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
278 removeAction(DeviceSelectAction.class);
279 addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
283 private void handleSelectInternalSource() {
284 assertRunOnServiceThread();
286 if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) {
287 updateActiveSource(mAddress, mService.getPhysicalAddress());
288 if (mSkipRoutingControl) {
289 mSkipRoutingControl = false;
292 HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
293 mAddress, mService.getPhysicalAddress());
294 mService.sendCecCommand(activeSource);
299 void updateActiveSource(int logicalAddress, int physicalAddress) {
300 assertRunOnServiceThread();
301 updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress));
305 void updateActiveSource(ActiveSource newActive) {
306 assertRunOnServiceThread();
308 if (mActiveSource.equals(newActive)) {
311 setActiveSource(newActive);
312 int logicalAddress = newActive.logicalAddress;
313 if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
314 if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
315 setPrevPortId(getActivePortId());
317 // TODO: Show the OSD banner related to the new active source device.
319 // TODO: If displayed, remove the OSD banner related to the previous
320 // active source device.
324 int getPortId(int physicalAddress) {
325 return mService.pathToPortId(physicalAddress);
329 * Returns the previous port id kept to handle input switching on <Inactive Source>.
331 int getPrevPortId() {
332 synchronized (mLock) {
338 * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
339 * taken for <Inactive Source>.
341 void setPrevPortId(int portId) {
342 synchronized (mLock) {
343 mPrevPortId = portId;
348 void updateActiveInput(int path, boolean notifyInputChange) {
349 assertRunOnServiceThread();
351 setPrevPortId(getActivePortId());
353 // TODO: Handle PAP/PIP case.
354 // Show OSD port change banner
355 if (notifyInputChange) {
356 ActiveSource activeSource = getActiveSource();
357 HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress);
359 info = mService.getDeviceInfoByPort(getActivePortId());
361 // No CEC/MHL device is present at the port. Attempt to switch to
362 // the hardware port itself for non-CEC devices that may be connected.
363 info = new HdmiDeviceInfo(path, getActivePortId());
366 mService.invokeInputChangeListener(info);
371 void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
372 assertRunOnServiceThread();
374 if (!mService.isValidPortId(portId)) {
375 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
378 if (portId == getActivePortId()) {
379 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
382 mActiveSource.invalidate();
383 if (!mService.isControlEnabled()) {
384 setActivePortId(portId);
385 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
388 int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
389 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
390 setActivePath(oldPath);
391 if (mSkipRoutingControl) {
392 mSkipRoutingControl = false;
395 int newPath = mService.portIdToPath(portId);
396 startRoutingControl(oldPath, newPath, true, callback);
400 void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus,
401 IHdmiControlCallback callback) {
402 assertRunOnServiceThread();
403 if (oldPath == newPath) {
406 HdmiCecMessage routingChange =
407 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
408 mService.sendCecCommand(routingChange);
409 removeAction(RoutingControlAction.class);
411 new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback));
415 int getPowerStatus() {
416 assertRunOnServiceThread();
417 return mService.getPowerStatus();
421 * Sends key to a target CEC device.
423 * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
424 * @param isPressed true if this is key press event
428 protected void sendKeyEvent(int keyCode, boolean isPressed) {
429 assertRunOnServiceThread();
430 if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
431 Slog.w(TAG, "Unsupported key: " + keyCode);
434 List<SendKeyAction> action = getActions(SendKeyAction.class);
435 if (!action.isEmpty()) {
436 action.get(0).processKeyEvent(keyCode, isPressed);
439 int logicalAddress = findKeyReceiverAddress();
440 if (logicalAddress != Constants.ADDR_INVALID) {
441 addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
445 Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed);
449 private int findKeyReceiverAddress() {
450 if (getActiveSource().isValid()) {
451 return getActiveSource().logicalAddress;
453 HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath());
455 return info.getLogicalAddress();
457 return Constants.ADDR_INVALID;
460 private static void invokeCallback(IHdmiControlCallback callback, int result) {
461 if (callback == null) {
465 callback.onComplete(result);
466 } catch (RemoteException e) {
467 Slog.e(TAG, "Invoking callback failed:" + e);
473 protected boolean handleActiveSource(HdmiCecMessage message) {
474 assertRunOnServiceThread();
475 int logicalAddress = message.getSource();
476 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
477 HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
479 if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
480 HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
481 mDelayedMessageBuffer.add(message);
483 } else if (!isInputReady(info.getId())) {
484 HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
485 mDelayedMessageBuffer.add(message);
487 ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
488 ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
495 protected boolean handleInactiveSource(HdmiCecMessage message) {
496 assertRunOnServiceThread();
499 // Ignore <Inactive Source> from non-active source device.
500 if (getActiveSource().logicalAddress != message.getSource()) {
503 if (isProhibitMode()) {
506 int portId = getPrevPortId();
507 if (portId != Constants.INVALID_PORT_ID) {
508 // TODO: Do this only if TV is not showing multiview like PIP/PAP.
510 HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
511 if (inactiveSource == null) {
514 if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
517 // TODO: Switch the TV freeze mode off
519 doManualPortSwitching(portId, null);
520 setPrevPortId(Constants.INVALID_PORT_ID);
522 // No HDMI port to switch to was found. Notify the input change listers to
523 // switch to the lastly shown internal input.
524 mActiveSource.invalidate();
525 setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
526 mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
533 protected boolean handleRequestActiveSource(HdmiCecMessage message) {
534 assertRunOnServiceThread();
536 if (mAddress == getActiveSource().logicalAddress) {
537 mService.sendCecCommand(
538 HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
545 protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
546 assertRunOnServiceThread();
547 if (!broadcastMenuLanguage(mService.getLanguage())) {
548 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
554 boolean broadcastMenuLanguage(String language) {
555 assertRunOnServiceThread();
556 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
558 if (command != null) {
559 mService.sendCecCommand(command);
567 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
568 assertRunOnServiceThread();
569 int path = HdmiUtils.twoBytesToInt(message.getParams());
570 int address = message.getSource();
571 int type = message.getParams()[2];
573 if (updateCecSwitchInfo(address, type, path)) return true;
575 // Ignore if [Device Discovery Action] is going on.
576 if (hasAction(DeviceDiscoveryAction.class)) {
577 Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
581 if (!isInDeviceList(address, path)) {
582 handleNewDeviceAtTheTailOfActivePath(path);
585 // Add the device ahead with default information to handle <Active Source>
586 // promptly, rather than waiting till the new device action is finished.
587 HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type,
588 Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address));
589 addCecDevice(deviceInfo);
590 startNewDeviceAction(ActiveSource.of(address, path), type);
595 protected boolean handleReportPowerStatus(HdmiCecMessage command) {
596 int newStatus = command.getParams()[0] & 0xFF;
597 updateDevicePowerStatus(command.getSource(), newStatus);
602 protected boolean handleTimerStatus(HdmiCecMessage message) {
608 protected boolean handleRecordStatus(HdmiCecMessage message) {
613 boolean updateCecSwitchInfo(int address, int type, int path) {
614 if (address == Constants.ADDR_UNREGISTERED
615 && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
616 mCecSwitches.add(path);
617 updateSafeDeviceInfoList();
618 return true; // Pure switch does not need further processing. Return here.
620 if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
621 mCecSwitches.add(path);
626 void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
627 for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
628 // If there is new device action which has the same logical address and path
629 // ignore new request.
630 // NewDeviceAction is created whenever it receives <Report Physical Address>.
631 // And there is a chance starting NewDeviceAction for the same source.
632 // Usually, new device sends <Report Physical Address> when it's plugged
633 // in. However, TV can detect a new device from HotPlugDetectionAction,
634 // which sends <Give Physical Address> to the source for newly detected
636 if (action.isActionOf(activeSource)) {
641 addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
642 activeSource.physicalAddress, deviceType));
645 private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
647 if (isTailOfActivePath(path, getActivePath())) {
648 int newPath = mService.portIdToPath(getActivePortId());
649 setActivePath(newPath);
650 startRoutingControl(getActivePath(), newPath, false, null);
657 * Whether the given path is located in the tail of current active path.
659 * @param path to be tested
660 * @param activePath current active path
661 * @return true if the given path is located in the tail of current active path; otherwise,
664 static boolean isTailOfActivePath(int path, int activePath) {
665 // If active routing path is internal source, return false.
666 if (activePath == 0) {
669 for (int i = 12; i >= 0; i -= 4) {
670 int curActivePath = (activePath >> i) & 0xF;
671 if (curActivePath == 0) {
674 int curPath = (path >> i) & 0xF;
675 if (curPath != curActivePath) {
685 protected boolean handleRoutingChange(HdmiCecMessage message) {
686 assertRunOnServiceThread();
688 byte[] params = message.getParams();
689 int currentPath = HdmiUtils.twoBytesToInt(params);
690 if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
691 mActiveSource.invalidate();
692 removeAction(RoutingControlAction.class);
693 int newPath = HdmiUtils.twoBytesToInt(params, 2);
694 addAndStartAction(new RoutingControlAction(this, newPath, true, null));
701 protected boolean handleReportAudioStatus(HdmiCecMessage message) {
702 assertRunOnServiceThread();
704 byte params[] = message.getParams();
705 int mute = params[0] & 0x80;
706 int volume = params[0] & 0x7F;
707 setAudioStatus(mute == 0x80, volume);
713 protected boolean handleTextViewOn(HdmiCecMessage message) {
714 assertRunOnServiceThread();
715 if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
723 protected boolean handleImageViewOn(HdmiCecMessage message) {
724 assertRunOnServiceThread();
725 // Currently, it's the same as <Text View On>.
726 return handleTextViewOn(message);
731 protected boolean handleSetOsdName(HdmiCecMessage message) {
732 int source = message.getSource();
733 HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
734 // If the device is not in device list, ignore it.
735 if (deviceInfo == null) {
736 Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
739 String osdName = null;
741 osdName = new String(message.getParams(), "US-ASCII");
742 } catch (UnsupportedEncodingException e) {
743 Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
747 if (deviceInfo.getDisplayName().equals(osdName)) {
748 Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
752 addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
753 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
754 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
759 private void launchDeviceDiscovery() {
760 assertRunOnServiceThread();
761 clearDeviceInfoList();
762 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
763 new DeviceDiscoveryCallback() {
765 public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
766 for (HdmiDeviceInfo info : deviceInfos) {
770 // Since we removed all devices when it's start and
771 // device discovery action does not poll local devices,
772 // we should put device info of local device manually here
773 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
774 addCecDevice(device.getDeviceInfo());
777 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
778 addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
780 // If there is AVR, initiate System Audio Auto initiation action,
781 // which turns on and off system audio according to last system
783 HdmiDeviceInfo avr = getAvrDeviceInfo();
789 addAndStartAction(action);
793 void onNewAvrAdded(HdmiDeviceInfo avr) {
794 assertRunOnServiceThread();
795 if (getSystemAudioModeSetting() && !isSystemAudioActivated()) {
796 addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
798 if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) {
799 startArcAction(true);
803 // Clear all device info.
805 private void clearDeviceInfoList() {
806 assertRunOnServiceThread();
807 for (HdmiDeviceInfo info : mSafeExternalInputs) {
808 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
810 mDeviceInfos.clear();
811 updateSafeDeviceInfoList();
816 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
817 assertRunOnServiceThread();
818 if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
819 setSystemAudioMode(false, true);
820 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
823 HdmiDeviceInfo avr = getAvrDeviceInfo();
825 setSystemAudioMode(false, true);
826 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
831 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
835 void setSystemAudioMode(boolean on, boolean updateSetting) {
836 HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on);
839 mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on);
841 updateAudioManagerForSystemAudio(on);
842 synchronized (mLock) {
843 if (mSystemAudioActivated != on) {
844 mSystemAudioActivated = on;
845 mService.announceSystemAudioModeChange(on);
850 private void updateAudioManagerForSystemAudio(boolean on) {
851 int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
852 HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
855 boolean isSystemAudioActivated() {
856 if (!hasSystemAudioDevice()) {
859 synchronized (mLock) {
860 return mSystemAudioActivated;
864 boolean getSystemAudioModeSetting() {
865 return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false);
869 * Change ARC status into the given {@code enabled} status.
871 * @return {@code true} if ARC was in "Enabled" status
874 boolean setArcStatus(boolean enabled) {
875 assertRunOnServiceThread();
877 HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
878 boolean oldStatus = mArcEstablished;
879 // 1. Enable/disable ARC circuit.
880 mService.setAudioReturnChannel(getAvrDeviceInfo().getPortId(), enabled);
881 // 2. Notify arc status to audio service.
882 notifyArcStatusToAudioService(enabled);
883 // 3. Update arc status;
884 mArcEstablished = enabled;
889 private void updateArcFeatureStatus(int portId, boolean isConnected) {
890 assertRunOnServiceThread();
891 // HEAC 2.4, HEACT 5-15
892 // Should not activate ARC if +5V status is false.
893 HdmiPortInfo portInfo = mService.getPortInfo(portId);
894 if (portInfo.isArcSupported()) {
895 changeArcFeatureEnabled(isConnected);
899 private void notifyArcStatusToAudioService(boolean enabled) {
900 // Note that we don't set any name to ARC.
901 mService.getAudioManager().setWiredDeviceConnectionState(
902 AudioSystem.DEVICE_OUT_HDMI_ARC,
903 enabled ? 1 : 0, "");
907 * Returns whether ARC is enabled or not.
910 boolean isArcEstabilished() {
911 assertRunOnServiceThread();
912 return mArcFeatureEnabled && mArcEstablished;
916 void changeArcFeatureEnabled(boolean enabled) {
917 assertRunOnServiceThread();
919 if (mArcFeatureEnabled != enabled) {
920 mArcFeatureEnabled = enabled;
922 if (!mArcEstablished) {
923 startArcAction(true);
926 if (mArcEstablished) {
927 startArcAction(false);
934 boolean isArcFeatureEnabled() {
935 assertRunOnServiceThread();
936 return mArcFeatureEnabled;
940 void startArcAction(boolean enabled) {
941 assertRunOnServiceThread();
942 HdmiDeviceInfo info = getAvrDeviceInfo();
944 Slog.w(TAG, "Failed to start arc action; No AVR device.");
947 if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
948 Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
949 if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
950 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
955 // Terminate opposite action and start action if not exist.
957 removeAction(RequestArcTerminationAction.class);
958 if (!hasAction(RequestArcInitiationAction.class)) {
959 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
962 removeAction(RequestArcInitiationAction.class);
963 if (!hasAction(RequestArcTerminationAction.class)) {
964 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
969 private boolean isDirectConnectAddress(int physicalAddress) {
970 return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
973 void setAudioStatus(boolean mute, int volume) {
974 synchronized (mLock) {
975 mSystemAudioMute = mute;
976 mSystemAudioVolume = volume;
977 int maxVolume = mService.getAudioManager().getStreamMaxVolume(
978 AudioManager.STREAM_MUSIC);
979 mService.setAudioStatus(mute,
980 VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
981 displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
982 mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
987 void changeVolume(int curVolume, int delta, int maxVolume) {
988 assertRunOnServiceThread();
989 if (delta == 0 || !isSystemAudioActivated()) {
993 int targetVolume = curVolume + delta;
994 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
995 synchronized (mLock) {
996 // If new volume is the same as current system audio volume, just ignore it.
997 // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
998 if (cecVolume == mSystemAudioVolume) {
999 // Update tv volume with system volume value.
1000 mService.setAudioStatus(false,
1001 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
1006 List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
1007 if (actions.isEmpty()) {
1008 addAndStartAction(new VolumeControlAction(this,
1009 getAvrDeviceInfo().getLogicalAddress(), delta > 0));
1011 actions.get(0).handleVolumeChange(delta > 0);
1016 void changeMute(boolean mute) {
1017 assertRunOnServiceThread();
1018 HdmiLogger.debug("[A]:Change mute:%b", mute);
1019 synchronized (mLock) {
1020 if (mSystemAudioMute == mute) {
1021 HdmiLogger.debug("No need to change mute.");
1025 if (!isSystemAudioActivated()) {
1026 HdmiLogger.debug("[A]:System audio is not activated.");
1030 // Remove existing volume action.
1031 removeAction(VolumeControlAction.class);
1032 sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
1033 mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION :
1034 HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
1039 protected boolean handleInitiateArc(HdmiCecMessage message) {
1040 assertRunOnServiceThread();
1042 if (!canStartArcUpdateAction(message.getSource(), true)) {
1043 if (getAvrDeviceInfo() == null) {
1044 // AVR may not have been discovered yet. Delay the message processing.
1045 mDelayedMessageBuffer.add(message);
1048 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1049 if (!isConnectedToArcPort(message.getSource())) {
1050 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
1055 // In case where <Initiate Arc> is started by <Request ARC Initiation>
1056 // need to clean up RequestArcInitiationAction.
1057 removeAction(RequestArcInitiationAction.class);
1058 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1059 message.getSource(), true);
1060 addAndStartAction(action);
1064 private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) {
1065 HdmiDeviceInfo avr = getAvrDeviceInfo();
1067 && (avrAddress == avr.getLogicalAddress())
1068 && isConnectedToArcPort(avr.getPhysicalAddress())
1069 && isDirectConnectAddress(avr.getPhysicalAddress())) {
1070 if (shouldCheckArcFeatureEnabled) {
1071 return isArcFeatureEnabled();
1082 protected boolean handleTerminateArc(HdmiCecMessage message) {
1083 assertRunOnServiceThread();
1084 // In cast of termination, do not check ARC configuration in that AVR device
1085 // might be removed already.
1087 // In case where <Terminate Arc> is started by <Request ARC Termination>
1088 // need to clean up RequestArcInitiationAction.
1089 removeAction(RequestArcTerminationAction.class);
1090 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1091 message.getSource(), false);
1092 addAndStartAction(action);
1098 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
1099 assertRunOnServiceThread();
1100 if (!isMessageForSystemAudio(message)) {
1101 if (getAvrDeviceInfo() == null) {
1102 // AVR may not have been discovered yet. Delay the message processing.
1103 mDelayedMessageBuffer.add(message);
1106 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
1107 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1110 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
1111 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
1112 addAndStartAction(action);
1118 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
1119 assertRunOnServiceThread();
1120 if (!isMessageForSystemAudio(message)) {
1121 HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
1122 // Ignore this message.
1125 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true);
1132 protected boolean handleRecordTvScreen(HdmiCecMessage message) {
1133 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
1134 if (!actions.isEmpty()) {
1135 // Assumes only one OneTouchRecordAction.
1136 OneTouchRecordAction action = actions.get(0);
1137 if (action.getRecorderAddress() != message.getSource()) {
1138 announceOneTouchRecordResult(
1139 message.getSource(),
1140 HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
1142 return super.handleRecordTvScreen(message);
1145 int recorderAddress = message.getSource();
1146 byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
1147 int reason = startOneTouchRecord(recorderAddress, recordSource);
1148 if (reason != Constants.ABORT_NO_ERROR) {
1149 mService.maySendFeatureAbortCommand(message, reason);
1155 protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
1156 byte[] params = message.getParams();
1157 int timerClearedStatusData = params[0] & 0xFF;
1158 announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
1162 void announceOneTouchRecordResult(int recorderAddress, int result) {
1163 mService.invokeOneTouchRecordResult(recorderAddress, result);
1166 void announceTimerRecordingResult(int recorderAddress, int result) {
1167 mService.invokeTimerRecordingResult(recorderAddress, result);
1170 void announceClearTimerRecordingResult(int recorderAddress, int result) {
1171 mService.invokeClearTimerRecordingResult(recorderAddress, result);
1174 private boolean isMessageForSystemAudio(HdmiCecMessage message) {
1175 return mService.isControlEnabled()
1176 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1177 && (message.getDestination() == Constants.ADDR_TV
1178 || message.getDestination() == Constants.ADDR_BROADCAST)
1179 && getAvrDeviceInfo() != null;
1183 * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
1184 * logical address as new device info's.
1186 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1188 * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
1189 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
1190 * that has the same logical address as new one has.
1193 private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
1194 assertRunOnServiceThread();
1195 HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
1196 if (oldDeviceInfo != null) {
1197 removeDeviceInfo(deviceInfo.getId());
1199 mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
1200 updateSafeDeviceInfoList();
1201 return oldDeviceInfo;
1205 * Remove a device info corresponding to the given {@code logicalAddress}.
1206 * It returns removed {@link HdmiDeviceInfo} if exists.
1208 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1210 * @param id id of device to be removed
1211 * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
1214 private HdmiDeviceInfo removeDeviceInfo(int id) {
1215 assertRunOnServiceThread();
1216 HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
1217 if (deviceInfo != null) {
1218 mDeviceInfos.remove(id);
1220 updateSafeDeviceInfoList();
1225 * Return a list of all {@link HdmiDeviceInfo}.
1227 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1228 * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
1229 * does not include local device.
1232 List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
1233 assertRunOnServiceThread();
1234 if (includeLocalDevice) {
1235 return HdmiUtils.sparseArrayToList(mDeviceInfos);
1237 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1238 for (int i = 0; i < mDeviceInfos.size(); ++i) {
1239 HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1240 if (!isLocalDeviceAddress(info.getLogicalAddress())) {
1249 * Return external input devices.
1251 List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
1252 return mSafeExternalInputs;
1256 private void updateSafeDeviceInfoList() {
1257 assertRunOnServiceThread();
1258 List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
1259 List<HdmiDeviceInfo> externalInputs = getInputDevices();
1260 synchronized (mLock) {
1261 mSafeAllDeviceInfos = copiedDevices;
1262 mSafeExternalInputs = externalInputs;
1267 * Return a list of external cec input (source) devices.
1269 * <p>Note that this effectively excludes non-source devices like system audio,
1272 private List<HdmiDeviceInfo> getInputDevices() {
1273 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1274 for (int i = 0; i < mDeviceInfos.size(); ++i) {
1275 HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1276 if (isLocalDeviceAddress(info.getLogicalAddress())) {
1279 if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
1286 // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
1287 // Returns true if the policy is set to true, and the device to check does not have
1288 // a parent CEC device (which should be the CEC-enabled switch) in the list.
1289 private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
1290 return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
1291 && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
1294 private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
1295 for (int switchPath : switches) {
1296 if (isParentPath(switchPath, path)) {
1303 private static boolean isParentPath(int parentPath, int childPath) {
1304 // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
1305 // If child's last non-zero nibble is removed, the result equals to the parent.
1306 for (int i = 0; i <= 12; i += 4) {
1307 int nibble = (childPath >> i) & 0xF;
1309 int parentNibble = (parentPath >> i) & 0xF;
1310 return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
1316 private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
1317 if (!hideDevicesBehindLegacySwitch(info)) {
1318 mService.invokeDeviceEventListeners(info, status);
1322 private boolean isLocalDeviceAddress(int address) {
1323 return mLocalDeviceAddresses.contains(address);
1327 HdmiDeviceInfo getAvrDeviceInfo() {
1328 assertRunOnServiceThread();
1329 return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1333 * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
1335 * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
1337 * @param logicalAddress logical address of the device to be retrieved
1338 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1339 * Returns null if no logical address matched
1342 HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
1343 assertRunOnServiceThread();
1344 return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
1347 boolean hasSystemAudioDevice() {
1348 return getSafeAvrDeviceInfo() != null;
1351 HdmiDeviceInfo getSafeAvrDeviceInfo() {
1352 return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1356 * Thread safe version of {@link #getCecDeviceInfo(int)}.
1358 * @param logicalAddress logical address to be retrieved
1359 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1360 * Returns null if no logical address matched
1362 HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
1363 synchronized (mLock) {
1364 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1365 if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
1373 List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
1374 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1375 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1376 if (isLocalDeviceAddress(info.getLogicalAddress())) {
1385 * Called when a device is newly added or a new device is detected or
1386 * existing device is updated.
1388 * @param info device info of a new device.
1391 final void addCecDevice(HdmiDeviceInfo info) {
1392 assertRunOnServiceThread();
1393 HdmiDeviceInfo old = addDeviceInfo(info);
1394 if (info.getLogicalAddress() == mAddress) {
1395 // The addition of TV device itself should not be notified.
1399 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1400 } else if (!old.equals(info)) {
1401 invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1402 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1407 * Called when a device is removed or removal of device is detected.
1409 * @param address a logical address of a device to be removed
1412 final void removeCecDevice(int address) {
1413 assertRunOnServiceThread();
1414 HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
1416 mCecMessageCache.flushMessagesFrom(address);
1417 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1421 void handleRemoveActiveRoutingPath(int path) {
1422 assertRunOnServiceThread();
1424 if (isTailOfActivePath(path, getActivePath())) {
1425 int newPath = mService.portIdToPath(getActivePortId());
1426 startRoutingControl(getActivePath(), newPath, true, null);
1431 * Launch routing control process.
1433 * @param routingForBootup true if routing control is initiated due to One Touch Play
1437 void launchRoutingControl(boolean routingForBootup) {
1438 assertRunOnServiceThread();
1440 if (getActivePortId() != Constants.INVALID_PORT_ID) {
1441 if (!routingForBootup && !isProhibitMode()) {
1442 int newPath = mService.portIdToPath(getActivePortId());
1443 setActivePath(newPath);
1444 startRoutingControl(getActivePath(), newPath, routingForBootup, null);
1447 int activePath = mService.getPhysicalAddress();
1448 setActivePath(activePath);
1449 if (!routingForBootup
1450 && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
1451 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
1458 * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
1459 * the given routing path. CEC devices use routing path for its physical address to
1460 * describe the hierarchy of the devices in the network.
1462 * @param path routing path or physical address
1463 * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
1466 final HdmiDeviceInfo getDeviceInfoByPath(int path) {
1467 assertRunOnServiceThread();
1468 for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
1469 if (info.getPhysicalAddress() == path) {
1477 * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
1478 * the given routing path. This is the version accessible safely from threads
1479 * other than service thread.
1481 * @param path routing path or physical address
1482 * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
1484 HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
1485 synchronized (mLock) {
1486 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1487 if (info.getPhysicalAddress() == path) {
1496 * Whether a device of the specified physical address and logical address exists
1497 * in a device info list. However, both are minimal condition and it could
1498 * be different device from the original one.
1500 * @param logicalAddress logical address of a device to be searched
1501 * @param physicalAddress physical address of a device to be searched
1502 * @return true if exist; otherwise false
1505 boolean isInDeviceList(int logicalAddress, int physicalAddress) {
1506 assertRunOnServiceThread();
1507 HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
1508 if (device == null) {
1511 return device.getPhysicalAddress() == physicalAddress;
1516 void onHotplug(int portId, boolean connected) {
1517 assertRunOnServiceThread();
1520 removeCecSwitches(portId);
1522 // Tv device will have permanent HotplugDetectionAction.
1523 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1524 if (!hotplugActions.isEmpty()) {
1525 // Note that hotplug action is single action running on a machine.
1526 // "pollAllDevicesNow" cleans up timer and start poll action immediately.
1527 // It covers seq #40, #43.
1528 hotplugActions.get(0).pollAllDevicesNow();
1530 updateArcFeatureStatus(portId, connected);
1533 private void removeCecSwitches(int portId) {
1534 Iterator<Integer> it = mCecSwitches.iterator();
1535 while (!it.hasNext()) {
1536 int path = it.next();
1537 if (pathToPortId(path) == portId) {
1544 void setAutoDeviceOff(boolean enabled) {
1545 assertRunOnServiceThread();
1546 mAutoDeviceOff = enabled;
1550 void setAutoWakeup(boolean enabled) {
1551 assertRunOnServiceThread();
1552 mAutoWakeup = enabled;
1556 boolean getAutoWakeup() {
1557 assertRunOnServiceThread();
1563 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1564 super.disableDevice(initiatedByCec, callback);
1565 assertRunOnServiceThread();
1566 mService.unregisterTvInputCallback(mTvInputCallback);
1567 // Remove any repeated working actions.
1568 // HotplugDetectionAction will be reinstated during the wake up process.
1569 // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1570 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
1571 removeAction(DeviceDiscoveryAction.class);
1572 removeAction(HotplugDetectionAction.class);
1573 removeAction(PowerStatusMonitorAction.class);
1574 // Remove recording actions.
1575 removeAction(OneTouchRecordAction.class);
1576 removeAction(TimerRecordingAction.class);
1578 disableSystemAudioIfExist();
1579 disableArcIfExist();
1580 clearDeviceInfoList();
1581 checkIfPendingActionsCleared();
1585 private void disableSystemAudioIfExist() {
1586 assertRunOnServiceThread();
1587 if (getAvrDeviceInfo() == null) {
1592 removeAction(SystemAudioActionFromAvr.class);
1593 removeAction(SystemAudioActionFromTv.class);
1594 removeAction(SystemAudioAutoInitiationAction.class);
1595 removeAction(SystemAudioStatusAction.class);
1596 removeAction(VolumeControlAction.class);
1598 // Turn off the mode but do not write it the settings, so that the next time TV powers on
1599 // the system audio mode setting can be restored automatically.
1600 setSystemAudioMode(false, false);
1604 private void disableArcIfExist() {
1605 assertRunOnServiceThread();
1606 HdmiDeviceInfo avr = getAvrDeviceInfo();
1612 removeAction(RequestArcInitiationAction.class);
1613 if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
1614 addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1620 protected void onStandby(boolean initiatedByCec) {
1621 assertRunOnServiceThread();
1623 if (!mService.isControlEnabled()) {
1626 if (!initiatedByCec && mAutoDeviceOff) {
1627 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1628 mAddress, Constants.ADDR_BROADCAST));
1632 boolean isProhibitMode() {
1633 return mService.isProhibitMode();
1636 boolean isPowerStandbyOrTransient() {
1637 return mService.isPowerStandbyOrTransient();
1641 void displayOsd(int messageId) {
1642 assertRunOnServiceThread();
1643 mService.displayOsd(messageId);
1647 void displayOsd(int messageId, int extra) {
1648 assertRunOnServiceThread();
1649 mService.displayOsd(messageId, extra);
1654 int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1655 assertRunOnServiceThread();
1656 if (!mService.isControlEnabled()) {
1657 Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1658 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1659 return Constants.ABORT_NOT_IN_CORRECT_MODE;
1662 if (!checkRecorder(recorderAddress)) {
1663 Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1664 announceOneTouchRecordResult(recorderAddress,
1665 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1666 return Constants.ABORT_NOT_IN_CORRECT_MODE;
1669 if (!checkRecordSource(recordSource)) {
1670 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1671 announceOneTouchRecordResult(recorderAddress,
1672 ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
1673 return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1676 addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1677 Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1678 + Arrays.toString(recordSource));
1679 return Constants.ABORT_NO_ERROR;
1683 void stopOneTouchRecord(int recorderAddress) {
1684 assertRunOnServiceThread();
1685 if (!mService.isControlEnabled()) {
1686 Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
1687 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1691 if (!checkRecorder(recorderAddress)) {
1692 Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1693 announceOneTouchRecordResult(recorderAddress,
1694 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1698 // Remove one touch record action so that other one touch record can be started.
1699 removeAction(OneTouchRecordAction.class);
1700 mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
1701 Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1704 private boolean checkRecorder(int recorderAddress) {
1705 HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
1706 return (device != null)
1707 && (HdmiUtils.getTypeFromAddress(recorderAddress)
1708 == HdmiDeviceInfo.DEVICE_RECORDER);
1711 private boolean checkRecordSource(byte[] recordSource) {
1712 return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1716 void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1717 assertRunOnServiceThread();
1718 if (!mService.isControlEnabled()) {
1719 Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1720 announceTimerRecordingResult(recorderAddress,
1721 TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
1725 if (!checkRecorder(recorderAddress)) {
1726 Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1727 announceTimerRecordingResult(recorderAddress,
1728 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
1732 if (!checkTimerRecordingSource(sourceType, recordSource)) {
1733 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1734 announceTimerRecordingResult(
1736 TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
1741 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
1742 Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
1743 + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
1746 private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
1747 return (recordSource != null)
1748 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
1752 void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1753 assertRunOnServiceThread();
1754 if (!mService.isControlEnabled()) {
1755 Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1756 announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
1760 if (!checkRecorder(recorderAddress)) {
1761 Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1762 announceClearTimerRecordingResult(recorderAddress,
1763 CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
1767 if (!checkTimerRecordingSource(sourceType, recordSource)) {
1768 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1769 announceClearTimerRecordingResult(recorderAddress,
1770 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1774 sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1777 private void sendClearTimerMessage(final int recorderAddress, int sourceType,
1778 byte[] recordSource) {
1779 HdmiCecMessage message = null;
1780 switch (sourceType) {
1781 case TIMER_RECORDING_TYPE_DIGITAL:
1782 message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
1785 case TIMER_RECORDING_TYPE_ANALOGUE:
1786 message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
1789 case TIMER_RECORDING_TYPE_EXTERNAL:
1790 message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
1794 Slog.w(TAG, "Invalid source type:" + recorderAddress);
1795 announceClearTimerRecordingResult(recorderAddress,
1796 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1800 mService.sendCecCommand(message, new SendMessageCallback() {
1802 public void onSendCompleted(int error) {
1803 if (error != Constants.SEND_RESULT_SUCCESS) {
1804 announceClearTimerRecordingResult(recorderAddress,
1805 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1811 void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
1812 HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
1814 Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
1818 if (info.getDevicePowerStatus() == newPowerStatus) {
1822 HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
1823 // addDeviceInfo replaces old device info with new one if exists.
1824 addDeviceInfo(newInfo);
1826 invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
1830 protected boolean handleMenuStatus(HdmiCecMessage message) {
1831 // Do nothing and just return true not to prevent from responding <Feature Abort>.
1836 protected void sendStandby(int deviceId) {
1837 HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
1838 if (targetDevice == null) {
1841 int targetAddress = targetDevice.getLogicalAddress();
1842 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
1846 void processAllDelayedMessages() {
1847 assertRunOnServiceThread();
1848 mDelayedMessageBuffer.processAllMessages();
1852 void processDelayedMessages(int address) {
1853 assertRunOnServiceThread();
1854 mDelayedMessageBuffer.processMessagesForDevice(address);
1858 void processDelayedActiveSource(int address) {
1859 assertRunOnServiceThread();
1860 mDelayedMessageBuffer.processActiveSource(address);
1864 protected void dump(final IndentingPrintWriter pw) {
1866 pw.println("mArcEstablished: " + mArcEstablished);
1867 pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1868 pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
1869 pw.println("mSystemAudioMute: " + mSystemAudioMute);
1870 pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
1871 pw.println("mAutoWakeup: " + mAutoWakeup);
1872 pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
1873 pw.println("mPrevPortId: " + mPrevPortId);
1874 pw.println("CEC devices:");
1875 pw.increaseIndent();
1876 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1879 pw.decreaseIndent();