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
;
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.
126 * <li>{@link Constants#SEND_RESULT_SUCCESS}
127 * <li>{@link Constants#SEND_RESULT_NAK}
128 * <li>{@link Constants#SEND_RESULT_FAILURE}
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
{
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
);
157 case Intent
.ACTION_SCREEN_ON
:
158 if (isPowerStandbyOrTransient()) {
162 case Intent
.ACTION_CONFIGURATION_CHANGED
:
163 String language
= getMenuLanguage();
164 if (!mLanguage
.equals(language
)) {
165 onLanguageChanged(language
);
168 case Intent
.ACTION_SHUTDOWN
:
169 if (isPowerOnOrTransient()) {
170 onStandby(STANDBY_SHUTDOWN
);
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.
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.
202 private final ArrayList
<HotplugEventListenerRecord
> mHotplugEventListenerRecords
=
205 // List of records for device event listener to handle the caller killed in action.
207 private final ArrayList
<DeviceEventListenerRecord
> mDeviceEventListenerRecords
=
210 // List of records for vendor command listener to handle the caller killed in action.
212 private final ArrayList
<VendorCommandListenerRecord
> mVendorCommandListenerRecords
=
216 private InputChangeListenerRecord mInputChangeListenerRecord
;
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.
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".
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();
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
;
263 private int mPowerStatus
= HdmiControlManager
.POWER_STATUS_STANDBY
;
266 private String mLanguage
= Locale
.getDefault().getISO3Language();
269 private boolean mStandbyMessageReceived
= false;
272 private boolean mWakeUpMessageReceived
= false;
275 private int mActivePortId
= Constants
.INVALID_PORT_ID
;
277 // Set to true while the input change by MHL is allowed.
279 private boolean mMhlInputChangeEnabled
;
281 // List of records for MHL Vendor command listener to handle the caller killed in action.
283 private final ArrayList
<HdmiMhlVendorCommandListenerRecord
>
284 mMhlVendorCommandListenerRecords
= new ArrayList
<>();
287 private List
<HdmiDeviceInfo
> mMhlDevices
;
290 private HdmiMhlControllerStub mMhlController
;
293 private TvInputManager mTvInputManager
;
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.
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
);
316 case Constants
.MESSAGE_IMAGE_VIEW_ON
:
317 case Constants
.MESSAGE_TEXT_VIEW_ON
:
318 bufferImageOrTextViewOn(message
);
320 // Add here if new message that needs to buffer
322 // Do not need to buffer messages other than above
327 public void processMessages() {
328 for (final HdmiCecMessage message
: mBuffer
) {
329 runOnServiceThread(new Runnable() {
332 handleCecCommand(message
);
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
);
365 private final CecMessageBuffer mCecMessageBuffer
= new CecMessageBuffer();
367 private final SelectRequestBuffer mSelectRequestBuffer
= new SelectRequestBuffer();
369 public HdmiControlService(Context 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
) {
381 list
.add(Integer
.parseInt(item
));
382 } catch (NumberFormatException e
) {
383 Slog
.w(TAG
, "Can't parseInt: " + item
);
386 return Collections
.unmodifiableList(list
);
390 public void onStart() {
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
);
403 Slog
.i(TAG
, "Device does not support HDMI-CEC.");
407 mMhlController
= HdmiMhlControllerStub
.create(this);
408 if (!mMhlController
.isReady()) {
409 Slog
.i(TAG
, "Device does not support MHL-control.");
411 mMhlDevices
= Collections
.emptyList();
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
);
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()));
472 switch (initiatedBy
) {
473 case INITIATED_BY_BOOT_UP
:
474 reason
= HdmiControlManager
.CONTROL_STATE_CHANGED_REASON_START
;
476 case INITIATED_BY_ENABLE_CEC
:
477 reason
= HdmiControlManager
.CONTROL_STATE_CHANGED_REASON_SETTING
;
479 case INITIATED_BY_SCREEN_ON
:
480 case INITIATED_BY_WAKE_UP_MESSAGE
:
481 reason
= HdmiControlManager
.CONTROL_STATE_CHANGED_REASON_WAKEUP
;
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
) {
509 // onChange is set up to run in service thread.
511 public void onChange(boolean selfChange
, Uri uri
) {
512 String option
= uri
.getLastPathSegment();
513 boolean enabled
= readBooleanSetting(option
, true);
515 case Global
.HDMI_CONTROL_ENABLED
:
516 setControlEnabled(enabled
);
518 case Global
.HDMI_CONTROL_AUTO_WAKEUP_ENABLED
:
519 if (isTvDeviceEnabled()) {
520 tv().setAutoWakeup(enabled
);
522 setCecOption(OPTION_CEC_AUTO_WAKEUP
, toInt(enabled
));
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.
533 case Global
.MHL_INPUT_SWITCHING_ENABLED
:
534 setMhlInputChangeEnabled(enabled
);
536 case Global
.MHL_POWER_CHARGE_ENABLED
:
537 mMhlController
.setOption(OPTION_MHL_POWER_CHARGE
, toInt(enabled
));
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
);
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
);
575 localDevices
.add(localDevice
);
577 // It's now safe to flush existing local devices from mCecController since they were
578 // already moved to 'localDevices'.
580 allocateLogicalAddress(localDevices
, initiatedBy
);
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
596 mSelectRequestBuffer
.clear();
598 for (final HdmiCecLocalDevice localDevice
: allocatingDevices
) {
599 mCecController
.allocateLogicalAddress(localDevice
.getType(),
600 localDevice
.getPreferredAddress(), new AllocateAddressCallback() {
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
+ "]");
606 // Set POWER_STATUS_ON to all local devices because they share lifetime
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();
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.
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) {
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
));
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()));
699 mPortInfo
= Collections
.unmodifiableList(result
);
702 List
<HdmiPortInfo
> getPortInfo() {
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
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.
774 return mCecController
.getVendorId();
778 HdmiDeviceInfo
getDeviceInfo(int logicalAddress
) {
779 assertRunOnServiceThread();
780 return tv() == null ?
null : tv().getCecDeviceInfo(logicalAddress
);
784 HdmiDeviceInfo
getDeviceInfoByPort(int port
) {
785 assertRunOnServiceThread();
786 HdmiMhlLocalDeviceStub info
= mMhlController
.getLocalDevice(port
);
788 return info
.getInfo();
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();
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
838 void sendCecCommand(HdmiCecMessage command
, @Nullable SendMessageCallback callback
) {
839 assertRunOnServiceThread();
840 if (mMessageValidator
.isValid(command
) == HdmiCecMessageValidator
.OK
) {
841 mCecController
.sendCommand(command
, callback
);
843 HdmiLogger
.error("Invalid message type:" + command
);
844 if (callback
!= null) {
845 callback
.onSendCompleted(Constants
.SEND_RESULT_FAILURE
);
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
863 void maySendFeatureAbortCommand(HdmiCecMessage command
, int reason
) {
864 assertRunOnServiceThread();
865 mCecController
.maySendFeatureAbortCommand(command
, reason
);
869 boolean handleCecCommand(HdmiCecMessage message
) {
870 assertRunOnServiceThread();
871 if (!mAddressAllocated
) {
872 mCecMessageBuffer
.bufferMessage(message
);
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
);
884 return dispatchMessageToLocalDevice(message
);
887 void setAudioReturnChannel(int portId
, boolean enabled
) {
888 mCecController
.setAudioReturnChannel(portId
, enabled
);
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
) {
901 if (message
.getDestination() != Constants
.ADDR_BROADCAST
) {
902 HdmiLogger
.warning("Unhandled cec command:" + message
);
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
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
);
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 <Polling Message> to all remote
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
947 void pollDevices(DevicePollingCallback callback
, int sourceAddress
, int pickStrategy
,
949 assertRunOnServiceThread();
950 mCecController
.pollDevices(callback
, sourceAddress
, checkPollStrategy(pickStrategy
),
954 private int checkPollStrategy(int pickStrategy
) {
955 int strategy
= pickStrategy
& Constants
.POLL_STRATEGY_MASK
;
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() {
975 void setAudioStatus(boolean mute
, int volume
) {
976 AudioManager audioManager
= getAudioManager();
977 boolean muted
= audioManager
.isStreamMute(AudioManager
.STREAM_MUSIC
);
980 audioManager
.setStreamMute(AudioManager
.STREAM_MUSIC
, true);
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
);
1011 void handleMhlHotplugEvent(int portId
, boolean connected
) {
1012 assertRunOnServiceThread();
1013 // Hotplug event is used to add/remove MHL devices as TV input.
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();
1024 HdmiMhlLocalDeviceStub device
= mMhlController
.removeLocalDevice(portId
);
1025 if (device
!= null) {
1026 device
.onDeviceRemoved();
1027 invokeDeviceEventListeners(device
.getInfo(), DEVICE_EVENT_REMOVE_DEVICE
);
1028 updateSafeMhlInput();
1030 Slog
.w(TAG
, "No device to remove:[portId=" + portId
);
1033 announceHotplugEvent(portId
, connected
);
1037 void handleMhlBusModeChanged(int portId
, int busmode
) {
1038 assertRunOnServiceThread();
1039 HdmiMhlLocalDeviceStub device
= mMhlController
.getLocalDevice(portId
);
1040 if (device
!= null) {
1041 device
.setBusMode(busmode
);
1043 Slog
.w(TAG
, "No mhl device exists for bus mode change[portId:" + portId
+
1044 ", busmode:" + busmode
+ "]");
1049 void handleMhlBusOvercurrent(int portId
, boolean on
) {
1050 assertRunOnServiceThread();
1051 HdmiMhlLocalDeviceStub device
= mMhlController
.getLocalDevice(portId
);
1052 if (device
!= null) {
1053 device
.onBusOvercurrentDetected(on
);
1055 Slog
.w(TAG
, "No mhl device exists for bus overcurrent event[portId:" + portId
+ "]");
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
);
1067 Slog
.w(TAG
, "No mhl device exists for device status event[portId:"
1068 + portId
+ ", adopterId:" + adopterId
+ ", deviceId:" + deviceId
+ "]");
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();
1081 if (inputs
.isEmpty()) {
1082 inputs
= new ArrayList
<>();
1084 inputs
.add(device
.getInfo());
1087 synchronized (mLock
) {
1088 mMhlDevices
= inputs
;
1092 private List
<HdmiDeviceInfo
> getMhlDevicesLocked() {
1096 private class HdmiMhlVendorCommandListenerRecord
implements IBinder
.DeathRecipient
{
1097 private final IHdmiMhlVendorCommandListener mListener
;
1099 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener
) {
1100 mListener
= listener
;
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
;
1119 public void binderDied() {
1120 synchronized (mLock
) {
1121 mHotplugEventListenerRecords
.remove(this);
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
;
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
;
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
;
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
;
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
;
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
{
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
;
1220 public HdmiDeviceInfo
getActiveSource() {
1221 enforceAccessPermission();
1222 HdmiCecLocalDeviceTv tv
= tv();
1224 Slog
.w(TAG
, "Local tv device not available");
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());
1242 public void deviceSelect(final int deviceId
, final IHdmiControlCallback callback
) {
1243 enforceAccessPermission();
1244 runOnServiceThread(new Runnable() {
1247 if (callback
== null) {
1248 Slog
.e(TAG
, "Callback cannot be null");
1251 HdmiCecLocalDeviceTv tv
= tv();
1253 if (!mAddressAllocated
) {
1254 mSelectRequestBuffer
.set(SelectRequestBuffer
.newDeviceSelect(
1255 HdmiControlService
.this, deviceId
, callback
));
1258 Slog
.w(TAG
, "Local tv device not available");
1259 invokeCallback(callback
, HdmiControlManager
.RESULT_SOURCE_NOT_AVAILABLE
);
1262 HdmiMhlLocalDeviceStub device
= mMhlController
.getLocalDeviceById(deviceId
);
1263 if (device
!= null) {
1264 if (device
.getPortId() == tv
.getActivePortId()) {
1265 invokeCallback(callback
, HdmiControlManager
.RESULT_SUCCESS
);
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);
1275 tv
.deviceSelect(deviceId
, callback
);
1281 public void portSelect(final int portId
, final IHdmiControlCallback callback
) {
1282 enforceAccessPermission();
1283 runOnServiceThread(new Runnable() {
1286 if (callback
== null) {
1287 Slog
.e(TAG
, "Callback cannot be null");
1290 HdmiCecLocalDeviceTv tv
= tv();
1292 if (!mAddressAllocated
) {
1293 mSelectRequestBuffer
.set(SelectRequestBuffer
.newPortSelect(
1294 HdmiControlService
.this, portId
, callback
));
1297 Slog
.w(TAG
, "Local tv device not available");
1298 invokeCallback(callback
, HdmiControlManager
.RESULT_SOURCE_NOT_AVAILABLE
);
1301 tv
.doManualPortSwitching(portId
, callback
);
1307 public void sendKeyEvent(final int deviceType
, final int keyCode
, final boolean isPressed
) {
1308 enforceAccessPermission();
1309 runOnServiceThread(new Runnable() {
1312 HdmiMhlLocalDeviceStub device
= mMhlController
.getLocalDevice(mActivePortId
);
1313 if (device
!= null) {
1314 device
.sendKeyEvent(keyCode
, isPressed
);
1317 if (mCecController
!= null) {
1318 HdmiCecLocalDevice localDevice
= mCecController
.getLocalDevice(deviceType
);
1319 if (localDevice
== null) {
1320 Slog
.w(TAG
, "Local device not available");
1323 localDevice
.sendKeyEvent(keyCode
, isPressed
);
1330 public void oneTouchPlay(final IHdmiControlCallback callback
) {
1331 enforceAccessPermission();
1332 runOnServiceThread(new Runnable() {
1335 HdmiControlService
.this.oneTouchPlay(callback
);
1341 public void queryDisplayStatus(final IHdmiControlCallback callback
) {
1342 enforceAccessPermission();
1343 runOnServiceThread(new Runnable() {
1346 HdmiControlService
.this.queryDisplayStatus(callback
);
1352 public void addHotplugEventListener(final IHdmiHotplugEventListener listener
) {
1353 enforceAccessPermission();
1354 HdmiControlService
.this.addHotplugEventListener(listener
);
1358 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener
) {
1359 enforceAccessPermission();
1360 HdmiControlService
.this.removeHotplugEventListener(listener
);
1364 public void addDeviceEventListener(final IHdmiDeviceEventListener listener
) {
1365 enforceAccessPermission();
1366 HdmiControlService
.this.addDeviceEventListener(listener
);
1370 public List
<HdmiPortInfo
> getPortInfo() {
1371 enforceAccessPermission();
1372 return HdmiControlService
.this.getPortInfo();
1376 public boolean canChangeSystemAudioMode() {
1377 enforceAccessPermission();
1378 HdmiCecLocalDeviceTv tv
= tv();
1382 return tv
.hasSystemAudioDevice();
1386 public boolean getSystemAudioMode() {
1387 enforceAccessPermission();
1388 HdmiCecLocalDeviceTv tv
= tv();
1392 return tv
.isSystemAudioActivated();
1396 public void setSystemAudioMode(final boolean enabled
, final IHdmiControlCallback callback
) {
1397 enforceAccessPermission();
1398 runOnServiceThread(new Runnable() {
1401 HdmiCecLocalDeviceTv tv
= tv();
1403 Slog
.w(TAG
, "Local tv device not available");
1404 invokeCallback(callback
, HdmiControlManager
.RESULT_SOURCE_NOT_AVAILABLE
);
1407 tv
.changeSystemAudioMode(enabled
, callback
);
1413 public void addSystemAudioModeChangeListener(
1414 final IHdmiSystemAudioModeChangeListener listener
) {
1415 enforceAccessPermission();
1416 HdmiControlService
.this.addSystemAudioModeChangeListner(listener
);
1420 public void removeSystemAudioModeChangeListener(
1421 final IHdmiSystemAudioModeChangeListener listener
) {
1422 enforceAccessPermission();
1423 HdmiControlService
.this.removeSystemAudioModeChangeListener(listener
);
1427 public void setInputChangeListener(final IHdmiInputChangeListener listener
) {
1428 enforceAccessPermission();
1429 HdmiControlService
.this.setInputChangeListener(listener
);
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.
1449 public List
<HdmiDeviceInfo
> getDeviceList() {
1450 enforceAccessPermission();
1451 HdmiCecLocalDeviceTv tv
= tv();
1452 synchronized (mLock
) {
1454 ? Collections
.<HdmiDeviceInfo
>emptyList()
1455 : tv
.getSafeCecDevicesLocked();
1460 public void setSystemAudioVolume(final int oldIndex
, final int newIndex
,
1461 final int maxIndex
) {
1462 enforceAccessPermission();
1463 runOnServiceThread(new Runnable() {
1466 HdmiCecLocalDeviceTv tv
= tv();
1468 Slog
.w(TAG
, "Local tv device not available");
1471 tv
.changeVolume(oldIndex
, newIndex
- oldIndex
, maxIndex
);
1477 public void setSystemAudioMute(final boolean mute
) {
1478 enforceAccessPermission();
1479 runOnServiceThread(new Runnable() {
1482 HdmiCecLocalDeviceTv tv
= tv();
1484 Slog
.w(TAG
, "Local tv device not available");
1487 tv
.changeMute(mute
);
1493 public void setArcMode(final boolean enabled
) {
1494 enforceAccessPermission();
1495 runOnServiceThread(new Runnable() {
1498 HdmiCecLocalDeviceTv tv
= tv();
1500 Slog
.w(TAG
, "Local tv device not available to change arc mode.");
1508 public void setProhibitMode(final boolean enabled
) {
1509 enforceAccessPermission();
1510 if (!isTvDevice()) {
1513 HdmiControlService
.this.setProhibitMode(enabled
);
1517 public void addVendorCommandListener(final IHdmiVendorCommandListener listener
,
1518 final int deviceType
) {
1519 enforceAccessPermission();
1520 HdmiControlService
.this.addVendorCommandListener(listener
, deviceType
);
1524 public void sendVendorCommand(final int deviceType
, final int targetAddress
,
1525 final byte[] params
, final boolean hasVendorId
) {
1526 enforceAccessPermission();
1527 runOnServiceThread(new Runnable() {
1530 HdmiCecLocalDevice device
= mCecController
.getLocalDevice(deviceType
);
1531 if (device
== null) {
1532 Slog
.w(TAG
, "Local device not available");
1536 sendCecCommand(HdmiCecMessageBuilder
.buildVendorCommandWithId(
1537 device
.getDeviceInfo().getLogicalAddress(), targetAddress
,
1538 getVendorId(), params
));
1540 sendCecCommand(HdmiCecMessageBuilder
.buildVendorCommand(
1541 device
.getDeviceInfo().getLogicalAddress(), targetAddress
, params
));
1548 public void sendStandby(final int deviceType
, final int deviceId
) {
1549 enforceAccessPermission();
1550 runOnServiceThread(new Runnable() {
1553 HdmiMhlLocalDeviceStub mhlDevice
= mMhlController
.getLocalDeviceById(deviceId
);
1554 if (mhlDevice
!= null) {
1555 mhlDevice
.sendStandby();
1558 HdmiCecLocalDevice device
= mCecController
.getLocalDevice(deviceType
);
1559 if (device
== null) {
1560 Slog
.w(TAG
, "Local device not available");
1563 device
.sendStandby(deviceId
);
1569 public void setHdmiRecordListener(IHdmiRecordListener listener
) {
1570 enforceAccessPermission();
1571 HdmiControlService
.this.setHdmiRecordListener(listener
);
1575 public void startOneTouchRecord(final int recorderAddress
, final byte[] recordSource
) {
1576 enforceAccessPermission();
1577 runOnServiceThread(new Runnable() {
1580 if (!isTvDeviceEnabled()) {
1581 Slog
.w(TAG
, "TV device is not enabled.");
1584 tv().startOneTouchRecord(recorderAddress
, recordSource
);
1590 public void stopOneTouchRecord(final int recorderAddress
) {
1591 enforceAccessPermission();
1592 runOnServiceThread(new Runnable() {
1595 if (!isTvDeviceEnabled()) {
1596 Slog
.w(TAG
, "TV device is not enabled.");
1599 tv().stopOneTouchRecord(recorderAddress
);
1605 public void startTimerRecording(final int recorderAddress
, final int sourceType
,
1606 final byte[] recordSource
) {
1607 enforceAccessPermission();
1608 runOnServiceThread(new Runnable() {
1611 if (!isTvDeviceEnabled()) {
1612 Slog
.w(TAG
, "TV device is not enabled.");
1615 tv().startTimerRecording(recorderAddress
, sourceType
, recordSource
);
1621 public void clearTimerRecording(final int recorderAddress
, final int sourceType
,
1622 final byte[] recordSource
) {
1623 enforceAccessPermission();
1624 runOnServiceThread(new Runnable() {
1627 if (!isTvDeviceEnabled()) {
1628 Slog
.w(TAG
, "TV device is not enabled.");
1631 tv().clearTimerRecording(recorderAddress
, sourceType
, recordSource
);
1637 public void sendMhlVendorCommand(final int portId
, final int offset
, final int length
,
1638 final byte[] data
) {
1639 enforceAccessPermission();
1640 runOnServiceThread(new Runnable() {
1643 if (!isControlEnabled()) {
1644 Slog
.w(TAG
, "Hdmi control is disabled.");
1647 HdmiMhlLocalDeviceStub device
= mMhlController
.getLocalDevice(portId
);
1648 if (device
== null) {
1649 Slog
.w(TAG
, "Invalid port id:" + portId
);
1652 mMhlController
.sendVendorCommand(portId
, offset
, length
, data
);
1658 public void addHdmiMhlVendorCommandListener(
1659 IHdmiMhlVendorCommandListener listener
) {
1660 enforceAccessPermission();
1661 HdmiControlService
.this.addHdmiMhlVendorCommandListener(listener
);
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
);
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
);
1702 source
.oneTouchPlay(callback
);
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
);
1714 source
.queryDisplayStatus(callback
);
1717 private void addHotplugEventListener(final IHdmiHotplugEventListener listener
) {
1718 final HotplugEventListenerRecord record
= new HotplugEventListenerRecord(listener
);
1720 listener
.asBinder().linkToDeath(record
, 0);
1721 } catch (RemoteException e
) {
1722 Slog
.w(TAG
, "Listener already died");
1725 synchronized (mLock
) {
1726 mHotplugEventListenerRecords
.add(record
);
1729 // Inform the listener of the initial state of each HDMI port by generating
1731 runOnServiceThread(new Runnable() {
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
);
1760 private void addDeviceEventListener(IHdmiDeviceEventListener listener
) {
1761 DeviceEventListenerRecord record
= new DeviceEventListenerRecord(listener
);
1763 listener
.asBinder().linkToDeath(record
, 0);
1764 } catch (RemoteException e
) {
1765 Slog
.w(TAG
, "Listener already died");
1768 synchronized (mLock
) {
1769 mDeviceEventListenerRecords
.add(record
);
1773 void invokeDeviceEventListeners(HdmiDeviceInfo device
, int status
) {
1774 synchronized (mLock
) {
1775 for (DeviceEventListenerRecord record
: mDeviceEventListenerRecords
) {
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(
1789 listener
.asBinder().linkToDeath(record
, 0);
1790 } catch (RemoteException e
) {
1791 Slog
.w(TAG
, "Listener already died");
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
);
1812 private final class InputChangeListenerRecord
implements IBinder
.DeathRecipient
{
1813 private final IHdmiInputChangeListener mListener
;
1815 public InputChangeListenerRecord(IHdmiInputChangeListener listener
) {
1816 mListener
= listener
;
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
);
1833 listener
.asBinder().linkToDeath(mInputChangeListenerRecord
, 0);
1834 } catch (RemoteException e
) {
1835 Slog
.w(TAG
, "Listener already died");
1841 void invokeInputChangeListener(HdmiDeviceInfo info
) {
1842 synchronized (mLock
) {
1843 if (mInputChangeListenerRecord
!= null) {
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
);
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) {
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) {
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) {
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) {
1905 mRecordListenerRecord
.mListener
.onClearTimerRecordingResult(recorderAddress
,
1907 } catch (RemoteException e
) {
1908 Slog
.w(TAG
, "Failed to call onClearTimerRecordingResult.", e
);
1914 private void invokeCallback(IHdmiControlCallback callback
, int result
) {
1916 callback
.onComplete(result
);
1917 } catch (RemoteException e
) {
1918 Slog
.e(TAG
, "Invoking callback failed:" + e
);
1922 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener
,
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
) {
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
;
1977 int getPowerStatus() {
1978 assertRunOnServiceThread();
1979 return mPowerStatus
;
1983 boolean isPowerOnOrTransient() {
1984 assertRunOnServiceThread();
1985 return mPowerStatus
== HdmiControlManager
.POWER_STATUS_ON
1986 || mPowerStatus
== HdmiControlManager
.POWER_STATUS_TRANSIENT_TO_ON
;
1990 boolean isPowerStandbyOrTransient() {
1991 assertRunOnServiceThread();
1992 return mPowerStatus
== HdmiControlManager
.POWER_STATUS_STANDBY
1993 || mPowerStatus
== HdmiControlManager
.POWER_STATUS_TRANSIENT_TO_STANDBY
;
1997 boolean isPowerStandby() {
1998 assertRunOnServiceThread();
1999 return mPowerStatus
== HdmiControlManager
.POWER_STATUS_STANDBY
;
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().
2013 assertRunOnServiceThread();
2014 if (!canGoToStandby()) {
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().
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
);
2036 Slog
.i(TAG
, "Device does not support HDMI-CEC.");
2038 // TODO: Initialize MHL local devices.
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
;
2052 final List
<HdmiCecLocalDevice
> devices
= getAllLocalDevices();
2053 disableDevices(new PendingActionClearedCallback() {
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
2068 private boolean canGoToStandby() {
2069 for (HdmiCecLocalDevice device
: mCecController
.getLocalDeviceList()) {
2070 if (!device
.canGoToStandby()) return false;
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
));
2087 String
getLanguage() {
2088 assertRunOnServiceThread();
2092 private void disableDevices(PendingActionClearedCallback callback
) {
2093 if (mCecController
!= null) {
2094 for (HdmiCecLocalDevice device
: mCecController
.getLocalDeviceList()) {
2095 device
.disableDevice(mStandbyMessageReceived
, callback
);
2099 mMhlController
.clearAllLocalDevices();
2103 private void clearLocalDevices() {
2104 assertRunOnServiceThread();
2105 if (mCecController
== null) {
2108 mCecController
.clearLogicalAddress();
2109 mCecController
.clearLocalDevices();
2113 private void onStandbyCompleted(int standbyAction
) {
2114 assertRunOnServiceThread();
2115 Slog
.v(TAG
, "onStandbyCompleted");
2117 if (mPowerStatus
!= HdmiControlManager
.POWER_STATUS_TRANSIENT_TO_STANDBY
) {
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
);
2133 listener
.asBinder().linkToDeath(record
, 0);
2134 } catch (RemoteException e
) {
2135 Slog
.w(TAG
, "Listener already died");
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()) {
2149 for (VendorCommandListenerRecord record
: mVendorCommandListenerRecords
) {
2150 if (record
.mDeviceType
!= deviceType
) {
2154 record
.mListener
.onReceived(srcAddress
, destAddress
, params
, hasVendorId
);
2155 } catch (RemoteException e
) {
2156 Slog
.e(TAG
, "Failed to notify vendor command reception", e
);
2163 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled
, int reason
) {
2164 synchronized (mLock
) {
2165 if (mVendorCommandListenerRecords
.isEmpty()) {
2168 for (VendorCommandListenerRecord record
: mVendorCommandListenerRecords
) {
2170 record
.mListener
.onControlStateChanged(enabled
, reason
);
2171 } catch (RemoteException e
) {
2172 Slog
.e(TAG
, "Failed to notify control-state-changed to vendor handler", e
);
2179 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener
) {
2180 HdmiMhlVendorCommandListenerRecord record
=
2181 new HdmiMhlVendorCommandListenerRecord(listener
);
2183 listener
.asBinder().linkToDeath(record
, 0);
2184 } catch (RemoteException e
) {
2185 Slog
.w(TAG
, "Listener already died.");
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
) {
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
;
2219 void setCecOption(int key
, int value
) {
2220 assertRunOnServiceThread();
2221 mCecController
.setOption(key
, value
);
2225 void setControlEnabled(boolean enabled
) {
2226 assertRunOnServiceThread();
2228 synchronized (mLock
) {
2229 mHdmiControlEnabled
= enabled
;
2233 enableHdmiControlService();
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
2241 runOnServiceThread(new Runnable() {
2244 disableHdmiControlService();
2251 private void enableHdmiControlService() {
2252 mCecController
.setOption(OPTION_CEC_ENABLE
, ENABLED
);
2253 mMhlController
.setOption(OPTION_MHL_ENABLE
, ENABLED
);
2255 initializeCec(INITIATED_BY_ENABLE_CEC
);
2259 private void disableHdmiControlService() {
2260 disableDevices(new PendingActionClearedCallback() {
2262 public void onCleared(HdmiCecLocalDevice device
) {
2263 assertRunOnServiceThread();
2264 mCecController
.flush(new Runnable() {
2267 mCecController
.setOption(OPTION_CEC_ENABLE
, DISABLED
);
2268 mMhlController
.setOption(OPTION_MHL_ENABLE
, DISABLED
);
2269 clearLocalDevices();
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
);
2287 void setLastInputForMhl(int portId
) {
2288 assertRunOnServiceThread();
2289 mLastInputMhl
= portId
;
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
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() {
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
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
;
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
);
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
);