Move to Android N-MR1 SDK.
[android_tools.git] / sdk / sources / android-25 / com / android / server / hdmi / HdmiControlService.java
blob72ee218fba2c8d92d29b384ede8dc056d72f75ea
1 /*
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_CEC_SET_LANGUAGE;
27 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
28 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
29 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
30 import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
32 import android.annotation.Nullable;
33 import android.content.BroadcastReceiver;
34 import android.content.ContentResolver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.database.ContentObserver;
39 import android.hardware.hdmi.HdmiControlManager;
40 import android.hardware.hdmi.HdmiDeviceInfo;
41 import android.hardware.hdmi.HdmiHotplugEvent;
42 import android.hardware.hdmi.HdmiPortInfo;
43 import android.hardware.hdmi.IHdmiControlCallback;
44 import android.hardware.hdmi.IHdmiControlService;
45 import android.hardware.hdmi.IHdmiDeviceEventListener;
46 import android.hardware.hdmi.IHdmiHotplugEventListener;
47 import android.hardware.hdmi.IHdmiInputChangeListener;
48 import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
49 import android.hardware.hdmi.IHdmiRecordListener;
50 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
51 import android.hardware.hdmi.IHdmiVendorCommandListener;
52 import android.media.AudioManager;
53 import android.media.tv.TvInputManager;
54 import android.media.tv.TvInputManager.TvInputCallback;
55 import android.net.Uri;
56 import android.os.Build;
57 import android.os.Handler;
58 import android.os.HandlerThread;
59 import android.os.IBinder;
60 import android.os.Looper;
61 import android.os.PowerManager;
62 import android.os.RemoteException;
63 import android.os.SystemClock;
64 import android.os.SystemProperties;
65 import android.os.UserHandle;
66 import android.provider.Settings.Global;
67 import android.text.TextUtils;
68 import android.util.ArraySet;
69 import android.util.Slog;
70 import android.util.SparseArray;
71 import android.util.SparseIntArray;
73 import com.android.internal.annotations.GuardedBy;
74 import com.android.internal.util.IndentingPrintWriter;
75 import com.android.server.SystemService;
76 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
77 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
78 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
79 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
80 import com.android.server.hdmi.SelectRequestBuffer.DeviceSelectRequest;
81 import com.android.server.hdmi.SelectRequestBuffer.PortSelectRequest;
83 import libcore.util.EmptyArray;
85 import java.io.FileDescriptor;
86 import java.io.PrintWriter;
87 import java.util.ArrayList;
88 import java.util.Arrays;
89 import java.util.Collections;
90 import java.util.List;
91 import java.util.Locale;
93 /**
94 * Provides a service for sending and processing HDMI control messages,
95 * HDMI-CEC and MHL control command, and providing the information on both standard.
97 public final class HdmiControlService extends SystemService {
98 private static final String TAG = "HdmiControlService";
99 private final Locale HONG_KONG = new Locale("zh", "HK");
100 private final Locale MACAU = new Locale("zh", "MO");
102 static final String PERMISSION = "android.permission.HDMI_CEC";
104 // The reason code to initiate intializeCec().
105 static final int INITIATED_BY_ENABLE_CEC = 0;
106 static final int INITIATED_BY_BOOT_UP = 1;
107 static final int INITIATED_BY_SCREEN_ON = 2;
108 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
109 static final int INITIATED_BY_HOTPLUG = 4;
111 // The reason code representing the intent action that drives the standby
112 // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
113 // Intent.ACTION_SHUTDOWN.
114 static final int STANDBY_SCREEN_OFF = 0;
115 static final int STANDBY_SHUTDOWN = 1;
118 * Interface to report send result.
120 interface SendMessageCallback {
122 * Called when {@link HdmiControlService#sendCecCommand} is completed.
124 * @param error result of send request.
125 * <ul>
126 * <li>{@link Constants#SEND_RESULT_SUCCESS}
127 * <li>{@link Constants#SEND_RESULT_NAK}
128 * <li>{@link Constants#SEND_RESULT_FAILURE}
129 * </ul>
131 void onSendCompleted(int error);
135 * Interface to get a list of available logical devices.
137 interface DevicePollingCallback {
139 * Called when device polling is finished.
141 * @param ackedAddress a list of logical addresses of available devices
143 void onPollingFinished(List<Integer> ackedAddress);
146 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
147 @ServiceThreadOnly
148 @Override
149 public void onReceive(Context context, Intent intent) {
150 assertRunOnServiceThread();
151 switch (intent.getAction()) {
152 case Intent.ACTION_SCREEN_OFF:
153 if (isPowerOnOrTransient()) {
154 onStandby(STANDBY_SCREEN_OFF);
156 break;
157 case Intent.ACTION_SCREEN_ON:
158 if (isPowerStandbyOrTransient()) {
159 onWakeUp();
161 break;
162 case Intent.ACTION_CONFIGURATION_CHANGED:
163 String language = getMenuLanguage();
164 if (!mLanguage.equals(language)) {
165 onLanguageChanged(language);
167 break;
168 case Intent.ACTION_SHUTDOWN:
169 if (isPowerOnOrTransient()) {
170 onStandby(STANDBY_SHUTDOWN);
172 break;
176 private String getMenuLanguage() {
177 Locale locale = Locale.getDefault();
178 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
179 // Android always returns "zho" for all Chinese variants.
180 // Use "bibliographic" code defined in CEC639-2 for traditional
181 // Chinese used in Taiwan/Hong Kong/Macau.
182 return "chi";
183 } else {
184 return locale.getISO3Language();
189 // A thread to handle synchronous IO of CEC and MHL control service.
190 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
191 // and sparse call it shares a thread to handle IO operations.
192 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
194 // Used to synchronize the access to the service.
195 private final Object mLock = new Object();
197 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
198 private final List<Integer> mLocalDevices;
200 // List of records for hotplug event listener to handle the the caller killed in action.
201 @GuardedBy("mLock")
202 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
203 new ArrayList<>();
205 // List of records for device event listener to handle the caller killed in action.
206 @GuardedBy("mLock")
207 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
208 new ArrayList<>();
210 // List of records for vendor command listener to handle the caller killed in action.
211 @GuardedBy("mLock")
212 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
213 new ArrayList<>();
215 @GuardedBy("mLock")
216 private InputChangeListenerRecord mInputChangeListenerRecord;
218 @GuardedBy("mLock")
219 private HdmiRecordListenerRecord mRecordListenerRecord;
221 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
222 // handling will be disabled and no request will be handled.
223 @GuardedBy("mLock")
224 private boolean mHdmiControlEnabled;
226 // Set to true while the service is in normal mode. While set to false, no input change is
227 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
228 // system upgrade, etc., a.k.a. "prohibit mode".
229 @GuardedBy("mLock")
230 private boolean mProhibitMode;
232 // List of records for system audio mode change to handle the the caller killed in action.
233 private final ArrayList<SystemAudioModeChangeListenerRecord>
234 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
236 // Handler used to run a task in service thread.
237 private final Handler mHandler = new Handler();
239 private final SettingsObserver mSettingsObserver;
241 private final HdmiControlBroadcastReceiver
242 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
244 @Nullable
245 private HdmiCecController mCecController;
247 // HDMI port information. Stored in the unmodifiable list to keep the static information
248 // from being modified.
249 private List<HdmiPortInfo> mPortInfo;
251 // Map from path(physical address) to port ID.
252 private UnmodifiableSparseIntArray mPortIdMap;
254 // Map from port ID to HdmiPortInfo.
255 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
257 // Map from port ID to HdmiDeviceInfo.
258 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
260 private HdmiCecMessageValidator mMessageValidator;
262 @ServiceThreadOnly
263 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
265 @ServiceThreadOnly
266 private String mLanguage = Locale.getDefault().getISO3Language();
268 @ServiceThreadOnly
269 private boolean mStandbyMessageReceived = false;
271 @ServiceThreadOnly
272 private boolean mWakeUpMessageReceived = false;
274 @ServiceThreadOnly
275 private int mActivePortId = Constants.INVALID_PORT_ID;
277 // Set to true while the input change by MHL is allowed.
278 @GuardedBy("mLock")
279 private boolean mMhlInputChangeEnabled;
281 // List of records for MHL Vendor command listener to handle the caller killed in action.
282 @GuardedBy("mLock")
283 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
284 mMhlVendorCommandListenerRecords = new ArrayList<>();
286 @GuardedBy("mLock")
287 private List<HdmiDeviceInfo> mMhlDevices;
289 @Nullable
290 private HdmiMhlControllerStub mMhlController;
292 @Nullable
293 private TvInputManager mTvInputManager;
295 @Nullable
296 private PowerManager mPowerManager;
298 // Last input port before switching to the MHL port. Should switch back to this port
299 // when the mobile device sends the request one touch play with off.
300 // Gets invalidated if we go to other port/input.
301 @ServiceThreadOnly
302 private int mLastInputMhl = Constants.INVALID_PORT_ID;
304 // Set to true if the logical address allocation is completed.
305 private boolean mAddressAllocated = false;
307 // Buffer for processing the incoming cec messages while allocating logical addresses.
308 private final class CecMessageBuffer {
309 private List<HdmiCecMessage> mBuffer = new ArrayList<>();
311 public void bufferMessage(HdmiCecMessage message) {
312 switch (message.getOpcode()) {
313 case Constants.MESSAGE_ACTIVE_SOURCE:
314 bufferActiveSource(message);
315 break;
316 case Constants.MESSAGE_IMAGE_VIEW_ON:
317 case Constants.MESSAGE_TEXT_VIEW_ON:
318 bufferImageOrTextViewOn(message);
319 break;
320 // Add here if new message that needs to buffer
321 default:
322 // Do not need to buffer messages other than above
323 break;
327 public void processMessages() {
328 for (final HdmiCecMessage message : mBuffer) {
329 runOnServiceThread(new Runnable() {
330 @Override
331 public void run() {
332 handleCecCommand(message);
336 mBuffer.clear();
339 private void bufferActiveSource(HdmiCecMessage message) {
340 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
341 mBuffer.add(message);
345 private void bufferImageOrTextViewOn(HdmiCecMessage message) {
346 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
347 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
348 mBuffer.add(message);
352 // Returns true if the message is replaced
353 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
354 for (int i = 0; i < mBuffer.size(); i++) {
355 HdmiCecMessage bufferedMessage = mBuffer.get(i);
356 if (bufferedMessage.getOpcode() == opcode) {
357 mBuffer.set(i, message);
358 return true;
361 return false;
365 private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
367 private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
369 public HdmiControlService(Context context) {
370 super(context);
371 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
372 mSettingsObserver = new SettingsObserver(mHandler);
375 private static List<Integer> getIntList(String string) {
376 ArrayList<Integer> list = new ArrayList<>();
377 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
378 splitter.setString(string);
379 for (String item : splitter) {
380 try {
381 list.add(Integer.parseInt(item));
382 } catch (NumberFormatException e) {
383 Slog.w(TAG, "Can't parseInt: " + item);
386 return Collections.unmodifiableList(list);
389 @Override
390 public void onStart() {
391 mIoThread.start();
392 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
393 mProhibitMode = false;
394 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
395 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
397 mCecController = HdmiCecController.create(this);
398 if (mCecController != null) {
399 if (mHdmiControlEnabled) {
400 initializeCec(INITIATED_BY_BOOT_UP);
402 } else {
403 Slog.i(TAG, "Device does not support HDMI-CEC.");
404 return;
407 mMhlController = HdmiMhlControllerStub.create(this);
408 if (!mMhlController.isReady()) {
409 Slog.i(TAG, "Device does not support MHL-control.");
411 mMhlDevices = Collections.emptyList();
413 initPortInfo();
414 mMessageValidator = new HdmiCecMessageValidator(this);
415 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
417 if (mCecController != null) {
418 // Register broadcast receiver for power state change.
419 IntentFilter filter = new IntentFilter();
420 filter.addAction(Intent.ACTION_SCREEN_OFF);
421 filter.addAction(Intent.ACTION_SCREEN_ON);
422 filter.addAction(Intent.ACTION_SHUTDOWN);
423 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
424 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
426 // Register ContentObserver to monitor the settings change.
427 registerContentObserver();
429 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
432 @Override
433 public void onBootPhase(int phase) {
434 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
435 mTvInputManager = (TvInputManager) getContext().getSystemService(
436 Context.TV_INPUT_SERVICE);
437 mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
441 TvInputManager getTvInputManager() {
442 return mTvInputManager;
445 void registerTvInputCallback(TvInputCallback callback) {
446 if (mTvInputManager == null) return;
447 mTvInputManager.registerCallback(callback, mHandler);
450 void unregisterTvInputCallback(TvInputCallback callback) {
451 if (mTvInputManager == null) return;
452 mTvInputManager.unregisterCallback(callback);
455 PowerManager getPowerManager() {
456 return mPowerManager;
460 * Called when the initialization of local devices is complete.
462 private void onInitializeCecComplete(int initiatedBy) {
463 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
464 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
466 mWakeUpMessageReceived = false;
468 if (isTvDeviceEnabled()) {
469 mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
471 int reason = -1;
472 switch (initiatedBy) {
473 case INITIATED_BY_BOOT_UP:
474 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
475 break;
476 case INITIATED_BY_ENABLE_CEC:
477 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
478 break;
479 case INITIATED_BY_SCREEN_ON:
480 case INITIATED_BY_WAKE_UP_MESSAGE:
481 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
482 break;
484 if (reason != -1) {
485 invokeVendorCommandListenersOnControlStateChanged(true, reason);
489 private void registerContentObserver() {
490 ContentResolver resolver = getContext().getContentResolver();
491 String[] settings = new String[] {
492 Global.HDMI_CONTROL_ENABLED,
493 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
494 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
495 Global.MHL_INPUT_SWITCHING_ENABLED,
496 Global.MHL_POWER_CHARGE_ENABLED
498 for (String s : settings) {
499 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
500 UserHandle.USER_ALL);
504 private class SettingsObserver extends ContentObserver {
505 public SettingsObserver(Handler handler) {
506 super(handler);
509 // onChange is set up to run in service thread.
510 @Override
511 public void onChange(boolean selfChange, Uri uri) {
512 String option = uri.getLastPathSegment();
513 boolean enabled = readBooleanSetting(option, true);
514 switch (option) {
515 case Global.HDMI_CONTROL_ENABLED:
516 setControlEnabled(enabled);
517 break;
518 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
519 if (isTvDeviceEnabled()) {
520 tv().setAutoWakeup(enabled);
522 setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
523 break;
524 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
525 for (int type : mLocalDevices) {
526 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
527 if (localDevice != null) {
528 localDevice.setAutoDeviceOff(enabled);
531 // No need to propagate to HAL.
532 break;
533 case Global.MHL_INPUT_SWITCHING_ENABLED:
534 setMhlInputChangeEnabled(enabled);
535 break;
536 case Global.MHL_POWER_CHARGE_ENABLED:
537 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
538 break;
543 private static int toInt(boolean enabled) {
544 return enabled ? ENABLED : DISABLED;
547 boolean readBooleanSetting(String key, boolean defVal) {
548 ContentResolver cr = getContext().getContentResolver();
549 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
552 void writeBooleanSetting(String key, boolean value) {
553 ContentResolver cr = getContext().getContentResolver();
554 Global.putInt(cr, key, toInt(value));
557 private void initializeCec(int initiatedBy) {
558 mAddressAllocated = false;
559 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
560 mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(mLanguage));
561 initializeLocalDevices(initiatedBy);
564 @ServiceThreadOnly
565 private void initializeLocalDevices(final int initiatedBy) {
566 assertRunOnServiceThread();
567 // A container for [Device type, Local device info].
568 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
569 for (int type : mLocalDevices) {
570 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
571 if (localDevice == null) {
572 localDevice = HdmiCecLocalDevice.create(this, type);
574 localDevice.init();
575 localDevices.add(localDevice);
577 // It's now safe to flush existing local devices from mCecController since they were
578 // already moved to 'localDevices'.
579 clearLocalDevices();
580 allocateLogicalAddress(localDevices, initiatedBy);
583 @ServiceThreadOnly
584 private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
585 final int initiatedBy) {
586 assertRunOnServiceThread();
587 mCecController.clearLogicalAddress();
588 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
589 final int[] finished = new int[1];
590 mAddressAllocated = allocatingDevices.isEmpty();
592 // For TV device, select request can be invoked while address allocation or device
593 // discovery is in progress. Initialize the request here at the start of allocation,
594 // and process the collected requests later when the allocation and device discovery
595 // is all completed.
596 mSelectRequestBuffer.clear();
598 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
599 mCecController.allocateLogicalAddress(localDevice.getType(),
600 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
601 @Override
602 public void onAllocated(int deviceType, int logicalAddress) {
603 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
604 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
605 } else {
606 // Set POWER_STATUS_ON to all local devices because they share lifetime
607 // with system.
608 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
609 HdmiControlManager.POWER_STATUS_ON);
610 localDevice.setDeviceInfo(deviceInfo);
611 mCecController.addLocalDevice(deviceType, localDevice);
612 mCecController.addLogicalAddress(logicalAddress);
613 allocatedDevices.add(localDevice);
616 // Address allocation completed for all devices. Notify each device.
617 if (allocatingDevices.size() == ++finished[0]) {
618 mAddressAllocated = true;
619 if (initiatedBy != INITIATED_BY_HOTPLUG) {
620 // In case of the hotplug we don't call onInitializeCecComplete()
621 // since we reallocate the logical address only.
622 onInitializeCecComplete(initiatedBy);
624 notifyAddressAllocated(allocatedDevices, initiatedBy);
625 mCecMessageBuffer.processMessages();
632 @ServiceThreadOnly
633 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
634 assertRunOnServiceThread();
635 for (HdmiCecLocalDevice device : devices) {
636 int address = device.getDeviceInfo().getLogicalAddress();
637 device.handleAddressAllocated(address, initiatedBy);
639 if (isTvDeviceEnabled()) {
640 tv().setSelectRequestBuffer(mSelectRequestBuffer);
644 boolean isAddressAllocated() {
645 return mAddressAllocated;
648 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
649 // keep them in one place.
650 @ServiceThreadOnly
651 private void initPortInfo() {
652 assertRunOnServiceThread();
653 HdmiPortInfo[] cecPortInfo = null;
655 // CEC HAL provides majority of the info while MHL does only MHL support flag for
656 // each port. Return empty array if CEC HAL didn't provide the info.
657 if (mCecController != null) {
658 cecPortInfo = mCecController.getPortInfos();
660 if (cecPortInfo == null) {
661 return;
664 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
665 SparseIntArray portIdMap = new SparseIntArray();
666 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
667 for (HdmiPortInfo info : cecPortInfo) {
668 portIdMap.put(info.getAddress(), info.getId());
669 portInfoMap.put(info.getId(), info);
670 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
672 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
673 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
674 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
676 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
677 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
678 for (HdmiPortInfo info : mhlPortInfo) {
679 if (info.isMhlSupported()) {
680 mhlSupportedPorts.add(info.getId());
684 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
685 // cec port info if we do not have have port that supports MHL.
686 if (mhlSupportedPorts.isEmpty()) {
687 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
688 return;
690 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
691 for (HdmiPortInfo info : cecPortInfo) {
692 if (mhlSupportedPorts.contains(info.getId())) {
693 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
694 info.isCecSupported(), true, info.isArcSupported()));
695 } else {
696 result.add(info);
699 mPortInfo = Collections.unmodifiableList(result);
702 List<HdmiPortInfo> getPortInfo() {
703 return mPortInfo;
707 * Returns HDMI port information for the given port id.
709 * @param portId HDMI port id
710 * @return {@link HdmiPortInfo} for the given port
712 HdmiPortInfo getPortInfo(int portId) {
713 return mPortInfoMap.get(portId, null);
717 * Returns the routing path (physical address) of the HDMI port for the given
718 * port id.
720 int portIdToPath(int portId) {
721 HdmiPortInfo portInfo = getPortInfo(portId);
722 if (portInfo == null) {
723 Slog.e(TAG, "Cannot find the port info: " + portId);
724 return Constants.INVALID_PHYSICAL_ADDRESS;
726 return portInfo.getAddress();
730 * Returns the id of HDMI port located at the top of the hierarchy of
731 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
732 * the port id to be returned is the ID associated with the port address
733 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
735 int pathToPortId(int path) {
736 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
737 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
740 boolean isValidPortId(int portId) {
741 return getPortInfo(portId) != null;
745 * Returns {@link Looper} for IO operation.
747 * <p>Declared as package-private.
749 Looper getIoLooper() {
750 return mIoThread.getLooper();
754 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
755 * for tasks that are running on main service thread.
757 * <p>Declared as package-private.
759 Looper getServiceLooper() {
760 return mHandler.getLooper();
764 * Returns physical address of the device.
766 int getPhysicalAddress() {
767 return mCecController.getPhysicalAddress();
771 * Returns vendor id of CEC service.
773 int getVendorId() {
774 return mCecController.getVendorId();
777 @ServiceThreadOnly
778 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
779 assertRunOnServiceThread();
780 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
783 @ServiceThreadOnly
784 HdmiDeviceInfo getDeviceInfoByPort(int port) {
785 assertRunOnServiceThread();
786 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
787 if (info != null) {
788 return info.getInfo();
790 return null;
794 * Returns version of CEC.
796 int getCecVersion() {
797 return mCecController.getVersion();
801 * Whether a device of the specified physical address is connected to ARC enabled port.
803 boolean isConnectedToArcPort(int physicalAddress) {
804 int portId = pathToPortId(physicalAddress);
805 if (portId != Constants.INVALID_PORT_ID) {
806 return mPortInfoMap.get(portId).isArcSupported();
808 return false;
811 @ServiceThreadOnly
812 boolean isConnected(int portId) {
813 assertRunOnServiceThread();
814 return mCecController.isConnected(portId);
817 void runOnServiceThread(Runnable runnable) {
818 mHandler.post(runnable);
821 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
822 mHandler.postAtFrontOfQueue(runnable);
825 private void assertRunOnServiceThread() {
826 if (Looper.myLooper() != mHandler.getLooper()) {
827 throw new IllegalStateException("Should run on service thread.");
832 * Transmit a CEC command to CEC bus.
834 * @param command CEC command to send out
835 * @param callback interface used to the result of send command
837 @ServiceThreadOnly
838 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
839 assertRunOnServiceThread();
840 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
841 mCecController.sendCommand(command, callback);
842 } else {
843 HdmiLogger.error("Invalid message type:" + command);
844 if (callback != null) {
845 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
850 @ServiceThreadOnly
851 void sendCecCommand(HdmiCecMessage command) {
852 assertRunOnServiceThread();
853 sendCecCommand(command, null);
857 * Send <Feature Abort> command on the given CEC message if possible.
858 * If the aborted message is invalid, then it wont send the message.
859 * @param command original command to be aborted
860 * @param reason reason of feature abort
862 @ServiceThreadOnly
863 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
864 assertRunOnServiceThread();
865 mCecController.maySendFeatureAbortCommand(command, reason);
868 @ServiceThreadOnly
869 boolean handleCecCommand(HdmiCecMessage message) {
870 assertRunOnServiceThread();
871 if (!mAddressAllocated) {
872 mCecMessageBuffer.bufferMessage(message);
873 return true;
875 int errorCode = mMessageValidator.isValid(message);
876 if (errorCode != HdmiCecMessageValidator.OK) {
877 // We'll not response on the messages with the invalid source or destination
878 // or with parameter length shorter than specified in the standard.
879 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
880 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
882 return true;
884 return dispatchMessageToLocalDevice(message);
887 void setAudioReturnChannel(int portId, boolean enabled) {
888 mCecController.setAudioReturnChannel(portId, enabled);
891 @ServiceThreadOnly
892 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
893 assertRunOnServiceThread();
894 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
895 if (device.dispatchMessage(message)
896 && message.getDestination() != Constants.ADDR_BROADCAST) {
897 return true;
901 if (message.getDestination() != Constants.ADDR_BROADCAST) {
902 HdmiLogger.warning("Unhandled cec command:" + message);
904 return false;
908 * Called when a new hotplug event is issued.
910 * @param portId hdmi port number where hot plug event issued.
911 * @param connected whether to be plugged in or not
913 @ServiceThreadOnly
914 void onHotplug(int portId, boolean connected) {
915 assertRunOnServiceThread();
917 if (connected && !isTvDevice()) {
918 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
919 for (int type : mLocalDevices) {
920 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
921 if (localDevice == null) {
922 localDevice = HdmiCecLocalDevice.create(this, type);
923 localDevice.init();
925 localDevices.add(localDevice);
927 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
930 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
931 device.onHotplug(portId, connected);
933 announceHotplugEvent(portId, connected);
937 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
938 * devices.
940 * @param callback an interface used to get a list of all remote devices' address
941 * @param sourceAddress a logical address of source device where sends polling message
942 * @param pickStrategy strategy how to pick polling candidates
943 * @param retryCount the number of retry used to send polling message to remote devices
944 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
946 @ServiceThreadOnly
947 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
948 int retryCount) {
949 assertRunOnServiceThread();
950 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
951 retryCount);
954 private int checkPollStrategy(int pickStrategy) {
955 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
956 if (strategy == 0) {
957 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
959 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
960 if (iterationStrategy == 0) {
961 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
963 return strategy | iterationStrategy;
966 List<HdmiCecLocalDevice> getAllLocalDevices() {
967 assertRunOnServiceThread();
968 return mCecController.getLocalDeviceList();
971 Object getServiceLock() {
972 return mLock;
975 void setAudioStatus(boolean mute, int volume) {
976 AudioManager audioManager = getAudioManager();
977 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
978 if (mute) {
979 if (!muted) {
980 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
982 } else {
983 if (muted) {
984 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
986 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
987 // volume change notification back to hdmi control service.
988 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
989 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
993 void announceSystemAudioModeChange(boolean enabled) {
994 synchronized (mLock) {
995 for (SystemAudioModeChangeListenerRecord record :
996 mSystemAudioModeChangeListenerRecords) {
997 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
1002 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
1003 // TODO: find better name instead of model name.
1004 String displayName = Build.MODEL;
1005 return new HdmiDeviceInfo(logicalAddress,
1006 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
1007 getVendorId(), displayName);
1010 @ServiceThreadOnly
1011 void handleMhlHotplugEvent(int portId, boolean connected) {
1012 assertRunOnServiceThread();
1013 // Hotplug event is used to add/remove MHL devices as TV input.
1014 if (connected) {
1015 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1016 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
1017 if (oldDevice != null) {
1018 oldDevice.onDeviceRemoved();
1019 Slog.i(TAG, "Old device of port " + portId + " is removed");
1021 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1022 updateSafeMhlInput();
1023 } else {
1024 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
1025 if (device != null) {
1026 device.onDeviceRemoved();
1027 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1028 updateSafeMhlInput();
1029 } else {
1030 Slog.w(TAG, "No device to remove:[portId=" + portId);
1033 announceHotplugEvent(portId, connected);
1036 @ServiceThreadOnly
1037 void handleMhlBusModeChanged(int portId, int busmode) {
1038 assertRunOnServiceThread();
1039 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1040 if (device != null) {
1041 device.setBusMode(busmode);
1042 } else {
1043 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1044 ", busmode:" + busmode + "]");
1048 @ServiceThreadOnly
1049 void handleMhlBusOvercurrent(int portId, boolean on) {
1050 assertRunOnServiceThread();
1051 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1052 if (device != null) {
1053 device.onBusOvercurrentDetected(on);
1054 } else {
1055 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
1059 @ServiceThreadOnly
1060 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
1061 assertRunOnServiceThread();
1062 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1064 if (device != null) {
1065 device.setDeviceStatusChange(adopterId, deviceId);
1066 } else {
1067 Slog.w(TAG, "No mhl device exists for device status event[portId:"
1068 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1072 @ServiceThreadOnly
1073 private void updateSafeMhlInput() {
1074 assertRunOnServiceThread();
1075 List<HdmiDeviceInfo> inputs = Collections.emptyList();
1076 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
1077 for (int i = 0; i < devices.size(); ++i) {
1078 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
1079 HdmiDeviceInfo info = device.getInfo();
1080 if (info != null) {
1081 if (inputs.isEmpty()) {
1082 inputs = new ArrayList<>();
1084 inputs.add(device.getInfo());
1087 synchronized (mLock) {
1088 mMhlDevices = inputs;
1092 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1093 return mMhlDevices;
1096 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1097 private final IHdmiMhlVendorCommandListener mListener;
1099 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
1100 mListener = listener;
1103 @Override
1104 public void binderDied() {
1105 mMhlVendorCommandListenerRecords.remove(this);
1109 // Record class that monitors the event of the caller of being killed. Used to clean up
1110 // the listener list and record list accordingly.
1111 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1112 private final IHdmiHotplugEventListener mListener;
1114 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1115 mListener = listener;
1118 @Override
1119 public void binderDied() {
1120 synchronized (mLock) {
1121 mHotplugEventListenerRecords.remove(this);
1125 @Override
1126 public boolean equals(Object obj) {
1127 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1128 if (obj == this) return true;
1129 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1130 return other.mListener == this.mListener;
1133 @Override
1134 public int hashCode() {
1135 return mListener.hashCode();
1139 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1140 private final IHdmiDeviceEventListener mListener;
1142 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1143 mListener = listener;
1146 @Override
1147 public void binderDied() {
1148 synchronized (mLock) {
1149 mDeviceEventListenerRecords.remove(this);
1154 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
1155 private final IHdmiSystemAudioModeChangeListener mListener;
1157 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1158 mListener = listener;
1161 @Override
1162 public void binderDied() {
1163 synchronized (mLock) {
1164 mSystemAudioModeChangeListenerRecords.remove(this);
1169 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1170 private final IHdmiVendorCommandListener mListener;
1171 private final int mDeviceType;
1173 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1174 mListener = listener;
1175 mDeviceType = deviceType;
1178 @Override
1179 public void binderDied() {
1180 synchronized (mLock) {
1181 mVendorCommandListenerRecords.remove(this);
1186 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
1187 private final IHdmiRecordListener mListener;
1189 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1190 mListener = listener;
1193 @Override
1194 public void binderDied() {
1195 synchronized (mLock) {
1196 if (mRecordListenerRecord == this) {
1197 mRecordListenerRecord = null;
1203 private void enforceAccessPermission() {
1204 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1207 private final class BinderService extends IHdmiControlService.Stub {
1208 @Override
1209 public int[] getSupportedTypes() {
1210 enforceAccessPermission();
1211 // mLocalDevices is an unmodifiable list - no lock necesary.
1212 int[] localDevices = new int[mLocalDevices.size()];
1213 for (int i = 0; i < localDevices.length; ++i) {
1214 localDevices[i] = mLocalDevices.get(i);
1216 return localDevices;
1219 @Override
1220 public HdmiDeviceInfo getActiveSource() {
1221 enforceAccessPermission();
1222 HdmiCecLocalDeviceTv tv = tv();
1223 if (tv == null) {
1224 Slog.w(TAG, "Local tv device not available");
1225 return null;
1227 ActiveSource activeSource = tv.getActiveSource();
1228 if (activeSource.isValid()) {
1229 return new HdmiDeviceInfo(activeSource.logicalAddress,
1230 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1231 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1233 int activePath = tv.getActivePath();
1234 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1235 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
1236 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
1238 return null;
1241 @Override
1242 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1243 enforceAccessPermission();
1244 runOnServiceThread(new Runnable() {
1245 @Override
1246 public void run() {
1247 if (callback == null) {
1248 Slog.e(TAG, "Callback cannot be null");
1249 return;
1251 HdmiCecLocalDeviceTv tv = tv();
1252 if (tv == null) {
1253 if (!mAddressAllocated) {
1254 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1255 HdmiControlService.this, deviceId, callback));
1256 return;
1258 Slog.w(TAG, "Local tv device not available");
1259 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1260 return;
1262 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1263 if (device != null) {
1264 if (device.getPortId() == tv.getActivePortId()) {
1265 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1266 return;
1268 // Upon selecting MHL device, we send RAP[Content On] to wake up
1269 // the connected mobile device, start routing control to switch ports.
1270 // callback is handled by MHL action.
1271 device.turnOn(callback);
1272 tv.doManualPortSwitching(device.getPortId(), null);
1273 return;
1275 tv.deviceSelect(deviceId, callback);
1280 @Override
1281 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1282 enforceAccessPermission();
1283 runOnServiceThread(new Runnable() {
1284 @Override
1285 public void run() {
1286 if (callback == null) {
1287 Slog.e(TAG, "Callback cannot be null");
1288 return;
1290 HdmiCecLocalDeviceTv tv = tv();
1291 if (tv == null) {
1292 if (!mAddressAllocated) {
1293 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1294 HdmiControlService.this, portId, callback));
1295 return;
1297 Slog.w(TAG, "Local tv device not available");
1298 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1299 return;
1301 tv.doManualPortSwitching(portId, callback);
1306 @Override
1307 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1308 enforceAccessPermission();
1309 runOnServiceThread(new Runnable() {
1310 @Override
1311 public void run() {
1312 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1313 if (device != null) {
1314 device.sendKeyEvent(keyCode, isPressed);
1315 return;
1317 if (mCecController != null) {
1318 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1319 if (localDevice == null) {
1320 Slog.w(TAG, "Local device not available");
1321 return;
1323 localDevice.sendKeyEvent(keyCode, isPressed);
1329 @Override
1330 public void oneTouchPlay(final IHdmiControlCallback callback) {
1331 enforceAccessPermission();
1332 runOnServiceThread(new Runnable() {
1333 @Override
1334 public void run() {
1335 HdmiControlService.this.oneTouchPlay(callback);
1340 @Override
1341 public void queryDisplayStatus(final IHdmiControlCallback callback) {
1342 enforceAccessPermission();
1343 runOnServiceThread(new Runnable() {
1344 @Override
1345 public void run() {
1346 HdmiControlService.this.queryDisplayStatus(callback);
1351 @Override
1352 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1353 enforceAccessPermission();
1354 HdmiControlService.this.addHotplugEventListener(listener);
1357 @Override
1358 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1359 enforceAccessPermission();
1360 HdmiControlService.this.removeHotplugEventListener(listener);
1363 @Override
1364 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1365 enforceAccessPermission();
1366 HdmiControlService.this.addDeviceEventListener(listener);
1369 @Override
1370 public List<HdmiPortInfo> getPortInfo() {
1371 enforceAccessPermission();
1372 return HdmiControlService.this.getPortInfo();
1375 @Override
1376 public boolean canChangeSystemAudioMode() {
1377 enforceAccessPermission();
1378 HdmiCecLocalDeviceTv tv = tv();
1379 if (tv == null) {
1380 return false;
1382 return tv.hasSystemAudioDevice();
1385 @Override
1386 public boolean getSystemAudioMode() {
1387 enforceAccessPermission();
1388 HdmiCecLocalDeviceTv tv = tv();
1389 if (tv == null) {
1390 return false;
1392 return tv.isSystemAudioActivated();
1395 @Override
1396 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1397 enforceAccessPermission();
1398 runOnServiceThread(new Runnable() {
1399 @Override
1400 public void run() {
1401 HdmiCecLocalDeviceTv tv = tv();
1402 if (tv == null) {
1403 Slog.w(TAG, "Local tv device not available");
1404 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1405 return;
1407 tv.changeSystemAudioMode(enabled, callback);
1412 @Override
1413 public void addSystemAudioModeChangeListener(
1414 final IHdmiSystemAudioModeChangeListener listener) {
1415 enforceAccessPermission();
1416 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1419 @Override
1420 public void removeSystemAudioModeChangeListener(
1421 final IHdmiSystemAudioModeChangeListener listener) {
1422 enforceAccessPermission();
1423 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1426 @Override
1427 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1428 enforceAccessPermission();
1429 HdmiControlService.this.setInputChangeListener(listener);
1432 @Override
1433 public List<HdmiDeviceInfo> getInputDevices() {
1434 enforceAccessPermission();
1435 // No need to hold the lock for obtaining TV device as the local device instance
1436 // is preserved while the HDMI control is enabled.
1437 HdmiCecLocalDeviceTv tv = tv();
1438 synchronized (mLock) {
1439 List<HdmiDeviceInfo> cecDevices = (tv == null)
1440 ? Collections.<HdmiDeviceInfo>emptyList()
1441 : tv.getSafeExternalInputsLocked();
1442 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1446 // Returns all the CEC devices on the bus including system audio, switch,
1447 // even those of reserved type.
1448 @Override
1449 public List<HdmiDeviceInfo> getDeviceList() {
1450 enforceAccessPermission();
1451 HdmiCecLocalDeviceTv tv = tv();
1452 synchronized (mLock) {
1453 return (tv == null)
1454 ? Collections.<HdmiDeviceInfo>emptyList()
1455 : tv.getSafeCecDevicesLocked();
1459 @Override
1460 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1461 final int maxIndex) {
1462 enforceAccessPermission();
1463 runOnServiceThread(new Runnable() {
1464 @Override
1465 public void run() {
1466 HdmiCecLocalDeviceTv tv = tv();
1467 if (tv == null) {
1468 Slog.w(TAG, "Local tv device not available");
1469 return;
1471 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1476 @Override
1477 public void setSystemAudioMute(final boolean mute) {
1478 enforceAccessPermission();
1479 runOnServiceThread(new Runnable() {
1480 @Override
1481 public void run() {
1482 HdmiCecLocalDeviceTv tv = tv();
1483 if (tv == null) {
1484 Slog.w(TAG, "Local tv device not available");
1485 return;
1487 tv.changeMute(mute);
1492 @Override
1493 public void setArcMode(final boolean enabled) {
1494 enforceAccessPermission();
1495 runOnServiceThread(new Runnable() {
1496 @Override
1497 public void run() {
1498 HdmiCecLocalDeviceTv tv = tv();
1499 if (tv == null) {
1500 Slog.w(TAG, "Local tv device not available to change arc mode.");
1501 return;
1507 @Override
1508 public void setProhibitMode(final boolean enabled) {
1509 enforceAccessPermission();
1510 if (!isTvDevice()) {
1511 return;
1513 HdmiControlService.this.setProhibitMode(enabled);
1516 @Override
1517 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1518 final int deviceType) {
1519 enforceAccessPermission();
1520 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1523 @Override
1524 public void sendVendorCommand(final int deviceType, final int targetAddress,
1525 final byte[] params, final boolean hasVendorId) {
1526 enforceAccessPermission();
1527 runOnServiceThread(new Runnable() {
1528 @Override
1529 public void run() {
1530 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1531 if (device == null) {
1532 Slog.w(TAG, "Local device not available");
1533 return;
1535 if (hasVendorId) {
1536 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1537 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1538 getVendorId(), params));
1539 } else {
1540 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1541 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1547 @Override
1548 public void sendStandby(final int deviceType, final int deviceId) {
1549 enforceAccessPermission();
1550 runOnServiceThread(new Runnable() {
1551 @Override
1552 public void run() {
1553 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1554 if (mhlDevice != null) {
1555 mhlDevice.sendStandby();
1556 return;
1558 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1559 if (device == null) {
1560 Slog.w(TAG, "Local device not available");
1561 return;
1563 device.sendStandby(deviceId);
1568 @Override
1569 public void setHdmiRecordListener(IHdmiRecordListener listener) {
1570 enforceAccessPermission();
1571 HdmiControlService.this.setHdmiRecordListener(listener);
1574 @Override
1575 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1576 enforceAccessPermission();
1577 runOnServiceThread(new Runnable() {
1578 @Override
1579 public void run() {
1580 if (!isTvDeviceEnabled()) {
1581 Slog.w(TAG, "TV device is not enabled.");
1582 return;
1584 tv().startOneTouchRecord(recorderAddress, recordSource);
1589 @Override
1590 public void stopOneTouchRecord(final int recorderAddress) {
1591 enforceAccessPermission();
1592 runOnServiceThread(new Runnable() {
1593 @Override
1594 public void run() {
1595 if (!isTvDeviceEnabled()) {
1596 Slog.w(TAG, "TV device is not enabled.");
1597 return;
1599 tv().stopOneTouchRecord(recorderAddress);
1604 @Override
1605 public void startTimerRecording(final int recorderAddress, final int sourceType,
1606 final byte[] recordSource) {
1607 enforceAccessPermission();
1608 runOnServiceThread(new Runnable() {
1609 @Override
1610 public void run() {
1611 if (!isTvDeviceEnabled()) {
1612 Slog.w(TAG, "TV device is not enabled.");
1613 return;
1615 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1620 @Override
1621 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1622 final byte[] recordSource) {
1623 enforceAccessPermission();
1624 runOnServiceThread(new Runnable() {
1625 @Override
1626 public void run() {
1627 if (!isTvDeviceEnabled()) {
1628 Slog.w(TAG, "TV device is not enabled.");
1629 return;
1631 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1636 @Override
1637 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1638 final byte[] data) {
1639 enforceAccessPermission();
1640 runOnServiceThread(new Runnable() {
1641 @Override
1642 public void run() {
1643 if (!isControlEnabled()) {
1644 Slog.w(TAG, "Hdmi control is disabled.");
1645 return ;
1647 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1648 if (device == null) {
1649 Slog.w(TAG, "Invalid port id:" + portId);
1650 return;
1652 mMhlController.sendVendorCommand(portId, offset, length, data);
1657 @Override
1658 public void addHdmiMhlVendorCommandListener(
1659 IHdmiMhlVendorCommandListener listener) {
1660 enforceAccessPermission();
1661 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1664 @Override
1665 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1666 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1667 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1669 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1670 pw.println("mProhibitMode: " + mProhibitMode);
1671 if (mCecController != null) {
1672 pw.println("mCecController: ");
1673 pw.increaseIndent();
1674 mCecController.dump(pw);
1675 pw.decreaseIndent();
1678 pw.println("mMhlController: ");
1679 pw.increaseIndent();
1680 mMhlController.dump(pw);
1681 pw.decreaseIndent();
1683 pw.println("mPortInfo: ");
1684 pw.increaseIndent();
1685 for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1686 pw.println("- " + hdmiPortInfo);
1688 pw.decreaseIndent();
1689 pw.println("mPowerStatus: " + mPowerStatus);
1693 @ServiceThreadOnly
1694 private void oneTouchPlay(final IHdmiControlCallback callback) {
1695 assertRunOnServiceThread();
1696 HdmiCecLocalDevicePlayback source = playback();
1697 if (source == null) {
1698 Slog.w(TAG, "Local playback device not available");
1699 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1700 return;
1702 source.oneTouchPlay(callback);
1705 @ServiceThreadOnly
1706 private void queryDisplayStatus(final IHdmiControlCallback callback) {
1707 assertRunOnServiceThread();
1708 HdmiCecLocalDevicePlayback source = playback();
1709 if (source == null) {
1710 Slog.w(TAG, "Local playback device not available");
1711 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1712 return;
1714 source.queryDisplayStatus(callback);
1717 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1718 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1719 try {
1720 listener.asBinder().linkToDeath(record, 0);
1721 } catch (RemoteException e) {
1722 Slog.w(TAG, "Listener already died");
1723 return;
1725 synchronized (mLock) {
1726 mHotplugEventListenerRecords.add(record);
1729 // Inform the listener of the initial state of each HDMI port by generating
1730 // hotplug events.
1731 runOnServiceThread(new Runnable() {
1732 @Override
1733 public void run() {
1734 synchronized (mLock) {
1735 if (!mHotplugEventListenerRecords.contains(record)) return;
1737 for (HdmiPortInfo port : mPortInfo) {
1738 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
1739 mCecController.isConnected(port.getId()));
1740 synchronized (mLock) {
1741 invokeHotplugEventListenerLocked(listener, event);
1748 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1749 synchronized (mLock) {
1750 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1751 if (record.mListener.asBinder() == listener.asBinder()) {
1752 listener.asBinder().unlinkToDeath(record, 0);
1753 mHotplugEventListenerRecords.remove(record);
1754 break;
1760 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1761 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1762 try {
1763 listener.asBinder().linkToDeath(record, 0);
1764 } catch (RemoteException e) {
1765 Slog.w(TAG, "Listener already died");
1766 return;
1768 synchronized (mLock) {
1769 mDeviceEventListenerRecords.add(record);
1773 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1774 synchronized (mLock) {
1775 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1776 try {
1777 record.mListener.onStatusChanged(device, status);
1778 } catch (RemoteException e) {
1779 Slog.e(TAG, "Failed to report device event:" + e);
1785 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1786 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1787 listener);
1788 try {
1789 listener.asBinder().linkToDeath(record, 0);
1790 } catch (RemoteException e) {
1791 Slog.w(TAG, "Listener already died");
1792 return;
1794 synchronized (mLock) {
1795 mSystemAudioModeChangeListenerRecords.add(record);
1799 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1800 synchronized (mLock) {
1801 for (SystemAudioModeChangeListenerRecord record :
1802 mSystemAudioModeChangeListenerRecords) {
1803 if (record.mListener.asBinder() == listener) {
1804 listener.asBinder().unlinkToDeath(record, 0);
1805 mSystemAudioModeChangeListenerRecords.remove(record);
1806 break;
1812 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1813 private final IHdmiInputChangeListener mListener;
1815 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1816 mListener = listener;
1819 @Override
1820 public void binderDied() {
1821 synchronized (mLock) {
1822 if (mInputChangeListenerRecord == this) {
1823 mInputChangeListenerRecord = null;
1829 private void setInputChangeListener(IHdmiInputChangeListener listener) {
1830 synchronized (mLock) {
1831 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1832 try {
1833 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1834 } catch (RemoteException e) {
1835 Slog.w(TAG, "Listener already died");
1836 return;
1841 void invokeInputChangeListener(HdmiDeviceInfo info) {
1842 synchronized (mLock) {
1843 if (mInputChangeListenerRecord != null) {
1844 try {
1845 mInputChangeListenerRecord.mListener.onChanged(info);
1846 } catch (RemoteException e) {
1847 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1853 private void setHdmiRecordListener(IHdmiRecordListener listener) {
1854 synchronized (mLock) {
1855 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1856 try {
1857 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1858 } catch (RemoteException e) {
1859 Slog.w(TAG, "Listener already died.", e);
1864 byte[] invokeRecordRequestListener(int recorderAddress) {
1865 synchronized (mLock) {
1866 if (mRecordListenerRecord != null) {
1867 try {
1868 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1869 } catch (RemoteException e) {
1870 Slog.w(TAG, "Failed to start record.", e);
1873 return EmptyArray.BYTE;
1877 void invokeOneTouchRecordResult(int recorderAddress, int result) {
1878 synchronized (mLock) {
1879 if (mRecordListenerRecord != null) {
1880 try {
1881 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
1882 } catch (RemoteException e) {
1883 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1889 void invokeTimerRecordingResult(int recorderAddress, int result) {
1890 synchronized (mLock) {
1891 if (mRecordListenerRecord != null) {
1892 try {
1893 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
1894 } catch (RemoteException e) {
1895 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1901 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
1902 synchronized (mLock) {
1903 if (mRecordListenerRecord != null) {
1904 try {
1905 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
1906 result);
1907 } catch (RemoteException e) {
1908 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1914 private void invokeCallback(IHdmiControlCallback callback, int result) {
1915 try {
1916 callback.onComplete(result);
1917 } catch (RemoteException e) {
1918 Slog.e(TAG, "Invoking callback failed:" + e);
1922 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1923 boolean enabled) {
1924 try {
1925 listener.onStatusChanged(enabled);
1926 } catch (RemoteException e) {
1927 Slog.e(TAG, "Invoking callback failed:" + e);
1931 private void announceHotplugEvent(int portId, boolean connected) {
1932 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1933 synchronized (mLock) {
1934 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1935 invokeHotplugEventListenerLocked(record.mListener, event);
1940 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1941 HdmiHotplugEvent event) {
1942 try {
1943 listener.onReceived(event);
1944 } catch (RemoteException e) {
1945 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1949 public HdmiCecLocalDeviceTv tv() {
1950 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1953 boolean isTvDevice() {
1954 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
1957 boolean isTvDeviceEnabled() {
1958 return isTvDevice() && tv() != null;
1961 private HdmiCecLocalDevicePlayback playback() {
1962 return (HdmiCecLocalDevicePlayback)
1963 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1966 AudioManager getAudioManager() {
1967 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1970 boolean isControlEnabled() {
1971 synchronized (mLock) {
1972 return mHdmiControlEnabled;
1976 @ServiceThreadOnly
1977 int getPowerStatus() {
1978 assertRunOnServiceThread();
1979 return mPowerStatus;
1982 @ServiceThreadOnly
1983 boolean isPowerOnOrTransient() {
1984 assertRunOnServiceThread();
1985 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1986 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1989 @ServiceThreadOnly
1990 boolean isPowerStandbyOrTransient() {
1991 assertRunOnServiceThread();
1992 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1993 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1996 @ServiceThreadOnly
1997 boolean isPowerStandby() {
1998 assertRunOnServiceThread();
1999 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
2002 @ServiceThreadOnly
2003 void wakeUp() {
2004 assertRunOnServiceThread();
2005 mWakeUpMessageReceived = true;
2006 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
2007 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2008 // the intent, the sequence will continue at onWakeUp().
2011 @ServiceThreadOnly
2012 void standby() {
2013 assertRunOnServiceThread();
2014 if (!canGoToStandby()) {
2015 return;
2017 mStandbyMessageReceived = true;
2018 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2019 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2020 // the intent, the sequence will continue at onStandby().
2023 @ServiceThreadOnly
2024 private void onWakeUp() {
2025 assertRunOnServiceThread();
2026 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
2027 if (mCecController != null) {
2028 if (mHdmiControlEnabled) {
2029 int startReason = INITIATED_BY_SCREEN_ON;
2030 if (mWakeUpMessageReceived) {
2031 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2033 initializeCec(startReason);
2035 } else {
2036 Slog.i(TAG, "Device does not support HDMI-CEC.");
2038 // TODO: Initialize MHL local devices.
2041 @ServiceThreadOnly
2042 private void onStandby(final int standbyAction) {
2043 assertRunOnServiceThread();
2044 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
2045 invokeVendorCommandListenersOnControlStateChanged(false,
2046 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
2047 if (!canGoToStandby()) {
2048 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2049 return;
2052 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2053 disableDevices(new PendingActionClearedCallback() {
2054 @Override
2055 public void onCleared(HdmiCecLocalDevice device) {
2056 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2057 devices.remove(device);
2058 if (devices.isEmpty()) {
2059 onStandbyCompleted(standbyAction);
2060 // We will not clear local devices here, since some OEM/SOC will keep passing
2061 // the received packets until the application processor enters to the sleep
2062 // actually.
2068 private boolean canGoToStandby() {
2069 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2070 if (!device.canGoToStandby()) return false;
2072 return true;
2075 @ServiceThreadOnly
2076 private void onLanguageChanged(String language) {
2077 assertRunOnServiceThread();
2078 mLanguage = language;
2080 if (isTvDeviceEnabled()) {
2081 tv().broadcastMenuLanguage(language);
2082 mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(language));
2086 @ServiceThreadOnly
2087 String getLanguage() {
2088 assertRunOnServiceThread();
2089 return mLanguage;
2092 private void disableDevices(PendingActionClearedCallback callback) {
2093 if (mCecController != null) {
2094 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2095 device.disableDevice(mStandbyMessageReceived, callback);
2099 mMhlController.clearAllLocalDevices();
2102 @ServiceThreadOnly
2103 private void clearLocalDevices() {
2104 assertRunOnServiceThread();
2105 if (mCecController == null) {
2106 return;
2108 mCecController.clearLogicalAddress();
2109 mCecController.clearLocalDevices();
2112 @ServiceThreadOnly
2113 private void onStandbyCompleted(int standbyAction) {
2114 assertRunOnServiceThread();
2115 Slog.v(TAG, "onStandbyCompleted");
2117 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
2118 return;
2120 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2121 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2122 device.onStandby(mStandbyMessageReceived, standbyAction);
2124 mStandbyMessageReceived = false;
2125 mAddressAllocated = false;
2126 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
2127 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
2130 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2131 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2132 try {
2133 listener.asBinder().linkToDeath(record, 0);
2134 } catch (RemoteException e) {
2135 Slog.w(TAG, "Listener already died");
2136 return;
2138 synchronized (mLock) {
2139 mVendorCommandListenerRecords.add(record);
2143 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2144 byte[] params, boolean hasVendorId) {
2145 synchronized (mLock) {
2146 if (mVendorCommandListenerRecords.isEmpty()) {
2147 return false;
2149 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2150 if (record.mDeviceType != deviceType) {
2151 continue;
2153 try {
2154 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
2155 } catch (RemoteException e) {
2156 Slog.e(TAG, "Failed to notify vendor command reception", e);
2159 return true;
2163 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2164 synchronized (mLock) {
2165 if (mVendorCommandListenerRecords.isEmpty()) {
2166 return false;
2168 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2169 try {
2170 record.mListener.onControlStateChanged(enabled, reason);
2171 } catch (RemoteException e) {
2172 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2175 return true;
2179 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2180 HdmiMhlVendorCommandListenerRecord record =
2181 new HdmiMhlVendorCommandListenerRecord(listener);
2182 try {
2183 listener.asBinder().linkToDeath(record, 0);
2184 } catch (RemoteException e) {
2185 Slog.w(TAG, "Listener already died.");
2186 return;
2189 synchronized (mLock) {
2190 mMhlVendorCommandListenerRecords.add(record);
2194 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
2195 synchronized (mLock) {
2196 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
2197 try {
2198 record.mListener.onReceived(portId, offest, length, data);
2199 } catch (RemoteException e) {
2200 Slog.e(TAG, "Failed to notify MHL vendor command", e);
2206 boolean isProhibitMode() {
2207 synchronized (mLock) {
2208 return mProhibitMode;
2212 void setProhibitMode(boolean enabled) {
2213 synchronized (mLock) {
2214 mProhibitMode = enabled;
2218 @ServiceThreadOnly
2219 void setCecOption(int key, int value) {
2220 assertRunOnServiceThread();
2221 mCecController.setOption(key, value);
2224 @ServiceThreadOnly
2225 void setControlEnabled(boolean enabled) {
2226 assertRunOnServiceThread();
2228 synchronized (mLock) {
2229 mHdmiControlEnabled = enabled;
2232 if (enabled) {
2233 enableHdmiControlService();
2234 return;
2236 // Call the vendor handler before the service is disabled.
2237 invokeVendorCommandListenersOnControlStateChanged(false,
2238 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2239 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2240 // a chance to run.
2241 runOnServiceThread(new Runnable() {
2242 @Override
2243 public void run() {
2244 disableHdmiControlService();
2247 return;
2250 @ServiceThreadOnly
2251 private void enableHdmiControlService() {
2252 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
2253 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2255 initializeCec(INITIATED_BY_ENABLE_CEC);
2258 @ServiceThreadOnly
2259 private void disableHdmiControlService() {
2260 disableDevices(new PendingActionClearedCallback() {
2261 @Override
2262 public void onCleared(HdmiCecLocalDevice device) {
2263 assertRunOnServiceThread();
2264 mCecController.flush(new Runnable() {
2265 @Override
2266 public void run() {
2267 mCecController.setOption(OPTION_CEC_ENABLE, DISABLED);
2268 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2269 clearLocalDevices();
2276 @ServiceThreadOnly
2277 void setActivePortId(int portId) {
2278 assertRunOnServiceThread();
2279 mActivePortId = portId;
2281 // Resets last input for MHL, which stays valid only after the MHL device was selected,
2282 // and no further switching is done.
2283 setLastInputForMhl(Constants.INVALID_PORT_ID);
2286 @ServiceThreadOnly
2287 void setLastInputForMhl(int portId) {
2288 assertRunOnServiceThread();
2289 mLastInputMhl = portId;
2292 @ServiceThreadOnly
2293 int getLastInputForMhl() {
2294 assertRunOnServiceThread();
2295 return mLastInputMhl;
2299 * Performs input change, routing control for MHL device.
2301 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2302 * @param contentOn {@code true} if RAP data is content on; otherwise false
2304 @ServiceThreadOnly
2305 void changeInputForMhl(int portId, boolean contentOn) {
2306 assertRunOnServiceThread();
2307 if (tv() == null) return;
2308 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
2309 if (portId != Constants.INVALID_PORT_ID) {
2310 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2311 @Override
2312 public void onComplete(int result) throws RemoteException {
2313 // Keep the last input to switch back later when RAP[ContentOff] is received.
2314 // This effectively sets the port to invalid one if the switching is for
2315 // RAP[ContentOff].
2316 setLastInputForMhl(lastInput);
2320 // MHL device is always directly connected to the port. Update the active port ID to avoid
2321 // unnecessary post-routing control task.
2322 tv().setActivePortId(portId);
2324 // The port is either the MHL-enabled port where the mobile device is connected, or
2325 // the last port to go back to when turnoff command is received. Note that the last port
2326 // may not be the MHL-enabled one. In this case the device info to be passed to
2327 // input change listener should be the one describing the corresponding HDMI port.
2328 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
2329 HdmiDeviceInfo info = (device != null) ? device.getInfo()
2330 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
2331 invokeInputChangeListener(info);
2334 void setMhlInputChangeEnabled(boolean enabled) {
2335 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2337 synchronized (mLock) {
2338 mMhlInputChangeEnabled = enabled;
2342 boolean isMhlInputChangeEnabled() {
2343 synchronized (mLock) {
2344 return mMhlInputChangeEnabled;
2348 @ServiceThreadOnly
2349 void displayOsd(int messageId) {
2350 assertRunOnServiceThread();
2351 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2352 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2353 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2354 HdmiControlService.PERMISSION);
2357 @ServiceThreadOnly
2358 void displayOsd(int messageId, int extra) {
2359 assertRunOnServiceThread();
2360 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2361 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2362 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
2363 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2364 HdmiControlService.PERMISSION);