OSDN Git Service

CEC: add getDeviceList()
[android-x86/frameworks-base.git] / services / core / java / com / android / server / hdmi / HdmiControlService.java
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.server.hdmi;
18
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;
29
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;
68
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;
76
77 import libcore.util.EmptyArray;
78
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;
86
87 /**
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.
90  */
91 public final class HdmiControlService extends SystemService {
92     private static final String TAG = "HdmiControlService";
93
94     static final String PERMISSION = "android.permission.HDMI_CEC";
95
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;
102
103     /**
104      * Interface to report send result.
105      */
106     interface SendMessageCallback {
107         /**
108          * Called when {@link HdmiControlService#sendCecCommand} is completed.
109          *
110          * @param error result of send request.
111          * <ul>
112          * <li>{@link Constants#SEND_RESULT_SUCCESS}
113          * <li>{@link Constants#SEND_RESULT_NAK}
114          * <li>{@link Constants#SEND_RESULT_FAILURE}
115          * </ul>
116          */
117         void onSendCompleted(int error);
118     }
119
120     /**
121      * Interface to get a list of available logical devices.
122      */
123     interface DevicePollingCallback {
124         /**
125          * Called when device polling is finished.
126          *
127          * @param ackedAddress a list of logical addresses of available devices
128          */
129         void onPollingFinished(List<Integer> ackedAddress);
130     }
131
132     private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
133         @ServiceThreadOnly
134         @Override
135         public void onReceive(Context context, Intent intent) {
136             assertRunOnServiceThread();
137             switch (intent.getAction()) {
138                 case Intent.ACTION_SCREEN_OFF:
139                     if (isPowerOnOrTransient()) {
140                         onStandby();
141                     }
142                     break;
143                 case Intent.ACTION_SCREEN_ON:
144                     if (isPowerStandbyOrTransient()) {
145                         onWakeUp();
146                     }
147                     break;
148                 case Intent.ACTION_CONFIGURATION_CHANGED:
149                     String language = Locale.getDefault().getISO3Language();
150                     if (!mLanguage.equals(language)) {
151                         onLanguageChanged(language);
152                     }
153                     break;
154             }
155         }
156     }
157
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");
162
163     // Used to synchronize the access to the service.
164     private final Object mLock = new Object();
165
166     // Type of logical devices hosted in the system. Stored in the unmodifiable list.
167     private final List<Integer> mLocalDevices;
168
169     // List of records for hotplug event listener to handle the the caller killed in action.
170     @GuardedBy("mLock")
171     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
172             new ArrayList<>();
173
174     // List of records for device event listener to handle the caller killed in action.
175     @GuardedBy("mLock")
176     private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
177             new ArrayList<>();
178
179     // List of records for vendor command listener to handle the caller killed in action.
180     @GuardedBy("mLock")
181     private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
182             new ArrayList<>();
183
184     @GuardedBy("mLock")
185     private InputChangeListenerRecord mInputChangeListenerRecord;
186
187     @GuardedBy("mLock")
188     private HdmiRecordListenerRecord mRecordListenerRecord;
189
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.
192     @GuardedBy("mLock")
193     private boolean mHdmiControlEnabled;
194
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".
198     @GuardedBy("mLock")
199     private boolean mProhibitMode;
200
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<>();
204
205     // Handler used to run a task in service thread.
206     private final Handler mHandler = new Handler();
207
208     private final SettingsObserver mSettingsObserver;
209
210     private final HdmiControlBroadcastReceiver
211             mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
212
213     @Nullable
214     private HdmiCecController mCecController;
215
216     // HDMI port information. Stored in the unmodifiable list to keep the static information
217     // from being modified.
218     private List<HdmiPortInfo> mPortInfo;
219
220     // Map from path(physical address) to port ID.
221     private UnmodifiableSparseIntArray mPortIdMap;
222
223     // Map from port ID to HdmiPortInfo.
224     private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
225
226     // Map from port ID to HdmiDeviceInfo.
227     private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
228
229     private HdmiCecMessageValidator mMessageValidator;
230
231     @ServiceThreadOnly
232     private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
233
234     @ServiceThreadOnly
235     private String mLanguage = Locale.getDefault().getISO3Language();
236
237     @ServiceThreadOnly
238     private boolean mStandbyMessageReceived = false;
239
240     @ServiceThreadOnly
241     private boolean mWakeUpMessageReceived = false;
242
243     @ServiceThreadOnly
244     private int mActivePortId = Constants.INVALID_PORT_ID;
245
246     // Set to true while the input change by MHL is allowed.
247     @GuardedBy("mLock")
248     private boolean mMhlInputChangeEnabled;
249
250     // List of records for MHL Vendor command listener to handle the caller killed in action.
251     @GuardedBy("mLock")
252     private final ArrayList<HdmiMhlVendorCommandListenerRecord>
253             mMhlVendorCommandListenerRecords = new ArrayList<>();
254
255     @GuardedBy("mLock")
256     private List<HdmiDeviceInfo> mMhlDevices;
257
258     @Nullable
259     private HdmiMhlControllerStub mMhlController;
260
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.
264     @ServiceThreadOnly
265     private int mLastInputMhl = Constants.INVALID_PORT_ID;
266
267     public HdmiControlService(Context context) {
268         super(context);
269         mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
270         mSettingsObserver = new SettingsObserver(mHandler);
271     }
272
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) {
278             try {
279                 list.add(Integer.parseInt(item));
280             } catch (NumberFormatException e) {
281                 Slog.w(TAG, "Can't parseInt: " + item);
282             }
283         }
284         return Collections.unmodifiableList(list);
285     }
286
287     @Override
288     public void onStart() {
289         mIoThread.start();
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);
294
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);
299
300             // TODO: load value for mHdmiControlEnabled from preference.
301             if (mHdmiControlEnabled) {
302                 initializeCec(INITIATED_BY_BOOT_UP);
303             }
304         } else {
305             Slog.i(TAG, "Device does not support HDMI-CEC.");
306             return;
307         }
308
309         mMhlController = HdmiMhlControllerStub.create(this);
310         if (!mMhlController.isReady()) {
311             Slog.i(TAG, "Device does not support MHL-control.");
312         }
313         mMhlDevices = Collections.emptyList();
314
315         initPortInfo();
316         mMessageValidator = new HdmiCecMessageValidator(this);
317         publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
318
319         // Register broadcast receiver for power state change.
320         if (mCecController != null) {
321             IntentFilter filter = new IntentFilter();
322             filter.addAction(Intent.ACTION_SCREEN_OFF);
323             filter.addAction(Intent.ACTION_SCREEN_ON);
324             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
325             getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
326         }
327     }
328
329     /**
330      * Called when the initialization of local devices is complete.
331      */
332     private void onInitializeCecComplete() {
333         if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
334             mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
335         }
336         mWakeUpMessageReceived = false;
337
338         if (isTvDevice()) {
339             mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
340             registerContentObserver();
341         }
342     }
343
344     private void registerContentObserver() {
345         ContentResolver resolver = getContext().getContentResolver();
346         String[] settings = new String[] {
347                 Global.HDMI_CONTROL_ENABLED,
348                 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
349                 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
350                 Global.MHL_INPUT_SWITCHING_ENABLED,
351                 Global.MHL_POWER_CHARGE_ENABLED
352         };
353         for (String s : settings) {
354             resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
355                     UserHandle.USER_ALL);
356         }
357     }
358
359     private class SettingsObserver extends ContentObserver {
360         public SettingsObserver(Handler handler) {
361             super(handler);
362         }
363
364         // onChange is set up to run in service thread.
365         @Override
366         public void onChange(boolean selfChange, Uri uri) {
367             String option = uri.getLastPathSegment();
368             boolean enabled = readBooleanSetting(option, true);
369             switch (option) {
370                 case Global.HDMI_CONTROL_ENABLED:
371                     setControlEnabled(enabled);
372                     break;
373                 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
374                     tv().setAutoWakeup(enabled);
375                     setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
376                     break;
377                 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
378                     tv().setAutoDeviceOff(enabled);
379                     // No need to propagate to HAL.
380                     break;
381                 case Global.MHL_INPUT_SWITCHING_ENABLED:
382                     setMhlInputChangeEnabled(enabled);
383                     break;
384                 case Global.MHL_POWER_CHARGE_ENABLED:
385                     mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
386                     break;
387             }
388         }
389     }
390
391     private static int toInt(boolean enabled) {
392         return enabled ? ENABLED : DISABLED;
393     }
394
395     boolean readBooleanSetting(String key, boolean defVal) {
396         ContentResolver cr = getContext().getContentResolver();
397         return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
398     }
399
400     void writeBooleanSetting(String key, boolean value) {
401         ContentResolver cr = getContext().getContentResolver();
402         Global.putInt(cr, key, toInt(value));
403     }
404
405     private void unregisterSettingsObserver() {
406         getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
407     }
408
409     private void initializeCec(int initiatedBy) {
410         mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
411         initializeLocalDevices(initiatedBy);
412     }
413
414     @ServiceThreadOnly
415     private void initializeLocalDevices(final int initiatedBy) {
416         assertRunOnServiceThread();
417         // A container for [Device type, Local device info].
418         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
419         for (int type : mLocalDevices) {
420             HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
421             if (localDevice == null) {
422                 localDevice = HdmiCecLocalDevice.create(this, type);
423             }
424             localDevice.init();
425             localDevices.add(localDevice);
426         }
427         // It's now safe to flush existing local devices from mCecController since they were
428         // already moved to 'localDevices'.
429         clearLocalDevices();
430         allocateLogicalAddress(localDevices, initiatedBy);
431     }
432
433     @ServiceThreadOnly
434     private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
435             final int initiatedBy) {
436         assertRunOnServiceThread();
437         mCecController.clearLogicalAddress();
438         final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
439         final int[] finished = new int[1];
440         for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
441             mCecController.allocateLogicalAddress(localDevice.getType(),
442                     localDevice.getPreferredAddress(), new AllocateAddressCallback() {
443                 @Override
444                 public void onAllocated(int deviceType, int logicalAddress) {
445                     if (logicalAddress == Constants.ADDR_UNREGISTERED) {
446                         Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
447                     } else {
448                         // Set POWER_STATUS_ON to all local devices because they share lifetime
449                         // with system.
450                         HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
451                                 HdmiControlManager.POWER_STATUS_ON);
452                         localDevice.setDeviceInfo(deviceInfo);
453                         mCecController.addLocalDevice(deviceType, localDevice);
454                         mCecController.addLogicalAddress(logicalAddress);
455                         allocatedDevices.add(localDevice);
456                     }
457
458                     // Address allocation completed for all devices. Notify each device.
459                     if (allocatingDevices.size() == ++finished[0]) {
460                         if (initiatedBy != INITIATED_BY_HOTPLUG) {
461                             // In case of the hotplug we don't call onInitializeCecComplete()
462                             // since we reallocate the logical address only.
463                             onInitializeCecComplete();
464                         }
465                         notifyAddressAllocated(allocatedDevices, initiatedBy);
466                     }
467                 }
468             });
469         }
470     }
471
472     @ServiceThreadOnly
473     private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
474         assertRunOnServiceThread();
475         for (HdmiCecLocalDevice device : devices) {
476             int address = device.getDeviceInfo().getLogicalAddress();
477             device.handleAddressAllocated(address, initiatedBy);
478         }
479     }
480
481     // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
482     // keep them in one place.
483     @ServiceThreadOnly
484     private void initPortInfo() {
485         assertRunOnServiceThread();
486         HdmiPortInfo[] cecPortInfo = null;
487
488         // CEC HAL provides majority of the info while MHL does only MHL support flag for
489         // each port. Return empty array if CEC HAL didn't provide the info.
490         if (mCecController != null) {
491             cecPortInfo = mCecController.getPortInfos();
492         }
493         if (cecPortInfo == null) {
494             return;
495         }
496
497         SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
498         SparseIntArray portIdMap = new SparseIntArray();
499         SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
500         for (HdmiPortInfo info : cecPortInfo) {
501             portIdMap.put(info.getAddress(), info.getId());
502             portInfoMap.put(info.getId(), info);
503             portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
504         }
505         mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
506         mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
507         mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
508
509         HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
510         ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
511         for (HdmiPortInfo info : mhlPortInfo) {
512             if (info.isMhlSupported()) {
513                 mhlSupportedPorts.add(info.getId());
514             }
515         }
516
517         // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
518         // cec port info if we do not have have port that supports MHL.
519         if (mhlSupportedPorts.isEmpty()) {
520             mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
521             return;
522         }
523         ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
524         for (HdmiPortInfo info : cecPortInfo) {
525             if (mhlSupportedPorts.contains(info.getId())) {
526                 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
527                         info.isCecSupported(), true, info.isArcSupported()));
528             } else {
529                 result.add(info);
530             }
531         }
532         mPortInfo = Collections.unmodifiableList(result);
533     }
534
535     List<HdmiPortInfo> getPortInfo() {
536         return mPortInfo;
537     }
538
539     /**
540      * Returns HDMI port information for the given port id.
541      *
542      * @param portId HDMI port id
543      * @return {@link HdmiPortInfo} for the given port
544      */
545     HdmiPortInfo getPortInfo(int portId) {
546         return mPortInfoMap.get(portId, null);
547     }
548
549     /**
550      * Returns the routing path (physical address) of the HDMI port for the given
551      * port id.
552      */
553     int portIdToPath(int portId) {
554         HdmiPortInfo portInfo = getPortInfo(portId);
555         if (portInfo == null) {
556             Slog.e(TAG, "Cannot find the port info: " + portId);
557             return Constants.INVALID_PHYSICAL_ADDRESS;
558         }
559         return portInfo.getAddress();
560     }
561
562     /**
563      * Returns the id of HDMI port located at the top of the hierarchy of
564      * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
565      * the port id to be returned is the ID associated with the port address
566      * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
567      */
568     int pathToPortId(int path) {
569         int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
570         return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
571     }
572
573     boolean isValidPortId(int portId) {
574         return getPortInfo(portId) != null;
575     }
576
577     /**
578      * Returns {@link Looper} for IO operation.
579      *
580      * <p>Declared as package-private.
581      */
582     Looper getIoLooper() {
583         return mIoThread.getLooper();
584     }
585
586     /**
587      * Returns {@link Looper} of main thread. Use this {@link Looper} instance
588      * for tasks that are running on main service thread.
589      *
590      * <p>Declared as package-private.
591      */
592     Looper getServiceLooper() {
593         return mHandler.getLooper();
594     }
595
596     /**
597      * Returns physical address of the device.
598      */
599     int getPhysicalAddress() {
600         return mCecController.getPhysicalAddress();
601     }
602
603     /**
604      * Returns vendor id of CEC service.
605      */
606     int getVendorId() {
607         return mCecController.getVendorId();
608     }
609
610     @ServiceThreadOnly
611     HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
612         assertRunOnServiceThread();
613         HdmiCecLocalDeviceTv tv = tv();
614         if (tv == null) {
615             return null;
616         }
617         return tv.getCecDeviceInfo(logicalAddress);
618     }
619
620     /**
621      * Returns version of CEC.
622      */
623     int getCecVersion() {
624         return mCecController.getVersion();
625     }
626
627     /**
628      * Whether a device of the specified physical address is connected to ARC enabled port.
629      */
630     boolean isConnectedToArcPort(int physicalAddress) {
631         int portId = pathToPortId(physicalAddress);
632         if (portId != Constants.INVALID_PORT_ID) {
633             return mPortInfoMap.get(portId).isArcSupported();
634         }
635         return false;
636     }
637
638     void runOnServiceThread(Runnable runnable) {
639         mHandler.post(runnable);
640     }
641
642     void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
643         mHandler.postAtFrontOfQueue(runnable);
644     }
645
646     private void assertRunOnServiceThread() {
647         if (Looper.myLooper() != mHandler.getLooper()) {
648             throw new IllegalStateException("Should run on service thread.");
649         }
650     }
651
652     /**
653      * Transmit a CEC command to CEC bus.
654      *
655      * @param command CEC command to send out
656      * @param callback interface used to the result of send command
657      */
658     @ServiceThreadOnly
659     void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
660         assertRunOnServiceThread();
661         if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
662             mCecController.sendCommand(command, callback);
663         } else {
664             HdmiLogger.error("Invalid message type:" + command);
665             if (callback != null) {
666                 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
667             }
668         }
669     }
670
671     @ServiceThreadOnly
672     void sendCecCommand(HdmiCecMessage command) {
673         assertRunOnServiceThread();
674         sendCecCommand(command, null);
675     }
676
677     /**
678      * Send <Feature Abort> command on the given CEC message if possible.
679      * If the aborted message is invalid, then it wont send the message.
680      * @param command original command to be aborted
681      * @param reason reason of feature abort
682      */
683     @ServiceThreadOnly
684     void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
685         assertRunOnServiceThread();
686         mCecController.maySendFeatureAbortCommand(command, reason);
687     }
688
689     @ServiceThreadOnly
690     boolean handleCecCommand(HdmiCecMessage message) {
691         assertRunOnServiceThread();
692         int errorCode = mMessageValidator.isValid(message);
693         if (errorCode != HdmiCecMessageValidator.OK) {
694             // We'll not response on the messages with the invalid source or destination.
695             if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
696                 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
697             }
698             return true;
699         }
700         return dispatchMessageToLocalDevice(message);
701     }
702
703     void setAudioReturnChannel(boolean enabled) {
704         mCecController.setAudioReturnChannel(enabled);
705     }
706
707     @ServiceThreadOnly
708     private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
709         assertRunOnServiceThread();
710         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
711             if (device.dispatchMessage(message)
712                     && message.getDestination() != Constants.ADDR_BROADCAST) {
713                 return true;
714             }
715         }
716
717         if (message.getDestination() != Constants.ADDR_BROADCAST) {
718             HdmiLogger.warning("Unhandled cec command:" + message);
719         }
720         return false;
721     }
722
723     /**
724      * Called when a new hotplug event is issued.
725      *
726      * @param portId hdmi port number where hot plug event issued.
727      * @param connected whether to be plugged in or not
728      */
729     @ServiceThreadOnly
730     void onHotplug(int portId, boolean connected) {
731         assertRunOnServiceThread();
732
733         if (connected && !isTvDevice()) {
734             ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
735             for (int type : mLocalDevices) {
736                 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
737                 if (localDevice == null) {
738                     localDevice = HdmiCecLocalDevice.create(this, type);
739                     localDevice.init();
740                 }
741                 localDevices.add(localDevice);
742             }
743             allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
744         }
745
746         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
747             device.onHotplug(portId, connected);
748         }
749         announceHotplugEvent(portId, connected);
750     }
751
752     /**
753      * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
754      * devices.
755      *
756      * @param callback an interface used to get a list of all remote devices' address
757      * @param sourceAddress a logical address of source device where sends polling message
758      * @param pickStrategy strategy how to pick polling candidates
759      * @param retryCount the number of retry used to send polling message to remote devices
760      * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
761      */
762     @ServiceThreadOnly
763     void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
764             int retryCount) {
765         assertRunOnServiceThread();
766         mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
767                 retryCount);
768     }
769
770     private int checkPollStrategy(int pickStrategy) {
771         int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
772         if (strategy == 0) {
773             throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
774         }
775         int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
776         if (iterationStrategy == 0) {
777             throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
778         }
779         return strategy | iterationStrategy;
780     }
781
782     List<HdmiCecLocalDevice> getAllLocalDevices() {
783         assertRunOnServiceThread();
784         return mCecController.getLocalDeviceList();
785     }
786
787     Object getServiceLock() {
788         return mLock;
789     }
790
791     void setAudioStatus(boolean mute, int volume) {
792         AudioManager audioManager = getAudioManager();
793         boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
794         if (mute) {
795             if (!muted) {
796                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
797             }
798         } else {
799             if (muted) {
800                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
801             }
802             // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
803             // volume change notification back to hdmi control service.
804             audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
805                     AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
806         }
807     }
808
809     void announceSystemAudioModeChange(boolean enabled) {
810         synchronized (mLock) {
811             for (SystemAudioModeChangeListenerRecord record :
812                     mSystemAudioModeChangeListenerRecords) {
813                 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
814             }
815         }
816     }
817
818     private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
819         // TODO: find better name instead of model name.
820         String displayName = Build.MODEL;
821         return new HdmiDeviceInfo(logicalAddress,
822                 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
823                 getVendorId(), displayName);
824     }
825
826     @ServiceThreadOnly
827     void handleMhlHotplugEvent(int portId, boolean connected) {
828         assertRunOnServiceThread();
829         // Hotplug event is used to add/remove MHL devices as TV input.
830         if (connected) {
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");
836             }
837             invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
838             updateSafeMhlInput();
839         } else {
840             HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
841             if (device != null) {
842                 device.onDeviceRemoved();
843                 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
844                 updateSafeMhlInput();
845             } else {
846                 Slog.w(TAG, "No device to remove:[portId=" + portId);
847             }
848         }
849         announceHotplugEvent(portId, connected);
850     }
851
852     @ServiceThreadOnly
853     void handleMhlBusModeChanged(int portId, int busmode) {
854         assertRunOnServiceThread();
855         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
856         if (device != null) {
857             device.setBusMode(busmode);
858         } else {
859             Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
860                     ", busmode:" + busmode + "]");
861         }
862     }
863
864     @ServiceThreadOnly
865     void handleMhlBusOvercurrent(int portId, boolean on) {
866         assertRunOnServiceThread();
867         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
868         if (device != null) {
869             device.onBusOvercurrentDetected(on);
870         } else {
871             Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
872         }
873     }
874
875     @ServiceThreadOnly
876     void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
877         assertRunOnServiceThread();
878         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
879
880         if (device != null) {
881             device.setDeviceStatusChange(adopterId, deviceId);
882         } else {
883             Slog.w(TAG, "No mhl device exists for device status event[portId:"
884                     + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
885         }
886     }
887
888     @ServiceThreadOnly
889     private void updateSafeMhlInput() {
890         assertRunOnServiceThread();
891         List<HdmiDeviceInfo> inputs = Collections.emptyList();
892         SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
893         for (int i = 0; i < devices.size(); ++i) {
894             HdmiMhlLocalDeviceStub device = devices.valueAt(i);
895             HdmiDeviceInfo info = device.getInfo();
896             if (info != null) {
897                 if (inputs.isEmpty()) {
898                     inputs = new ArrayList<>();
899                 }
900                 inputs.add(device.getInfo());
901             }
902         }
903         synchronized (mLock) {
904             mMhlDevices = inputs;
905         }
906     }
907
908     private List<HdmiDeviceInfo> getMhlDevicesLocked() {
909         return mMhlDevices;
910     }
911
912     private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
913         private final IHdmiMhlVendorCommandListener mListener;
914
915         public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
916             mListener = listener;
917         }
918
919         @Override
920         public void binderDied() {
921             mMhlVendorCommandListenerRecords.remove(this);
922         }
923     }
924
925     // Record class that monitors the event of the caller of being killed. Used to clean up
926     // the listener list and record list accordingly.
927     private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
928         private final IHdmiHotplugEventListener mListener;
929
930         public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
931             mListener = listener;
932         }
933
934         @Override
935         public void binderDied() {
936             synchronized (mLock) {
937                 mHotplugEventListenerRecords.remove(this);
938             }
939         }
940     }
941
942     private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
943         private final IHdmiDeviceEventListener mListener;
944
945         public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
946             mListener = listener;
947         }
948
949         @Override
950         public void binderDied() {
951             synchronized (mLock) {
952                 mDeviceEventListenerRecords.remove(this);
953             }
954         }
955     }
956
957     private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
958         private final IHdmiSystemAudioModeChangeListener mListener;
959
960         public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
961             mListener = listener;
962         }
963
964         @Override
965         public void binderDied() {
966             synchronized (mLock) {
967                 mSystemAudioModeChangeListenerRecords.remove(this);
968             }
969         }
970     }
971
972     class VendorCommandListenerRecord implements IBinder.DeathRecipient {
973         private final IHdmiVendorCommandListener mListener;
974         private final int mDeviceType;
975
976         public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
977             mListener = listener;
978             mDeviceType = deviceType;
979         }
980
981         @Override
982         public void binderDied() {
983             synchronized (mLock) {
984                 mVendorCommandListenerRecords.remove(this);
985             }
986         }
987     }
988
989     private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
990         private final IHdmiRecordListener mListener;
991
992         public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
993             mListener = listener;
994         }
995
996         @Override
997         public void binderDied() {
998             synchronized (mLock) {
999                 mRecordListenerRecord = null;
1000             }
1001         }
1002     }
1003
1004     private void enforceAccessPermission() {
1005         getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1006     }
1007
1008     private final class BinderService extends IHdmiControlService.Stub {
1009         @Override
1010         public int[] getSupportedTypes() {
1011             enforceAccessPermission();
1012             // mLocalDevices is an unmodifiable list - no lock necesary.
1013             int[] localDevices = new int[mLocalDevices.size()];
1014             for (int i = 0; i < localDevices.length; ++i) {
1015                 localDevices[i] = mLocalDevices.get(i);
1016             }
1017             return localDevices;
1018         }
1019
1020         @Override
1021         public HdmiDeviceInfo getActiveSource() {
1022             HdmiCecLocalDeviceTv tv = tv();
1023             if (tv == null) {
1024                 Slog.w(TAG, "Local tv device not available");
1025                 return null;
1026             }
1027             ActiveSource activeSource = tv.getActiveSource();
1028             if (activeSource.isValid()) {
1029                 return new HdmiDeviceInfo(activeSource.logicalAddress,
1030                         activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1031                         HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1032             }
1033             int activePath = tv.getActivePath();
1034             if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1035                 return new HdmiDeviceInfo(activePath, tv.getActivePortId());
1036             }
1037             return null;
1038         }
1039
1040         @Override
1041         public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1042             enforceAccessPermission();
1043             runOnServiceThread(new Runnable() {
1044                 @Override
1045                 public void run() {
1046                     if (callback == null) {
1047                         Slog.e(TAG, "Callback cannot be null");
1048                         return;
1049                     }
1050                     HdmiCecLocalDeviceTv tv = tv();
1051                     if (tv == null) {
1052                         Slog.w(TAG, "Local tv device not available");
1053                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1054                         return;
1055                     }
1056                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1057                     if (device != null) {
1058                         if (device.getPortId() == tv.getActivePortId()) {
1059                             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1060                             return;
1061                         }
1062                         // Upon selecting MHL device, we send RAP[Content On] to wake up
1063                         // the connected mobile device, start routing control to switch ports.
1064                         // callback is handled by MHL action.
1065                         device.turnOn(callback);
1066                         tv.doManualPortSwitching(device.getPortId(), null);
1067                         return;
1068                     }
1069                     tv.deviceSelect(deviceId, callback);
1070                 }
1071             });
1072         }
1073
1074         @Override
1075         public void portSelect(final int portId, final IHdmiControlCallback callback) {
1076             enforceAccessPermission();
1077             runOnServiceThread(new Runnable() {
1078                 @Override
1079                 public void run() {
1080                     if (callback == null) {
1081                         Slog.e(TAG, "Callback cannot be null");
1082                         return;
1083                     }
1084                     HdmiCecLocalDeviceTv tv = tv();
1085                     if (tv == null) {
1086                         Slog.w(TAG, "Local tv device not available");
1087                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1088                         return;
1089                     }
1090                     tv.doManualPortSwitching(portId, callback);
1091                 }
1092             });
1093         }
1094
1095         @Override
1096         public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1097             enforceAccessPermission();
1098             runOnServiceThread(new Runnable() {
1099                 @Override
1100                 public void run() {
1101                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1102                     if (device != null) {
1103                         device.sendKeyEvent(keyCode, isPressed);
1104                         return;
1105                     }
1106                     if (mCecController != null) {
1107                         HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1108                         if (localDevice == null) {
1109                             Slog.w(TAG, "Local device not available");
1110                             return;
1111                         }
1112                         localDevice.sendKeyEvent(keyCode, isPressed);
1113                     }
1114                 }
1115             });
1116         }
1117
1118         @Override
1119         public void oneTouchPlay(final IHdmiControlCallback callback) {
1120             enforceAccessPermission();
1121             runOnServiceThread(new Runnable() {
1122                 @Override
1123                 public void run() {
1124                     HdmiControlService.this.oneTouchPlay(callback);
1125                 }
1126             });
1127         }
1128
1129         @Override
1130         public void queryDisplayStatus(final IHdmiControlCallback callback) {
1131             enforceAccessPermission();
1132             runOnServiceThread(new Runnable() {
1133                 @Override
1134                 public void run() {
1135                     HdmiControlService.this.queryDisplayStatus(callback);
1136                 }
1137             });
1138         }
1139
1140         @Override
1141         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1142             enforceAccessPermission();
1143             HdmiControlService.this.addHotplugEventListener(listener);
1144         }
1145
1146         @Override
1147         public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1148             enforceAccessPermission();
1149             HdmiControlService.this.removeHotplugEventListener(listener);
1150         }
1151
1152         @Override
1153         public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1154             enforceAccessPermission();
1155             HdmiControlService.this.addDeviceEventListener(listener);
1156         }
1157
1158         @Override
1159         public List<HdmiPortInfo> getPortInfo() {
1160             enforceAccessPermission();
1161             return HdmiControlService.this.getPortInfo();
1162         }
1163
1164         @Override
1165         public boolean canChangeSystemAudioMode() {
1166             enforceAccessPermission();
1167             HdmiCecLocalDeviceTv tv = tv();
1168             if (tv == null) {
1169                 return false;
1170             }
1171             return tv.hasSystemAudioDevice();
1172         }
1173
1174         @Override
1175         public boolean getSystemAudioMode() {
1176             enforceAccessPermission();
1177             HdmiCecLocalDeviceTv tv = tv();
1178             if (tv == null) {
1179                 return false;
1180             }
1181             return tv.isSystemAudioActivated();
1182         }
1183
1184         @Override
1185         public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1186             enforceAccessPermission();
1187             runOnServiceThread(new Runnable() {
1188                 @Override
1189                 public void run() {
1190                     HdmiCecLocalDeviceTv tv = tv();
1191                     if (tv == null) {
1192                         Slog.w(TAG, "Local tv device not available");
1193                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1194                         return;
1195                     }
1196                     tv.changeSystemAudioMode(enabled, callback);
1197                 }
1198             });
1199         }
1200
1201         @Override
1202         public void addSystemAudioModeChangeListener(
1203                 final IHdmiSystemAudioModeChangeListener listener) {
1204             enforceAccessPermission();
1205             HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1206         }
1207
1208         @Override
1209         public void removeSystemAudioModeChangeListener(
1210                 final IHdmiSystemAudioModeChangeListener listener) {
1211             enforceAccessPermission();
1212             HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1213         }
1214
1215         @Override
1216         public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1217             enforceAccessPermission();
1218             HdmiControlService.this.setInputChangeListener(listener);
1219         }
1220
1221         @Override
1222         public List<HdmiDeviceInfo> getInputDevices() {
1223             enforceAccessPermission();
1224             // No need to hold the lock for obtaining TV device as the local device instance
1225             // is preserved while the HDMI control is enabled.
1226             HdmiCecLocalDeviceTv tv = tv();
1227             synchronized (mLock) {
1228                 List<HdmiDeviceInfo> cecDevices = (tv == null)
1229                         ? Collections.<HdmiDeviceInfo>emptyList()
1230                         : tv.getSafeExternalInputsLocked();
1231                 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1232             }
1233         }
1234
1235         // Returns all the CEC devices on the bus including system audio, switch,
1236         // even those of reserved type.
1237         @Override
1238         public List<HdmiDeviceInfo> getDeviceList() {
1239             enforceAccessPermission();
1240             HdmiCecLocalDeviceTv tv = tv();
1241             synchronized (mLock) {
1242                 return (tv == null)
1243                         ? Collections.<HdmiDeviceInfo>emptyList()
1244                         : tv.getSafeCecDevicesLocked();
1245             }
1246         }
1247
1248         @Override
1249         public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1250                 final int maxIndex) {
1251             enforceAccessPermission();
1252             runOnServiceThread(new Runnable() {
1253                 @Override
1254                 public void run() {
1255                     HdmiCecLocalDeviceTv tv = tv();
1256                     if (tv == null) {
1257                         Slog.w(TAG, "Local tv device not available");
1258                         return;
1259                     }
1260                     tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1261                 }
1262             });
1263         }
1264
1265         @Override
1266         public void setSystemAudioMute(final boolean mute) {
1267             enforceAccessPermission();
1268             runOnServiceThread(new Runnable() {
1269                 @Override
1270                 public void run() {
1271                     HdmiCecLocalDeviceTv tv = tv();
1272                     if (tv == null) {
1273                         Slog.w(TAG, "Local tv device not available");
1274                         return;
1275                     }
1276                     tv.changeMute(mute);
1277                 }
1278             });
1279         }
1280
1281         @Override
1282         public void setArcMode(final boolean enabled) {
1283             enforceAccessPermission();
1284             runOnServiceThread(new Runnable() {
1285                 @Override
1286                 public void run() {
1287                     HdmiCecLocalDeviceTv tv = tv();
1288                     if (tv == null) {
1289                         Slog.w(TAG, "Local tv device not available to change arc mode.");
1290                         return;
1291                     }
1292                 }
1293             });
1294         }
1295
1296         @Override
1297         public void setProhibitMode(final boolean enabled) {
1298             enforceAccessPermission();
1299             if (!isTvDevice()) {
1300                 return;
1301             }
1302             HdmiControlService.this.setProhibitMode(enabled);
1303         }
1304
1305         @Override
1306         public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1307                 final int deviceType) {
1308             enforceAccessPermission();
1309             HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1310         }
1311
1312         @Override
1313         public void sendVendorCommand(final int deviceType, final int targetAddress,
1314                 final byte[] params, final boolean hasVendorId) {
1315             enforceAccessPermission();
1316             runOnServiceThread(new Runnable() {
1317                 @Override
1318                 public void run() {
1319                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1320                     if (device == null) {
1321                         Slog.w(TAG, "Local device not available");
1322                         return;
1323                     }
1324                     if (hasVendorId) {
1325                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1326                                 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1327                                 getVendorId(), params));
1328                     } else {
1329                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1330                                 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1331                     }
1332                 }
1333             });
1334         }
1335
1336         @Override
1337         public void sendStandby(final int deviceType, final int deviceId) {
1338             enforceAccessPermission();
1339             runOnServiceThread(new Runnable() {
1340                 @Override
1341                 public void run() {
1342                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1343                     if (device == null) {
1344                         Slog.w(TAG, "Local device not available");
1345                         return;
1346                     }
1347                     device.sendStandby(deviceId);
1348                 }
1349             });
1350         }
1351
1352         @Override
1353         public void setHdmiRecordListener(IHdmiRecordListener listener) {
1354             HdmiControlService.this.setHdmiRecordListener(listener);
1355         }
1356
1357         @Override
1358         public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1359             runOnServiceThread(new Runnable() {
1360                 @Override
1361                 public void run() {
1362                     if (!isTvDevice()) {
1363                         Slog.w(TAG, "No TV is available.");
1364                         return;
1365                     }
1366                     tv().startOneTouchRecord(recorderAddress, recordSource);
1367                 }
1368             });
1369         }
1370
1371         @Override
1372         public void stopOneTouchRecord(final int recorderAddress) {
1373             runOnServiceThread(new Runnable() {
1374                 @Override
1375                 public void run() {
1376                     if (!isTvDevice()) {
1377                         Slog.w(TAG, "No TV is available.");
1378                         return;
1379                     }
1380                     tv().stopOneTouchRecord(recorderAddress);
1381                 }
1382             });
1383         }
1384
1385         @Override
1386         public void startTimerRecording(final int recorderAddress, final int sourceType,
1387                 final byte[] recordSource) {
1388             runOnServiceThread(new Runnable() {
1389                 @Override
1390                 public void run() {
1391                     if (!isTvDevice()) {
1392                         Slog.w(TAG, "No TV is available.");
1393                         return;
1394                     }
1395                     tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1396                 }
1397             });
1398         }
1399
1400         @Override
1401         public void clearTimerRecording(final int recorderAddress, final int sourceType,
1402                 final byte[] recordSource) {
1403             runOnServiceThread(new Runnable() {
1404                 @Override
1405                 public void run() {
1406                     if (!isTvDevice()) {
1407                         Slog.w(TAG, "No TV is available.");
1408                         return;
1409                     }
1410                     tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1411                 }
1412             });
1413         }
1414
1415         @Override
1416         public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1417                 final byte[] data) {
1418             enforceAccessPermission();
1419             runOnServiceThread(new Runnable() {
1420                 @Override
1421                 public void run() {
1422                     if (!isControlEnabled()) {
1423                         Slog.w(TAG, "Hdmi control is disabled.");
1424                         return ;
1425                     }
1426                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1427                     if (device == null) {
1428                         Slog.w(TAG, "Invalid port id:" + portId);
1429                         return;
1430                     }
1431                     mMhlController.sendVendorCommand(portId, offset, length, data);
1432                 }
1433             });
1434         }
1435
1436         @Override
1437         public void addHdmiMhlVendorCommandListener(
1438                 IHdmiMhlVendorCommandListener listener) {
1439             enforceAccessPermission();
1440             HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1441         }
1442
1443         @Override
1444         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1445             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1446             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1447
1448             pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1449             pw.println("mProhibitMode: " + mProhibitMode);
1450             if (mCecController != null) {
1451                 pw.println("mCecController: ");
1452                 pw.increaseIndent();
1453                 mCecController.dump(pw);
1454                 pw.decreaseIndent();
1455             }
1456             pw.println("mPortInfo: ");
1457             pw.increaseIndent();
1458             for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1459                 pw.println("- " + hdmiPortInfo);
1460             }
1461             pw.decreaseIndent();
1462             pw.println("mPowerStatus: " + mPowerStatus);
1463         }
1464     }
1465
1466     @ServiceThreadOnly
1467     private void oneTouchPlay(final IHdmiControlCallback callback) {
1468         assertRunOnServiceThread();
1469         HdmiCecLocalDevicePlayback source = playback();
1470         if (source == null) {
1471             Slog.w(TAG, "Local playback device not available");
1472             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1473             return;
1474         }
1475         source.oneTouchPlay(callback);
1476     }
1477
1478     @ServiceThreadOnly
1479     private void queryDisplayStatus(final IHdmiControlCallback callback) {
1480         assertRunOnServiceThread();
1481         HdmiCecLocalDevicePlayback source = playback();
1482         if (source == null) {
1483             Slog.w(TAG, "Local playback device not available");
1484             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1485             return;
1486         }
1487         source.queryDisplayStatus(callback);
1488     }
1489
1490     private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1491         HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1492         try {
1493             listener.asBinder().linkToDeath(record, 0);
1494         } catch (RemoteException e) {
1495             Slog.w(TAG, "Listener already died");
1496             return;
1497         }
1498         synchronized (mLock) {
1499             mHotplugEventListenerRecords.add(record);
1500         }
1501     }
1502
1503     private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1504         synchronized (mLock) {
1505             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1506                 if (record.mListener.asBinder() == listener.asBinder()) {
1507                     listener.asBinder().unlinkToDeath(record, 0);
1508                     mHotplugEventListenerRecords.remove(record);
1509                     break;
1510                 }
1511             }
1512         }
1513     }
1514
1515     private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1516         DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1517         try {
1518             listener.asBinder().linkToDeath(record, 0);
1519         } catch (RemoteException e) {
1520             Slog.w(TAG, "Listener already died");
1521             return;
1522         }
1523         synchronized (mLock) {
1524             mDeviceEventListenerRecords.add(record);
1525         }
1526     }
1527
1528     void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1529         synchronized (mLock) {
1530             for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1531                 try {
1532                     record.mListener.onStatusChanged(device, status);
1533                 } catch (RemoteException e) {
1534                     Slog.e(TAG, "Failed to report device event:" + e);
1535                 }
1536             }
1537         }
1538     }
1539
1540     private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1541         SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1542                 listener);
1543         try {
1544             listener.asBinder().linkToDeath(record, 0);
1545         } catch (RemoteException e) {
1546             Slog.w(TAG, "Listener already died");
1547             return;
1548         }
1549         synchronized (mLock) {
1550             mSystemAudioModeChangeListenerRecords.add(record);
1551         }
1552     }
1553
1554     private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1555         synchronized (mLock) {
1556             for (SystemAudioModeChangeListenerRecord record :
1557                     mSystemAudioModeChangeListenerRecords) {
1558                 if (record.mListener.asBinder() == listener) {
1559                     listener.asBinder().unlinkToDeath(record, 0);
1560                     mSystemAudioModeChangeListenerRecords.remove(record);
1561                     break;
1562                 }
1563             }
1564         }
1565     }
1566
1567     private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1568         private final IHdmiInputChangeListener mListener;
1569
1570         public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1571             mListener = listener;
1572         }
1573
1574         @Override
1575         public void binderDied() {
1576             synchronized (mLock) {
1577                 mInputChangeListenerRecord = null;
1578             }
1579         }
1580     }
1581
1582     private void setInputChangeListener(IHdmiInputChangeListener listener) {
1583         synchronized (mLock) {
1584             mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1585             try {
1586                 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1587             } catch (RemoteException e) {
1588                 Slog.w(TAG, "Listener already died");
1589                 return;
1590             }
1591         }
1592     }
1593
1594     void invokeInputChangeListener(HdmiDeviceInfo info) {
1595         synchronized (mLock) {
1596             if (mInputChangeListenerRecord != null) {
1597                 try {
1598                     mInputChangeListenerRecord.mListener.onChanged(info);
1599                 } catch (RemoteException e) {
1600                     Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1601                 }
1602             }
1603         }
1604     }
1605
1606     private void setHdmiRecordListener(IHdmiRecordListener listener) {
1607         synchronized (mLock) {
1608             mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1609             try {
1610                 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1611             } catch (RemoteException e) {
1612                 Slog.w(TAG, "Listener already died.", e);
1613             }
1614         }
1615     }
1616
1617     byte[] invokeRecordRequestListener(int recorderAddress) {
1618         synchronized (mLock) {
1619             if (mRecordListenerRecord != null) {
1620                 try {
1621                     return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1622                 } catch (RemoteException e) {
1623                     Slog.w(TAG, "Failed to start record.", e);
1624                 }
1625             }
1626             return EmptyArray.BYTE;
1627         }
1628     }
1629
1630     void invokeOneTouchRecordResult(int result) {
1631         synchronized (mLock) {
1632             if (mRecordListenerRecord != null) {
1633                 try {
1634                     mRecordListenerRecord.mListener.onOneTouchRecordResult(result);
1635                 } catch (RemoteException e) {
1636                     Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1637                 }
1638             }
1639         }
1640     }
1641
1642     void invokeTimerRecordingResult(int result) {
1643         synchronized (mLock) {
1644             if (mRecordListenerRecord != null) {
1645                 try {
1646                     mRecordListenerRecord.mListener.onTimerRecordingResult(result);
1647                 } catch (RemoteException e) {
1648                     Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1649                 }
1650             }
1651         }
1652     }
1653
1654     void invokeClearTimerRecordingResult(int result) {
1655         synchronized (mLock) {
1656             if (mRecordListenerRecord != null) {
1657                 try {
1658                     mRecordListenerRecord.mListener.onClearTimerRecordingResult(result);
1659                 } catch (RemoteException e) {
1660                     Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1661                 }
1662             }
1663         }
1664     }
1665
1666     private void invokeCallback(IHdmiControlCallback callback, int result) {
1667         try {
1668             callback.onComplete(result);
1669         } catch (RemoteException e) {
1670             Slog.e(TAG, "Invoking callback failed:" + e);
1671         }
1672     }
1673
1674     private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1675             boolean enabled) {
1676         try {
1677             listener.onStatusChanged(enabled);
1678         } catch (RemoteException e) {
1679             Slog.e(TAG, "Invoking callback failed:" + e);
1680         }
1681     }
1682
1683     private void announceHotplugEvent(int portId, boolean connected) {
1684         HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1685         synchronized (mLock) {
1686             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1687                 invokeHotplugEventListenerLocked(record.mListener, event);
1688             }
1689         }
1690     }
1691
1692     private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1693             HdmiHotplugEvent event) {
1694         try {
1695             listener.onReceived(event);
1696         } catch (RemoteException e) {
1697             Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1698         }
1699     }
1700
1701     private HdmiCecLocalDeviceTv tv() {
1702         return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1703     }
1704
1705     boolean isTvDevice() {
1706         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
1707     }
1708
1709     private HdmiCecLocalDevicePlayback playback() {
1710         return (HdmiCecLocalDevicePlayback)
1711                 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1712     }
1713
1714     AudioManager getAudioManager() {
1715         return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1716     }
1717
1718     boolean isControlEnabled() {
1719         synchronized (mLock) {
1720             return mHdmiControlEnabled;
1721         }
1722     }
1723
1724     @ServiceThreadOnly
1725     int getPowerStatus() {
1726         assertRunOnServiceThread();
1727         return mPowerStatus;
1728     }
1729
1730     @ServiceThreadOnly
1731     boolean isPowerOnOrTransient() {
1732         assertRunOnServiceThread();
1733         return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1734                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1735     }
1736
1737     @ServiceThreadOnly
1738     boolean isPowerStandbyOrTransient() {
1739         assertRunOnServiceThread();
1740         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1741                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1742     }
1743
1744     @ServiceThreadOnly
1745     boolean isPowerStandby() {
1746         assertRunOnServiceThread();
1747         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1748     }
1749
1750     @ServiceThreadOnly
1751     void wakeUp() {
1752         assertRunOnServiceThread();
1753         mWakeUpMessageReceived = true;
1754         PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1755         pm.wakeUp(SystemClock.uptimeMillis());
1756         // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1757         // the intent, the sequence will continue at onWakeUp().
1758     }
1759
1760     @ServiceThreadOnly
1761     void standby() {
1762         assertRunOnServiceThread();
1763         mStandbyMessageReceived = true;
1764         PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1765         pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
1766         // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1767         // the intent, the sequence will continue at onStandby().
1768     }
1769
1770     @ServiceThreadOnly
1771     private void onWakeUp() {
1772         assertRunOnServiceThread();
1773         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1774         if (mCecController != null) {
1775             if (mHdmiControlEnabled) {
1776                 int startReason = INITIATED_BY_SCREEN_ON;
1777                 if (mWakeUpMessageReceived) {
1778                     startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1779                 }
1780                 initializeCec(startReason);
1781             }
1782         } else {
1783             Slog.i(TAG, "Device does not support HDMI-CEC.");
1784         }
1785         // TODO: Initialize MHL local devices.
1786     }
1787
1788     @ServiceThreadOnly
1789     private void onStandby() {
1790         assertRunOnServiceThread();
1791         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1792
1793         final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1794         disableDevices(new PendingActionClearedCallback() {
1795             @Override
1796             public void onCleared(HdmiCecLocalDevice device) {
1797                 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1798                 devices.remove(device);
1799                 if (devices.isEmpty()) {
1800                     onStandbyCompleted();
1801                     // We will not clear local devices here, since some OEM/SOC will keep passing
1802                     // the received packets until the application processor enters to the sleep
1803                     // actually.
1804                 }
1805             }
1806         });
1807     }
1808
1809     @ServiceThreadOnly
1810     private void onLanguageChanged(String language) {
1811         assertRunOnServiceThread();
1812         mLanguage = language;
1813
1814         if (isTvDevice()) {
1815             tv().broadcastMenuLanguage(language);
1816         }
1817     }
1818
1819     @ServiceThreadOnly
1820     String getLanguage() {
1821         assertRunOnServiceThread();
1822         return mLanguage;
1823     }
1824
1825     private void disableDevices(PendingActionClearedCallback callback) {
1826         if (mCecController != null) {
1827             for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1828                 device.disableDevice(mStandbyMessageReceived, callback);
1829             }
1830             if (isTvDevice()) {
1831                 unregisterSettingsObserver();
1832             }
1833         }
1834
1835         mMhlController.clearAllLocalDevices();
1836     }
1837
1838     @ServiceThreadOnly
1839     private void clearLocalDevices() {
1840         assertRunOnServiceThread();
1841         if (mCecController == null) {
1842             return;
1843         }
1844         mCecController.clearLogicalAddress();
1845         mCecController.clearLocalDevices();
1846     }
1847
1848     @ServiceThreadOnly
1849     private void onStandbyCompleted() {
1850         assertRunOnServiceThread();
1851         Slog.v(TAG, "onStandbyCompleted");
1852
1853         if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1854             return;
1855         }
1856         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1857         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1858             device.onStandby(mStandbyMessageReceived);
1859         }
1860         mStandbyMessageReceived = false;
1861         mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
1862     }
1863
1864     private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1865         VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1866         try {
1867             listener.asBinder().linkToDeath(record, 0);
1868         } catch (RemoteException e) {
1869             Slog.w(TAG, "Listener already died");
1870             return;
1871         }
1872         synchronized (mLock) {
1873             mVendorCommandListenerRecords.add(record);
1874         }
1875     }
1876
1877     boolean invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1878             boolean hasVendorId) {
1879         synchronized (mLock) {
1880             if (mVendorCommandListenerRecords.isEmpty()) {
1881                 return false;
1882             }
1883             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1884                 if (record.mDeviceType != deviceType) {
1885                     continue;
1886                 }
1887                 try {
1888                     record.mListener.onReceived(srcAddress, params, hasVendorId);
1889                 } catch (RemoteException e) {
1890                     Slog.e(TAG, "Failed to notify vendor command reception", e);
1891                 }
1892             }
1893             return true;
1894         }
1895     }
1896
1897     private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
1898         HdmiMhlVendorCommandListenerRecord record =
1899                 new HdmiMhlVendorCommandListenerRecord(listener);
1900         try {
1901             listener.asBinder().linkToDeath(record, 0);
1902         } catch (RemoteException e) {
1903             Slog.w(TAG, "Listener already died.");
1904             return;
1905         }
1906
1907         synchronized (mLock) {
1908             mMhlVendorCommandListenerRecords.add(record);
1909         }
1910     }
1911
1912     void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
1913         synchronized (mLock) {
1914             for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
1915                 try {
1916                     record.mListener.onReceived(portId, offest, length, data);
1917                 } catch (RemoteException e) {
1918                     Slog.e(TAG, "Failed to notify MHL vendor command", e);
1919                 }
1920             }
1921         }
1922     }
1923
1924     boolean isProhibitMode() {
1925         synchronized (mLock) {
1926             return mProhibitMode;
1927         }
1928     }
1929
1930     void setProhibitMode(boolean enabled) {
1931         synchronized (mLock) {
1932             mProhibitMode = enabled;
1933         }
1934     }
1935
1936     @ServiceThreadOnly
1937     void setCecOption(int key, int value) {
1938         assertRunOnServiceThread();
1939         mCecController.setOption(key, value);
1940     }
1941
1942     @ServiceThreadOnly
1943     void setControlEnabled(boolean enabled) {
1944         assertRunOnServiceThread();
1945
1946         int value = toInt(enabled);
1947         mCecController.setOption(OPTION_CEC_ENABLE, value);
1948         mMhlController.setOption(OPTION_MHL_ENABLE, value);
1949
1950         synchronized (mLock) {
1951             mHdmiControlEnabled = enabled;
1952         }
1953
1954         if (enabled) {
1955             initializeCec(INITIATED_BY_ENABLE_CEC);
1956         } else {
1957             disableDevices(new PendingActionClearedCallback() {
1958                 @Override
1959                 public void onCleared(HdmiCecLocalDevice device) {
1960                     assertRunOnServiceThread();
1961                     clearLocalDevices();
1962                 }
1963             });
1964         }
1965     }
1966
1967     @ServiceThreadOnly
1968     void setActivePortId(int portId) {
1969         assertRunOnServiceThread();
1970         mActivePortId = portId;
1971
1972         // Resets last input for MHL, which stays valid only after the MHL device was selected,
1973         // and no further switching is done.
1974         setLastInputForMhl(Constants.INVALID_PORT_ID);
1975     }
1976
1977     @ServiceThreadOnly
1978     void setLastInputForMhl(int portId) {
1979         assertRunOnServiceThread();
1980         mLastInputMhl = portId;
1981     }
1982
1983     @ServiceThreadOnly
1984     int getLastInputForMhl() {
1985         assertRunOnServiceThread();
1986         return mLastInputMhl;
1987     }
1988
1989     /**
1990      * Performs input change, routing control for MHL device.
1991      *
1992      * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
1993      * @param contentOn {@code true} if RAP data is content on; otherwise false
1994      */
1995     @ServiceThreadOnly
1996     void changeInputForMhl(int portId, boolean contentOn) {
1997         assertRunOnServiceThread();
1998         final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
1999         tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2000             @Override
2001             public void onComplete(int result) throws RemoteException {
2002                 // Keep the last input to switch back later when RAP[ContentOff] is received.
2003                 // This effectively sets the port to invalid one if the switching is for
2004                 // RAP[ContentOff].
2005                 setLastInputForMhl(lastInput);
2006             }
2007         });
2008
2009         // MHL device is always directly connected to the port. Update the active port ID to avoid
2010         // unnecessary post-routing control task.
2011         tv().setActivePortId(portId);
2012
2013         // The port is either the MHL-enabled port where the mobile device is connected, or
2014         // the last port to go back to when turnoff command is received. Note that the last port
2015         // may not be the MHL-enabled one. In this case the device info to be passed to
2016         // input change listener should be the one describing the corresponding HDMI port.
2017         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
2018         HdmiDeviceInfo info = (device != null) ? device.getInfo() : mPortDeviceMap.get(portId);
2019         invokeInputChangeListener(info);
2020     }
2021
2022    void setMhlInputChangeEnabled(boolean enabled) {
2023        mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2024
2025         synchronized (mLock) {
2026             mMhlInputChangeEnabled = enabled;
2027         }
2028     }
2029
2030     boolean isMhlInputChangeEnabled() {
2031         synchronized (mLock) {
2032             return mMhlInputChangeEnabled;
2033         }
2034     }
2035
2036     @ServiceThreadOnly
2037     void displayOsd(int messageId) {
2038         assertRunOnServiceThread();
2039         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2040         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2041         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2042                 HdmiControlService.PERMISSION);
2043     }
2044
2045     @ServiceThreadOnly
2046     void displayOsd(int messageId, int extra) {
2047         assertRunOnServiceThread();
2048         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2049         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2050         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRAM_PARAM1, extra);
2051         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2052                 HdmiControlService.PERMISSION);
2053     }
2054 }