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.DEVICE_EVENT_ADD_DEVICE;
20 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
21 import static com.android.server.hdmi.Constants.DISABLED;
22 import static com.android.server.hdmi.Constants.ENABLED;
23 import static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_WAKEUP;
24 import static com.android.server.hdmi.Constants.OPTION_CEC_ENABLE;
25 import static com.android.server.hdmi.Constants.OPTION_CEC_SERVICE_CONTROL;
26 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
27 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
28 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
30 import android.annotation.Nullable;
31 import android.content.BroadcastReceiver;
32 import android.content.ContentResolver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.database.ContentObserver;
37 import android.hardware.hdmi.HdmiControlManager;
38 import android.hardware.hdmi.HdmiDeviceInfo;
39 import android.hardware.hdmi.HdmiHotplugEvent;
40 import android.hardware.hdmi.HdmiPortInfo;
41 import android.hardware.hdmi.IHdmiControlCallback;
42 import android.hardware.hdmi.IHdmiControlService;
43 import android.hardware.hdmi.IHdmiDeviceEventListener;
44 import android.hardware.hdmi.IHdmiHotplugEventListener;
45 import android.hardware.hdmi.IHdmiInputChangeListener;
46 import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
47 import android.hardware.hdmi.IHdmiRecordListener;
48 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
49 import android.hardware.hdmi.IHdmiVendorCommandListener;
50 import android.media.AudioManager;
51 import android.media.tv.TvInputManager;
52 import android.media.tv.TvInputManager.TvInputCallback;
53 import android.net.Uri;
54 import android.os.Build;
55 import android.os.Handler;
56 import android.os.HandlerThread;
57 import android.os.IBinder;
58 import android.os.Looper;
59 import android.os.PowerManager;
60 import android.os.RemoteException;
61 import android.os.SystemClock;
62 import android.os.SystemProperties;
63 import android.os.UserHandle;
64 import android.provider.Settings.Global;
65 import android.text.TextUtils;
66 import android.util.ArraySet;
67 import android.util.Slog;
68 import android.util.SparseArray;
69 import android.util.SparseIntArray;
71 import com.android.internal.annotations.GuardedBy;
72 import com.android.internal.util.IndentingPrintWriter;
73 import com.android.server.SystemService;
74 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
75 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
76 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
77 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
79 import libcore.util.EmptyArray;
81 import java.io.FileDescriptor;
82 import java.io.PrintWriter;
83 import java.util.ArrayList;
84 import java.util.Arrays;
85 import java.util.Collections;
86 import java.util.List;
87 import java.util.Locale;
90 * Provides a service for sending and processing HDMI control messages,
91 * HDMI-CEC and MHL control command, and providing the information on both standard.
93 public final class HdmiControlService extends SystemService {
94 private static final String TAG = "HdmiControlService";
95 private final Locale HONG_KONG = new Locale("zh", "HK");
96 private final Locale MACAU = new Locale("zh", "MO");
98 static final String PERMISSION = "android.permission.HDMI_CEC";
100 // The reason code to initiate intializeCec().
101 static final int INITIATED_BY_ENABLE_CEC = 0;
102 static final int INITIATED_BY_BOOT_UP = 1;
103 static final int INITIATED_BY_SCREEN_ON = 2;
104 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
105 static final int INITIATED_BY_HOTPLUG = 4;
108 * Interface to report send result.
110 interface SendMessageCallback {
112 * Called when {@link HdmiControlService#sendCecCommand} is completed.
114 * @param error result of send request.
116 * <li>{@link Constants#SEND_RESULT_SUCCESS}
117 * <li>{@link Constants#SEND_RESULT_NAK}
118 * <li>{@link Constants#SEND_RESULT_FAILURE}
121 void onSendCompleted(int error);
125 * Interface to get a list of available logical devices.
127 interface DevicePollingCallback {
129 * Called when device polling is finished.
131 * @param ackedAddress a list of logical addresses of available devices
133 void onPollingFinished(List<Integer> ackedAddress);
136 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
139 public void onReceive(Context context, Intent intent) {
140 assertRunOnServiceThread();
141 switch (intent.getAction()) {
142 case Intent.ACTION_SCREEN_OFF:
143 if (isPowerOnOrTransient()) {
147 case Intent.ACTION_SCREEN_ON:
148 if (isPowerStandbyOrTransient()) {
152 case Intent.ACTION_CONFIGURATION_CHANGED:
153 String language = getMenuLanguage();
154 if (!mLanguage.equals(language)) {
155 onLanguageChanged(language);
161 private String getMenuLanguage() {
162 Locale locale = Locale.getDefault();
163 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
164 // Android always returns "zho" for all Chinese variants.
165 // Use "bibliographic" code defined in CEC639-2 for traditional
166 // Chinese used in Taiwan/Hong Kong/Macau.
169 return locale.getISO3Language();
174 // A thread to handle synchronous IO of CEC and MHL control service.
175 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
176 // and sparse call it shares a thread to handle IO operations.
177 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
179 // Used to synchronize the access to the service.
180 private final Object mLock = new Object();
182 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
183 private final List<Integer> mLocalDevices;
185 // List of records for hotplug event listener to handle the the caller killed in action.
187 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
190 // List of records for device event listener to handle the caller killed in action.
192 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
195 // List of records for vendor command listener to handle the caller killed in action.
197 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
201 private InputChangeListenerRecord mInputChangeListenerRecord;
204 private HdmiRecordListenerRecord mRecordListenerRecord;
206 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
207 // handling will be disabled and no request will be handled.
209 private boolean mHdmiControlEnabled;
211 // Set to true while the service is in normal mode. While set to false, no input change is
212 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
213 // system upgrade, etc., a.k.a. "prohibit mode".
215 private boolean mProhibitMode;
217 // List of records for system audio mode change to handle the the caller killed in action.
218 private final ArrayList<SystemAudioModeChangeListenerRecord>
219 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
221 // Handler used to run a task in service thread.
222 private final Handler mHandler = new Handler();
224 private final SettingsObserver mSettingsObserver;
226 private final HdmiControlBroadcastReceiver
227 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
230 private HdmiCecController mCecController;
232 // HDMI port information. Stored in the unmodifiable list to keep the static information
233 // from being modified.
234 private List<HdmiPortInfo> mPortInfo;
236 // Map from path(physical address) to port ID.
237 private UnmodifiableSparseIntArray mPortIdMap;
239 // Map from port ID to HdmiPortInfo.
240 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
242 // Map from port ID to HdmiDeviceInfo.
243 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
245 private HdmiCecMessageValidator mMessageValidator;
248 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
251 private String mLanguage = Locale.getDefault().getISO3Language();
254 private boolean mStandbyMessageReceived = false;
257 private boolean mWakeUpMessageReceived = false;
260 private int mActivePortId = Constants.INVALID_PORT_ID;
262 // Set to true while the input change by MHL is allowed.
264 private boolean mMhlInputChangeEnabled;
266 // List of records for MHL Vendor command listener to handle the caller killed in action.
268 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
269 mMhlVendorCommandListenerRecords = new ArrayList<>();
272 private List<HdmiDeviceInfo> mMhlDevices;
275 private HdmiMhlControllerStub mMhlController;
278 private TvInputManager mTvInputManager;
281 private PowerManager mPowerManager;
283 // Last input port before switching to the MHL port. Should switch back to this port
284 // when the mobile device sends the request one touch play with off.
285 // Gets invalidated if we go to other port/input.
287 private int mLastInputMhl = Constants.INVALID_PORT_ID;
289 // Set to true if the logical address allocation is completed.
290 private boolean mAddressAllocated = false;
292 // Buffer for processing the incoming cec messages while allocating logical addresses.
293 private final class CecMessageBuffer {
294 private List<HdmiCecMessage> mBuffer = new ArrayList<>();
296 public void bufferMessage(HdmiCecMessage message) {
297 switch (message.getOpcode()) {
298 case Constants.MESSAGE_ACTIVE_SOURCE:
299 bufferActiveSource(message);
301 case Constants.MESSAGE_IMAGE_VIEW_ON:
302 case Constants.MESSAGE_TEXT_VIEW_ON:
303 bufferImageOrTextViewOn(message);
305 // Add here if new message that needs to buffer
307 // Do not need to buffer messages other than above
312 public void processMessages() {
313 for (final HdmiCecMessage message : mBuffer) {
314 runOnServiceThread(new Runnable() {
317 handleCecCommand(message);
324 private void bufferActiveSource(HdmiCecMessage message) {
325 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
326 mBuffer.add(message);
330 private void bufferImageOrTextViewOn(HdmiCecMessage message) {
331 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
332 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
333 mBuffer.add(message);
337 // Returns true if the message is replaced
338 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
339 for (int i = 0; i < mBuffer.size(); i++) {
340 HdmiCecMessage bufferedMessage = mBuffer.get(i);
341 if (bufferedMessage.getOpcode() == opcode) {
342 mBuffer.set(i, message);
350 private CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
352 public HdmiControlService(Context context) {
354 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
355 mSettingsObserver = new SettingsObserver(mHandler);
358 private static List<Integer> getIntList(String string) {
359 ArrayList<Integer> list = new ArrayList<>();
360 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
361 splitter.setString(string);
362 for (String item : splitter) {
364 list.add(Integer.parseInt(item));
365 } catch (NumberFormatException e) {
366 Slog.w(TAG, "Can't parseInt: " + item);
369 return Collections.unmodifiableList(list);
373 public void onStart() {
375 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
376 mProhibitMode = false;
377 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
378 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
380 mCecController = HdmiCecController.create(this);
381 if (mCecController != null) {
382 // TODO: Remove this as soon as OEM's HAL implementation is corrected.
383 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
385 // TODO: load value for mHdmiControlEnabled from preference.
386 if (mHdmiControlEnabled) {
387 initializeCec(INITIATED_BY_BOOT_UP);
390 Slog.i(TAG, "Device does not support HDMI-CEC.");
394 mMhlController = HdmiMhlControllerStub.create(this);
395 if (!mMhlController.isReady()) {
396 Slog.i(TAG, "Device does not support MHL-control.");
398 mMhlDevices = Collections.emptyList();
401 mMessageValidator = new HdmiCecMessageValidator(this);
402 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
404 if (mCecController != null) {
405 // Register broadcast receiver for power state change.
406 IntentFilter filter = new IntentFilter();
407 filter.addAction(Intent.ACTION_SCREEN_OFF);
408 filter.addAction(Intent.ACTION_SCREEN_ON);
409 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
410 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
412 // Register ContentObserver to monitor the settings change.
413 registerContentObserver();
418 public void onBootPhase(int phase) {
419 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
420 mTvInputManager = (TvInputManager) getContext().getSystemService(
421 Context.TV_INPUT_SERVICE);
422 mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
426 TvInputManager getTvInputManager() {
427 return mTvInputManager;
430 void registerTvInputCallback(TvInputCallback callback) {
431 if (mTvInputManager == null) return;
432 mTvInputManager.registerCallback(callback, mHandler);
435 void unregisterTvInputCallback(TvInputCallback callback) {
436 if (mTvInputManager == null) return;
437 mTvInputManager.unregisterCallback(callback);
440 PowerManager getPowerManager() {
441 return mPowerManager;
445 * Called when the initialization of local devices is complete.
447 private void onInitializeCecComplete(int initiatedBy) {
448 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
449 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
451 mWakeUpMessageReceived = false;
453 if (isTvDeviceEnabled()) {
454 mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
457 switch (initiatedBy) {
458 case INITIATED_BY_BOOT_UP:
459 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
461 case INITIATED_BY_ENABLE_CEC:
462 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
464 case INITIATED_BY_SCREEN_ON:
465 case INITIATED_BY_WAKE_UP_MESSAGE:
466 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
470 invokeVendorCommandListenersOnControlStateChanged(true, reason);
474 private void registerContentObserver() {
475 ContentResolver resolver = getContext().getContentResolver();
476 String[] settings = new String[] {
477 Global.HDMI_CONTROL_ENABLED,
478 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
479 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
480 Global.MHL_INPUT_SWITCHING_ENABLED,
481 Global.MHL_POWER_CHARGE_ENABLED
483 for (String s : settings) {
484 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
485 UserHandle.USER_ALL);
489 private class SettingsObserver extends ContentObserver {
490 public SettingsObserver(Handler handler) {
494 // onChange is set up to run in service thread.
496 public void onChange(boolean selfChange, Uri uri) {
497 String option = uri.getLastPathSegment();
498 boolean enabled = readBooleanSetting(option, true);
500 case Global.HDMI_CONTROL_ENABLED:
501 setControlEnabled(enabled);
503 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
504 if (isTvDeviceEnabled()) {
505 tv().setAutoWakeup(enabled);
507 setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
509 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
510 if (isTvDeviceEnabled()) {
511 tv().setAutoDeviceOff(enabled);
513 // No need to propagate to HAL.
515 case Global.MHL_INPUT_SWITCHING_ENABLED:
516 setMhlInputChangeEnabled(enabled);
518 case Global.MHL_POWER_CHARGE_ENABLED:
519 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
525 private static int toInt(boolean enabled) {
526 return enabled ? ENABLED : DISABLED;
529 boolean readBooleanSetting(String key, boolean defVal) {
530 ContentResolver cr = getContext().getContentResolver();
531 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
534 void writeBooleanSetting(String key, boolean value) {
535 ContentResolver cr = getContext().getContentResolver();
536 Global.putInt(cr, key, toInt(value));
539 private void initializeCec(int initiatedBy) {
540 mAddressAllocated = false;
541 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
542 initializeLocalDevices(initiatedBy);
546 private void initializeLocalDevices(final int initiatedBy) {
547 assertRunOnServiceThread();
548 // A container for [Device type, Local device info].
549 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
550 for (int type : mLocalDevices) {
551 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
552 if (localDevice == null) {
553 localDevice = HdmiCecLocalDevice.create(this, type);
556 localDevices.add(localDevice);
558 // It's now safe to flush existing local devices from mCecController since they were
559 // already moved to 'localDevices'.
561 allocateLogicalAddress(localDevices, initiatedBy);
565 private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
566 final int initiatedBy) {
567 assertRunOnServiceThread();
568 mCecController.clearLogicalAddress();
569 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
570 final int[] finished = new int[1];
571 mAddressAllocated = allocatingDevices.isEmpty();
573 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
574 mCecController.allocateLogicalAddress(localDevice.getType(),
575 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
577 public void onAllocated(int deviceType, int logicalAddress) {
578 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
579 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
581 // Set POWER_STATUS_ON to all local devices because they share lifetime
583 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
584 HdmiControlManager.POWER_STATUS_ON);
585 localDevice.setDeviceInfo(deviceInfo);
586 mCecController.addLocalDevice(deviceType, localDevice);
587 mCecController.addLogicalAddress(logicalAddress);
588 allocatedDevices.add(localDevice);
591 // Address allocation completed for all devices. Notify each device.
592 if (allocatingDevices.size() == ++finished[0]) {
593 mAddressAllocated = true;
594 if (initiatedBy != INITIATED_BY_HOTPLUG) {
595 // In case of the hotplug we don't call onInitializeCecComplete()
596 // since we reallocate the logical address only.
597 onInitializeCecComplete(initiatedBy);
599 notifyAddressAllocated(allocatedDevices, initiatedBy);
600 mCecMessageBuffer.processMessages();
608 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
609 assertRunOnServiceThread();
610 for (HdmiCecLocalDevice device : devices) {
611 int address = device.getDeviceInfo().getLogicalAddress();
612 device.handleAddressAllocated(address, initiatedBy);
616 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
617 // keep them in one place.
619 private void initPortInfo() {
620 assertRunOnServiceThread();
621 HdmiPortInfo[] cecPortInfo = null;
623 // CEC HAL provides majority of the info while MHL does only MHL support flag for
624 // each port. Return empty array if CEC HAL didn't provide the info.
625 if (mCecController != null) {
626 cecPortInfo = mCecController.getPortInfos();
628 if (cecPortInfo == null) {
632 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
633 SparseIntArray portIdMap = new SparseIntArray();
634 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
635 for (HdmiPortInfo info : cecPortInfo) {
636 portIdMap.put(info.getAddress(), info.getId());
637 portInfoMap.put(info.getId(), info);
638 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
640 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
641 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
642 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
644 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
645 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
646 for (HdmiPortInfo info : mhlPortInfo) {
647 if (info.isMhlSupported()) {
648 mhlSupportedPorts.add(info.getId());
652 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
653 // cec port info if we do not have have port that supports MHL.
654 if (mhlSupportedPorts.isEmpty()) {
655 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
658 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
659 for (HdmiPortInfo info : cecPortInfo) {
660 if (mhlSupportedPorts.contains(info.getId())) {
661 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
662 info.isCecSupported(), true, info.isArcSupported()));
667 mPortInfo = Collections.unmodifiableList(result);
670 List<HdmiPortInfo> getPortInfo() {
675 * Returns HDMI port information for the given port id.
677 * @param portId HDMI port id
678 * @return {@link HdmiPortInfo} for the given port
680 HdmiPortInfo getPortInfo(int portId) {
681 return mPortInfoMap.get(portId, null);
685 * Returns the routing path (physical address) of the HDMI port for the given
688 int portIdToPath(int portId) {
689 HdmiPortInfo portInfo = getPortInfo(portId);
690 if (portInfo == null) {
691 Slog.e(TAG, "Cannot find the port info: " + portId);
692 return Constants.INVALID_PHYSICAL_ADDRESS;
694 return portInfo.getAddress();
698 * Returns the id of HDMI port located at the top of the hierarchy of
699 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
700 * the port id to be returned is the ID associated with the port address
701 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
703 int pathToPortId(int path) {
704 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
705 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
708 boolean isValidPortId(int portId) {
709 return getPortInfo(portId) != null;
713 * Returns {@link Looper} for IO operation.
715 * <p>Declared as package-private.
717 Looper getIoLooper() {
718 return mIoThread.getLooper();
722 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
723 * for tasks that are running on main service thread.
725 * <p>Declared as package-private.
727 Looper getServiceLooper() {
728 return mHandler.getLooper();
732 * Returns physical address of the device.
734 int getPhysicalAddress() {
735 return mCecController.getPhysicalAddress();
739 * Returns vendor id of CEC service.
742 return mCecController.getVendorId();
746 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
747 assertRunOnServiceThread();
748 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
752 HdmiDeviceInfo getDeviceInfoByPort(int port) {
753 assertRunOnServiceThread();
754 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
756 return info.getInfo();
762 * Returns version of CEC.
764 int getCecVersion() {
765 return mCecController.getVersion();
769 * Whether a device of the specified physical address is connected to ARC enabled port.
771 boolean isConnectedToArcPort(int physicalAddress) {
772 int portId = pathToPortId(physicalAddress);
773 if (portId != Constants.INVALID_PORT_ID) {
774 return mPortInfoMap.get(portId).isArcSupported();
779 void runOnServiceThread(Runnable runnable) {
780 mHandler.post(runnable);
783 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
784 mHandler.postAtFrontOfQueue(runnable);
787 private void assertRunOnServiceThread() {
788 if (Looper.myLooper() != mHandler.getLooper()) {
789 throw new IllegalStateException("Should run on service thread.");
794 * Transmit a CEC command to CEC bus.
796 * @param command CEC command to send out
797 * @param callback interface used to the result of send command
800 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
801 assertRunOnServiceThread();
802 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
803 mCecController.sendCommand(command, callback);
805 HdmiLogger.error("Invalid message type:" + command);
806 if (callback != null) {
807 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
813 void sendCecCommand(HdmiCecMessage command) {
814 assertRunOnServiceThread();
815 sendCecCommand(command, null);
819 * Send <Feature Abort> command on the given CEC message if possible.
820 * If the aborted message is invalid, then it wont send the message.
821 * @param command original command to be aborted
822 * @param reason reason of feature abort
825 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
826 assertRunOnServiceThread();
827 mCecController.maySendFeatureAbortCommand(command, reason);
831 boolean handleCecCommand(HdmiCecMessage message) {
832 assertRunOnServiceThread();
833 if (!mAddressAllocated) {
834 mCecMessageBuffer.bufferMessage(message);
837 int errorCode = mMessageValidator.isValid(message);
838 if (errorCode != HdmiCecMessageValidator.OK) {
839 // We'll not response on the messages with the invalid source or destination
840 // or with parameter length shorter than specified in the standard.
841 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
842 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
846 return dispatchMessageToLocalDevice(message);
849 void setAudioReturnChannel(int portId, boolean enabled) {
850 mCecController.setAudioReturnChannel(portId, enabled);
854 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
855 assertRunOnServiceThread();
856 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
857 if (device.dispatchMessage(message)
858 && message.getDestination() != Constants.ADDR_BROADCAST) {
863 if (message.getDestination() != Constants.ADDR_BROADCAST) {
864 HdmiLogger.warning("Unhandled cec command:" + message);
870 * Called when a new hotplug event is issued.
872 * @param portId hdmi port number where hot plug event issued.
873 * @param connected whether to be plugged in or not
876 void onHotplug(int portId, boolean connected) {
877 assertRunOnServiceThread();
879 if (connected && !isTvDevice()) {
880 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
881 for (int type : mLocalDevices) {
882 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
883 if (localDevice == null) {
884 localDevice = HdmiCecLocalDevice.create(this, type);
887 localDevices.add(localDevice);
889 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
892 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
893 device.onHotplug(portId, connected);
895 announceHotplugEvent(portId, connected);
899 * Poll all remote devices. It sends <Polling Message> to all remote
902 * @param callback an interface used to get a list of all remote devices' address
903 * @param sourceAddress a logical address of source device where sends polling message
904 * @param pickStrategy strategy how to pick polling candidates
905 * @param retryCount the number of retry used to send polling message to remote devices
906 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
909 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
911 assertRunOnServiceThread();
912 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
916 private int checkPollStrategy(int pickStrategy) {
917 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
919 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
921 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
922 if (iterationStrategy == 0) {
923 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
925 return strategy | iterationStrategy;
928 List<HdmiCecLocalDevice> getAllLocalDevices() {
929 assertRunOnServiceThread();
930 return mCecController.getLocalDeviceList();
933 Object getServiceLock() {
937 void setAudioStatus(boolean mute, int volume) {
938 AudioManager audioManager = getAudioManager();
939 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
942 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
946 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
948 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
949 // volume change notification back to hdmi control service.
950 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
951 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
955 void announceSystemAudioModeChange(boolean enabled) {
956 synchronized (mLock) {
957 for (SystemAudioModeChangeListenerRecord record :
958 mSystemAudioModeChangeListenerRecords) {
959 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
964 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
965 // TODO: find better name instead of model name.
966 String displayName = Build.MODEL;
967 return new HdmiDeviceInfo(logicalAddress,
968 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
969 getVendorId(), displayName);
973 void handleMhlHotplugEvent(int portId, boolean connected) {
974 assertRunOnServiceThread();
975 // Hotplug event is used to add/remove MHL devices as TV input.
977 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
978 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
979 if (oldDevice != null) {
980 oldDevice.onDeviceRemoved();
981 Slog.i(TAG, "Old device of port " + portId + " is removed");
983 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
984 updateSafeMhlInput();
986 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
987 if (device != null) {
988 device.onDeviceRemoved();
989 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
990 updateSafeMhlInput();
992 Slog.w(TAG, "No device to remove:[portId=" + portId);
995 announceHotplugEvent(portId, connected);
999 void handleMhlBusModeChanged(int portId, int busmode) {
1000 assertRunOnServiceThread();
1001 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1002 if (device != null) {
1003 device.setBusMode(busmode);
1005 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1006 ", busmode:" + busmode + "]");
1011 void handleMhlBusOvercurrent(int portId, boolean on) {
1012 assertRunOnServiceThread();
1013 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1014 if (device != null) {
1015 device.onBusOvercurrentDetected(on);
1017 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
1022 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
1023 assertRunOnServiceThread();
1024 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1026 if (device != null) {
1027 device.setDeviceStatusChange(adopterId, deviceId);
1029 Slog.w(TAG, "No mhl device exists for device status event[portId:"
1030 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1035 private void updateSafeMhlInput() {
1036 assertRunOnServiceThread();
1037 List<HdmiDeviceInfo> inputs = Collections.emptyList();
1038 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
1039 for (int i = 0; i < devices.size(); ++i) {
1040 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
1041 HdmiDeviceInfo info = device.getInfo();
1043 if (inputs.isEmpty()) {
1044 inputs = new ArrayList<>();
1046 inputs.add(device.getInfo());
1049 synchronized (mLock) {
1050 mMhlDevices = inputs;
1054 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1058 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1059 private final IHdmiMhlVendorCommandListener mListener;
1061 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
1062 mListener = listener;
1066 public void binderDied() {
1067 mMhlVendorCommandListenerRecords.remove(this);
1071 // Record class that monitors the event of the caller of being killed. Used to clean up
1072 // the listener list and record list accordingly.
1073 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1074 private final IHdmiHotplugEventListener mListener;
1076 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1077 mListener = listener;
1081 public void binderDied() {
1082 synchronized (mLock) {
1083 mHotplugEventListenerRecords.remove(this);
1088 public boolean equals(Object obj) {
1089 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1090 if (obj == this) return true;
1091 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1092 return other.mListener == this.mListener;
1096 public int hashCode() {
1097 return mListener.hashCode();
1101 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1102 private final IHdmiDeviceEventListener mListener;
1104 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1105 mListener = listener;
1109 public void binderDied() {
1110 synchronized (mLock) {
1111 mDeviceEventListenerRecords.remove(this);
1116 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
1117 private final IHdmiSystemAudioModeChangeListener mListener;
1119 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1120 mListener = listener;
1124 public void binderDied() {
1125 synchronized (mLock) {
1126 mSystemAudioModeChangeListenerRecords.remove(this);
1131 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1132 private final IHdmiVendorCommandListener mListener;
1133 private final int mDeviceType;
1135 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1136 mListener = listener;
1137 mDeviceType = deviceType;
1141 public void binderDied() {
1142 synchronized (mLock) {
1143 mVendorCommandListenerRecords.remove(this);
1148 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
1149 private final IHdmiRecordListener mListener;
1151 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1152 mListener = listener;
1156 public void binderDied() {
1157 synchronized (mLock) {
1158 mRecordListenerRecord = null;
1163 private void enforceAccessPermission() {
1164 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1167 private final class BinderService extends IHdmiControlService.Stub {
1169 public int[] getSupportedTypes() {
1170 enforceAccessPermission();
1171 // mLocalDevices is an unmodifiable list - no lock necesary.
1172 int[] localDevices = new int[mLocalDevices.size()];
1173 for (int i = 0; i < localDevices.length; ++i) {
1174 localDevices[i] = mLocalDevices.get(i);
1176 return localDevices;
1180 public HdmiDeviceInfo getActiveSource() {
1181 enforceAccessPermission();
1182 HdmiCecLocalDeviceTv tv = tv();
1184 Slog.w(TAG, "Local tv device not available");
1187 ActiveSource activeSource = tv.getActiveSource();
1188 if (activeSource.isValid()) {
1189 return new HdmiDeviceInfo(activeSource.logicalAddress,
1190 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1191 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1193 int activePath = tv.getActivePath();
1194 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1195 HdmiDeviceInfo info = tv.getDeviceInfoByPath(activePath);
1196 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
1202 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1203 enforceAccessPermission();
1204 runOnServiceThread(new Runnable() {
1207 if (callback == null) {
1208 Slog.e(TAG, "Callback cannot be null");
1211 HdmiCecLocalDeviceTv tv = tv();
1213 Slog.w(TAG, "Local tv device not available");
1214 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1217 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1218 if (device != null) {
1219 if (device.getPortId() == tv.getActivePortId()) {
1220 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1223 // Upon selecting MHL device, we send RAP[Content On] to wake up
1224 // the connected mobile device, start routing control to switch ports.
1225 // callback is handled by MHL action.
1226 device.turnOn(callback);
1227 tv.doManualPortSwitching(device.getPortId(), null);
1230 tv.deviceSelect(deviceId, callback);
1236 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1237 enforceAccessPermission();
1238 runOnServiceThread(new Runnable() {
1241 if (callback == null) {
1242 Slog.e(TAG, "Callback cannot be null");
1245 HdmiCecLocalDeviceTv tv = tv();
1247 Slog.w(TAG, "Local tv device not available");
1248 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1251 tv.doManualPortSwitching(portId, callback);
1257 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1258 enforceAccessPermission();
1259 runOnServiceThread(new Runnable() {
1262 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1263 if (device != null) {
1264 device.sendKeyEvent(keyCode, isPressed);
1267 if (mCecController != null) {
1268 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1269 if (localDevice == null) {
1270 Slog.w(TAG, "Local device not available");
1273 localDevice.sendKeyEvent(keyCode, isPressed);
1280 public void oneTouchPlay(final IHdmiControlCallback callback) {
1281 enforceAccessPermission();
1282 runOnServiceThread(new Runnable() {
1285 HdmiControlService.this.oneTouchPlay(callback);
1291 public void queryDisplayStatus(final IHdmiControlCallback callback) {
1292 enforceAccessPermission();
1293 runOnServiceThread(new Runnable() {
1296 HdmiControlService.this.queryDisplayStatus(callback);
1302 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1303 enforceAccessPermission();
1304 HdmiControlService.this.addHotplugEventListener(listener);
1308 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1309 enforceAccessPermission();
1310 HdmiControlService.this.removeHotplugEventListener(listener);
1314 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1315 enforceAccessPermission();
1316 HdmiControlService.this.addDeviceEventListener(listener);
1320 public List<HdmiPortInfo> getPortInfo() {
1321 enforceAccessPermission();
1322 return HdmiControlService.this.getPortInfo();
1326 public boolean canChangeSystemAudioMode() {
1327 enforceAccessPermission();
1328 HdmiCecLocalDeviceTv tv = tv();
1332 return tv.hasSystemAudioDevice();
1336 public boolean getSystemAudioMode() {
1337 enforceAccessPermission();
1338 HdmiCecLocalDeviceTv tv = tv();
1342 return tv.isSystemAudioActivated();
1346 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1347 enforceAccessPermission();
1348 runOnServiceThread(new Runnable() {
1351 HdmiCecLocalDeviceTv tv = tv();
1353 Slog.w(TAG, "Local tv device not available");
1354 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1357 tv.changeSystemAudioMode(enabled, callback);
1363 public void addSystemAudioModeChangeListener(
1364 final IHdmiSystemAudioModeChangeListener listener) {
1365 enforceAccessPermission();
1366 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1370 public void removeSystemAudioModeChangeListener(
1371 final IHdmiSystemAudioModeChangeListener listener) {
1372 enforceAccessPermission();
1373 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1377 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1378 enforceAccessPermission();
1379 HdmiControlService.this.setInputChangeListener(listener);
1383 public List<HdmiDeviceInfo> getInputDevices() {
1384 enforceAccessPermission();
1385 // No need to hold the lock for obtaining TV device as the local device instance
1386 // is preserved while the HDMI control is enabled.
1387 HdmiCecLocalDeviceTv tv = tv();
1388 synchronized (mLock) {
1389 List<HdmiDeviceInfo> cecDevices = (tv == null)
1390 ? Collections.<HdmiDeviceInfo>emptyList()
1391 : tv.getSafeExternalInputsLocked();
1392 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1396 // Returns all the CEC devices on the bus including system audio, switch,
1397 // even those of reserved type.
1399 public List<HdmiDeviceInfo> getDeviceList() {
1400 enforceAccessPermission();
1401 HdmiCecLocalDeviceTv tv = tv();
1402 synchronized (mLock) {
1404 ? Collections.<HdmiDeviceInfo>emptyList()
1405 : tv.getSafeCecDevicesLocked();
1410 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1411 final int maxIndex) {
1412 enforceAccessPermission();
1413 runOnServiceThread(new Runnable() {
1416 HdmiCecLocalDeviceTv tv = tv();
1418 Slog.w(TAG, "Local tv device not available");
1421 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1427 public void setSystemAudioMute(final boolean mute) {
1428 enforceAccessPermission();
1429 runOnServiceThread(new Runnable() {
1432 HdmiCecLocalDeviceTv tv = tv();
1434 Slog.w(TAG, "Local tv device not available");
1437 tv.changeMute(mute);
1443 public void setArcMode(final boolean enabled) {
1444 enforceAccessPermission();
1445 runOnServiceThread(new Runnable() {
1448 HdmiCecLocalDeviceTv tv = tv();
1450 Slog.w(TAG, "Local tv device not available to change arc mode.");
1458 public void setProhibitMode(final boolean enabled) {
1459 enforceAccessPermission();
1460 if (!isTvDevice()) {
1463 HdmiControlService.this.setProhibitMode(enabled);
1467 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1468 final int deviceType) {
1469 enforceAccessPermission();
1470 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1474 public void sendVendorCommand(final int deviceType, final int targetAddress,
1475 final byte[] params, final boolean hasVendorId) {
1476 enforceAccessPermission();
1477 runOnServiceThread(new Runnable() {
1480 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1481 if (device == null) {
1482 Slog.w(TAG, "Local device not available");
1486 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1487 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1488 getVendorId(), params));
1490 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1491 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1498 public void sendStandby(final int deviceType, final int deviceId) {
1499 enforceAccessPermission();
1500 runOnServiceThread(new Runnable() {
1503 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1504 if (mhlDevice != null) {
1505 mhlDevice.sendStandby();
1508 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1509 if (device == null) {
1510 Slog.w(TAG, "Local device not available");
1513 device.sendStandby(deviceId);
1519 public void setHdmiRecordListener(IHdmiRecordListener listener) {
1520 enforceAccessPermission();
1521 HdmiControlService.this.setHdmiRecordListener(listener);
1525 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1526 enforceAccessPermission();
1527 runOnServiceThread(new Runnable() {
1530 if (!isTvDeviceEnabled()) {
1531 Slog.w(TAG, "TV device is not enabled.");
1534 tv().startOneTouchRecord(recorderAddress, recordSource);
1540 public void stopOneTouchRecord(final int recorderAddress) {
1541 enforceAccessPermission();
1542 runOnServiceThread(new Runnable() {
1545 if (!isTvDeviceEnabled()) {
1546 Slog.w(TAG, "TV device is not enabled.");
1549 tv().stopOneTouchRecord(recorderAddress);
1555 public void startTimerRecording(final int recorderAddress, final int sourceType,
1556 final byte[] recordSource) {
1557 enforceAccessPermission();
1558 runOnServiceThread(new Runnable() {
1561 if (!isTvDeviceEnabled()) {
1562 Slog.w(TAG, "TV device is not enabled.");
1565 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1571 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1572 final byte[] recordSource) {
1573 enforceAccessPermission();
1574 runOnServiceThread(new Runnable() {
1577 if (!isTvDeviceEnabled()) {
1578 Slog.w(TAG, "TV device is not enabled.");
1581 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1587 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1588 final byte[] data) {
1589 enforceAccessPermission();
1590 runOnServiceThread(new Runnable() {
1593 if (!isControlEnabled()) {
1594 Slog.w(TAG, "Hdmi control is disabled.");
1597 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1598 if (device == null) {
1599 Slog.w(TAG, "Invalid port id:" + portId);
1602 mMhlController.sendVendorCommand(portId, offset, length, data);
1608 public void addHdmiMhlVendorCommandListener(
1609 IHdmiMhlVendorCommandListener listener) {
1610 enforceAccessPermission();
1611 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1615 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1616 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1617 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1619 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1620 pw.println("mProhibitMode: " + mProhibitMode);
1621 if (mCecController != null) {
1622 pw.println("mCecController: ");
1623 pw.increaseIndent();
1624 mCecController.dump(pw);
1625 pw.decreaseIndent();
1628 pw.println("mMhlController: ");
1629 pw.increaseIndent();
1630 mMhlController.dump(pw);
1631 pw.decreaseIndent();
1633 pw.println("mPortInfo: ");
1634 pw.increaseIndent();
1635 for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1636 pw.println("- " + hdmiPortInfo);
1638 pw.decreaseIndent();
1639 pw.println("mPowerStatus: " + mPowerStatus);
1644 private void oneTouchPlay(final IHdmiControlCallback callback) {
1645 assertRunOnServiceThread();
1646 HdmiCecLocalDevicePlayback source = playback();
1647 if (source == null) {
1648 Slog.w(TAG, "Local playback device not available");
1649 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1652 source.oneTouchPlay(callback);
1656 private void queryDisplayStatus(final IHdmiControlCallback callback) {
1657 assertRunOnServiceThread();
1658 HdmiCecLocalDevicePlayback source = playback();
1659 if (source == null) {
1660 Slog.w(TAG, "Local playback device not available");
1661 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1664 source.queryDisplayStatus(callback);
1667 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1668 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1670 listener.asBinder().linkToDeath(record, 0);
1671 } catch (RemoteException e) {
1672 Slog.w(TAG, "Listener already died");
1675 synchronized (mLock) {
1676 mHotplugEventListenerRecords.add(record);
1679 // Inform the listener of the initial state of each HDMI port by generating
1681 runOnServiceThread(new Runnable() {
1684 synchronized (mLock) {
1685 if (!mHotplugEventListenerRecords.contains(record)) return;
1687 for (HdmiPortInfo port : mPortInfo) {
1688 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
1689 mCecController.isConnected(port.getId()));
1690 synchronized (mLock) {
1691 invokeHotplugEventListenerLocked(listener, event);
1698 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1699 synchronized (mLock) {
1700 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1701 if (record.mListener.asBinder() == listener.asBinder()) {
1702 listener.asBinder().unlinkToDeath(record, 0);
1703 mHotplugEventListenerRecords.remove(record);
1710 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1711 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1713 listener.asBinder().linkToDeath(record, 0);
1714 } catch (RemoteException e) {
1715 Slog.w(TAG, "Listener already died");
1718 synchronized (mLock) {
1719 mDeviceEventListenerRecords.add(record);
1723 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1724 synchronized (mLock) {
1725 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1727 record.mListener.onStatusChanged(device, status);
1728 } catch (RemoteException e) {
1729 Slog.e(TAG, "Failed to report device event:" + e);
1735 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1736 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1739 listener.asBinder().linkToDeath(record, 0);
1740 } catch (RemoteException e) {
1741 Slog.w(TAG, "Listener already died");
1744 synchronized (mLock) {
1745 mSystemAudioModeChangeListenerRecords.add(record);
1749 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1750 synchronized (mLock) {
1751 for (SystemAudioModeChangeListenerRecord record :
1752 mSystemAudioModeChangeListenerRecords) {
1753 if (record.mListener.asBinder() == listener) {
1754 listener.asBinder().unlinkToDeath(record, 0);
1755 mSystemAudioModeChangeListenerRecords.remove(record);
1762 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1763 private final IHdmiInputChangeListener mListener;
1765 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1766 mListener = listener;
1770 public void binderDied() {
1771 synchronized (mLock) {
1772 mInputChangeListenerRecord = null;
1777 private void setInputChangeListener(IHdmiInputChangeListener listener) {
1778 synchronized (mLock) {
1779 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1781 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1782 } catch (RemoteException e) {
1783 Slog.w(TAG, "Listener already died");
1789 void invokeInputChangeListener(HdmiDeviceInfo info) {
1790 synchronized (mLock) {
1791 if (mInputChangeListenerRecord != null) {
1793 mInputChangeListenerRecord.mListener.onChanged(info);
1794 } catch (RemoteException e) {
1795 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1801 private void setHdmiRecordListener(IHdmiRecordListener listener) {
1802 synchronized (mLock) {
1803 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1805 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1806 } catch (RemoteException e) {
1807 Slog.w(TAG, "Listener already died.", e);
1812 byte[] invokeRecordRequestListener(int recorderAddress) {
1813 synchronized (mLock) {
1814 if (mRecordListenerRecord != null) {
1816 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1817 } catch (RemoteException e) {
1818 Slog.w(TAG, "Failed to start record.", e);
1821 return EmptyArray.BYTE;
1825 void invokeOneTouchRecordResult(int recorderAddress, int result) {
1826 synchronized (mLock) {
1827 if (mRecordListenerRecord != null) {
1829 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
1830 } catch (RemoteException e) {
1831 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1837 void invokeTimerRecordingResult(int recorderAddress, int result) {
1838 synchronized (mLock) {
1839 if (mRecordListenerRecord != null) {
1841 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
1842 } catch (RemoteException e) {
1843 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1849 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
1850 synchronized (mLock) {
1851 if (mRecordListenerRecord != null) {
1853 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
1855 } catch (RemoteException e) {
1856 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1862 private void invokeCallback(IHdmiControlCallback callback, int result) {
1864 callback.onComplete(result);
1865 } catch (RemoteException e) {
1866 Slog.e(TAG, "Invoking callback failed:" + e);
1870 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1873 listener.onStatusChanged(enabled);
1874 } catch (RemoteException e) {
1875 Slog.e(TAG, "Invoking callback failed:" + e);
1879 private void announceHotplugEvent(int portId, boolean connected) {
1880 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1881 synchronized (mLock) {
1882 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1883 invokeHotplugEventListenerLocked(record.mListener, event);
1888 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1889 HdmiHotplugEvent event) {
1891 listener.onReceived(event);
1892 } catch (RemoteException e) {
1893 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1897 private HdmiCecLocalDeviceTv tv() {
1898 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1901 boolean isTvDevice() {
1902 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
1905 boolean isTvDeviceEnabled() {
1906 return isTvDevice() && tv() != null;
1909 private HdmiCecLocalDevicePlayback playback() {
1910 return (HdmiCecLocalDevicePlayback)
1911 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1914 AudioManager getAudioManager() {
1915 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1918 boolean isControlEnabled() {
1919 synchronized (mLock) {
1920 return mHdmiControlEnabled;
1925 int getPowerStatus() {
1926 assertRunOnServiceThread();
1927 return mPowerStatus;
1931 boolean isPowerOnOrTransient() {
1932 assertRunOnServiceThread();
1933 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1934 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1938 boolean isPowerStandbyOrTransient() {
1939 assertRunOnServiceThread();
1940 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1941 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1945 boolean isPowerStandby() {
1946 assertRunOnServiceThread();
1947 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1952 assertRunOnServiceThread();
1953 mWakeUpMessageReceived = true;
1954 mPowerManager.wakeUp(SystemClock.uptimeMillis());
1955 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1956 // the intent, the sequence will continue at onWakeUp().
1961 assertRunOnServiceThread();
1962 mStandbyMessageReceived = true;
1963 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
1964 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1965 // the intent, the sequence will continue at onStandby().
1969 private void onWakeUp() {
1970 assertRunOnServiceThread();
1971 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1972 if (mCecController != null) {
1973 if (mHdmiControlEnabled) {
1974 int startReason = INITIATED_BY_SCREEN_ON;
1975 if (mWakeUpMessageReceived) {
1976 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1978 initializeCec(startReason);
1981 Slog.i(TAG, "Device does not support HDMI-CEC.");
1983 // TODO: Initialize MHL local devices.
1987 private void onStandby() {
1988 assertRunOnServiceThread();
1989 if (!canGoToStandby()) return;
1990 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1991 invokeVendorCommandListenersOnControlStateChanged(false,
1992 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
1994 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1995 disableDevices(new PendingActionClearedCallback() {
1997 public void onCleared(HdmiCecLocalDevice device) {
1998 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1999 devices.remove(device);
2000 if (devices.isEmpty()) {
2001 onStandbyCompleted();
2002 // We will not clear local devices here, since some OEM/SOC will keep passing
2003 // the received packets until the application processor enters to the sleep
2010 private boolean canGoToStandby() {
2011 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2012 if (!device.canGoToStandby()) return false;
2018 private void onLanguageChanged(String language) {
2019 assertRunOnServiceThread();
2020 mLanguage = language;
2022 if (isTvDeviceEnabled()) {
2023 tv().broadcastMenuLanguage(language);
2028 String getLanguage() {
2029 assertRunOnServiceThread();
2033 private void disableDevices(PendingActionClearedCallback callback) {
2034 if (mCecController != null) {
2035 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2036 device.disableDevice(mStandbyMessageReceived, callback);
2040 mMhlController.clearAllLocalDevices();
2044 private void clearLocalDevices() {
2045 assertRunOnServiceThread();
2046 if (mCecController == null) {
2049 mCecController.clearLogicalAddress();
2050 mCecController.clearLocalDevices();
2054 private void onStandbyCompleted() {
2055 assertRunOnServiceThread();
2056 Slog.v(TAG, "onStandbyCompleted");
2058 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
2061 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2062 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2063 device.onStandby(mStandbyMessageReceived);
2065 mStandbyMessageReceived = false;
2066 mAddressAllocated = false;
2067 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
2070 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2071 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2073 listener.asBinder().linkToDeath(record, 0);
2074 } catch (RemoteException e) {
2075 Slog.w(TAG, "Listener already died");
2078 synchronized (mLock) {
2079 mVendorCommandListenerRecords.add(record);
2083 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2084 byte[] params, boolean hasVendorId) {
2085 synchronized (mLock) {
2086 if (mVendorCommandListenerRecords.isEmpty()) {
2089 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2090 if (record.mDeviceType != deviceType) {
2094 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
2095 } catch (RemoteException e) {
2096 Slog.e(TAG, "Failed to notify vendor command reception", e);
2103 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2104 synchronized (mLock) {
2105 if (mVendorCommandListenerRecords.isEmpty()) {
2108 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2110 record.mListener.onControlStateChanged(enabled, reason);
2111 } catch (RemoteException e) {
2112 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2119 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2120 HdmiMhlVendorCommandListenerRecord record =
2121 new HdmiMhlVendorCommandListenerRecord(listener);
2123 listener.asBinder().linkToDeath(record, 0);
2124 } catch (RemoteException e) {
2125 Slog.w(TAG, "Listener already died.");
2129 synchronized (mLock) {
2130 mMhlVendorCommandListenerRecords.add(record);
2134 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
2135 synchronized (mLock) {
2136 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
2138 record.mListener.onReceived(portId, offest, length, data);
2139 } catch (RemoteException e) {
2140 Slog.e(TAG, "Failed to notify MHL vendor command", e);
2146 boolean isProhibitMode() {
2147 synchronized (mLock) {
2148 return mProhibitMode;
2152 void setProhibitMode(boolean enabled) {
2153 synchronized (mLock) {
2154 mProhibitMode = enabled;
2159 void setCecOption(int key, int value) {
2160 assertRunOnServiceThread();
2161 mCecController.setOption(key, value);
2165 void setControlEnabled(boolean enabled) {
2166 assertRunOnServiceThread();
2168 synchronized (mLock) {
2169 mHdmiControlEnabled = enabled;
2173 enableHdmiControlService();
2176 // Call the vendor handler before the service is disabled.
2177 invokeVendorCommandListenersOnControlStateChanged(false,
2178 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2179 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2181 runOnServiceThread(new Runnable() {
2184 disableHdmiControlService();
2191 private void enableHdmiControlService() {
2192 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
2193 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2195 initializeCec(INITIATED_BY_ENABLE_CEC);
2199 private void disableHdmiControlService() {
2200 disableDevices(new PendingActionClearedCallback() {
2202 public void onCleared(HdmiCecLocalDevice device) {
2203 assertRunOnServiceThread();
2204 mCecController.flush(new Runnable() {
2207 mCecController.setOption(OPTION_CEC_ENABLE, DISABLED);
2208 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2209 clearLocalDevices();
2217 void setActivePortId(int portId) {
2218 assertRunOnServiceThread();
2219 mActivePortId = portId;
2221 // Resets last input for MHL, which stays valid only after the MHL device was selected,
2222 // and no further switching is done.
2223 setLastInputForMhl(Constants.INVALID_PORT_ID);
2227 void setLastInputForMhl(int portId) {
2228 assertRunOnServiceThread();
2229 mLastInputMhl = portId;
2233 int getLastInputForMhl() {
2234 assertRunOnServiceThread();
2235 return mLastInputMhl;
2239 * Performs input change, routing control for MHL device.
2241 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2242 * @param contentOn {@code true} if RAP data is content on; otherwise false
2245 void changeInputForMhl(int portId, boolean contentOn) {
2246 assertRunOnServiceThread();
2247 if (tv() == null) return;
2248 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
2249 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2251 public void onComplete(int result) throws RemoteException {
2252 // Keep the last input to switch back later when RAP[ContentOff] is received.
2253 // This effectively sets the port to invalid one if the switching is for
2255 setLastInputForMhl(lastInput);
2259 // MHL device is always directly connected to the port. Update the active port ID to avoid
2260 // unnecessary post-routing control task.
2261 tv().setActivePortId(portId);
2263 // The port is either the MHL-enabled port where the mobile device is connected, or
2264 // the last port to go back to when turnoff command is received. Note that the last port
2265 // may not be the MHL-enabled one. In this case the device info to be passed to
2266 // input change listener should be the one describing the corresponding HDMI port.
2267 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
2268 HdmiDeviceInfo info = (device != null) ? device.getInfo() : mPortDeviceMap.get(portId);
2269 invokeInputChangeListener(info);
2272 void setMhlInputChangeEnabled(boolean enabled) {
2273 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2275 synchronized (mLock) {
2276 mMhlInputChangeEnabled = enabled;
2280 boolean isMhlInputChangeEnabled() {
2281 synchronized (mLock) {
2282 return mMhlInputChangeEnabled;
2287 void displayOsd(int messageId) {
2288 assertRunOnServiceThread();
2289 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2290 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2291 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2292 HdmiControlService.PERMISSION);
2296 void displayOsd(int messageId, int extra) {
2297 assertRunOnServiceThread();
2298 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2299 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2300 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
2301 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2302 HdmiControlService.PERMISSION);