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.");
308 mMhlController = HdmiMhlControllerStub.create(this);
309 if (!mMhlController.isReady()) {
310 Slog.i(TAG, "Device does not support MHL-control.");
312 mMhlDevices = Collections.emptyList();
315 mMessageValidator = new HdmiCecMessageValidator(this);
316 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
318 // Register broadcast receiver for power state change.
319 if (mCecController != null) {
320 IntentFilter filter = new IntentFilter();
321 filter.addAction(Intent.ACTION_SCREEN_OFF);
322 filter.addAction(Intent.ACTION_SCREEN_ON);
323 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
324 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
329 * Called when the initialization of local devices is complete.
331 private void onInitializeCecComplete() {
332 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
333 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
335 mWakeUpMessageReceived = false;
338 mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
339 registerContentObserver();
343 private void registerContentObserver() {
344 ContentResolver resolver = getContext().getContentResolver();
345 String[] settings = new String[] {
346 Global.HDMI_CONTROL_ENABLED,
347 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
348 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
349 Global.MHL_INPUT_SWITCHING_ENABLED,
350 Global.MHL_POWER_CHARGE_ENABLED
352 for (String s : settings) {
353 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
354 UserHandle.USER_ALL);
358 private class SettingsObserver extends ContentObserver {
359 public SettingsObserver(Handler handler) {
363 // onChange is set up to run in service thread.
365 public void onChange(boolean selfChange, Uri uri) {
366 String option = uri.getLastPathSegment();
367 boolean enabled = readBooleanSetting(option, true);
369 case Global.HDMI_CONTROL_ENABLED:
370 setControlEnabled(enabled);
372 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
373 tv().setAutoWakeup(enabled);
374 setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
376 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
377 tv().setAutoDeviceOff(enabled);
378 // No need to propagate to HAL.
380 case Global.MHL_INPUT_SWITCHING_ENABLED:
381 setMhlInputChangeEnabled(enabled);
383 case Global.MHL_POWER_CHARGE_ENABLED:
384 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
390 private static int toInt(boolean enabled) {
391 return enabled ? ENABLED : DISABLED;
394 boolean readBooleanSetting(String key, boolean defVal) {
395 ContentResolver cr = getContext().getContentResolver();
396 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
399 void writeBooleanSetting(String key, boolean value) {
400 ContentResolver cr = getContext().getContentResolver();
401 Global.putInt(cr, key, toInt(value));
404 private void unregisterSettingsObserver() {
405 getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
408 private void initializeCec(int initiatedBy) {
409 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
410 initializeLocalDevices(initiatedBy);
414 private void initializeLocalDevices(final int initiatedBy) {
415 assertRunOnServiceThread();
416 // A container for [Device type, Local device info].
417 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
418 for (int type : mLocalDevices) {
419 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
420 if (localDevice == null) {
421 localDevice = HdmiCecLocalDevice.create(this, type);
424 localDevices.add(localDevice);
426 // It's now safe to flush existing local devices from mCecController since they were
427 // already moved to 'localDevices'.
429 allocateLogicalAddress(localDevices, initiatedBy);
433 private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
434 final int initiatedBy) {
435 assertRunOnServiceThread();
436 mCecController.clearLogicalAddress();
437 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
438 final int[] finished = new int[1];
439 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
440 mCecController.allocateLogicalAddress(localDevice.getType(),
441 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
443 public void onAllocated(int deviceType, int logicalAddress) {
444 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
445 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
447 // Set POWER_STATUS_ON to all local devices because they share lifetime
449 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
450 HdmiControlManager.POWER_STATUS_ON);
451 localDevice.setDeviceInfo(deviceInfo);
452 mCecController.addLocalDevice(deviceType, localDevice);
453 mCecController.addLogicalAddress(logicalAddress);
454 allocatedDevices.add(localDevice);
457 // Address allocation completed for all devices. Notify each device.
458 if (allocatingDevices.size() == ++finished[0]) {
459 if (initiatedBy != INITIATED_BY_HOTPLUG) {
460 // In case of the hotplug we don't call onInitializeCecComplete()
461 // since we reallocate the logical address only.
462 onInitializeCecComplete();
464 notifyAddressAllocated(allocatedDevices, initiatedBy);
472 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
473 assertRunOnServiceThread();
474 for (HdmiCecLocalDevice device : devices) {
475 int address = device.getDeviceInfo().getLogicalAddress();
476 device.handleAddressAllocated(address, initiatedBy);
480 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
481 // keep them in one place.
483 private void initPortInfo() {
484 assertRunOnServiceThread();
485 HdmiPortInfo[] cecPortInfo = null;
487 // CEC HAL provides majority of the info while MHL does only MHL support flag for
488 // each port. Return empty array if CEC HAL didn't provide the info.
489 if (mCecController != null) {
490 cecPortInfo = mCecController.getPortInfos();
492 if (cecPortInfo == null) {
496 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
497 SparseIntArray portIdMap = new SparseIntArray();
498 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
499 for (HdmiPortInfo info : cecPortInfo) {
500 portIdMap.put(info.getAddress(), info.getId());
501 portInfoMap.put(info.getId(), info);
502 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
504 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
505 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
506 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
508 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
509 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
510 for (HdmiPortInfo info : mhlPortInfo) {
511 if (info.isMhlSupported()) {
512 mhlSupportedPorts.add(info.getId());
516 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
517 // cec port info if we do not have have port that supports MHL.
518 if (mhlSupportedPorts.isEmpty()) {
519 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
522 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
523 for (HdmiPortInfo info : cecPortInfo) {
524 if (mhlSupportedPorts.contains(info.getId())) {
525 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
526 info.isCecSupported(), true, info.isArcSupported()));
531 mPortInfo = Collections.unmodifiableList(result);
534 List<HdmiPortInfo> getPortInfo() {
539 * Returns HDMI port information for the given port id.
541 * @param portId HDMI port id
542 * @return {@link HdmiPortInfo} for the given port
544 HdmiPortInfo getPortInfo(int portId) {
545 return mPortInfoMap.get(portId, null);
549 * Returns the routing path (physical address) of the HDMI port for the given
552 int portIdToPath(int portId) {
553 HdmiPortInfo portInfo = getPortInfo(portId);
554 if (portInfo == null) {
555 Slog.e(TAG, "Cannot find the port info: " + portId);
556 return Constants.INVALID_PHYSICAL_ADDRESS;
558 return portInfo.getAddress();
562 * Returns the id of HDMI port located at the top of the hierarchy of
563 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
564 * the port id to be returned is the ID associated with the port address
565 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
567 int pathToPortId(int path) {
568 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
569 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
572 boolean isValidPortId(int portId) {
573 return getPortInfo(portId) != null;
577 * Returns {@link Looper} for IO operation.
579 * <p>Declared as package-private.
581 Looper getIoLooper() {
582 return mIoThread.getLooper();
586 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
587 * for tasks that are running on main service thread.
589 * <p>Declared as package-private.
591 Looper getServiceLooper() {
592 return mHandler.getLooper();
596 * Returns physical address of the device.
598 int getPhysicalAddress() {
599 return mCecController.getPhysicalAddress();
603 * Returns vendor id of CEC service.
606 return mCecController.getVendorId();
610 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
611 assertRunOnServiceThread();
612 HdmiCecLocalDeviceTv tv = tv();
616 return tv.getCecDeviceInfo(logicalAddress);
620 * Returns version of CEC.
622 int getCecVersion() {
623 return mCecController.getVersion();
627 * Whether a device of the specified physical address is connected to ARC enabled port.
629 boolean isConnectedToArcPort(int physicalAddress) {
630 int portId = pathToPortId(physicalAddress);
631 if (portId != Constants.INVALID_PORT_ID) {
632 return mPortInfoMap.get(portId).isArcSupported();
637 void runOnServiceThread(Runnable runnable) {
638 mHandler.post(runnable);
641 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
642 mHandler.postAtFrontOfQueue(runnable);
645 private void assertRunOnServiceThread() {
646 if (Looper.myLooper() != mHandler.getLooper()) {
647 throw new IllegalStateException("Should run on service thread.");
652 * Transmit a CEC command to CEC bus.
654 * @param command CEC command to send out
655 * @param callback interface used to the result of send command
658 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
659 assertRunOnServiceThread();
660 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
661 mCecController.sendCommand(command, callback);
663 HdmiLogger.error("Invalid message type:" + command);
664 if (callback != null) {
665 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
671 void sendCecCommand(HdmiCecMessage command) {
672 assertRunOnServiceThread();
673 sendCecCommand(command, null);
677 * Send <Feature Abort> command on the given CEC message if possible.
678 * If the aborted message is invalid, then it wont send the message.
679 * @param command original command to be aborted
680 * @param reason reason of feature abort
683 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
684 assertRunOnServiceThread();
685 mCecController.maySendFeatureAbortCommand(command, reason);
689 boolean handleCecCommand(HdmiCecMessage message) {
690 assertRunOnServiceThread();
691 int errorCode = mMessageValidator.isValid(message);
692 if (errorCode != HdmiCecMessageValidator.OK) {
693 // We'll not response on the messages with the invalid source or destination.
694 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
695 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
699 return dispatchMessageToLocalDevice(message);
702 void setAudioReturnChannel(boolean enabled) {
703 mCecController.setAudioReturnChannel(enabled);
707 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
708 assertRunOnServiceThread();
709 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
710 if (device.dispatchMessage(message)
711 && message.getDestination() != Constants.ADDR_BROADCAST) {
716 if (message.getDestination() != Constants.ADDR_BROADCAST) {
717 HdmiLogger.warning("Unhandled cec command:" + message);
723 * Called when a new hotplug event is issued.
725 * @param portId hdmi port number where hot plug event issued.
726 * @param connected whether to be plugged in or not
729 void onHotplug(int portId, boolean connected) {
730 assertRunOnServiceThread();
732 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
733 for (int type : mLocalDevices) {
734 if (type == HdmiDeviceInfo.DEVICE_TV) {
735 // Skip the reallocation of the logical address on TV.
738 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
739 if (localDevice == null) {
740 localDevice = HdmiCecLocalDevice.create(this, type);
743 localDevices.add(localDevice);
745 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
747 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
748 device.onHotplug(portId, connected);
750 announceHotplugEvent(portId, connected);
754 * Poll all remote devices. It sends <Polling Message> to all remote
757 * @param callback an interface used to get a list of all remote devices' address
758 * @param sourceAddress a logical address of source device where sends polling message
759 * @param pickStrategy strategy how to pick polling candidates
760 * @param retryCount the number of retry used to send polling message to remote devices
761 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
764 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
766 assertRunOnServiceThread();
767 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
771 private int checkPollStrategy(int pickStrategy) {
772 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
774 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
776 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
777 if (iterationStrategy == 0) {
778 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
780 return strategy | iterationStrategy;
783 List<HdmiCecLocalDevice> getAllLocalDevices() {
784 assertRunOnServiceThread();
785 return mCecController.getLocalDeviceList();
788 Object getServiceLock() {
792 void setAudioStatus(boolean mute, int volume) {
793 AudioManager audioManager = getAudioManager();
794 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
797 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
801 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
803 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
804 // volume change notification back to hdmi control service.
805 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
806 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
810 void announceSystemAudioModeChange(boolean enabled) {
811 synchronized (mLock) {
812 for (SystemAudioModeChangeListenerRecord record :
813 mSystemAudioModeChangeListenerRecords) {
814 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
819 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
820 // TODO: find better name instead of model name.
821 String displayName = Build.MODEL;
822 return new HdmiDeviceInfo(logicalAddress,
823 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
824 getVendorId(), displayName);
828 void handleMhlHotplugEvent(int portId, boolean connected) {
829 assertRunOnServiceThread();
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");
838 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
839 if (device != null) {
840 device.onDeviceRemoved();
841 // There is no explicit event for device removal.
842 // Hence we remove the device on hotplug event.
843 HdmiDeviceInfo deviceInfo = device.getInfo();
844 if (deviceInfo != null) {
845 invokeDeviceEventListeners(deviceInfo, DEVICE_EVENT_REMOVE_DEVICE);
846 updateSafeMhlInput();
849 Slog.w(TAG, "No device to remove:[portId=" + portId);
852 announceHotplugEvent(portId, connected);
856 void handleMhlBusModeChanged(int portId, int busmode) {
857 assertRunOnServiceThread();
858 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
859 if (device != null) {
860 device.setBusMode(busmode);
862 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
863 ", busmode:" + busmode + "]");
868 void handleMhlBusOvercurrent(int portId, boolean on) {
869 assertRunOnServiceThread();
870 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
871 if (device != null) {
872 device.onBusOvercurrentDetected(on);
874 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
879 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
880 assertRunOnServiceThread();
881 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
883 // Hotplug event should already have been called before device status change event.
884 if (device != null) {
885 device.setDeviceStatusChange(adopterId, deviceId);
886 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_ADD_DEVICE);
887 updateSafeMhlInput();
889 Slog.w(TAG, "No mhl device exists for device status event[portId:"
890 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
895 private void updateSafeMhlInput() {
896 assertRunOnServiceThread();
897 List<HdmiDeviceInfo> inputs = Collections.emptyList();
898 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
899 for (int i = 0; i < devices.size(); ++i) {
900 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
901 HdmiDeviceInfo info = device.getInfo();
903 if (inputs.isEmpty()) {
904 inputs = new ArrayList<>();
906 inputs.add(device.getInfo());
909 synchronized (mLock) {
910 mMhlDevices = inputs;
914 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
918 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
919 private final IHdmiMhlVendorCommandListener mListener;
921 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
922 mListener = listener;
926 public void binderDied() {
927 mMhlVendorCommandListenerRecords.remove(this);
931 // Record class that monitors the event of the caller of being killed. Used to clean up
932 // the listener list and record list accordingly.
933 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
934 private final IHdmiHotplugEventListener mListener;
936 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
937 mListener = listener;
941 public void binderDied() {
942 synchronized (mLock) {
943 mHotplugEventListenerRecords.remove(this);
948 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
949 private final IHdmiDeviceEventListener mListener;
951 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
952 mListener = listener;
956 public void binderDied() {
957 synchronized (mLock) {
958 mDeviceEventListenerRecords.remove(this);
963 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
964 private final IHdmiSystemAudioModeChangeListener mListener;
966 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
967 mListener = listener;
971 public void binderDied() {
972 synchronized (mLock) {
973 mSystemAudioModeChangeListenerRecords.remove(this);
978 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
979 private final IHdmiVendorCommandListener mListener;
980 private final int mDeviceType;
982 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
983 mListener = listener;
984 mDeviceType = deviceType;
988 public void binderDied() {
989 synchronized (mLock) {
990 mVendorCommandListenerRecords.remove(this);
995 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
996 private final IHdmiRecordListener mListener;
998 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
999 mListener = listener;
1003 public void binderDied() {
1004 synchronized (mLock) {
1005 mRecordListenerRecord = null;
1010 private void enforceAccessPermission() {
1011 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1014 private final class BinderService extends IHdmiControlService.Stub {
1016 public int[] getSupportedTypes() {
1017 enforceAccessPermission();
1018 // mLocalDevices is an unmodifiable list - no lock necesary.
1019 int[] localDevices = new int[mLocalDevices.size()];
1020 for (int i = 0; i < localDevices.length; ++i) {
1021 localDevices[i] = mLocalDevices.get(i);
1023 return localDevices;
1027 public HdmiDeviceInfo getActiveSource() {
1028 enforceAccessPermission();
1029 HdmiCecLocalDeviceTv tv = tv();
1031 Slog.w(TAG, "Local tv device not available");
1034 ActiveSource activeSource = tv.getActiveSource();
1035 if (activeSource.isValid()) {
1036 return new HdmiDeviceInfo(activeSource.logicalAddress,
1037 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1038 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1040 int activePath = tv.getActivePath();
1041 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1042 return new HdmiDeviceInfo(activePath, tv.getActivePortId());
1048 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1049 enforceAccessPermission();
1050 runOnServiceThread(new Runnable() {
1053 if (callback == null) {
1054 Slog.e(TAG, "Callback cannot be null");
1057 HdmiCecLocalDeviceTv tv = tv();
1059 Slog.w(TAG, "Local tv device not available");
1060 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1063 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1064 if (device != null) {
1065 if (device.getPortId() == tv.getActivePortId()) {
1066 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1069 // Upon selecting MHL device, we send RAP[Content On] to wake up
1070 // the connected mobile device, start routing control to switch ports.
1071 // callback is handled by MHL action.
1072 device.turnOn(callback);
1073 tv.doManualPortSwitching(device.getPortId(), null);
1076 tv.deviceSelect(deviceId, callback);
1082 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1083 enforceAccessPermission();
1084 runOnServiceThread(new Runnable() {
1087 if (callback == null) {
1088 Slog.e(TAG, "Callback cannot be null");
1091 HdmiCecLocalDeviceTv tv = tv();
1093 Slog.w(TAG, "Local tv device not available");
1094 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1097 tv.doManualPortSwitching(portId, callback);
1103 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1104 enforceAccessPermission();
1105 runOnServiceThread(new Runnable() {
1108 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1109 if (device != null) {
1110 device.sendKeyEvent(keyCode, isPressed);
1113 if (mCecController != null) {
1114 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1115 if (localDevice == null) {
1116 Slog.w(TAG, "Local device not available");
1119 localDevice.sendKeyEvent(keyCode, isPressed);
1126 public void oneTouchPlay(final IHdmiControlCallback callback) {
1127 enforceAccessPermission();
1128 runOnServiceThread(new Runnable() {
1131 HdmiControlService.this.oneTouchPlay(callback);
1137 public void queryDisplayStatus(final IHdmiControlCallback callback) {
1138 enforceAccessPermission();
1139 runOnServiceThread(new Runnable() {
1142 HdmiControlService.this.queryDisplayStatus(callback);
1148 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1149 enforceAccessPermission();
1150 HdmiControlService.this.addHotplugEventListener(listener);
1154 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1155 enforceAccessPermission();
1156 HdmiControlService.this.removeHotplugEventListener(listener);
1160 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1161 enforceAccessPermission();
1162 HdmiControlService.this.addDeviceEventListener(listener);
1166 public List<HdmiPortInfo> getPortInfo() {
1167 enforceAccessPermission();
1168 return HdmiControlService.this.getPortInfo();
1172 public boolean canChangeSystemAudioMode() {
1173 enforceAccessPermission();
1174 HdmiCecLocalDeviceTv tv = tv();
1178 return tv.hasSystemAudioDevice();
1182 public boolean getSystemAudioMode() {
1183 enforceAccessPermission();
1184 HdmiCecLocalDeviceTv tv = tv();
1188 return tv.isSystemAudioActivated();
1192 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1193 enforceAccessPermission();
1194 runOnServiceThread(new Runnable() {
1197 HdmiCecLocalDeviceTv tv = tv();
1199 Slog.w(TAG, "Local tv device not available");
1200 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1203 tv.changeSystemAudioMode(enabled, callback);
1209 public void addSystemAudioModeChangeListener(
1210 final IHdmiSystemAudioModeChangeListener listener) {
1211 enforceAccessPermission();
1212 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1216 public void removeSystemAudioModeChangeListener(
1217 final IHdmiSystemAudioModeChangeListener listener) {
1218 enforceAccessPermission();
1219 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1223 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1224 enforceAccessPermission();
1225 HdmiControlService.this.setInputChangeListener(listener);
1229 public List<HdmiDeviceInfo> getInputDevices() {
1230 enforceAccessPermission();
1231 // No need to hold the lock for obtaining TV device as the local device instance
1232 // is preserved while the HDMI control is enabled.
1233 HdmiCecLocalDeviceTv tv = tv();
1234 synchronized (mLock) {
1235 List<HdmiDeviceInfo> cecDevices = (tv == null)
1236 ? Collections.<HdmiDeviceInfo>emptyList()
1237 : tv.getSafeExternalInputsLocked();
1238 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1243 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1244 final int maxIndex) {
1245 enforceAccessPermission();
1246 runOnServiceThread(new Runnable() {
1249 HdmiCecLocalDeviceTv tv = tv();
1251 Slog.w(TAG, "Local tv device not available");
1254 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1260 public void setSystemAudioMute(final boolean mute) {
1261 enforceAccessPermission();
1262 runOnServiceThread(new Runnable() {
1265 HdmiCecLocalDeviceTv tv = tv();
1267 Slog.w(TAG, "Local tv device not available");
1270 tv.changeMute(mute);
1276 public void setArcMode(final boolean enabled) {
1277 enforceAccessPermission();
1278 runOnServiceThread(new Runnable() {
1281 HdmiCecLocalDeviceTv tv = tv();
1283 Slog.w(TAG, "Local tv device not available to change arc mode.");
1291 public void setProhibitMode(final boolean enabled) {
1292 enforceAccessPermission();
1293 if (!isTvDevice()) {
1296 HdmiControlService.this.setProhibitMode(enabled);
1300 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1301 final int deviceType) {
1302 enforceAccessPermission();
1303 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1307 public void sendVendorCommand(final int deviceType, final int targetAddress,
1308 final byte[] params, final boolean hasVendorId) {
1309 enforceAccessPermission();
1310 runOnServiceThread(new Runnable() {
1313 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1314 if (device == null) {
1315 Slog.w(TAG, "Local device not available");
1319 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1320 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1321 getVendorId(), params));
1323 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1324 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1331 public void sendStandby(final int deviceType, final int deviceId) {
1332 enforceAccessPermission();
1333 runOnServiceThread(new Runnable() {
1336 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1337 if (device == null) {
1338 Slog.w(TAG, "Local device not available");
1341 device.sendStandby(deviceId);
1347 public void setHdmiRecordListener(IHdmiRecordListener listener) {
1348 enforceAccessPermission();
1349 HdmiControlService.this.setHdmiRecordListener(listener);
1353 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1354 enforceAccessPermission();
1355 runOnServiceThread(new Runnable() {
1358 if (!isTvDevice()) {
1359 Slog.w(TAG, "No TV is available.");
1362 tv().startOneTouchRecord(recorderAddress, recordSource);
1368 public void stopOneTouchRecord(final int recorderAddress) {
1369 enforceAccessPermission();
1370 runOnServiceThread(new Runnable() {
1373 if (!isTvDevice()) {
1374 Slog.w(TAG, "No TV is available.");
1377 tv().stopOneTouchRecord(recorderAddress);
1383 public void startTimerRecording(final int recorderAddress, final int sourceType,
1384 final byte[] recordSource) {
1385 enforceAccessPermission();
1386 runOnServiceThread(new Runnable() {
1389 if (!isTvDevice()) {
1390 Slog.w(TAG, "No TV is available.");
1393 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1399 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1400 final byte[] recordSource) {
1401 enforceAccessPermission();
1402 runOnServiceThread(new Runnable() {
1405 if (!isTvDevice()) {
1406 Slog.w(TAG, "No TV is available.");
1409 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1415 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1416 final byte[] data) {
1417 enforceAccessPermission();
1418 runOnServiceThread(new Runnable() {
1421 if (!isControlEnabled()) {
1422 Slog.w(TAG, "Hdmi control is disabled.");
1425 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1426 if (device == null) {
1427 Slog.w(TAG, "Invalid port id:" + portId);
1430 mMhlController.sendVendorCommand(portId, offset, length, data);
1436 public void addHdmiMhlVendorCommandListener(
1437 IHdmiMhlVendorCommandListener listener) {
1438 enforceAccessPermission();
1439 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1443 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1444 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1445 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1447 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1448 pw.println("mProhibitMode: " + mProhibitMode);
1449 if (mCecController != null) {
1450 pw.println("mCecController: ");
1451 pw.increaseIndent();
1452 mCecController.dump(pw);
1453 pw.decreaseIndent();
1455 pw.println("mPortInfo: ");
1456 pw.increaseIndent();
1457 for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1458 pw.println("- " + hdmiPortInfo);
1460 pw.decreaseIndent();
1461 pw.println("mPowerStatus: " + mPowerStatus);
1466 private void oneTouchPlay(final IHdmiControlCallback callback) {
1467 assertRunOnServiceThread();
1468 HdmiCecLocalDevicePlayback source = playback();
1469 if (source == null) {
1470 Slog.w(TAG, "Local playback device not available");
1471 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1474 source.oneTouchPlay(callback);
1478 private void queryDisplayStatus(final IHdmiControlCallback callback) {
1479 assertRunOnServiceThread();
1480 HdmiCecLocalDevicePlayback source = playback();
1481 if (source == null) {
1482 Slog.w(TAG, "Local playback device not available");
1483 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1486 source.queryDisplayStatus(callback);
1489 private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1490 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1492 listener.asBinder().linkToDeath(record, 0);
1493 } catch (RemoteException e) {
1494 Slog.w(TAG, "Listener already died");
1497 synchronized (mLock) {
1498 mHotplugEventListenerRecords.add(record);
1502 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1503 synchronized (mLock) {
1504 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1505 if (record.mListener.asBinder() == listener.asBinder()) {
1506 listener.asBinder().unlinkToDeath(record, 0);
1507 mHotplugEventListenerRecords.remove(record);
1514 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1515 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1517 listener.asBinder().linkToDeath(record, 0);
1518 } catch (RemoteException e) {
1519 Slog.w(TAG, "Listener already died");
1522 synchronized (mLock) {
1523 mDeviceEventListenerRecords.add(record);
1527 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1528 synchronized (mLock) {
1529 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1531 record.mListener.onStatusChanged(device, status);
1532 } catch (RemoteException e) {
1533 Slog.e(TAG, "Failed to report device event:" + e);
1539 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1540 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1543 listener.asBinder().linkToDeath(record, 0);
1544 } catch (RemoteException e) {
1545 Slog.w(TAG, "Listener already died");
1548 synchronized (mLock) {
1549 mSystemAudioModeChangeListenerRecords.add(record);
1553 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1554 synchronized (mLock) {
1555 for (SystemAudioModeChangeListenerRecord record :
1556 mSystemAudioModeChangeListenerRecords) {
1557 if (record.mListener.asBinder() == listener) {
1558 listener.asBinder().unlinkToDeath(record, 0);
1559 mSystemAudioModeChangeListenerRecords.remove(record);
1566 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1567 private final IHdmiInputChangeListener mListener;
1569 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1570 mListener = listener;
1574 public void binderDied() {
1575 synchronized (mLock) {
1576 mInputChangeListenerRecord = null;
1581 private void setInputChangeListener(IHdmiInputChangeListener listener) {
1582 synchronized (mLock) {
1583 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1585 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1586 } catch (RemoteException e) {
1587 Slog.w(TAG, "Listener already died");
1593 void invokeInputChangeListener(HdmiDeviceInfo info) {
1594 synchronized (mLock) {
1595 if (mInputChangeListenerRecord != null) {
1597 mInputChangeListenerRecord.mListener.onChanged(info);
1598 } catch (RemoteException e) {
1599 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1605 private void setHdmiRecordListener(IHdmiRecordListener listener) {
1606 synchronized (mLock) {
1607 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1609 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1610 } catch (RemoteException e) {
1611 Slog.w(TAG, "Listener already died.", e);
1616 byte[] invokeRecordRequestListener(int recorderAddress) {
1617 synchronized (mLock) {
1618 if (mRecordListenerRecord != null) {
1620 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1621 } catch (RemoteException e) {
1622 Slog.w(TAG, "Failed to start record.", e);
1625 return EmptyArray.BYTE;
1629 void invokeOneTouchRecordResult(int result) {
1630 synchronized (mLock) {
1631 if (mRecordListenerRecord != null) {
1633 mRecordListenerRecord.mListener.onOneTouchRecordResult(result);
1634 } catch (RemoteException e) {
1635 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1641 void invokeTimerRecordingResult(int result) {
1642 synchronized (mLock) {
1643 if (mRecordListenerRecord != null) {
1645 mRecordListenerRecord.mListener.onTimerRecordingResult(result);
1646 } catch (RemoteException e) {
1647 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1653 void invokeClearTimerRecordingResult(int result) {
1654 synchronized (mLock) {
1655 if (mRecordListenerRecord != null) {
1657 mRecordListenerRecord.mListener.onClearTimerRecordingResult(result);
1658 } catch (RemoteException e) {
1659 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1665 private void invokeCallback(IHdmiControlCallback callback, int result) {
1667 callback.onComplete(result);
1668 } catch (RemoteException e) {
1669 Slog.e(TAG, "Invoking callback failed:" + e);
1673 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1676 listener.onStatusChanged(enabled);
1677 } catch (RemoteException e) {
1678 Slog.e(TAG, "Invoking callback failed:" + e);
1682 private void announceHotplugEvent(int portId, boolean connected) {
1683 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1684 synchronized (mLock) {
1685 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1686 invokeHotplugEventListenerLocked(record.mListener, event);
1691 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1692 HdmiHotplugEvent event) {
1694 listener.onReceived(event);
1695 } catch (RemoteException e) {
1696 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1700 private HdmiCecLocalDeviceTv tv() {
1701 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1704 boolean isTvDevice() {
1705 return tv() != null;
1708 private HdmiCecLocalDevicePlayback playback() {
1709 return (HdmiCecLocalDevicePlayback)
1710 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1713 AudioManager getAudioManager() {
1714 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1717 boolean isControlEnabled() {
1718 synchronized (mLock) {
1719 return mHdmiControlEnabled;
1724 int getPowerStatus() {
1725 assertRunOnServiceThread();
1726 return mPowerStatus;
1730 boolean isPowerOnOrTransient() {
1731 assertRunOnServiceThread();
1732 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1733 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1737 boolean isPowerStandbyOrTransient() {
1738 assertRunOnServiceThread();
1739 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1740 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1744 boolean isPowerStandby() {
1745 assertRunOnServiceThread();
1746 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1751 assertRunOnServiceThread();
1752 mWakeUpMessageReceived = true;
1753 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1754 pm.wakeUp(SystemClock.uptimeMillis());
1755 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1756 // the intent, the sequence will continue at onWakeUp().
1761 assertRunOnServiceThread();
1762 mStandbyMessageReceived = true;
1763 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1764 pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
1765 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1766 // the intent, the sequence will continue at onStandby().
1770 private void onWakeUp() {
1771 assertRunOnServiceThread();
1772 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1773 if (mCecController != null) {
1774 if (mHdmiControlEnabled) {
1775 int startReason = INITIATED_BY_SCREEN_ON;
1776 if (mWakeUpMessageReceived) {
1777 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1779 initializeCec(startReason);
1782 Slog.i(TAG, "Device does not support HDMI-CEC.");
1784 // TODO: Initialize MHL local devices.
1788 private void onStandby() {
1789 assertRunOnServiceThread();
1790 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1792 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1793 disableDevices(new PendingActionClearedCallback() {
1795 public void onCleared(HdmiCecLocalDevice device) {
1796 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1797 devices.remove(device);
1798 if (devices.isEmpty()) {
1799 onStandbyCompleted();
1800 // We will not clear local devices here, since some OEM/SOC will keep passing
1801 // the received packets until the application processor enters to the sleep
1809 private void onLanguageChanged(String language) {
1810 assertRunOnServiceThread();
1811 mLanguage = language;
1814 tv().broadcastMenuLanguage(language);
1819 String getLanguage() {
1820 assertRunOnServiceThread();
1824 private void disableDevices(PendingActionClearedCallback callback) {
1825 if (mCecController != null) {
1826 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1827 device.disableDevice(mStandbyMessageReceived, callback);
1830 unregisterSettingsObserver();
1834 mMhlController.clearAllLocalDevices();
1838 private void clearLocalDevices() {
1839 assertRunOnServiceThread();
1840 if (mCecController == null) {
1843 mCecController.clearLogicalAddress();
1844 mCecController.clearLocalDevices();
1848 private void onStandbyCompleted() {
1849 assertRunOnServiceThread();
1850 Slog.v(TAG, "onStandbyCompleted");
1852 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1855 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1856 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1857 device.onStandby(mStandbyMessageReceived);
1859 mStandbyMessageReceived = false;
1860 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
1863 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1864 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1866 listener.asBinder().linkToDeath(record, 0);
1867 } catch (RemoteException e) {
1868 Slog.w(TAG, "Listener already died");
1871 synchronized (mLock) {
1872 mVendorCommandListenerRecords.add(record);
1876 boolean invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1877 boolean hasVendorId) {
1878 synchronized (mLock) {
1879 if (mVendorCommandListenerRecords.isEmpty()) {
1882 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1883 if (record.mDeviceType != deviceType) {
1887 record.mListener.onReceived(srcAddress, params, hasVendorId);
1888 } catch (RemoteException e) {
1889 Slog.e(TAG, "Failed to notify vendor command reception", e);
1896 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
1897 HdmiMhlVendorCommandListenerRecord record =
1898 new HdmiMhlVendorCommandListenerRecord(listener);
1900 listener.asBinder().linkToDeath(record, 0);
1901 } catch (RemoteException e) {
1902 Slog.w(TAG, "Listener already died.");
1906 synchronized (mLock) {
1907 mMhlVendorCommandListenerRecords.add(record);
1911 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
1912 synchronized (mLock) {
1913 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
1915 record.mListener.onReceived(portId, offest, length, data);
1916 } catch (RemoteException e) {
1917 Slog.e(TAG, "Failed to notify MHL vendor command", e);
1923 boolean isProhibitMode() {
1924 synchronized (mLock) {
1925 return mProhibitMode;
1929 void setProhibitMode(boolean enabled) {
1930 synchronized (mLock) {
1931 mProhibitMode = enabled;
1936 void setCecOption(int key, int value) {
1937 assertRunOnServiceThread();
1938 mCecController.setOption(key, value);
1942 void setControlEnabled(boolean enabled) {
1943 assertRunOnServiceThread();
1945 int value = toInt(enabled);
1946 mCecController.setOption(OPTION_CEC_ENABLE, value);
1947 mMhlController.setOption(OPTION_MHL_ENABLE, value);
1949 synchronized (mLock) {
1950 mHdmiControlEnabled = enabled;
1954 initializeCec(INITIATED_BY_ENABLE_CEC);
1956 disableDevices(new PendingActionClearedCallback() {
1958 public void onCleared(HdmiCecLocalDevice device) {
1959 assertRunOnServiceThread();
1960 clearLocalDevices();
1967 void setActivePortId(int portId) {
1968 assertRunOnServiceThread();
1969 mActivePortId = portId;
1971 // Resets last input for MHL, which stays valid only after the MHL device was selected,
1972 // and no further switching is done.
1973 setLastInputForMhl(Constants.INVALID_PORT_ID);
1977 void setLastInputForMhl(int portId) {
1978 assertRunOnServiceThread();
1979 mLastInputMhl = portId;
1983 int getLastInputForMhl() {
1984 assertRunOnServiceThread();
1985 return mLastInputMhl;
1989 * Performs input change, routing control for MHL device.
1991 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
1992 * @param contentOn {@code true} if RAP data is content on; otherwise false
1995 void changeInputForMhl(int portId, boolean contentOn) {
1996 assertRunOnServiceThread();
1997 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
1998 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2000 public void onComplete(int result) throws RemoteException {
2001 // Keep the last input to switch back later when RAP[ContentOff] is received.
2002 // This effectively sets the port to invalid one if the switching is for
2004 setLastInputForMhl(lastInput);
2008 // MHL device is always directly connected to the port. Update the active port ID to avoid
2009 // unnecessary post-routing control task.
2010 tv().setActivePortId(portId);
2012 // The port is either the MHL-enabled port where the mobile device is connected, or
2013 // the last port to go back to when turnoff command is received. Note that the last port
2014 // may not be the MHL-enabled one. In this case the device info to be passed to
2015 // input change listener should be the one describing the corresponding HDMI port.
2016 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
2017 HdmiDeviceInfo info = (device != null && device.getInfo() != null)
2019 : mPortDeviceMap.get(portId);
2020 invokeInputChangeListener(info);
2023 void setMhlInputChangeEnabled(boolean enabled) {
2024 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2026 synchronized (mLock) {
2027 mMhlInputChangeEnabled = enabled;
2031 boolean isMhlInputChangeEnabled() {
2032 synchronized (mLock) {
2033 return mMhlInputChangeEnabled;
2038 void displayOsd(int messageId) {
2039 assertRunOnServiceThread();
2040 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2041 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2042 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2043 HdmiControlService.PERMISSION);
2047 void displayOsd(int messageId, int extra) {
2048 assertRunOnServiceThread();
2049 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2050 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2051 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRAM_PARAM1, extra);
2052 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2053 HdmiControlService.PERMISSION);