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<>();
419 for (int type : mLocalDevices) {
420 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
422 localDevices.add(localDevice);
424 allocateLogicalAddress(localDevices, initiatedBy);
428 private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
429 final int initiatedBy) {
430 assertRunOnServiceThread();
431 mCecController.clearLogicalAddress();
432 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
433 final int[] finished = new int[1];
434 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
435 mCecController.allocateLogicalAddress(localDevice.getType(),
436 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
438 public void onAllocated(int deviceType, int logicalAddress) {
439 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
440 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
442 // Set POWER_STATUS_ON to all local devices because they share lifetime
444 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
445 HdmiControlManager.POWER_STATUS_ON);
446 localDevice.setDeviceInfo(deviceInfo);
447 mCecController.addLocalDevice(deviceType, localDevice);
448 mCecController.addLogicalAddress(logicalAddress);
449 allocatedDevices.add(localDevice);
452 // Address allocation completed for all devices. Notify each device.
453 if (allocatingDevices.size() == ++finished[0]) {
454 if (initiatedBy != INITIATED_BY_HOTPLUG) {
455 // In case of the hotplug we don't call onInitializeCecComplete()
456 // since we reallocate the logical address only.
457 onInitializeCecComplete();
459 notifyAddressAllocated(allocatedDevices, initiatedBy);
467 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
468 assertRunOnServiceThread();
469 for (HdmiCecLocalDevice device : devices) {
470 int address = device.getDeviceInfo().getLogicalAddress();
471 device.handleAddressAllocated(address, initiatedBy);
475 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
476 // keep them in one place.
478 private void initPortInfo() {
479 assertRunOnServiceThread();
480 HdmiPortInfo[] cecPortInfo = null;
482 // CEC HAL provides majority of the info while MHL does only MHL support flag for
483 // each port. Return empty array if CEC HAL didn't provide the info.
484 if (mCecController != null) {
485 cecPortInfo = mCecController.getPortInfos();
487 if (cecPortInfo == null) {
491 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
492 SparseIntArray portIdMap = new SparseIntArray();
493 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
494 for (HdmiPortInfo info : cecPortInfo) {
495 portIdMap.put(info.getAddress(), info.getId());
496 portInfoMap.put(info.getId(), info);
497 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
499 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
500 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
501 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
503 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
504 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
505 for (HdmiPortInfo info : mhlPortInfo) {
506 if (info.isMhlSupported()) {
507 mhlSupportedPorts.add(info.getId());
511 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
512 // cec port info if we do not have have port that supports MHL.
513 if (mhlSupportedPorts.isEmpty()) {
514 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
517 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
518 for (HdmiPortInfo info : cecPortInfo) {
519 if (mhlSupportedPorts.contains(info.getId())) {
520 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
521 info.isCecSupported(), true, info.isArcSupported()));
526 mPortInfo = Collections.unmodifiableList(result);
529 List<HdmiPortInfo> getPortInfo() {
534 * Returns HDMI port information for the given port id.
536 * @param portId HDMI port id
537 * @return {@link HdmiPortInfo} for the given port
539 HdmiPortInfo getPortInfo(int portId) {
540 return mPortInfoMap.get(portId, null);
544 * Returns the routing path (physical address) of the HDMI port for the given
547 int portIdToPath(int portId) {
548 HdmiPortInfo portInfo = getPortInfo(portId);
549 if (portInfo == null) {
550 Slog.e(TAG, "Cannot find the port info: " + portId);
551 return Constants.INVALID_PHYSICAL_ADDRESS;
553 return portInfo.getAddress();
557 * Returns the id of HDMI port located at the top of the hierarchy of
558 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
559 * the port id to be returned is the ID associated with the port address
560 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
562 int pathToPortId(int path) {
563 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
564 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
567 boolean isValidPortId(int portId) {
568 return getPortInfo(portId) != null;
572 * Returns {@link Looper} for IO operation.
574 * <p>Declared as package-private.
576 Looper getIoLooper() {
577 return mIoThread.getLooper();
581 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
582 * for tasks that are running on main service thread.
584 * <p>Declared as package-private.
586 Looper getServiceLooper() {
587 return mHandler.getLooper();
591 * Returns physical address of the device.
593 int getPhysicalAddress() {
594 return mCecController.getPhysicalAddress();
598 * Returns vendor id of CEC service.
601 return mCecController.getVendorId();
605 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
606 assertRunOnServiceThread();
607 HdmiCecLocalDeviceTv tv = tv();
611 return tv.getCecDeviceInfo(logicalAddress);
615 * Returns version of CEC.
617 int getCecVersion() {
618 return mCecController.getVersion();
622 * Whether a device of the specified physical address is connected to ARC enabled port.
624 boolean isConnectedToArcPort(int physicalAddress) {
625 int portId = pathToPortId(physicalAddress);
626 if (portId != Constants.INVALID_PORT_ID) {
627 return mPortInfoMap.get(portId).isArcSupported();
632 void runOnServiceThread(Runnable runnable) {
633 mHandler.post(runnable);
636 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
637 mHandler.postAtFrontOfQueue(runnable);
640 private void assertRunOnServiceThread() {
641 if (Looper.myLooper() != mHandler.getLooper()) {
642 throw new IllegalStateException("Should run on service thread.");
647 * Transmit a CEC command to CEC bus.
649 * @param command CEC command to send out
650 * @param callback interface used to the result of send command
653 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
654 assertRunOnServiceThread();
655 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
656 mCecController.sendCommand(command, callback);
658 HdmiLogger.error("Invalid message type:" + command);
659 if (callback != null) {
660 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
666 void sendCecCommand(HdmiCecMessage command) {
667 assertRunOnServiceThread();
668 sendCecCommand(command, null);
672 * Send <Feature Abort> command on the given CEC message if possible.
673 * If the aborted message is invalid, then it wont send the message.
674 * @param command original command to be aborted
675 * @param reason reason of feature abort
678 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
679 assertRunOnServiceThread();
680 mCecController.maySendFeatureAbortCommand(command, reason);
684 boolean handleCecCommand(HdmiCecMessage message) {
685 assertRunOnServiceThread();
686 int errorCode = mMessageValidator.isValid(message);
687 if (errorCode != HdmiCecMessageValidator.OK) {
688 // We'll not response on the messages with the invalid source or destination.
689 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
690 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
694 return dispatchMessageToLocalDevice(message);
697 void setAudioReturnChannel(boolean enabled) {
698 mCecController.setAudioReturnChannel(enabled);
702 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
703 assertRunOnServiceThread();
704 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
705 if (device.dispatchMessage(message)
706 && message.getDestination() != Constants.ADDR_BROADCAST) {
711 if (message.getDestination() != Constants.ADDR_BROADCAST) {
712 HdmiLogger.warning("Unhandled cec command:" + message);
718 * Called when a new hotplug event is issued.
720 * @param portId hdmi port number where hot plug event issued.
721 * @param connected whether to be plugged in or not
724 void onHotplug(int portId, boolean connected) {
725 assertRunOnServiceThread();
727 if (connected && !isTvDevice()) {
728 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
729 for (int type : mLocalDevices) {
730 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
731 if (localDevice == null) {
732 localDevice = HdmiCecLocalDevice.create(this, type);
735 localDevices.add(localDevice);
737 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
740 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
741 device.onHotplug(portId, connected);
743 announceHotplugEvent(portId, connected);
747 * Poll all remote devices. It sends <Polling Message> to all remote
750 * @param callback an interface used to get a list of all remote devices' address
751 * @param sourceAddress a logical address of source device where sends polling message
752 * @param pickStrategy strategy how to pick polling candidates
753 * @param retryCount the number of retry used to send polling message to remote devices
754 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
757 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
759 assertRunOnServiceThread();
760 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
764 private int checkPollStrategy(int pickStrategy) {
765 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
767 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
769 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
770 if (iterationStrategy == 0) {
771 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
773 return strategy | iterationStrategy;
776 List<HdmiCecLocalDevice> getAllLocalDevices() {
777 assertRunOnServiceThread();
778 return mCecController.getLocalDeviceList();
781 Object getServiceLock() {
785 void setAudioStatus(boolean mute, int volume) {
786 AudioManager audioManager = getAudioManager();
787 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
790 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
794 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
796 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
797 // volume change notification back to hdmi control service.
798 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
799 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
803 void announceSystemAudioModeChange(boolean enabled) {
804 synchronized (mLock) {
805 for (SystemAudioModeChangeListenerRecord record :
806 mSystemAudioModeChangeListenerRecords) {
807 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
812 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
813 // TODO: find better name instead of model name.
814 String displayName = Build.MODEL;
815 return new HdmiDeviceInfo(logicalAddress,
816 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
817 getVendorId(), displayName);
821 void handleMhlHotplugEvent(int portId, boolean connected) {
822 assertRunOnServiceThread();
824 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
825 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
826 if (oldDevice != null) {
827 oldDevice.onDeviceRemoved();
828 Slog.i(TAG, "Old device of port " + portId + " is removed");
831 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
832 if (device != null) {
833 device.onDeviceRemoved();
834 // There is no explicit event for device removal.
835 // Hence we remove the device on hotplug event.
836 HdmiDeviceInfo deviceInfo = device.getInfo();
837 if (deviceInfo != null) {
838 invokeDeviceEventListeners(deviceInfo, DEVICE_EVENT_REMOVE_DEVICE);
839 updateSafeMhlInput();
842 Slog.w(TAG, "No device to remove:[portId=" + portId);
845 announceHotplugEvent(portId, connected);
849 void handleMhlBusModeChanged(int portId, int busmode) {
850 assertRunOnServiceThread();
851 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
852 if (device != null) {
853 device.setBusMode(busmode);
855 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
856 ", busmode:" + busmode + "]");
861 void handleMhlBusOvercurrent(int portId, boolean on) {
862 assertRunOnServiceThread();
863 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
864 if (device != null) {
865 device.onBusOvercurrentDetected(on);
867 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
872 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
873 assertRunOnServiceThread();
874 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
876 // Hotplug event should already have been called before device status change event.
877 if (device != null) {
878 device.setDeviceStatusChange(adopterId, deviceId);
879 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_ADD_DEVICE);
880 updateSafeMhlInput();
882 Slog.w(TAG, "No mhl device exists for device status event[portId:"
883 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
888 private void updateSafeMhlInput() {
889 assertRunOnServiceThread();
890 List<HdmiDeviceInfo> inputs = Collections.emptyList();
891 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
892 for (int i = 0; i < devices.size(); ++i) {
893 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
894 HdmiDeviceInfo info = device.getInfo();
896 if (inputs.isEmpty()) {
897 inputs = new ArrayList<>();
899 inputs.add(device.getInfo());
902 synchronized (mLock) {
903 mMhlDevices = inputs;
907 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
911 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
912 private final IHdmiMhlVendorCommandListener mListener;
914 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
915 mListener = listener;
919 public void binderDied() {
920 mMhlVendorCommandListenerRecords.remove(this);
924 // Record class that monitors the event of the caller of being killed. Used to clean up
925 // the listener list and record list accordingly.
926 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
927 private final IHdmiHotplugEventListener mListener;
929 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
930 mListener = listener;
934 public void binderDied() {
935 synchronized (mLock) {
936 mHotplugEventListenerRecords.remove(this);
941 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
942 private final IHdmiDeviceEventListener mListener;
944 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
945 mListener = listener;
949 public void binderDied() {
950 synchronized (mLock) {
951 mDeviceEventListenerRecords.remove(this);
956 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
957 private final IHdmiSystemAudioModeChangeListener mListener;
959 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
960 mListener = listener;
964 public void binderDied() {
965 synchronized (mLock) {
966 mSystemAudioModeChangeListenerRecords.remove(this);
971 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
972 private final IHdmiVendorCommandListener mListener;
973 private final int mDeviceType;
975 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
976 mListener = listener;
977 mDeviceType = deviceType;
981 public void binderDied() {
982 synchronized (mLock) {
983 mVendorCommandListenerRecords.remove(this);
988 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
989 private final IHdmiRecordListener mListener;
991 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
992 mListener = listener;
996 public void binderDied() {
997 synchronized (mLock) {
998 mRecordListenerRecord = null;
1003 private void enforceAccessPermission() {
1004 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1007 private final class BinderService extends IHdmiControlService.Stub {
1009 public int[] getSupportedTypes() {
1010 enforceAccessPermission();
1011 // mLocalDevices is an unmodifiable list - no lock necesary.
1012 int[] localDevices = new int[mLocalDevices.size()];
1013 for (int i = 0; i < localDevices.length; ++i) {
1014 localDevices[i] = mLocalDevices.get(i);
1016 return localDevices;
1020 public HdmiDeviceInfo getActiveSource() {
1021 HdmiCecLocalDeviceTv tv = tv();
1023 Slog.w(TAG, "Local tv device not available");
1026 ActiveSource activeSource = tv.getActiveSource();
1027 if (activeSource.isValid()) {
1028 return new HdmiDeviceInfo(activeSource.logicalAddress,
1029 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1030 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1032 int activePath = tv.getActivePath();
1033 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1034 return new HdmiDeviceInfo(activePath, tv.getActivePortId());
1040 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1041 enforceAccessPermission();
1042 runOnServiceThread(new Runnable() {
1045 if (callback == null) {
1046 Slog.e(TAG, "Callback cannot be null");
1049 HdmiCecLocalDeviceTv tv = tv();
1051 Slog.w(TAG, "Local tv device not available");
1052 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1055 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1056 if (device != null) {
1057 if (device.getPortId() == tv.getActivePortId()) {
1058 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1061 // Upon selecting MHL device, we send RAP[Content On] to wake up
1062 // the connected mobile device, start routing control to switch ports.
1063 // callback is handled by MHL action.
1064 device.turnOn(callback);
1065 tv.doManualPortSwitching(device.getPortId(), null);
1068 tv.deviceSelect(deviceId, callback);
1074 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1075 enforceAccessPermission();
1076 runOnServiceThread(new Runnable() {
1079 if (callback == null) {
1080 Slog.e(TAG, "Callback cannot be null");
1083 HdmiCecLocalDeviceTv tv = tv();
1085 Slog.w(TAG, "Local tv device not available");
1086 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1089 tv.doManualPortSwitching(portId, callback);
1095 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1096 enforceAccessPermission();
1097 runOnServiceThread(new Runnable() {
1100 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1101 if (device != null) {
1102 device.sendKeyEvent(keyCode, isPressed);
1105 if (mCecController != null) {
1106 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1107 if (localDevice == null) {
1108 Slog.w(TAG, "Local device not available");
1111 localDevice.sendKeyEvent(keyCode, isPressed);
1118 public void oneTouchPlay(final IHdmiControlCallback callback) {
1119 enforceAccessPermission();
1120 runOnServiceThread(new Runnable() {
1123 HdmiControlService.this.oneTouchPlay(callback);
1129 public void queryDisplayStatus(final IHdmiControlCallback callback) {
1130 enforceAccessPermission();
1131 runOnServiceThread(new Runnable() {
1134 HdmiControlService.this.queryDisplayStatus(callback);
1140 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1141 enforceAccessPermission();
1142 HdmiControlService.this.addHotplugEventListener(listener);
1146 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1147 enforceAccessPermission();
1148 HdmiControlService.this.removeHotplugEventListener(listener);
1152 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1153 enforceAccessPermission();
1154 HdmiControlService.this.addDeviceEventListener(listener);
1158 public List<HdmiPortInfo> getPortInfo() {
1159 enforceAccessPermission();
1160 return HdmiControlService.this.getPortInfo();
1164 public boolean canChangeSystemAudioMode() {
1165 enforceAccessPermission();
1166 HdmiCecLocalDeviceTv tv = tv();
1170 return tv.hasSystemAudioDevice();
1174 public boolean getSystemAudioMode() {
1175 enforceAccessPermission();
1176 HdmiCecLocalDeviceTv tv = tv();
1180 return tv.isSystemAudioActivated();
1184 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1185 enforceAccessPermission();
1186 runOnServiceThread(new Runnable() {
1189 HdmiCecLocalDeviceTv tv = tv();
1191 Slog.w(TAG, "Local tv device not available");
1192 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1195 tv.changeSystemAudioMode(enabled, callback);
1201 public void addSystemAudioModeChangeListener(
1202 final IHdmiSystemAudioModeChangeListener listener) {
1203 enforceAccessPermission();
1204 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1208 public void removeSystemAudioModeChangeListener(
1209 final IHdmiSystemAudioModeChangeListener listener) {
1210 enforceAccessPermission();
1211 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1215 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1216 enforceAccessPermission();
1217 HdmiControlService.this.setInputChangeListener(listener);
1221 public List<HdmiDeviceInfo> getInputDevices() {
1222 enforceAccessPermission();
1223 // No need to hold the lock for obtaining TV device as the local device instance
1224 // is preserved while the HDMI control is enabled.
1225 HdmiCecLocalDeviceTv tv = tv();
1226 synchronized (mLock) {
1227 List<HdmiDeviceInfo> cecDevices = (tv == null)
1228 ? Collections.<HdmiDeviceInfo>emptyList()
1229 : tv.getSafeExternalInputsLocked();
1230 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1235 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1236 final int maxIndex) {
1237 enforceAccessPermission();
1238 runOnServiceThread(new Runnable() {
1241 HdmiCecLocalDeviceTv tv = tv();
1243 Slog.w(TAG, "Local tv device not available");
1246 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1252 public void setSystemAudioMute(final boolean mute) {
1253 enforceAccessPermission();
1254 runOnServiceThread(new Runnable() {
1257 HdmiCecLocalDeviceTv tv = tv();
1259 Slog.w(TAG, "Local tv device not available");
1262 tv.changeMute(mute);
1268 public void setArcMode(final boolean enabled) {
1269 enforceAccessPermission();
1270 runOnServiceThread(new Runnable() {
1273 HdmiCecLocalDeviceTv tv = tv();
1275 Slog.w(TAG, "Local tv device not available to change arc mode.");
1283 public void setProhibitMode(final boolean enabled) {
1284 enforceAccessPermission();
1285 if (!isTvDevice()) {
1288 HdmiControlService.this.setProhibitMode(enabled);
1292 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1293 final int deviceType) {
1294 enforceAccessPermission();
1295 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1299 public void sendVendorCommand(final int deviceType, final int targetAddress,
1300 final byte[] params, final boolean hasVendorId) {
1301 enforceAccessPermission();
1302 runOnServiceThread(new Runnable() {
1305 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1306 if (device == null) {
1307 Slog.w(TAG, "Local device not available");
1311 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1312 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1313 getVendorId(), params));
1315 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1316 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1323 public void sendStandby(final int deviceType, final int deviceId) {
1324 enforceAccessPermission();
1325 runOnServiceThread(new Runnable() {
1328 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1329 if (device == null) {
1330 Slog.w(TAG, "Local device not available");
1333 device.sendStandby(deviceId);
1339 public void setHdmiRecordListener(IHdmiRecordListener listener) {
1340 HdmiControlService.this.setHdmiRecordListener(listener);
1344 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1345 runOnServiceThread(new Runnable() {
1348 if (!isTvDevice()) {
1349 Slog.w(TAG, "No TV is available.");
1352 tv().startOneTouchRecord(recorderAddress, recordSource);
1358 public void stopOneTouchRecord(final int recorderAddress) {
1359 runOnServiceThread(new Runnable() {
1362 if (!isTvDevice()) {
1363 Slog.w(TAG, "No TV is available.");
1366 tv().stopOneTouchRecord(recorderAddress);
1372 public void startTimerRecording(final int recorderAddress, final int sourceType,
1373 final byte[] recordSource) {
1374 runOnServiceThread(new Runnable() {
1377 if (!isTvDevice()) {
1378 Slog.w(TAG, "No TV is available.");
1381 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1387 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1388 final byte[] recordSource) {
1389 runOnServiceThread(new Runnable() {
1392 if (!isTvDevice()) {
1393 Slog.w(TAG, "No TV is available.");
1396 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1402 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1403 final byte[] data) {
1404 enforceAccessPermission();
1405 runOnServiceThread(new Runnable() {
1408 if (!isControlEnabled()) {
1409 Slog.w(TAG, "Hdmi control is disabled.");
1412 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1413 if (device == null) {
1414 Slog.w(TAG, "Invalid port id:" + portId);
1417 mMhlController.sendVendorCommand(portId, offset, length, data);
1423 public void addHdmiMhlVendorCommandListener(
1424 IHdmiMhlVendorCommandListener listener) {
1425 enforceAccessPermission();
1426 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1430 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1431 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1432 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1434 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1435 pw.println("mProhibitMode: " + mProhibitMode);
1436 if (mCecController != null) {
1437 pw.println("mCecController: ");
1438 pw.increaseIndent();
1439 mCecController.dump(pw);
1440 pw.decreaseIndent();
1442 pw.println("mPortInfo: ");
1443 pw.increaseIndent();
1444 for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1445 pw.println("- " + hdmiPortInfo);
1447 pw.decreaseIndent();
1448 pw.println("mPowerStatus: " + mPowerStatus);
1453 private void oneTouchPlay(final IHdmiControlCallback callback) {
1454 assertRunOnServiceThread();
1455 HdmiCecLocalDevicePlayback source = playback();
1456 if (source == null) {
1457 Slog.w(TAG, "Local playback device not available");
1458 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1461 source.oneTouchPlay(callback);
1465 private void queryDisplayStatus(final IHdmiControlCallback callback) {
1466 assertRunOnServiceThread();
1467 HdmiCecLocalDevicePlayback source = playback();
1468 if (source == null) {
1469 Slog.w(TAG, "Local playback device not available");
1470 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1473 source.queryDisplayStatus(callback);
1476 private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1477 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1479 listener.asBinder().linkToDeath(record, 0);
1480 } catch (RemoteException e) {
1481 Slog.w(TAG, "Listener already died");
1484 synchronized (mLock) {
1485 mHotplugEventListenerRecords.add(record);
1489 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1490 synchronized (mLock) {
1491 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1492 if (record.mListener.asBinder() == listener.asBinder()) {
1493 listener.asBinder().unlinkToDeath(record, 0);
1494 mHotplugEventListenerRecords.remove(record);
1501 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1502 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1504 listener.asBinder().linkToDeath(record, 0);
1505 } catch (RemoteException e) {
1506 Slog.w(TAG, "Listener already died");
1509 synchronized (mLock) {
1510 mDeviceEventListenerRecords.add(record);
1514 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1515 synchronized (mLock) {
1516 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1518 record.mListener.onStatusChanged(device, status);
1519 } catch (RemoteException e) {
1520 Slog.e(TAG, "Failed to report device event:" + e);
1526 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1527 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1530 listener.asBinder().linkToDeath(record, 0);
1531 } catch (RemoteException e) {
1532 Slog.w(TAG, "Listener already died");
1535 synchronized (mLock) {
1536 mSystemAudioModeChangeListenerRecords.add(record);
1540 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1541 synchronized (mLock) {
1542 for (SystemAudioModeChangeListenerRecord record :
1543 mSystemAudioModeChangeListenerRecords) {
1544 if (record.mListener.asBinder() == listener) {
1545 listener.asBinder().unlinkToDeath(record, 0);
1546 mSystemAudioModeChangeListenerRecords.remove(record);
1553 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1554 private final IHdmiInputChangeListener mListener;
1556 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1557 mListener = listener;
1561 public void binderDied() {
1562 synchronized (mLock) {
1563 mInputChangeListenerRecord = null;
1568 private void setInputChangeListener(IHdmiInputChangeListener listener) {
1569 synchronized (mLock) {
1570 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1572 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1573 } catch (RemoteException e) {
1574 Slog.w(TAG, "Listener already died");
1580 void invokeInputChangeListener(HdmiDeviceInfo info) {
1581 synchronized (mLock) {
1582 if (mInputChangeListenerRecord != null) {
1584 mInputChangeListenerRecord.mListener.onChanged(info);
1585 } catch (RemoteException e) {
1586 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1592 private void setHdmiRecordListener(IHdmiRecordListener listener) {
1593 synchronized (mLock) {
1594 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1596 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1597 } catch (RemoteException e) {
1598 Slog.w(TAG, "Listener already died.", e);
1603 byte[] invokeRecordRequestListener(int recorderAddress) {
1604 synchronized (mLock) {
1605 if (mRecordListenerRecord != null) {
1607 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1608 } catch (RemoteException e) {
1609 Slog.w(TAG, "Failed to start record.", e);
1612 return EmptyArray.BYTE;
1616 void invokeOneTouchRecordResult(int result) {
1617 synchronized (mLock) {
1618 if (mRecordListenerRecord != null) {
1620 mRecordListenerRecord.mListener.onOneTouchRecordResult(result);
1621 } catch (RemoteException e) {
1622 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1628 void invokeTimerRecordingResult(int result) {
1629 synchronized (mLock) {
1630 if (mRecordListenerRecord != null) {
1632 mRecordListenerRecord.mListener.onTimerRecordingResult(result);
1633 } catch (RemoteException e) {
1634 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1640 void invokeClearTimerRecordingResult(int result) {
1641 synchronized (mLock) {
1642 if (mRecordListenerRecord != null) {
1644 mRecordListenerRecord.mListener.onClearTimerRecordingResult(result);
1645 } catch (RemoteException e) {
1646 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1652 private void invokeCallback(IHdmiControlCallback callback, int result) {
1654 callback.onComplete(result);
1655 } catch (RemoteException e) {
1656 Slog.e(TAG, "Invoking callback failed:" + e);
1660 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1663 listener.onStatusChanged(enabled);
1664 } catch (RemoteException e) {
1665 Slog.e(TAG, "Invoking callback failed:" + e);
1669 private void announceHotplugEvent(int portId, boolean connected) {
1670 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1671 synchronized (mLock) {
1672 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1673 invokeHotplugEventListenerLocked(record.mListener, event);
1678 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1679 HdmiHotplugEvent event) {
1681 listener.onReceived(event);
1682 } catch (RemoteException e) {
1683 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1687 private HdmiCecLocalDeviceTv tv() {
1688 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1691 boolean isTvDevice() {
1692 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
1695 private HdmiCecLocalDevicePlayback playback() {
1696 return (HdmiCecLocalDevicePlayback)
1697 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1700 AudioManager getAudioManager() {
1701 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1704 boolean isControlEnabled() {
1705 synchronized (mLock) {
1706 return mHdmiControlEnabled;
1711 int getPowerStatus() {
1712 assertRunOnServiceThread();
1713 return mPowerStatus;
1717 boolean isPowerOnOrTransient() {
1718 assertRunOnServiceThread();
1719 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1720 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1724 boolean isPowerStandbyOrTransient() {
1725 assertRunOnServiceThread();
1726 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1727 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1731 boolean isPowerStandby() {
1732 assertRunOnServiceThread();
1733 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1738 assertRunOnServiceThread();
1739 mWakeUpMessageReceived = true;
1740 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1741 pm.wakeUp(SystemClock.uptimeMillis());
1742 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1743 // the intent, the sequence will continue at onWakeUp().
1748 assertRunOnServiceThread();
1749 mStandbyMessageReceived = true;
1750 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1751 pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
1752 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1753 // the intent, the sequence will continue at onStandby().
1757 private void onWakeUp() {
1758 assertRunOnServiceThread();
1759 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1760 if (mCecController != null) {
1761 if (mHdmiControlEnabled) {
1762 int startReason = INITIATED_BY_SCREEN_ON;
1763 if (mWakeUpMessageReceived) {
1764 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1766 initializeCec(startReason);
1769 Slog.i(TAG, "Device does not support HDMI-CEC.");
1771 // TODO: Initialize MHL local devices.
1775 private void onStandby() {
1776 assertRunOnServiceThread();
1777 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1779 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1780 disableDevices(new PendingActionClearedCallback() {
1782 public void onCleared(HdmiCecLocalDevice device) {
1783 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1784 devices.remove(device);
1785 if (devices.isEmpty()) {
1786 onStandbyCompleted();
1787 // We will not clear local devices here, since some OEM/SOC will keep passing
1788 // the received packets until the application processor enters to the sleep
1796 private void onLanguageChanged(String language) {
1797 assertRunOnServiceThread();
1798 mLanguage = language;
1801 tv().broadcastMenuLanguage(language);
1806 String getLanguage() {
1807 assertRunOnServiceThread();
1811 private void disableDevices(PendingActionClearedCallback callback) {
1812 if (mCecController != null) {
1813 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1814 device.disableDevice(mStandbyMessageReceived, callback);
1817 unregisterSettingsObserver();
1821 mMhlController.clearAllLocalDevices();
1825 private void clearLocalDevices() {
1826 assertRunOnServiceThread();
1827 if (mCecController == null) {
1830 mCecController.clearLogicalAddress();
1831 mCecController.clearLocalDevices();
1835 private void onStandbyCompleted() {
1836 assertRunOnServiceThread();
1837 Slog.v(TAG, "onStandbyCompleted");
1839 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1842 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1843 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1844 device.onStandby(mStandbyMessageReceived);
1846 mStandbyMessageReceived = false;
1847 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
1850 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1851 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1853 listener.asBinder().linkToDeath(record, 0);
1854 } catch (RemoteException e) {
1855 Slog.w(TAG, "Listener already died");
1858 synchronized (mLock) {
1859 mVendorCommandListenerRecords.add(record);
1863 boolean invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1864 boolean hasVendorId) {
1865 synchronized (mLock) {
1866 if (mVendorCommandListenerRecords.isEmpty()) {
1869 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1870 if (record.mDeviceType != deviceType) {
1874 record.mListener.onReceived(srcAddress, params, hasVendorId);
1875 } catch (RemoteException e) {
1876 Slog.e(TAG, "Failed to notify vendor command reception", e);
1883 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
1884 HdmiMhlVendorCommandListenerRecord record =
1885 new HdmiMhlVendorCommandListenerRecord(listener);
1887 listener.asBinder().linkToDeath(record, 0);
1888 } catch (RemoteException e) {
1889 Slog.w(TAG, "Listener already died.");
1893 synchronized (mLock) {
1894 mMhlVendorCommandListenerRecords.add(record);
1898 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
1899 synchronized (mLock) {
1900 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
1902 record.mListener.onReceived(portId, offest, length, data);
1903 } catch (RemoteException e) {
1904 Slog.e(TAG, "Failed to notify MHL vendor command", e);
1910 boolean isProhibitMode() {
1911 synchronized (mLock) {
1912 return mProhibitMode;
1916 void setProhibitMode(boolean enabled) {
1917 synchronized (mLock) {
1918 mProhibitMode = enabled;
1923 void setCecOption(int key, int value) {
1924 assertRunOnServiceThread();
1925 mCecController.setOption(key, value);
1929 void setControlEnabled(boolean enabled) {
1930 assertRunOnServiceThread();
1932 int value = toInt(enabled);
1933 mCecController.setOption(OPTION_CEC_ENABLE, value);
1934 mMhlController.setOption(OPTION_MHL_ENABLE, value);
1936 synchronized (mLock) {
1937 mHdmiControlEnabled = enabled;
1941 initializeCec(INITIATED_BY_ENABLE_CEC);
1943 disableDevices(new PendingActionClearedCallback() {
1945 public void onCleared(HdmiCecLocalDevice device) {
1946 assertRunOnServiceThread();
1947 clearLocalDevices();
1954 void setActivePortId(int portId) {
1955 assertRunOnServiceThread();
1956 mActivePortId = portId;
1958 // Resets last input for MHL, which stays valid only after the MHL device was selected,
1959 // and no further switching is done.
1960 setLastInputForMhl(Constants.INVALID_PORT_ID);
1964 void setLastInputForMhl(int portId) {
1965 assertRunOnServiceThread();
1966 mLastInputMhl = portId;
1970 int getLastInputForMhl() {
1971 assertRunOnServiceThread();
1972 return mLastInputMhl;
1976 * Performs input change, routing control for MHL device.
1978 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
1979 * @param contentOn {@code true} if RAP data is content on; otherwise false
1982 void changeInputForMhl(int portId, boolean contentOn) {
1983 assertRunOnServiceThread();
1984 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
1985 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
1987 public void onComplete(int result) throws RemoteException {
1988 // Keep the last input to switch back later when RAP[ContentOff] is received.
1989 // This effectively sets the port to invalid one if the switching is for
1991 setLastInputForMhl(lastInput);
1995 // MHL device is always directly connected to the port. Update the active port ID to avoid
1996 // unnecessary post-routing control task.
1997 tv().setActivePortId(portId);
1999 // The port is either the MHL-enabled port where the mobile device is connected, or
2000 // the last port to go back to when turnoff command is received. Note that the last port
2001 // may not be the MHL-enabled one. In this case the device info to be passed to
2002 // input change listener should be the one describing the corresponding HDMI port.
2003 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
2004 HdmiDeviceInfo info = (device != null && device.getInfo() != null)
2006 : mPortDeviceMap.get(portId);
2007 invokeInputChangeListener(info);
2010 void setMhlInputChangeEnabled(boolean enabled) {
2011 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2013 synchronized (mLock) {
2014 mMhlInputChangeEnabled = enabled;
2018 boolean isMhlInputChangeEnabled() {
2019 synchronized (mLock) {
2020 return mMhlInputChangeEnabled;
2025 void displayOsd(int messageId) {
2026 assertRunOnServiceThread();
2027 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2028 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2029 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2030 HdmiControlService.PERMISSION);
2034 void displayOsd(int messageId, int extra) {
2035 assertRunOnServiceThread();
2036 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2037 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2038 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRAM_PARAM1, extra);
2039 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2040 HdmiControlService.PERMISSION);