OSDN Git Service

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