OSDN Git Service

am 6ed3fadb: am 75f2f993: am 925abb6d: (-s ours) am 891f38d1: Merge "Build layoutlib...
[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         }
307
308         mMhlController = HdmiMhlControllerStub.create(this);
309         if (!mMhlController.isReady()) {
310             Slog.i(TAG, "Device does not support MHL-control.");
311         }
312         mMhlDevices = Collections.emptyList();
313
314         initPortInfo();
315         mMessageValidator = new HdmiCecMessageValidator(this);
316         publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
317
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);
325         }
326     }
327
328     /**
329      * Called when the initialization of local devices is complete.
330      */
331     private void onInitializeCecComplete() {
332         if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
333             mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
334         }
335         mWakeUpMessageReceived = false;
336
337         if (isTvDevice()) {
338             mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
339             registerContentObserver();
340         }
341     }
342
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
351         };
352         for (String s : settings) {
353             resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
354                     UserHandle.USER_ALL);
355         }
356     }
357
358     private class SettingsObserver extends ContentObserver {
359         public SettingsObserver(Handler handler) {
360             super(handler);
361         }
362
363         // onChange is set up to run in service thread.
364         @Override
365         public void onChange(boolean selfChange, Uri uri) {
366             String option = uri.getLastPathSegment();
367             boolean enabled = readBooleanSetting(option, true);
368             switch (option) {
369                 case Global.HDMI_CONTROL_ENABLED:
370                     setControlEnabled(enabled);
371                     break;
372                 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
373                     tv().setAutoWakeup(enabled);
374                     setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
375                     break;
376                 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
377                     tv().setAutoDeviceOff(enabled);
378                     // No need to propagate to HAL.
379                     break;
380                 case Global.MHL_INPUT_SWITCHING_ENABLED:
381                     setMhlInputChangeEnabled(enabled);
382                     break;
383                 case Global.MHL_POWER_CHARGE_ENABLED:
384                     mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
385                     break;
386             }
387         }
388     }
389
390     private static int toInt(boolean enabled) {
391         return enabled ? ENABLED : DISABLED;
392     }
393
394     boolean readBooleanSetting(String key, boolean defVal) {
395         ContentResolver cr = getContext().getContentResolver();
396         return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
397     }
398
399     void writeBooleanSetting(String key, boolean value) {
400         ContentResolver cr = getContext().getContentResolver();
401         Global.putInt(cr, key, toInt(value));
402     }
403
404     private void unregisterSettingsObserver() {
405         getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
406     }
407
408     private void initializeCec(int initiatedBy) {
409         mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
410         initializeLocalDevices(initiatedBy);
411     }
412
413     @ServiceThreadOnly
414     private void initializeLocalDevices(final int initiatedBy) {
415         assertRunOnServiceThread();
416         // A container for [Device type, Local device info].
417         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
418         clearLocalDevices();
419         for (int type : mLocalDevices) {
420             final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
421             localDevice.init();
422             localDevices.add(localDevice);
423         }
424         allocateLogicalAddress(localDevices, initiatedBy);
425     }
426
427     @ServiceThreadOnly
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() {
437                 @Override
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 + "]");
441                     } else {
442                         // Set POWER_STATUS_ON to all local devices because they share lifetime
443                         // with system.
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);
450                     }
451
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();
458                         }
459                         notifyAddressAllocated(allocatedDevices, initiatedBy);
460                     }
461                 }
462             });
463         }
464     }
465
466     @ServiceThreadOnly
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);
472         }
473     }
474
475     // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
476     // keep them in one place.
477     @ServiceThreadOnly
478     private void initPortInfo() {
479         assertRunOnServiceThread();
480         HdmiPortInfo[] cecPortInfo = null;
481
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();
486         }
487         if (cecPortInfo == null) {
488             return;
489         }
490
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()));
498         }
499         mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
500         mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
501         mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
502
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());
508             }
509         }
510
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));
515             return;
516         }
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()));
522             } else {
523                 result.add(info);
524             }
525         }
526         mPortInfo = Collections.unmodifiableList(result);
527     }
528
529     List<HdmiPortInfo> getPortInfo() {
530         return mPortInfo;
531     }
532
533     /**
534      * Returns HDMI port information for the given port id.
535      *
536      * @param portId HDMI port id
537      * @return {@link HdmiPortInfo} for the given port
538      */
539     HdmiPortInfo getPortInfo(int portId) {
540         return mPortInfoMap.get(portId, null);
541     }
542
543     /**
544      * Returns the routing path (physical address) of the HDMI port for the given
545      * port id.
546      */
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;
552         }
553         return portInfo.getAddress();
554     }
555
556     /**
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.
561      */
562     int pathToPortId(int path) {
563         int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
564         return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
565     }
566
567     boolean isValidPortId(int portId) {
568         return getPortInfo(portId) != null;
569     }
570
571     /**
572      * Returns {@link Looper} for IO operation.
573      *
574      * <p>Declared as package-private.
575      */
576     Looper getIoLooper() {
577         return mIoThread.getLooper();
578     }
579
580     /**
581      * Returns {@link Looper} of main thread. Use this {@link Looper} instance
582      * for tasks that are running on main service thread.
583      *
584      * <p>Declared as package-private.
585      */
586     Looper getServiceLooper() {
587         return mHandler.getLooper();
588     }
589
590     /**
591      * Returns physical address of the device.
592      */
593     int getPhysicalAddress() {
594         return mCecController.getPhysicalAddress();
595     }
596
597     /**
598      * Returns vendor id of CEC service.
599      */
600     int getVendorId() {
601         return mCecController.getVendorId();
602     }
603
604     @ServiceThreadOnly
605     HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
606         assertRunOnServiceThread();
607         HdmiCecLocalDeviceTv tv = tv();
608         if (tv == null) {
609             return null;
610         }
611         return tv.getCecDeviceInfo(logicalAddress);
612     }
613
614     /**
615      * Returns version of CEC.
616      */
617     int getCecVersion() {
618         return mCecController.getVersion();
619     }
620
621     /**
622      * Whether a device of the specified physical address is connected to ARC enabled port.
623      */
624     boolean isConnectedToArcPort(int physicalAddress) {
625         int portId = pathToPortId(physicalAddress);
626         if (portId != Constants.INVALID_PORT_ID) {
627             return mPortInfoMap.get(portId).isArcSupported();
628         }
629         return false;
630     }
631
632     void runOnServiceThread(Runnable runnable) {
633         mHandler.post(runnable);
634     }
635
636     void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
637         mHandler.postAtFrontOfQueue(runnable);
638     }
639
640     private void assertRunOnServiceThread() {
641         if (Looper.myLooper() != mHandler.getLooper()) {
642             throw new IllegalStateException("Should run on service thread.");
643         }
644     }
645
646     /**
647      * Transmit a CEC command to CEC bus.
648      *
649      * @param command CEC command to send out
650      * @param callback interface used to the result of send command
651      */
652     @ServiceThreadOnly
653     void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
654         assertRunOnServiceThread();
655         if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
656             mCecController.sendCommand(command, callback);
657         } else {
658             HdmiLogger.error("Invalid message type:" + command);
659             if (callback != null) {
660                 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
661             }
662         }
663     }
664
665     @ServiceThreadOnly
666     void sendCecCommand(HdmiCecMessage command) {
667         assertRunOnServiceThread();
668         sendCecCommand(command, null);
669     }
670
671     /**
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
676      */
677     @ServiceThreadOnly
678     void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
679         assertRunOnServiceThread();
680         mCecController.maySendFeatureAbortCommand(command, reason);
681     }
682
683     @ServiceThreadOnly
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);
691             }
692             return true;
693         }
694         return dispatchMessageToLocalDevice(message);
695     }
696
697     void setAudioReturnChannel(boolean enabled) {
698         mCecController.setAudioReturnChannel(enabled);
699     }
700
701     @ServiceThreadOnly
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) {
707                 return true;
708             }
709         }
710
711         if (message.getDestination() != Constants.ADDR_BROADCAST) {
712             HdmiLogger.warning("Unhandled cec command:" + message);
713         }
714         return false;
715     }
716
717     /**
718      * Called when a new hotplug event is issued.
719      *
720      * @param portId hdmi port number where hot plug event issued.
721      * @param connected whether to be plugged in or not
722      */
723     @ServiceThreadOnly
724     void onHotplug(int portId, boolean connected) {
725         assertRunOnServiceThread();
726
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);
733                     localDevice.init();
734                 }
735                 localDevices.add(localDevice);
736             }
737             allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
738         }
739
740         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
741             device.onHotplug(portId, connected);
742         }
743         announceHotplugEvent(portId, connected);
744     }
745
746     /**
747      * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
748      * devices.
749      *
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
755      */
756     @ServiceThreadOnly
757     void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
758             int retryCount) {
759         assertRunOnServiceThread();
760         mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
761                 retryCount);
762     }
763
764     private int checkPollStrategy(int pickStrategy) {
765         int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
766         if (strategy == 0) {
767             throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
768         }
769         int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
770         if (iterationStrategy == 0) {
771             throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
772         }
773         return strategy | iterationStrategy;
774     }
775
776     List<HdmiCecLocalDevice> getAllLocalDevices() {
777         assertRunOnServiceThread();
778         return mCecController.getLocalDeviceList();
779     }
780
781     Object getServiceLock() {
782         return mLock;
783     }
784
785     void setAudioStatus(boolean mute, int volume) {
786         AudioManager audioManager = getAudioManager();
787         boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
788         if (mute) {
789             if (!muted) {
790                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
791             }
792         } else {
793             if (muted) {
794                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
795             }
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);
800         }
801     }
802
803     void announceSystemAudioModeChange(boolean enabled) {
804         synchronized (mLock) {
805             for (SystemAudioModeChangeListenerRecord record :
806                     mSystemAudioModeChangeListenerRecords) {
807                 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
808             }
809         }
810     }
811
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);
818     }
819
820     @ServiceThreadOnly
821     void handleMhlHotplugEvent(int portId, boolean connected) {
822         assertRunOnServiceThread();
823         if (connected) {
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");
829             }
830         } else {
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();
840                 }
841             } else {
842                 Slog.w(TAG, "No device to remove:[portId=" + portId);
843             }
844         }
845         announceHotplugEvent(portId, connected);
846     }
847
848     @ServiceThreadOnly
849     void handleMhlBusModeChanged(int portId, int busmode) {
850         assertRunOnServiceThread();
851         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
852         if (device != null) {
853             device.setBusMode(busmode);
854         } else {
855             Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
856                     ", busmode:" + busmode + "]");
857         }
858     }
859
860     @ServiceThreadOnly
861     void handleMhlBusOvercurrent(int portId, boolean on) {
862         assertRunOnServiceThread();
863         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
864         if (device != null) {
865             device.onBusOvercurrentDetected(on);
866         } else {
867             Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
868         }
869     }
870
871     @ServiceThreadOnly
872     void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
873         assertRunOnServiceThread();
874         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
875
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();
881         } else {
882             Slog.w(TAG, "No mhl device exists for device status event[portId:"
883                     + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
884         }
885     }
886
887     @ServiceThreadOnly
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();
895             if (info != null) {
896                 if (inputs.isEmpty()) {
897                     inputs = new ArrayList<>();
898                 }
899                 inputs.add(device.getInfo());
900             }
901         }
902         synchronized (mLock) {
903             mMhlDevices = inputs;
904         }
905     }
906
907     private List<HdmiDeviceInfo> getMhlDevicesLocked() {
908         return mMhlDevices;
909     }
910
911     private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
912         private final IHdmiMhlVendorCommandListener mListener;
913
914         public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
915             mListener = listener;
916         }
917
918         @Override
919         public void binderDied() {
920             mMhlVendorCommandListenerRecords.remove(this);
921         }
922     }
923
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;
928
929         public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
930             mListener = listener;
931         }
932
933         @Override
934         public void binderDied() {
935             synchronized (mLock) {
936                 mHotplugEventListenerRecords.remove(this);
937             }
938         }
939     }
940
941     private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
942         private final IHdmiDeviceEventListener mListener;
943
944         public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
945             mListener = listener;
946         }
947
948         @Override
949         public void binderDied() {
950             synchronized (mLock) {
951                 mDeviceEventListenerRecords.remove(this);
952             }
953         }
954     }
955
956     private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
957         private final IHdmiSystemAudioModeChangeListener mListener;
958
959         public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
960             mListener = listener;
961         }
962
963         @Override
964         public void binderDied() {
965             synchronized (mLock) {
966                 mSystemAudioModeChangeListenerRecords.remove(this);
967             }
968         }
969     }
970
971     class VendorCommandListenerRecord implements IBinder.DeathRecipient {
972         private final IHdmiVendorCommandListener mListener;
973         private final int mDeviceType;
974
975         public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
976             mListener = listener;
977             mDeviceType = deviceType;
978         }
979
980         @Override
981         public void binderDied() {
982             synchronized (mLock) {
983                 mVendorCommandListenerRecords.remove(this);
984             }
985         }
986     }
987
988     private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
989         private final IHdmiRecordListener mListener;
990
991         public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
992             mListener = listener;
993         }
994
995         @Override
996         public void binderDied() {
997             synchronized (mLock) {
998                 mRecordListenerRecord = null;
999             }
1000         }
1001     }
1002
1003     private void enforceAccessPermission() {
1004         getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1005     }
1006
1007     private final class BinderService extends IHdmiControlService.Stub {
1008         @Override
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);
1015             }
1016             return localDevices;
1017         }
1018
1019         @Override
1020         public HdmiDeviceInfo getActiveSource() {
1021             HdmiCecLocalDeviceTv tv = tv();
1022             if (tv == null) {
1023                 Slog.w(TAG, "Local tv device not available");
1024                 return null;
1025             }
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, "");
1031             }
1032             int activePath = tv.getActivePath();
1033             if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1034                 return new HdmiDeviceInfo(activePath, tv.getActivePortId());
1035             }
1036             return null;
1037         }
1038
1039         @Override
1040         public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1041             enforceAccessPermission();
1042             runOnServiceThread(new Runnable() {
1043                 @Override
1044                 public void run() {
1045                     if (callback == null) {
1046                         Slog.e(TAG, "Callback cannot be null");
1047                         return;
1048                     }
1049                     HdmiCecLocalDeviceTv tv = tv();
1050                     if (tv == null) {
1051                         Slog.w(TAG, "Local tv device not available");
1052                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1053                         return;
1054                     }
1055                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1056                     if (device != null) {
1057                         if (device.getPortId() == tv.getActivePortId()) {
1058                             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1059                             return;
1060                         }
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);
1066                         return;
1067                     }
1068                     tv.deviceSelect(deviceId, callback);
1069                 }
1070             });
1071         }
1072
1073         @Override
1074         public void portSelect(final int portId, final IHdmiControlCallback callback) {
1075             enforceAccessPermission();
1076             runOnServiceThread(new Runnable() {
1077                 @Override
1078                 public void run() {
1079                     if (callback == null) {
1080                         Slog.e(TAG, "Callback cannot be null");
1081                         return;
1082                     }
1083                     HdmiCecLocalDeviceTv tv = tv();
1084                     if (tv == null) {
1085                         Slog.w(TAG, "Local tv device not available");
1086                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1087                         return;
1088                     }
1089                     tv.doManualPortSwitching(portId, callback);
1090                 }
1091             });
1092         }
1093
1094         @Override
1095         public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1096             enforceAccessPermission();
1097             runOnServiceThread(new Runnable() {
1098                 @Override
1099                 public void run() {
1100                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1101                     if (device != null) {
1102                         device.sendKeyEvent(keyCode, isPressed);
1103                         return;
1104                     }
1105                     if (mCecController != null) {
1106                         HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1107                         if (localDevice == null) {
1108                             Slog.w(TAG, "Local device not available");
1109                             return;
1110                         }
1111                         localDevice.sendKeyEvent(keyCode, isPressed);
1112                     }
1113                 }
1114             });
1115         }
1116
1117         @Override
1118         public void oneTouchPlay(final IHdmiControlCallback callback) {
1119             enforceAccessPermission();
1120             runOnServiceThread(new Runnable() {
1121                 @Override
1122                 public void run() {
1123                     HdmiControlService.this.oneTouchPlay(callback);
1124                 }
1125             });
1126         }
1127
1128         @Override
1129         public void queryDisplayStatus(final IHdmiControlCallback callback) {
1130             enforceAccessPermission();
1131             runOnServiceThread(new Runnable() {
1132                 @Override
1133                 public void run() {
1134                     HdmiControlService.this.queryDisplayStatus(callback);
1135                 }
1136             });
1137         }
1138
1139         @Override
1140         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1141             enforceAccessPermission();
1142             HdmiControlService.this.addHotplugEventListener(listener);
1143         }
1144
1145         @Override
1146         public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1147             enforceAccessPermission();
1148             HdmiControlService.this.removeHotplugEventListener(listener);
1149         }
1150
1151         @Override
1152         public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1153             enforceAccessPermission();
1154             HdmiControlService.this.addDeviceEventListener(listener);
1155         }
1156
1157         @Override
1158         public List<HdmiPortInfo> getPortInfo() {
1159             enforceAccessPermission();
1160             return HdmiControlService.this.getPortInfo();
1161         }
1162
1163         @Override
1164         public boolean canChangeSystemAudioMode() {
1165             enforceAccessPermission();
1166             HdmiCecLocalDeviceTv tv = tv();
1167             if (tv == null) {
1168                 return false;
1169             }
1170             return tv.hasSystemAudioDevice();
1171         }
1172
1173         @Override
1174         public boolean getSystemAudioMode() {
1175             enforceAccessPermission();
1176             HdmiCecLocalDeviceTv tv = tv();
1177             if (tv == null) {
1178                 return false;
1179             }
1180             return tv.isSystemAudioActivated();
1181         }
1182
1183         @Override
1184         public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1185             enforceAccessPermission();
1186             runOnServiceThread(new Runnable() {
1187                 @Override
1188                 public void run() {
1189                     HdmiCecLocalDeviceTv tv = tv();
1190                     if (tv == null) {
1191                         Slog.w(TAG, "Local tv device not available");
1192                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1193                         return;
1194                     }
1195                     tv.changeSystemAudioMode(enabled, callback);
1196                 }
1197             });
1198         }
1199
1200         @Override
1201         public void addSystemAudioModeChangeListener(
1202                 final IHdmiSystemAudioModeChangeListener listener) {
1203             enforceAccessPermission();
1204             HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1205         }
1206
1207         @Override
1208         public void removeSystemAudioModeChangeListener(
1209                 final IHdmiSystemAudioModeChangeListener listener) {
1210             enforceAccessPermission();
1211             HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1212         }
1213
1214         @Override
1215         public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1216             enforceAccessPermission();
1217             HdmiControlService.this.setInputChangeListener(listener);
1218         }
1219
1220         @Override
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());
1231             }
1232         }
1233
1234         @Override
1235         public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1236                 final int maxIndex) {
1237             enforceAccessPermission();
1238             runOnServiceThread(new Runnable() {
1239                 @Override
1240                 public void run() {
1241                     HdmiCecLocalDeviceTv tv = tv();
1242                     if (tv == null) {
1243                         Slog.w(TAG, "Local tv device not available");
1244                         return;
1245                     }
1246                     tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1247                 }
1248             });
1249         }
1250
1251         @Override
1252         public void setSystemAudioMute(final boolean mute) {
1253             enforceAccessPermission();
1254             runOnServiceThread(new Runnable() {
1255                 @Override
1256                 public void run() {
1257                     HdmiCecLocalDeviceTv tv = tv();
1258                     if (tv == null) {
1259                         Slog.w(TAG, "Local tv device not available");
1260                         return;
1261                     }
1262                     tv.changeMute(mute);
1263                 }
1264             });
1265         }
1266
1267         @Override
1268         public void setArcMode(final boolean enabled) {
1269             enforceAccessPermission();
1270             runOnServiceThread(new Runnable() {
1271                 @Override
1272                 public void run() {
1273                     HdmiCecLocalDeviceTv tv = tv();
1274                     if (tv == null) {
1275                         Slog.w(TAG, "Local tv device not available to change arc mode.");
1276                         return;
1277                     }
1278                 }
1279             });
1280         }
1281
1282         @Override
1283         public void setProhibitMode(final boolean enabled) {
1284             enforceAccessPermission();
1285             if (!isTvDevice()) {
1286                 return;
1287             }
1288             HdmiControlService.this.setProhibitMode(enabled);
1289         }
1290
1291         @Override
1292         public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1293                 final int deviceType) {
1294             enforceAccessPermission();
1295             HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1296         }
1297
1298         @Override
1299         public void sendVendorCommand(final int deviceType, final int targetAddress,
1300                 final byte[] params, final boolean hasVendorId) {
1301             enforceAccessPermission();
1302             runOnServiceThread(new Runnable() {
1303                 @Override
1304                 public void run() {
1305                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1306                     if (device == null) {
1307                         Slog.w(TAG, "Local device not available");
1308                         return;
1309                     }
1310                     if (hasVendorId) {
1311                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1312                                 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1313                                 getVendorId(), params));
1314                     } else {
1315                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1316                                 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1317                     }
1318                 }
1319             });
1320         }
1321
1322         @Override
1323         public void sendStandby(final int deviceType, final int deviceId) {
1324             enforceAccessPermission();
1325             runOnServiceThread(new Runnable() {
1326                 @Override
1327                 public void run() {
1328                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1329                     if (device == null) {
1330                         Slog.w(TAG, "Local device not available");
1331                         return;
1332                     }
1333                     device.sendStandby(deviceId);
1334                 }
1335             });
1336         }
1337
1338         @Override
1339         public void setHdmiRecordListener(IHdmiRecordListener listener) {
1340             HdmiControlService.this.setHdmiRecordListener(listener);
1341         }
1342
1343         @Override
1344         public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1345             runOnServiceThread(new Runnable() {
1346                 @Override
1347                 public void run() {
1348                     if (!isTvDevice()) {
1349                         Slog.w(TAG, "No TV is available.");
1350                         return;
1351                     }
1352                     tv().startOneTouchRecord(recorderAddress, recordSource);
1353                 }
1354             });
1355         }
1356
1357         @Override
1358         public void stopOneTouchRecord(final int recorderAddress) {
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().stopOneTouchRecord(recorderAddress);
1367                 }
1368             });
1369         }
1370
1371         @Override
1372         public void startTimerRecording(final int recorderAddress, final int sourceType,
1373                 final byte[] recordSource) {
1374             runOnServiceThread(new Runnable() {
1375                 @Override
1376                 public void run() {
1377                     if (!isTvDevice()) {
1378                         Slog.w(TAG, "No TV is available.");
1379                         return;
1380                     }
1381                     tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1382                 }
1383             });
1384         }
1385
1386         @Override
1387         public void clearTimerRecording(final int recorderAddress, final int sourceType,
1388                 final byte[] recordSource) {
1389             runOnServiceThread(new Runnable() {
1390                 @Override
1391                 public void run() {
1392                     if (!isTvDevice()) {
1393                         Slog.w(TAG, "No TV is available.");
1394                         return;
1395                     }
1396                     tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1397                 }
1398             });
1399         }
1400
1401         @Override
1402         public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1403                 final byte[] data) {
1404             enforceAccessPermission();
1405             runOnServiceThread(new Runnable() {
1406                 @Override
1407                 public void run() {
1408                     if (!isControlEnabled()) {
1409                         Slog.w(TAG, "Hdmi control is disabled.");
1410                         return ;
1411                     }
1412                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1413                     if (device == null) {
1414                         Slog.w(TAG, "Invalid port id:" + portId);
1415                         return;
1416                     }
1417                     mMhlController.sendVendorCommand(portId, offset, length, data);
1418                 }
1419             });
1420         }
1421
1422         @Override
1423         public void addHdmiMhlVendorCommandListener(
1424                 IHdmiMhlVendorCommandListener listener) {
1425             enforceAccessPermission();
1426             HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1427         }
1428
1429         @Override
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, "  ");
1433
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();
1441             }
1442             pw.println("mPortInfo: ");
1443             pw.increaseIndent();
1444             for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1445                 pw.println("- " + hdmiPortInfo);
1446             }
1447             pw.decreaseIndent();
1448             pw.println("mPowerStatus: " + mPowerStatus);
1449         }
1450     }
1451
1452     @ServiceThreadOnly
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);
1459             return;
1460         }
1461         source.oneTouchPlay(callback);
1462     }
1463
1464     @ServiceThreadOnly
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);
1471             return;
1472         }
1473         source.queryDisplayStatus(callback);
1474     }
1475
1476     private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1477         HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1478         try {
1479             listener.asBinder().linkToDeath(record, 0);
1480         } catch (RemoteException e) {
1481             Slog.w(TAG, "Listener already died");
1482             return;
1483         }
1484         synchronized (mLock) {
1485             mHotplugEventListenerRecords.add(record);
1486         }
1487     }
1488
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);
1495                     break;
1496                 }
1497             }
1498         }
1499     }
1500
1501     private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1502         DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1503         try {
1504             listener.asBinder().linkToDeath(record, 0);
1505         } catch (RemoteException e) {
1506             Slog.w(TAG, "Listener already died");
1507             return;
1508         }
1509         synchronized (mLock) {
1510             mDeviceEventListenerRecords.add(record);
1511         }
1512     }
1513
1514     void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1515         synchronized (mLock) {
1516             for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1517                 try {
1518                     record.mListener.onStatusChanged(device, status);
1519                 } catch (RemoteException e) {
1520                     Slog.e(TAG, "Failed to report device event:" + e);
1521                 }
1522             }
1523         }
1524     }
1525
1526     private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1527         SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1528                 listener);
1529         try {
1530             listener.asBinder().linkToDeath(record, 0);
1531         } catch (RemoteException e) {
1532             Slog.w(TAG, "Listener already died");
1533             return;
1534         }
1535         synchronized (mLock) {
1536             mSystemAudioModeChangeListenerRecords.add(record);
1537         }
1538     }
1539
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);
1547                     break;
1548                 }
1549             }
1550         }
1551     }
1552
1553     private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1554         private final IHdmiInputChangeListener mListener;
1555
1556         public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1557             mListener = listener;
1558         }
1559
1560         @Override
1561         public void binderDied() {
1562             synchronized (mLock) {
1563                 mInputChangeListenerRecord = null;
1564             }
1565         }
1566     }
1567
1568     private void setInputChangeListener(IHdmiInputChangeListener listener) {
1569         synchronized (mLock) {
1570             mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1571             try {
1572                 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1573             } catch (RemoteException e) {
1574                 Slog.w(TAG, "Listener already died");
1575                 return;
1576             }
1577         }
1578     }
1579
1580     void invokeInputChangeListener(HdmiDeviceInfo info) {
1581         synchronized (mLock) {
1582             if (mInputChangeListenerRecord != null) {
1583                 try {
1584                     mInputChangeListenerRecord.mListener.onChanged(info);
1585                 } catch (RemoteException e) {
1586                     Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1587                 }
1588             }
1589         }
1590     }
1591
1592     private void setHdmiRecordListener(IHdmiRecordListener listener) {
1593         synchronized (mLock) {
1594             mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1595             try {
1596                 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1597             } catch (RemoteException e) {
1598                 Slog.w(TAG, "Listener already died.", e);
1599             }
1600         }
1601     }
1602
1603     byte[] invokeRecordRequestListener(int recorderAddress) {
1604         synchronized (mLock) {
1605             if (mRecordListenerRecord != null) {
1606                 try {
1607                     return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1608                 } catch (RemoteException e) {
1609                     Slog.w(TAG, "Failed to start record.", e);
1610                 }
1611             }
1612             return EmptyArray.BYTE;
1613         }
1614     }
1615
1616     void invokeOneTouchRecordResult(int result) {
1617         synchronized (mLock) {
1618             if (mRecordListenerRecord != null) {
1619                 try {
1620                     mRecordListenerRecord.mListener.onOneTouchRecordResult(result);
1621                 } catch (RemoteException e) {
1622                     Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1623                 }
1624             }
1625         }
1626     }
1627
1628     void invokeTimerRecordingResult(int result) {
1629         synchronized (mLock) {
1630             if (mRecordListenerRecord != null) {
1631                 try {
1632                     mRecordListenerRecord.mListener.onTimerRecordingResult(result);
1633                 } catch (RemoteException e) {
1634                     Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1635                 }
1636             }
1637         }
1638     }
1639
1640     void invokeClearTimerRecordingResult(int result) {
1641         synchronized (mLock) {
1642             if (mRecordListenerRecord != null) {
1643                 try {
1644                     mRecordListenerRecord.mListener.onClearTimerRecordingResult(result);
1645                 } catch (RemoteException e) {
1646                     Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1647                 }
1648             }
1649         }
1650     }
1651
1652     private void invokeCallback(IHdmiControlCallback callback, int result) {
1653         try {
1654             callback.onComplete(result);
1655         } catch (RemoteException e) {
1656             Slog.e(TAG, "Invoking callback failed:" + e);
1657         }
1658     }
1659
1660     private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1661             boolean enabled) {
1662         try {
1663             listener.onStatusChanged(enabled);
1664         } catch (RemoteException e) {
1665             Slog.e(TAG, "Invoking callback failed:" + e);
1666         }
1667     }
1668
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);
1674             }
1675         }
1676     }
1677
1678     private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1679             HdmiHotplugEvent event) {
1680         try {
1681             listener.onReceived(event);
1682         } catch (RemoteException e) {
1683             Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1684         }
1685     }
1686
1687     private HdmiCecLocalDeviceTv tv() {
1688         return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1689     }
1690
1691     boolean isTvDevice() {
1692         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
1693     }
1694
1695     private HdmiCecLocalDevicePlayback playback() {
1696         return (HdmiCecLocalDevicePlayback)
1697                 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1698     }
1699
1700     AudioManager getAudioManager() {
1701         return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1702     }
1703
1704     boolean isControlEnabled() {
1705         synchronized (mLock) {
1706             return mHdmiControlEnabled;
1707         }
1708     }
1709
1710     @ServiceThreadOnly
1711     int getPowerStatus() {
1712         assertRunOnServiceThread();
1713         return mPowerStatus;
1714     }
1715
1716     @ServiceThreadOnly
1717     boolean isPowerOnOrTransient() {
1718         assertRunOnServiceThread();
1719         return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1720                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1721     }
1722
1723     @ServiceThreadOnly
1724     boolean isPowerStandbyOrTransient() {
1725         assertRunOnServiceThread();
1726         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1727                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1728     }
1729
1730     @ServiceThreadOnly
1731     boolean isPowerStandby() {
1732         assertRunOnServiceThread();
1733         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1734     }
1735
1736     @ServiceThreadOnly
1737     void wakeUp() {
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().
1744     }
1745
1746     @ServiceThreadOnly
1747     void standby() {
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().
1754     }
1755
1756     @ServiceThreadOnly
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;
1765                 }
1766                 initializeCec(startReason);
1767             }
1768         } else {
1769             Slog.i(TAG, "Device does not support HDMI-CEC.");
1770         }
1771         // TODO: Initialize MHL local devices.
1772     }
1773
1774     @ServiceThreadOnly
1775     private void onStandby() {
1776         assertRunOnServiceThread();
1777         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1778
1779         final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1780         disableDevices(new PendingActionClearedCallback() {
1781             @Override
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
1789                     // actually.
1790                 }
1791             }
1792         });
1793     }
1794
1795     @ServiceThreadOnly
1796     private void onLanguageChanged(String language) {
1797         assertRunOnServiceThread();
1798         mLanguage = language;
1799
1800         if (isTvDevice()) {
1801             tv().broadcastMenuLanguage(language);
1802         }
1803     }
1804
1805     @ServiceThreadOnly
1806     String getLanguage() {
1807         assertRunOnServiceThread();
1808         return mLanguage;
1809     }
1810
1811     private void disableDevices(PendingActionClearedCallback callback) {
1812         if (mCecController != null) {
1813             for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1814                 device.disableDevice(mStandbyMessageReceived, callback);
1815             }
1816             if (isTvDevice()) {
1817                 unregisterSettingsObserver();
1818             }
1819         }
1820
1821         mMhlController.clearAllLocalDevices();
1822     }
1823
1824     @ServiceThreadOnly
1825     private void clearLocalDevices() {
1826         assertRunOnServiceThread();
1827         if (mCecController == null) {
1828             return;
1829         }
1830         mCecController.clearLogicalAddress();
1831         mCecController.clearLocalDevices();
1832     }
1833
1834     @ServiceThreadOnly
1835     private void onStandbyCompleted() {
1836         assertRunOnServiceThread();
1837         Slog.v(TAG, "onStandbyCompleted");
1838
1839         if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1840             return;
1841         }
1842         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1843         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1844             device.onStandby(mStandbyMessageReceived);
1845         }
1846         mStandbyMessageReceived = false;
1847         mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
1848     }
1849
1850     private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1851         VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1852         try {
1853             listener.asBinder().linkToDeath(record, 0);
1854         } catch (RemoteException e) {
1855             Slog.w(TAG, "Listener already died");
1856             return;
1857         }
1858         synchronized (mLock) {
1859             mVendorCommandListenerRecords.add(record);
1860         }
1861     }
1862
1863     boolean invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1864             boolean hasVendorId) {
1865         synchronized (mLock) {
1866             if (mVendorCommandListenerRecords.isEmpty()) {
1867                 return false;
1868             }
1869             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1870                 if (record.mDeviceType != deviceType) {
1871                     continue;
1872                 }
1873                 try {
1874                     record.mListener.onReceived(srcAddress, params, hasVendorId);
1875                 } catch (RemoteException e) {
1876                     Slog.e(TAG, "Failed to notify vendor command reception", e);
1877                 }
1878             }
1879             return true;
1880         }
1881     }
1882
1883     private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
1884         HdmiMhlVendorCommandListenerRecord record =
1885                 new HdmiMhlVendorCommandListenerRecord(listener);
1886         try {
1887             listener.asBinder().linkToDeath(record, 0);
1888         } catch (RemoteException e) {
1889             Slog.w(TAG, "Listener already died.");
1890             return;
1891         }
1892
1893         synchronized (mLock) {
1894             mMhlVendorCommandListenerRecords.add(record);
1895         }
1896     }
1897
1898     void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
1899         synchronized (mLock) {
1900             for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
1901                 try {
1902                     record.mListener.onReceived(portId, offest, length, data);
1903                 } catch (RemoteException e) {
1904                     Slog.e(TAG, "Failed to notify MHL vendor command", e);
1905                 }
1906             }
1907         }
1908     }
1909
1910     boolean isProhibitMode() {
1911         synchronized (mLock) {
1912             return mProhibitMode;
1913         }
1914     }
1915
1916     void setProhibitMode(boolean enabled) {
1917         synchronized (mLock) {
1918             mProhibitMode = enabled;
1919         }
1920     }
1921
1922     @ServiceThreadOnly
1923     void setCecOption(int key, int value) {
1924         assertRunOnServiceThread();
1925         mCecController.setOption(key, value);
1926     }
1927
1928     @ServiceThreadOnly
1929     void setControlEnabled(boolean enabled) {
1930         assertRunOnServiceThread();
1931
1932         int value = toInt(enabled);
1933         mCecController.setOption(OPTION_CEC_ENABLE, value);
1934         mMhlController.setOption(OPTION_MHL_ENABLE, value);
1935
1936         synchronized (mLock) {
1937             mHdmiControlEnabled = enabled;
1938         }
1939
1940         if (enabled) {
1941             initializeCec(INITIATED_BY_ENABLE_CEC);
1942         } else {
1943             disableDevices(new PendingActionClearedCallback() {
1944                 @Override
1945                 public void onCleared(HdmiCecLocalDevice device) {
1946                     assertRunOnServiceThread();
1947                     clearLocalDevices();
1948                 }
1949             });
1950         }
1951     }
1952
1953     @ServiceThreadOnly
1954     void setActivePortId(int portId) {
1955         assertRunOnServiceThread();
1956         mActivePortId = portId;
1957
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);
1961     }
1962
1963     @ServiceThreadOnly
1964     void setLastInputForMhl(int portId) {
1965         assertRunOnServiceThread();
1966         mLastInputMhl = portId;
1967     }
1968
1969     @ServiceThreadOnly
1970     int getLastInputForMhl() {
1971         assertRunOnServiceThread();
1972         return mLastInputMhl;
1973     }
1974
1975     /**
1976      * Performs input change, routing control for MHL device.
1977      *
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
1980      */
1981     @ServiceThreadOnly
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() {
1986             @Override
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
1990                 // RAP[ContentOff].
1991                 setLastInputForMhl(lastInput);
1992             }
1993         });
1994
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);
1998
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)
2005                 ? device.getInfo()
2006                 : mPortDeviceMap.get(portId);
2007         invokeInputChangeListener(info);
2008     }
2009
2010    void setMhlInputChangeEnabled(boolean enabled) {
2011        mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2012
2013         synchronized (mLock) {
2014             mMhlInputChangeEnabled = enabled;
2015         }
2016     }
2017
2018     boolean isMhlInputChangeEnabled() {
2019         synchronized (mLock) {
2020             return mMhlInputChangeEnabled;
2021         }
2022     }
2023
2024     @ServiceThreadOnly
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);
2031     }
2032
2033     @ServiceThreadOnly
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);
2041     }
2042 }