2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.server.hdmi;
19 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
20 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
21 import static com.android.server.hdmi.Constants.DISABLED;
22 import static com.android.server.hdmi.Constants.ENABLED;
23 import static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_WAKEUP;
24 import static com.android.server.hdmi.Constants.OPTION_CEC_ENABLE;
25 import static com.android.server.hdmi.Constants.OPTION_CEC_SERVICE_CONTROL;
26 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
27 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
28 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
30 import android.annotation.Nullable;
31 import android.content.BroadcastReceiver;
32 import android.content.ContentResolver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.database.ContentObserver;
37 import android.hardware.hdmi.HdmiControlManager;
38 import android.hardware.hdmi.HdmiDeviceInfo;
39 import android.hardware.hdmi.HdmiHotplugEvent;
40 import android.hardware.hdmi.HdmiPortInfo;
41 import android.hardware.hdmi.IHdmiControlCallback;
42 import android.hardware.hdmi.IHdmiControlService;
43 import android.hardware.hdmi.IHdmiDeviceEventListener;
44 import android.hardware.hdmi.IHdmiHotplugEventListener;
45 import android.hardware.hdmi.IHdmiInputChangeListener;
46 import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
47 import android.hardware.hdmi.IHdmiRecordListener;
48 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
49 import android.hardware.hdmi.IHdmiVendorCommandListener;
50 import android.media.AudioManager;
51 import android.net.Uri;
52 import android.os.Build;
53 import android.os.Handler;
54 import android.os.HandlerThread;
55 import android.os.IBinder;
56 import android.os.Looper;
57 import android.os.PowerManager;
58 import android.os.RemoteException;
59 import android.os.SystemClock;
60 import android.os.SystemProperties;
61 import android.os.UserHandle;
62 import android.provider.Settings.Global;
63 import android.text.TextUtils;
64 import android.util.ArraySet;
65 import android.util.Slog;
66 import android.util.SparseArray;
67 import android.util.SparseIntArray;
69 import com.android.internal.annotations.GuardedBy;
70 import com.android.internal.util.IndentingPrintWriter;
71 import com.android.server.SystemService;
72 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
73 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
74 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
75 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
77 import libcore.util.EmptyArray;
79 import java.io.FileDescriptor;
80 import java.io.PrintWriter;
81 import java.util.ArrayList;
82 import java.util.Arrays;
83 import java.util.Collections;
84 import java.util.List;
85 import java.util.Locale;
88 * Provides a service for sending and processing HDMI control messages,
89 * HDMI-CEC and MHL control command, and providing the information on both standard.
91 public final class HdmiControlService extends SystemService {
92 private static final String TAG = "HdmiControlService";
94 static final String PERMISSION = "android.permission.HDMI_CEC";
96 // The reason code to initiate intializeCec().
97 static final int INITIATED_BY_ENABLE_CEC = 0;
98 static final int INITIATED_BY_BOOT_UP = 1;
99 static final int INITIATED_BY_SCREEN_ON = 2;
100 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
101 static final int INITIATED_BY_HOTPLUG = 4;
104 * Interface to report send result.
106 interface SendMessageCallback {
108 * Called when {@link HdmiControlService#sendCecCommand} is completed.
110 * @param error result of send request.
112 * <li>{@link Constants#SEND_RESULT_SUCCESS}
113 * <li>{@link Constants#SEND_RESULT_NAK}
114 * <li>{@link Constants#SEND_RESULT_FAILURE}
117 void onSendCompleted(int error);
121 * Interface to get a list of available logical devices.
123 interface DevicePollingCallback {
125 * Called when device polling is finished.
127 * @param ackedAddress a list of logical addresses of available devices
129 void onPollingFinished(List<Integer> ackedAddress);
132 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
135 public void onReceive(Context context, Intent intent) {
136 assertRunOnServiceThread();
137 switch (intent.getAction()) {
138 case Intent.ACTION_SCREEN_OFF:
139 if (isPowerOnOrTransient()) {
143 case Intent.ACTION_SCREEN_ON:
144 if (isPowerStandbyOrTransient()) {
148 case Intent.ACTION_CONFIGURATION_CHANGED:
149 String language = Locale.getDefault().getISO3Language();
150 if (!mLanguage.equals(language)) {
151 onLanguageChanged(language);
158 // A thread to handle synchronous IO of CEC and MHL control service.
159 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
160 // and sparse call it shares a thread to handle IO operations.
161 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
163 // Used to synchronize the access to the service.
164 private final Object mLock = new Object();
166 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
167 private final List<Integer> mLocalDevices;
169 // List of records for hotplug event listener to handle the the caller killed in action.
171 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
174 // List of records for device event listener to handle the caller killed in action.
176 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
179 // List of records for vendor command listener to handle the caller killed in action.
181 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
185 private InputChangeListenerRecord mInputChangeListenerRecord;
188 private HdmiRecordListenerRecord mRecordListenerRecord;
190 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
191 // handling will be disabled and no request will be handled.
193 private boolean mHdmiControlEnabled;
195 // Set to true while the service is in normal mode. While set to false, no input change is
196 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
197 // system upgrade, etc., a.k.a. "prohibit mode".
199 private boolean mProhibitMode;
201 // List of records for system audio mode change to handle the the caller killed in action.
202 private final ArrayList<SystemAudioModeChangeListenerRecord>
203 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
205 // Handler used to run a task in service thread.
206 private final Handler mHandler = new Handler();
208 private final SettingsObserver mSettingsObserver;
210 private final HdmiControlBroadcastReceiver
211 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
214 private HdmiCecController mCecController;
216 // HDMI port information. Stored in the unmodifiable list to keep the static information
217 // from being modified.
218 private List<HdmiPortInfo> mPortInfo;
220 // Map from path(physical address) to port ID.
221 private UnmodifiableSparseIntArray mPortIdMap;
223 // Map from port ID to HdmiPortInfo.
224 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
226 // Map from port ID to HdmiDeviceInfo.
227 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
229 private HdmiCecMessageValidator mMessageValidator;
232 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
235 private String mLanguage = Locale.getDefault().getISO3Language();
238 private boolean mStandbyMessageReceived = false;
241 private boolean mWakeUpMessageReceived = false;
244 private int mActivePortId = Constants.INVALID_PORT_ID;
246 // Set to true while the input change by MHL is allowed.
248 private boolean mMhlInputChangeEnabled;
250 // List of records for MHL Vendor command listener to handle the caller killed in action.
252 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
253 mMhlVendorCommandListenerRecords = new ArrayList<>();
256 private List<HdmiDeviceInfo> mMhlDevices;
259 private HdmiMhlControllerStub mMhlController;
261 // Last input port before switching to the MHL port. Should switch back to this port
262 // when the mobile device sends the request one touch play with off.
263 // Gets invalidated if we go to other port/input.
265 private int mLastInputMhl = Constants.INVALID_PORT_ID;
267 public HdmiControlService(Context context) {
269 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
270 mSettingsObserver = new SettingsObserver(mHandler);
273 private static List<Integer> getIntList(String string) {
274 ArrayList<Integer> list = new ArrayList<>();
275 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
276 splitter.setString(string);
277 for (String item : splitter) {
279 list.add(Integer.parseInt(item));
280 } catch (NumberFormatException e) {
281 Slog.w(TAG, "Can't parseInt: " + item);
284 return Collections.unmodifiableList(list);
288 public void onStart() {
290 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
291 mProhibitMode = false;
292 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
293 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
295 mCecController = HdmiCecController.create(this);
296 if (mCecController != null) {
297 // TODO: Remove this as soon as OEM's HAL implementation is corrected.
298 mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
300 // TODO: load value for mHdmiControlEnabled from preference.
301 if (mHdmiControlEnabled) {
302 initializeCec(INITIATED_BY_BOOT_UP);
305 Slog.i(TAG, "Device does not support HDMI-CEC.");
309 mMhlController = HdmiMhlControllerStub.create(this);
310 if (!mMhlController.isReady()) {
311 Slog.i(TAG, "Device does not support MHL-control.");
313 mMhlDevices = Collections.emptyList();
316 mMessageValidator = new HdmiCecMessageValidator(this);
317 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
319 // Register broadcast receiver for power state change.
320 if (mCecController != null) {
321 IntentFilter filter = new IntentFilter();
322 filter.addAction(Intent.ACTION_SCREEN_OFF);
323 filter.addAction(Intent.ACTION_SCREEN_ON);
324 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
325 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
330 * Called when the initialization of local devices is complete.
332 private void onInitializeCecComplete() {
333 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
334 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
336 mWakeUpMessageReceived = false;
339 mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
340 registerContentObserver();
344 private void registerContentObserver() {
345 ContentResolver resolver = getContext().getContentResolver();
346 String[] settings = new String[] {
347 Global.HDMI_CONTROL_ENABLED,
348 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
349 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
350 Global.MHL_INPUT_SWITCHING_ENABLED,
351 Global.MHL_POWER_CHARGE_ENABLED
353 for (String s : settings) {
354 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
355 UserHandle.USER_ALL);
359 private class SettingsObserver extends ContentObserver {
360 public SettingsObserver(Handler handler) {
364 // onChange is set up to run in service thread.
366 public void onChange(boolean selfChange, Uri uri) {
367 String option = uri.getLastPathSegment();
368 boolean enabled = readBooleanSetting(option, true);
370 case Global.HDMI_CONTROL_ENABLED:
371 setControlEnabled(enabled);
373 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
374 tv().setAutoWakeup(enabled);
375 setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
377 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
378 tv().setAutoDeviceOff(enabled);
379 // No need to propagate to HAL.
381 case Global.MHL_INPUT_SWITCHING_ENABLED:
382 setMhlInputChangeEnabled(enabled);
384 case Global.MHL_POWER_CHARGE_ENABLED:
385 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
391 private static int toInt(boolean enabled) {
392 return enabled ? ENABLED : DISABLED;
395 boolean readBooleanSetting(String key, boolean defVal) {
396 ContentResolver cr = getContext().getContentResolver();
397 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
400 void writeBooleanSetting(String key, boolean value) {
401 ContentResolver cr = getContext().getContentResolver();
402 Global.putInt(cr, key, toInt(value));
405 private void unregisterSettingsObserver() {
406 getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
409 private void initializeCec(int initiatedBy) {
410 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
411 initializeLocalDevices(initiatedBy);
415 private void initializeLocalDevices(final int initiatedBy) {
416 assertRunOnServiceThread();
417 // A container for [Device type, Local device info].
418 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
419 for (int type : mLocalDevices) {
420 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
421 if (localDevice == null) {
422 localDevice = HdmiCecLocalDevice.create(this, type);
425 localDevices.add(localDevice);
427 // It's now safe to flush existing local devices from mCecController since they were
428 // already moved to 'localDevices'.
430 allocateLogicalAddress(localDevices, initiatedBy);
434 private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
435 final int initiatedBy) {
436 assertRunOnServiceThread();
437 mCecController.clearLogicalAddress();
438 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
439 final int[] finished = new int[1];
440 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
441 mCecController.allocateLogicalAddress(localDevice.getType(),
442 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
444 public void onAllocated(int deviceType, int logicalAddress) {
445 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
446 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
448 // Set POWER_STATUS_ON to all local devices because they share lifetime
450 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
451 HdmiControlManager.POWER_STATUS_ON);
452 localDevice.setDeviceInfo(deviceInfo);
453 mCecController.addLocalDevice(deviceType, localDevice);
454 mCecController.addLogicalAddress(logicalAddress);
455 allocatedDevices.add(localDevice);
458 // Address allocation completed for all devices. Notify each device.
459 if (allocatingDevices.size() == ++finished[0]) {
460 if (initiatedBy != INITIATED_BY_HOTPLUG) {
461 // In case of the hotplug we don't call onInitializeCecComplete()
462 // since we reallocate the logical address only.
463 onInitializeCecComplete();
465 notifyAddressAllocated(allocatedDevices, initiatedBy);
473 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
474 assertRunOnServiceThread();
475 for (HdmiCecLocalDevice device : devices) {
476 int address = device.getDeviceInfo().getLogicalAddress();
477 device.handleAddressAllocated(address, initiatedBy);
481 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
482 // keep them in one place.
484 private void initPortInfo() {
485 assertRunOnServiceThread();
486 HdmiPortInfo[] cecPortInfo = null;
488 // CEC HAL provides majority of the info while MHL does only MHL support flag for
489 // each port. Return empty array if CEC HAL didn't provide the info.
490 if (mCecController != null) {
491 cecPortInfo = mCecController.getPortInfos();
493 if (cecPortInfo == null) {
497 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
498 SparseIntArray portIdMap = new SparseIntArray();
499 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
500 for (HdmiPortInfo info : cecPortInfo) {
501 portIdMap.put(info.getAddress(), info.getId());
502 portInfoMap.put(info.getId(), info);
503 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
505 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
506 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
507 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
509 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
510 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
511 for (HdmiPortInfo info : mhlPortInfo) {
512 if (info.isMhlSupported()) {
513 mhlSupportedPorts.add(info.getId());
517 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
518 // cec port info if we do not have have port that supports MHL.
519 if (mhlSupportedPorts.isEmpty()) {
520 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
523 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
524 for (HdmiPortInfo info : cecPortInfo) {
525 if (mhlSupportedPorts.contains(info.getId())) {
526 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
527 info.isCecSupported(), true, info.isArcSupported()));
532 mPortInfo = Collections.unmodifiableList(result);
535 List<HdmiPortInfo> getPortInfo() {
540 * Returns HDMI port information for the given port id.
542 * @param portId HDMI port id
543 * @return {@link HdmiPortInfo} for the given port
545 HdmiPortInfo getPortInfo(int portId) {
546 return mPortInfoMap.get(portId, null);
550 * Returns the routing path (physical address) of the HDMI port for the given
553 int portIdToPath(int portId) {
554 HdmiPortInfo portInfo = getPortInfo(portId);
555 if (portInfo == null) {
556 Slog.e(TAG, "Cannot find the port info: " + portId);
557 return Constants.INVALID_PHYSICAL_ADDRESS;
559 return portInfo.getAddress();
563 * Returns the id of HDMI port located at the top of the hierarchy of
564 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
565 * the port id to be returned is the ID associated with the port address
566 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
568 int pathToPortId(int path) {
569 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
570 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
573 boolean isValidPortId(int portId) {
574 return getPortInfo(portId) != null;
578 * Returns {@link Looper} for IO operation.
580 * <p>Declared as package-private.
582 Looper getIoLooper() {
583 return mIoThread.getLooper();
587 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
588 * for tasks that are running on main service thread.
590 * <p>Declared as package-private.
592 Looper getServiceLooper() {
593 return mHandler.getLooper();
597 * Returns physical address of the device.
599 int getPhysicalAddress() {
600 return mCecController.getPhysicalAddress();
604 * Returns vendor id of CEC service.
607 return mCecController.getVendorId();
611 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
612 assertRunOnServiceThread();
613 HdmiCecLocalDeviceTv tv = tv();
617 return tv.getCecDeviceInfo(logicalAddress);
621 * Returns version of CEC.
623 int getCecVersion() {
624 return mCecController.getVersion();
628 * Whether a device of the specified physical address is connected to ARC enabled port.
630 boolean isConnectedToArcPort(int physicalAddress) {
631 int portId = pathToPortId(physicalAddress);
632 if (portId != Constants.INVALID_PORT_ID) {
633 return mPortInfoMap.get(portId).isArcSupported();
638 void runOnServiceThread(Runnable runnable) {
639 mHandler.post(runnable);
642 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
643 mHandler.postAtFrontOfQueue(runnable);
646 private void assertRunOnServiceThread() {
647 if (Looper.myLooper() != mHandler.getLooper()) {
648 throw new IllegalStateException("Should run on service thread.");
653 * Transmit a CEC command to CEC bus.
655 * @param command CEC command to send out
656 * @param callback interface used to the result of send command
659 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
660 assertRunOnServiceThread();
661 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
662 mCecController.sendCommand(command, callback);
664 HdmiLogger.error("Invalid message type:" + command);
665 if (callback != null) {
666 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
672 void sendCecCommand(HdmiCecMessage command) {
673 assertRunOnServiceThread();
674 sendCecCommand(command, null);
678 * Send <Feature Abort> command on the given CEC message if possible.
679 * If the aborted message is invalid, then it wont send the message.
680 * @param command original command to be aborted
681 * @param reason reason of feature abort
684 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
685 assertRunOnServiceThread();
686 mCecController.maySendFeatureAbortCommand(command, reason);
690 boolean handleCecCommand(HdmiCecMessage message) {
691 assertRunOnServiceThread();
692 int errorCode = mMessageValidator.isValid(message);
693 if (errorCode != HdmiCecMessageValidator.OK) {
694 // We'll not response on the messages with the invalid source or destination.
695 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
696 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
700 return dispatchMessageToLocalDevice(message);
703 void setAudioReturnChannel(boolean enabled) {
704 mCecController.setAudioReturnChannel(enabled);
708 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
709 assertRunOnServiceThread();
710 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
711 if (device.dispatchMessage(message)
712 && message.getDestination() != Constants.ADDR_BROADCAST) {
717 if (message.getDestination() != Constants.ADDR_BROADCAST) {
718 HdmiLogger.warning("Unhandled cec command:" + message);
724 * Called when a new hotplug event is issued.
726 * @param portId hdmi port number where hot plug event issued.
727 * @param connected whether to be plugged in or not
730 void onHotplug(int portId, boolean connected) {
731 assertRunOnServiceThread();
733 if (connected && !isTvDevice()) {
734 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
735 for (int type : mLocalDevices) {
736 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
737 if (localDevice == null) {
738 localDevice = HdmiCecLocalDevice.create(this, type);
741 localDevices.add(localDevice);
743 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
746 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
747 device.onHotplug(portId, connected);
749 announceHotplugEvent(portId, connected);
753 * Poll all remote devices. It sends <Polling Message> to all remote
756 * @param callback an interface used to get a list of all remote devices' address
757 * @param sourceAddress a logical address of source device where sends polling message
758 * @param pickStrategy strategy how to pick polling candidates
759 * @param retryCount the number of retry used to send polling message to remote devices
760 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
763 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
765 assertRunOnServiceThread();
766 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
770 private int checkPollStrategy(int pickStrategy) {
771 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
773 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
775 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
776 if (iterationStrategy == 0) {
777 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
779 return strategy | iterationStrategy;
782 List<HdmiCecLocalDevice> getAllLocalDevices() {
783 assertRunOnServiceThread();
784 return mCecController.getLocalDeviceList();
787 Object getServiceLock() {
791 void setAudioStatus(boolean mute, int volume) {
792 AudioManager audioManager = getAudioManager();
793 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
796 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
800 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
802 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
803 // volume change notification back to hdmi control service.
804 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
805 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
809 void announceSystemAudioModeChange(boolean enabled) {
810 synchronized (mLock) {
811 for (SystemAudioModeChangeListenerRecord record :
812 mSystemAudioModeChangeListenerRecords) {
813 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
818 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
819 // TODO: find better name instead of model name.
820 String displayName = Build.MODEL;
821 return new HdmiDeviceInfo(logicalAddress,
822 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
823 getVendorId(), displayName);
827 void handleMhlHotplugEvent(int portId, boolean connected) {
828 assertRunOnServiceThread();
829 // Hotplug event is used to add/remove MHL devices as TV input.
831 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
832 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
833 if (oldDevice != null) {
834 oldDevice.onDeviceRemoved();
835 Slog.i(TAG, "Old device of port " + portId + " is removed");
837 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
838 updateSafeMhlInput();
840 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
841 if (device != null) {
842 device.onDeviceRemoved();
843 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
844 updateSafeMhlInput();
846 Slog.w(TAG, "No device to remove:[portId=" + portId);
849 announceHotplugEvent(portId, connected);
853 void handleMhlBusModeChanged(int portId, int busmode) {
854 assertRunOnServiceThread();
855 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
856 if (device != null) {
857 device.setBusMode(busmode);
859 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
860 ", busmode:" + busmode + "]");
865 void handleMhlBusOvercurrent(int portId, boolean on) {
866 assertRunOnServiceThread();
867 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
868 if (device != null) {
869 device.onBusOvercurrentDetected(on);
871 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
876 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
877 assertRunOnServiceThread();
878 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
880 if (device != null) {
881 device.setDeviceStatusChange(adopterId, deviceId);
883 Slog.w(TAG, "No mhl device exists for device status event[portId:"
884 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
889 private void updateSafeMhlInput() {
890 assertRunOnServiceThread();
891 List<HdmiDeviceInfo> inputs = Collections.emptyList();
892 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
893 for (int i = 0; i < devices.size(); ++i) {
894 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
895 HdmiDeviceInfo info = device.getInfo();
897 if (inputs.isEmpty()) {
898 inputs = new ArrayList<>();
900 inputs.add(device.getInfo());
903 synchronized (mLock) {
904 mMhlDevices = inputs;
908 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
912 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
913 private final IHdmiMhlVendorCommandListener mListener;
915 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
916 mListener = listener;
920 public void binderDied() {
921 mMhlVendorCommandListenerRecords.remove(this);
925 // Record class that monitors the event of the caller of being killed. Used to clean up
926 // the listener list and record list accordingly.
927 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
928 private final IHdmiHotplugEventListener mListener;
930 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
931 mListener = listener;
935 public void binderDied() {
936 synchronized (mLock) {
937 mHotplugEventListenerRecords.remove(this);
942 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
943 private final IHdmiDeviceEventListener mListener;
945 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
946 mListener = listener;
950 public void binderDied() {
951 synchronized (mLock) {
952 mDeviceEventListenerRecords.remove(this);
957 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
958 private final IHdmiSystemAudioModeChangeListener mListener;
960 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
961 mListener = listener;
965 public void binderDied() {
966 synchronized (mLock) {
967 mSystemAudioModeChangeListenerRecords.remove(this);
972 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
973 private final IHdmiVendorCommandListener mListener;
974 private final int mDeviceType;
976 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
977 mListener = listener;
978 mDeviceType = deviceType;
982 public void binderDied() {
983 synchronized (mLock) {
984 mVendorCommandListenerRecords.remove(this);
989 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
990 private final IHdmiRecordListener mListener;
992 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
993 mListener = listener;
997 public void binderDied() {
998 synchronized (mLock) {
999 mRecordListenerRecord = null;
1004 private void enforceAccessPermission() {
1005 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1008 private final class BinderService extends IHdmiControlService.Stub {
1010 public int[] getSupportedTypes() {
1011 enforceAccessPermission();
1012 // mLocalDevices is an unmodifiable list - no lock necesary.
1013 int[] localDevices = new int[mLocalDevices.size()];
1014 for (int i = 0; i < localDevices.length; ++i) {
1015 localDevices[i] = mLocalDevices.get(i);
1017 return localDevices;
1021 public HdmiDeviceInfo getActiveSource() {
1022 HdmiCecLocalDeviceTv tv = tv();
1024 Slog.w(TAG, "Local tv device not available");
1027 ActiveSource activeSource = tv.getActiveSource();
1028 if (activeSource.isValid()) {
1029 return new HdmiDeviceInfo(activeSource.logicalAddress,
1030 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1031 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1033 int activePath = tv.getActivePath();
1034 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1035 return new HdmiDeviceInfo(activePath, tv.getActivePortId());
1041 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1042 enforceAccessPermission();
1043 runOnServiceThread(new Runnable() {
1046 if (callback == null) {
1047 Slog.e(TAG, "Callback cannot be null");
1050 HdmiCecLocalDeviceTv tv = tv();
1052 Slog.w(TAG, "Local tv device not available");
1053 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1056 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1057 if (device != null) {
1058 if (device.getPortId() == tv.getActivePortId()) {
1059 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1062 // Upon selecting MHL device, we send RAP[Content On] to wake up
1063 // the connected mobile device, start routing control to switch ports.
1064 // callback is handled by MHL action.
1065 device.turnOn(callback);
1066 tv.doManualPortSwitching(device.getPortId(), null);
1069 tv.deviceSelect(deviceId, callback);
1075 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1076 enforceAccessPermission();
1077 runOnServiceThread(new Runnable() {
1080 if (callback == null) {
1081 Slog.e(TAG, "Callback cannot be null");
1084 HdmiCecLocalDeviceTv tv = tv();
1086 Slog.w(TAG, "Local tv device not available");
1087 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1090 tv.doManualPortSwitching(portId, callback);
1096 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1097 enforceAccessPermission();
1098 runOnServiceThread(new Runnable() {
1101 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1102 if (device != null) {
1103 device.sendKeyEvent(keyCode, isPressed);
1106 if (mCecController != null) {
1107 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1108 if (localDevice == null) {
1109 Slog.w(TAG, "Local device not available");
1112 localDevice.sendKeyEvent(keyCode, isPressed);
1119 public void oneTouchPlay(final IHdmiControlCallback callback) {
1120 enforceAccessPermission();
1121 runOnServiceThread(new Runnable() {
1124 HdmiControlService.this.oneTouchPlay(callback);
1130 public void queryDisplayStatus(final IHdmiControlCallback callback) {
1131 enforceAccessPermission();
1132 runOnServiceThread(new Runnable() {
1135 HdmiControlService.this.queryDisplayStatus(callback);
1141 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1142 enforceAccessPermission();
1143 HdmiControlService.this.addHotplugEventListener(listener);
1147 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1148 enforceAccessPermission();
1149 HdmiControlService.this.removeHotplugEventListener(listener);
1153 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1154 enforceAccessPermission();
1155 HdmiControlService.this.addDeviceEventListener(listener);
1159 public List<HdmiPortInfo> getPortInfo() {
1160 enforceAccessPermission();
1161 return HdmiControlService.this.getPortInfo();
1165 public boolean canChangeSystemAudioMode() {
1166 enforceAccessPermission();
1167 HdmiCecLocalDeviceTv tv = tv();
1171 return tv.hasSystemAudioDevice();
1175 public boolean getSystemAudioMode() {
1176 enforceAccessPermission();
1177 HdmiCecLocalDeviceTv tv = tv();
1181 return tv.isSystemAudioActivated();
1185 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1186 enforceAccessPermission();
1187 runOnServiceThread(new Runnable() {
1190 HdmiCecLocalDeviceTv tv = tv();
1192 Slog.w(TAG, "Local tv device not available");
1193 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1196 tv.changeSystemAudioMode(enabled, callback);
1202 public void addSystemAudioModeChangeListener(
1203 final IHdmiSystemAudioModeChangeListener listener) {
1204 enforceAccessPermission();
1205 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1209 public void removeSystemAudioModeChangeListener(
1210 final IHdmiSystemAudioModeChangeListener listener) {
1211 enforceAccessPermission();
1212 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1216 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1217 enforceAccessPermission();
1218 HdmiControlService.this.setInputChangeListener(listener);
1222 public List<HdmiDeviceInfo> getInputDevices() {
1223 enforceAccessPermission();
1224 // No need to hold the lock for obtaining TV device as the local device instance
1225 // is preserved while the HDMI control is enabled.
1226 HdmiCecLocalDeviceTv tv = tv();
1227 synchronized (mLock) {
1228 List<HdmiDeviceInfo> cecDevices = (tv == null)
1229 ? Collections.<HdmiDeviceInfo>emptyList()
1230 : tv.getSafeExternalInputsLocked();
1231 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1235 // Returns all the CEC devices on the bus including system audio, switch,
1236 // even those of reserved type.
1238 public List<HdmiDeviceInfo> getDeviceList() {
1239 enforceAccessPermission();
1240 HdmiCecLocalDeviceTv tv = tv();
1241 synchronized (mLock) {
1243 ? Collections.<HdmiDeviceInfo>emptyList()
1244 : tv.getSafeCecDevicesLocked();
1249 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1250 final int maxIndex) {
1251 enforceAccessPermission();
1252 runOnServiceThread(new Runnable() {
1255 HdmiCecLocalDeviceTv tv = tv();
1257 Slog.w(TAG, "Local tv device not available");
1260 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1266 public void setSystemAudioMute(final boolean mute) {
1267 enforceAccessPermission();
1268 runOnServiceThread(new Runnable() {
1271 HdmiCecLocalDeviceTv tv = tv();
1273 Slog.w(TAG, "Local tv device not available");
1276 tv.changeMute(mute);
1282 public void setArcMode(final boolean enabled) {
1283 enforceAccessPermission();
1284 runOnServiceThread(new Runnable() {
1287 HdmiCecLocalDeviceTv tv = tv();
1289 Slog.w(TAG, "Local tv device not available to change arc mode.");
1297 public void setProhibitMode(final boolean enabled) {
1298 enforceAccessPermission();
1299 if (!isTvDevice()) {
1302 HdmiControlService.this.setProhibitMode(enabled);
1306 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1307 final int deviceType) {
1308 enforceAccessPermission();
1309 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1313 public void sendVendorCommand(final int deviceType, final int targetAddress,
1314 final byte[] params, final boolean hasVendorId) {
1315 enforceAccessPermission();
1316 runOnServiceThread(new Runnable() {
1319 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1320 if (device == null) {
1321 Slog.w(TAG, "Local device not available");
1325 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1326 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1327 getVendorId(), params));
1329 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1330 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1337 public void sendStandby(final int deviceType, final int deviceId) {
1338 enforceAccessPermission();
1339 runOnServiceThread(new Runnable() {
1342 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1343 if (device == null) {
1344 Slog.w(TAG, "Local device not available");
1347 device.sendStandby(deviceId);
1353 public void setHdmiRecordListener(IHdmiRecordListener listener) {
1354 HdmiControlService.this.setHdmiRecordListener(listener);
1358 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1359 runOnServiceThread(new Runnable() {
1362 if (!isTvDevice()) {
1363 Slog.w(TAG, "No TV is available.");
1366 tv().startOneTouchRecord(recorderAddress, recordSource);
1372 public void stopOneTouchRecord(final int recorderAddress) {
1373 runOnServiceThread(new Runnable() {
1376 if (!isTvDevice()) {
1377 Slog.w(TAG, "No TV is available.");
1380 tv().stopOneTouchRecord(recorderAddress);
1386 public void startTimerRecording(final int recorderAddress, final int sourceType,
1387 final byte[] recordSource) {
1388 runOnServiceThread(new Runnable() {
1391 if (!isTvDevice()) {
1392 Slog.w(TAG, "No TV is available.");
1395 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1401 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1402 final byte[] recordSource) {
1403 runOnServiceThread(new Runnable() {
1406 if (!isTvDevice()) {
1407 Slog.w(TAG, "No TV is available.");
1410 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1416 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1417 final byte[] data) {
1418 enforceAccessPermission();
1419 runOnServiceThread(new Runnable() {
1422 if (!isControlEnabled()) {
1423 Slog.w(TAG, "Hdmi control is disabled.");
1426 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1427 if (device == null) {
1428 Slog.w(TAG, "Invalid port id:" + portId);
1431 mMhlController.sendVendorCommand(portId, offset, length, data);
1437 public void addHdmiMhlVendorCommandListener(
1438 IHdmiMhlVendorCommandListener listener) {
1439 enforceAccessPermission();
1440 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1444 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1445 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1446 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1448 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1449 pw.println("mProhibitMode: " + mProhibitMode);
1450 if (mCecController != null) {
1451 pw.println("mCecController: ");
1452 pw.increaseIndent();
1453 mCecController.dump(pw);
1454 pw.decreaseIndent();
1456 pw.println("mPortInfo: ");
1457 pw.increaseIndent();
1458 for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1459 pw.println("- " + hdmiPortInfo);
1461 pw.decreaseIndent();
1462 pw.println("mPowerStatus: " + mPowerStatus);
1467 private void oneTouchPlay(final IHdmiControlCallback callback) {
1468 assertRunOnServiceThread();
1469 HdmiCecLocalDevicePlayback source = playback();
1470 if (source == null) {
1471 Slog.w(TAG, "Local playback device not available");
1472 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1475 source.oneTouchPlay(callback);
1479 private void queryDisplayStatus(final IHdmiControlCallback callback) {
1480 assertRunOnServiceThread();
1481 HdmiCecLocalDevicePlayback source = playback();
1482 if (source == null) {
1483 Slog.w(TAG, "Local playback device not available");
1484 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1487 source.queryDisplayStatus(callback);
1490 private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1491 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1493 listener.asBinder().linkToDeath(record, 0);
1494 } catch (RemoteException e) {
1495 Slog.w(TAG, "Listener already died");
1498 synchronized (mLock) {
1499 mHotplugEventListenerRecords.add(record);
1503 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1504 synchronized (mLock) {
1505 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1506 if (record.mListener.asBinder() == listener.asBinder()) {
1507 listener.asBinder().unlinkToDeath(record, 0);
1508 mHotplugEventListenerRecords.remove(record);
1515 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1516 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1518 listener.asBinder().linkToDeath(record, 0);
1519 } catch (RemoteException e) {
1520 Slog.w(TAG, "Listener already died");
1523 synchronized (mLock) {
1524 mDeviceEventListenerRecords.add(record);
1528 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1529 synchronized (mLock) {
1530 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1532 record.mListener.onStatusChanged(device, status);
1533 } catch (RemoteException e) {
1534 Slog.e(TAG, "Failed to report device event:" + e);
1540 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1541 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1544 listener.asBinder().linkToDeath(record, 0);
1545 } catch (RemoteException e) {
1546 Slog.w(TAG, "Listener already died");
1549 synchronized (mLock) {
1550 mSystemAudioModeChangeListenerRecords.add(record);
1554 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1555 synchronized (mLock) {
1556 for (SystemAudioModeChangeListenerRecord record :
1557 mSystemAudioModeChangeListenerRecords) {
1558 if (record.mListener.asBinder() == listener) {
1559 listener.asBinder().unlinkToDeath(record, 0);
1560 mSystemAudioModeChangeListenerRecords.remove(record);
1567 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1568 private final IHdmiInputChangeListener mListener;
1570 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1571 mListener = listener;
1575 public void binderDied() {
1576 synchronized (mLock) {
1577 mInputChangeListenerRecord = null;
1582 private void setInputChangeListener(IHdmiInputChangeListener listener) {
1583 synchronized (mLock) {
1584 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1586 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1587 } catch (RemoteException e) {
1588 Slog.w(TAG, "Listener already died");
1594 void invokeInputChangeListener(HdmiDeviceInfo info) {
1595 synchronized (mLock) {
1596 if (mInputChangeListenerRecord != null) {
1598 mInputChangeListenerRecord.mListener.onChanged(info);
1599 } catch (RemoteException e) {
1600 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1606 private void setHdmiRecordListener(IHdmiRecordListener listener) {
1607 synchronized (mLock) {
1608 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1610 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1611 } catch (RemoteException e) {
1612 Slog.w(TAG, "Listener already died.", e);
1617 byte[] invokeRecordRequestListener(int recorderAddress) {
1618 synchronized (mLock) {
1619 if (mRecordListenerRecord != null) {
1621 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1622 } catch (RemoteException e) {
1623 Slog.w(TAG, "Failed to start record.", e);
1626 return EmptyArray.BYTE;
1630 void invokeOneTouchRecordResult(int result) {
1631 synchronized (mLock) {
1632 if (mRecordListenerRecord != null) {
1634 mRecordListenerRecord.mListener.onOneTouchRecordResult(result);
1635 } catch (RemoteException e) {
1636 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1642 void invokeTimerRecordingResult(int result) {
1643 synchronized (mLock) {
1644 if (mRecordListenerRecord != null) {
1646 mRecordListenerRecord.mListener.onTimerRecordingResult(result);
1647 } catch (RemoteException e) {
1648 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1654 void invokeClearTimerRecordingResult(int result) {
1655 synchronized (mLock) {
1656 if (mRecordListenerRecord != null) {
1658 mRecordListenerRecord.mListener.onClearTimerRecordingResult(result);
1659 } catch (RemoteException e) {
1660 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1666 private void invokeCallback(IHdmiControlCallback callback, int result) {
1668 callback.onComplete(result);
1669 } catch (RemoteException e) {
1670 Slog.e(TAG, "Invoking callback failed:" + e);
1674 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1677 listener.onStatusChanged(enabled);
1678 } catch (RemoteException e) {
1679 Slog.e(TAG, "Invoking callback failed:" + e);
1683 private void announceHotplugEvent(int portId, boolean connected) {
1684 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1685 synchronized (mLock) {
1686 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1687 invokeHotplugEventListenerLocked(record.mListener, event);
1692 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1693 HdmiHotplugEvent event) {
1695 listener.onReceived(event);
1696 } catch (RemoteException e) {
1697 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1701 private HdmiCecLocalDeviceTv tv() {
1702 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1705 boolean isTvDevice() {
1706 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
1709 private HdmiCecLocalDevicePlayback playback() {
1710 return (HdmiCecLocalDevicePlayback)
1711 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1714 AudioManager getAudioManager() {
1715 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1718 boolean isControlEnabled() {
1719 synchronized (mLock) {
1720 return mHdmiControlEnabled;
1725 int getPowerStatus() {
1726 assertRunOnServiceThread();
1727 return mPowerStatus;
1731 boolean isPowerOnOrTransient() {
1732 assertRunOnServiceThread();
1733 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1734 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1738 boolean isPowerStandbyOrTransient() {
1739 assertRunOnServiceThread();
1740 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1741 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1745 boolean isPowerStandby() {
1746 assertRunOnServiceThread();
1747 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1752 assertRunOnServiceThread();
1753 mWakeUpMessageReceived = true;
1754 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1755 pm.wakeUp(SystemClock.uptimeMillis());
1756 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1757 // the intent, the sequence will continue at onWakeUp().
1762 assertRunOnServiceThread();
1763 mStandbyMessageReceived = true;
1764 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1765 pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
1766 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1767 // the intent, the sequence will continue at onStandby().
1771 private void onWakeUp() {
1772 assertRunOnServiceThread();
1773 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1774 if (mCecController != null) {
1775 if (mHdmiControlEnabled) {
1776 int startReason = INITIATED_BY_SCREEN_ON;
1777 if (mWakeUpMessageReceived) {
1778 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1780 initializeCec(startReason);
1783 Slog.i(TAG, "Device does not support HDMI-CEC.");
1785 // TODO: Initialize MHL local devices.
1789 private void onStandby() {
1790 assertRunOnServiceThread();
1791 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1793 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1794 disableDevices(new PendingActionClearedCallback() {
1796 public void onCleared(HdmiCecLocalDevice device) {
1797 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1798 devices.remove(device);
1799 if (devices.isEmpty()) {
1800 onStandbyCompleted();
1801 // We will not clear local devices here, since some OEM/SOC will keep passing
1802 // the received packets until the application processor enters to the sleep
1810 private void onLanguageChanged(String language) {
1811 assertRunOnServiceThread();
1812 mLanguage = language;
1815 tv().broadcastMenuLanguage(language);
1820 String getLanguage() {
1821 assertRunOnServiceThread();
1825 private void disableDevices(PendingActionClearedCallback callback) {
1826 if (mCecController != null) {
1827 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1828 device.disableDevice(mStandbyMessageReceived, callback);
1831 unregisterSettingsObserver();
1835 mMhlController.clearAllLocalDevices();
1839 private void clearLocalDevices() {
1840 assertRunOnServiceThread();
1841 if (mCecController == null) {
1844 mCecController.clearLogicalAddress();
1845 mCecController.clearLocalDevices();
1849 private void onStandbyCompleted() {
1850 assertRunOnServiceThread();
1851 Slog.v(TAG, "onStandbyCompleted");
1853 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1856 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1857 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1858 device.onStandby(mStandbyMessageReceived);
1860 mStandbyMessageReceived = false;
1861 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
1864 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1865 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1867 listener.asBinder().linkToDeath(record, 0);
1868 } catch (RemoteException e) {
1869 Slog.w(TAG, "Listener already died");
1872 synchronized (mLock) {
1873 mVendorCommandListenerRecords.add(record);
1877 boolean invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1878 boolean hasVendorId) {
1879 synchronized (mLock) {
1880 if (mVendorCommandListenerRecords.isEmpty()) {
1883 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1884 if (record.mDeviceType != deviceType) {
1888 record.mListener.onReceived(srcAddress, params, hasVendorId);
1889 } catch (RemoteException e) {
1890 Slog.e(TAG, "Failed to notify vendor command reception", e);
1897 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
1898 HdmiMhlVendorCommandListenerRecord record =
1899 new HdmiMhlVendorCommandListenerRecord(listener);
1901 listener.asBinder().linkToDeath(record, 0);
1902 } catch (RemoteException e) {
1903 Slog.w(TAG, "Listener already died.");
1907 synchronized (mLock) {
1908 mMhlVendorCommandListenerRecords.add(record);
1912 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
1913 synchronized (mLock) {
1914 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
1916 record.mListener.onReceived(portId, offest, length, data);
1917 } catch (RemoteException e) {
1918 Slog.e(TAG, "Failed to notify MHL vendor command", e);
1924 boolean isProhibitMode() {
1925 synchronized (mLock) {
1926 return mProhibitMode;
1930 void setProhibitMode(boolean enabled) {
1931 synchronized (mLock) {
1932 mProhibitMode = enabled;
1937 void setCecOption(int key, int value) {
1938 assertRunOnServiceThread();
1939 mCecController.setOption(key, value);
1943 void setControlEnabled(boolean enabled) {
1944 assertRunOnServiceThread();
1946 int value = toInt(enabled);
1947 mCecController.setOption(OPTION_CEC_ENABLE, value);
1948 mMhlController.setOption(OPTION_MHL_ENABLE, value);
1950 synchronized (mLock) {
1951 mHdmiControlEnabled = enabled;
1955 initializeCec(INITIATED_BY_ENABLE_CEC);
1957 disableDevices(new PendingActionClearedCallback() {
1959 public void onCleared(HdmiCecLocalDevice device) {
1960 assertRunOnServiceThread();
1961 clearLocalDevices();
1968 void setActivePortId(int portId) {
1969 assertRunOnServiceThread();
1970 mActivePortId = portId;
1972 // Resets last input for MHL, which stays valid only after the MHL device was selected,
1973 // and no further switching is done.
1974 setLastInputForMhl(Constants.INVALID_PORT_ID);
1978 void setLastInputForMhl(int portId) {
1979 assertRunOnServiceThread();
1980 mLastInputMhl = portId;
1984 int getLastInputForMhl() {
1985 assertRunOnServiceThread();
1986 return mLastInputMhl;
1990 * Performs input change, routing control for MHL device.
1992 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
1993 * @param contentOn {@code true} if RAP data is content on; otherwise false
1996 void changeInputForMhl(int portId, boolean contentOn) {
1997 assertRunOnServiceThread();
1998 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
1999 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2001 public void onComplete(int result) throws RemoteException {
2002 // Keep the last input to switch back later when RAP[ContentOff] is received.
2003 // This effectively sets the port to invalid one if the switching is for
2005 setLastInputForMhl(lastInput);
2009 // MHL device is always directly connected to the port. Update the active port ID to avoid
2010 // unnecessary post-routing control task.
2011 tv().setActivePortId(portId);
2013 // The port is either the MHL-enabled port where the mobile device is connected, or
2014 // the last port to go back to when turnoff command is received. Note that the last port
2015 // may not be the MHL-enabled one. In this case the device info to be passed to
2016 // input change listener should be the one describing the corresponding HDMI port.
2017 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
2018 HdmiDeviceInfo info = (device != null) ? device.getInfo() : mPortDeviceMap.get(portId);
2019 invokeInputChangeListener(info);
2022 void setMhlInputChangeEnabled(boolean enabled) {
2023 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2025 synchronized (mLock) {
2026 mMhlInputChangeEnabled = enabled;
2030 boolean isMhlInputChangeEnabled() {
2031 synchronized (mLock) {
2032 return mMhlInputChangeEnabled;
2037 void displayOsd(int messageId) {
2038 assertRunOnServiceThread();
2039 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2040 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2041 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2042 HdmiControlService.PERMISSION);
2046 void displayOsd(int messageId, int extra) {
2047 assertRunOnServiceThread();
2048 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2049 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2050 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRAM_PARAM1, extra);
2051 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2052 HdmiControlService.PERMISSION);