HdmiControlService.java revision bffb0635aaaaf9140d9120e3f3d95a4f7391a0ac
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
17package com.android.server.hdmi;
18
19import android.annotation.Nullable;
20import android.content.BroadcastReceiver;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.hardware.hdmi.HdmiCecDeviceInfo;
26import android.hardware.hdmi.HdmiControlManager;
27import android.hardware.hdmi.HdmiHotplugEvent;
28import android.hardware.hdmi.HdmiPortInfo;
29import android.hardware.hdmi.HdmiTvClient;
30import android.hardware.hdmi.IHdmiControlCallback;
31import android.hardware.hdmi.IHdmiControlService;
32import android.hardware.hdmi.IHdmiDeviceEventListener;
33import android.hardware.hdmi.IHdmiHotplugEventListener;
34import android.hardware.hdmi.IHdmiInputChangeListener;
35import android.hardware.hdmi.IHdmiRecordRequestListener;
36import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
37import android.hardware.hdmi.IHdmiVendorCommandListener;
38import android.media.AudioManager;
39import android.os.Build;
40import android.os.Handler;
41import android.os.HandlerThread;
42import android.os.IBinder;
43import android.os.Looper;
44import android.os.PowerManager;
45import android.os.RemoteException;
46import android.os.SystemClock;
47import android.provider.Settings.Global;
48import android.util.Slog;
49import android.util.SparseArray;
50import android.util.SparseIntArray;
51
52import com.android.internal.annotations.GuardedBy;
53import com.android.server.SystemService;
54import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
55import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
56import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
57
58import java.util.ArrayList;
59import java.util.Collections;
60import java.util.List;
61
62/**
63 * Provides a service for sending and processing HDMI control messages,
64 * HDMI-CEC and MHL control command, and providing the information on both standard.
65 */
66public final class HdmiControlService extends SystemService {
67    private static final String TAG = "HdmiControlService";
68
69    static final String PERMISSION = "android.permission.HDMI_CEC";
70
71    /**
72     * Interface to report send result.
73     */
74    interface SendMessageCallback {
75        /**
76         * Called when {@link HdmiControlService#sendCecCommand} is completed.
77         *
78         * @param error result of send request.
79         * <ul>
80         * <li>{@link Constants#SEND_RESULT_SUCCESS}
81         * <li>{@link Constants#SEND_RESULT_NAK}
82         * <li>{@link Constants#SEND_RESULT_FAILURE}
83         * </ul>
84         */
85        void onSendCompleted(int error);
86    }
87
88    /**
89     * Interface to get a list of available logical devices.
90     */
91    interface DevicePollingCallback {
92        /**
93         * Called when device polling is finished.
94         *
95         * @param ackedAddress a list of logical addresses of available devices
96         */
97        void onPollingFinished(List<Integer> ackedAddress);
98    }
99
100    private class PowerStateReceiver extends BroadcastReceiver {
101        @Override
102        public void onReceive(Context context, Intent intent) {
103            switch (intent.getAction()) {
104                case Intent.ACTION_SCREEN_OFF:
105                    if (isPowerOnOrTransient()) {
106                        onStandby();
107                    }
108                    break;
109                case Intent.ACTION_SCREEN_ON:
110                    if (isPowerStandbyOrTransient()) {
111                        onWakeUp();
112                    }
113                    break;
114            }
115        }
116    }
117
118    // A thread to handle synchronous IO of CEC and MHL control service.
119    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
120    // and sparse call it shares a thread to handle IO operations.
121    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
122
123    // Used to synchronize the access to the service.
124    private final Object mLock = new Object();
125
126    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
127    private final List<Integer> mLocalDevices;
128
129    // List of listeners registered by callers that want to get notified of
130    // hotplug events.
131    @GuardedBy("mLock")
132    private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
133
134    // List of records for hotplug event listener to handle the the caller killed in action.
135    @GuardedBy("mLock")
136    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
137            new ArrayList<>();
138
139    // List of listeners registered by callers that want to get notified of
140    // device status events.
141    @GuardedBy("mLock")
142    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
143
144    // List of records for device event listener to handle the the caller killed in action.
145    @GuardedBy("mLock")
146    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
147            new ArrayList<>();
148
149    // List of records for vendor command listener to handle the the caller killed in action.
150    @GuardedBy("mLock")
151    private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
152            new ArrayList<>();
153
154    @GuardedBy("mLock")
155    private IHdmiInputChangeListener mInputChangeListener;
156
157    @GuardedBy("mLock")
158    private InputChangeListenerRecord mInputChangeListenerRecord;
159
160    // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
161    // handling will be disabled and no request will be handled.
162    @GuardedBy("mLock")
163    private boolean mHdmiControlEnabled;
164
165    // Set to true while the service is in normal mode. While set to false, no input change is
166    // allowed. Used for situations where input change can confuse users such as channel auto-scan,
167    // system upgrade, etc., a.k.a. "prohibit mode".
168    @GuardedBy("mLock")
169    private boolean mProhibitMode;
170
171    // List of listeners registered by callers that want to get notified of
172    // system audio mode changes.
173    private final ArrayList<IHdmiSystemAudioModeChangeListener>
174            mSystemAudioModeChangeListeners = new ArrayList<>();
175    // List of records for system audio mode change to handle the the caller killed in action.
176    private final ArrayList<SystemAudioModeChangeListenerRecord>
177            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
178
179    // Handler used to run a task in service thread.
180    private final Handler mHandler = new Handler();
181
182    @Nullable
183    private HdmiCecController mCecController;
184
185    @Nullable
186    private HdmiMhlController mMhlController;
187
188    // HDMI port information. Stored in the unmodifiable list to keep the static information
189    // from being modified.
190    private List<HdmiPortInfo> mPortInfo;
191
192    private HdmiCecMessageValidator mMessageValidator;
193
194    private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver();
195
196    @ServiceThreadOnly
197    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
198
199    @ServiceThreadOnly
200    private boolean mStandbyMessageReceived = false;
201
202    public HdmiControlService(Context context) {
203        super(context);
204        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
205                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
206    }
207
208    @Override
209    public void onStart() {
210        mIoThread.start();
211        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
212        mProhibitMode = false;
213        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
214
215        mCecController = HdmiCecController.create(this);
216        if (mCecController != null) {
217            // TODO: Remove this as soon as OEM's HAL implementation is corrected.
218            mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE,
219                    HdmiTvClient.ENABLED);
220
221            // TODO: load value for mHdmiControlEnabled from preference.
222            if (mHdmiControlEnabled) {
223                initializeCec(true);
224            }
225        } else {
226            Slog.i(TAG, "Device does not support HDMI-CEC.");
227        }
228
229        mMhlController = HdmiMhlController.create(this);
230        if (mMhlController == null) {
231            Slog.i(TAG, "Device does not support MHL-control.");
232        }
233        mPortInfo = initPortInfo();
234        mMessageValidator = new HdmiCecMessageValidator(this);
235        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
236
237        // Register broadcast receiver for power state change.
238        if (mCecController != null || mMhlController != null) {
239            IntentFilter filter = new IntentFilter();
240            filter.addAction(Intent.ACTION_SCREEN_OFF);
241            filter.addAction(Intent.ACTION_SCREEN_ON);
242            getContext().registerReceiver(mPowerStateReceiver, filter);
243        }
244    }
245
246    boolean readBooleanSetting(String key, boolean defVal) {
247        ContentResolver cr = getContext().getContentResolver();
248        return Global.getInt(cr, key, defVal ? Constants.TRUE : Constants.FALSE) == Constants.TRUE;
249    }
250
251    void writeBooleanSetting(String key, boolean value) {
252        ContentResolver cr = getContext().getContentResolver();
253        Global.putInt(cr, key, value ? Constants.TRUE : Constants.FALSE);
254    }
255
256    private void initializeCec(boolean fromBootup) {
257        mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL,
258                HdmiTvClient.ENABLED);
259        initializeLocalDevices(mLocalDevices, fromBootup);
260    }
261
262    @ServiceThreadOnly
263    private void initializeLocalDevices(final List<Integer> deviceTypes, final boolean fromBootup) {
264        assertRunOnServiceThread();
265        // A container for [Logical Address, Local device info].
266        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
267        final SparseIntArray finished = new SparseIntArray();
268        mCecController.clearLogicalAddress();
269        for (int type : deviceTypes) {
270            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
271            localDevice.init();
272            mCecController.allocateLogicalAddress(type,
273                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
274                @Override
275                public void onAllocated(int deviceType, int logicalAddress) {
276                    if (logicalAddress == Constants.ADDR_UNREGISTERED) {
277                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
278                    } else {
279                        HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType);
280                        localDevice.setDeviceInfo(deviceInfo);
281                        mCecController.addLocalDevice(deviceType, localDevice);
282                        mCecController.addLogicalAddress(logicalAddress);
283                        devices.append(logicalAddress, localDevice);
284                    }
285                    finished.append(deviceType, logicalAddress);
286
287                    // Address allocation completed for all devices. Notify each device.
288                    if (deviceTypes.size() == finished.size()) {
289                        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
290                            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
291                        }
292                        notifyAddressAllocated(devices, fromBootup);
293                    }
294                }
295            });
296        }
297    }
298
299    @ServiceThreadOnly
300    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices,
301            boolean fromBootup) {
302        assertRunOnServiceThread();
303        for (int i = 0; i < devices.size(); ++i) {
304            int address = devices.keyAt(i);
305            HdmiCecLocalDevice device = devices.valueAt(i);
306            device.handleAddressAllocated(address, fromBootup);
307        }
308    }
309
310    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
311    // keep them in one place.
312    @ServiceThreadOnly
313    private List<HdmiPortInfo> initPortInfo() {
314        assertRunOnServiceThread();
315        HdmiPortInfo[] cecPortInfo = null;
316
317        // CEC HAL provides majority of the info while MHL does only MHL support flag for
318        // each port. Return empty array if CEC HAL didn't provide the info.
319        if (mCecController != null) {
320            cecPortInfo = mCecController.getPortInfos();
321        }
322        if (cecPortInfo == null) {
323            return Collections.emptyList();
324        }
325
326        HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0];
327        if (mMhlController != null) {
328            // TODO: Implement plumbing logic to get MHL port information.
329            // mhlPortInfo = mMhlController.getPortInfos();
330        }
331
332        // Use the id (port number) to find the matched info between CEC and MHL to combine them
333        // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found.
334        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
335        for (int i = 0; i < cecPortInfo.length; ++i) {
336            HdmiPortInfo cec = cecPortInfo[i];
337            int id = cec.getId();
338            boolean mhlInfoFound = false;
339            for (HdmiPortInfo mhl : mhlPortInfo) {
340                if (id == mhl.getId()) {
341                    result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(),
342                            cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported()));
343                    mhlInfoFound = true;
344                    break;
345                }
346            }
347            if (!mhlInfoFound) {
348                result.add(cec);
349            }
350        }
351
352        return Collections.unmodifiableList(result);
353    }
354
355    /**
356     * Returns HDMI port information for the given port id.
357     *
358     * @param portId HDMI port id
359     * @return {@link HdmiPortInfo} for the given port
360     */
361    HdmiPortInfo getPortInfo(int portId) {
362        // mPortInfo is an unmodifiable list and the only reference to its inner list.
363        // No lock is necessary.
364        for (HdmiPortInfo info : mPortInfo) {
365            if (portId == info.getId()) {
366                return info;
367            }
368        }
369        return null;
370    }
371
372    /**
373     * Returns the routing path (physical address) of the HDMI port for the given
374     * port id.
375     */
376    int portIdToPath(int portId) {
377        HdmiPortInfo portInfo = getPortInfo(portId);
378        if (portInfo == null) {
379            Slog.e(TAG, "Cannot find the port info: " + portId);
380            return Constants.INVALID_PHYSICAL_ADDRESS;
381        }
382        return portInfo.getAddress();
383    }
384
385    /**
386     * Returns the id of HDMI port located at the top of the hierarchy of
387     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
388     * the port id to be returned is the ID associated with the port address
389     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
390     */
391    int pathToPortId(int path) {
392        int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
393        for (HdmiPortInfo info : mPortInfo) {
394            if (portAddress == info.getAddress()) {
395                return info.getId();
396            }
397        }
398        return Constants.INVALID_PORT_ID;
399    }
400
401    boolean isValidPortId(int portId) {
402        for (HdmiPortInfo info : mPortInfo) {
403            if (portId == info.getId()) {
404                return true;
405            }
406        }
407        return false;
408    }
409
410    /**
411     * Returns {@link Looper} for IO operation.
412     *
413     * <p>Declared as package-private.
414     */
415    Looper getIoLooper() {
416        return mIoThread.getLooper();
417    }
418
419    /**
420     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
421     * for tasks that are running on main service thread.
422     *
423     * <p>Declared as package-private.
424     */
425    Looper getServiceLooper() {
426        return mHandler.getLooper();
427    }
428
429    /**
430     * Returns physical address of the device.
431     */
432    int getPhysicalAddress() {
433        return mCecController.getPhysicalAddress();
434    }
435
436    /**
437     * Returns vendor id of CEC service.
438     */
439    int getVendorId() {
440        return mCecController.getVendorId();
441    }
442
443    @ServiceThreadOnly
444    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
445        assertRunOnServiceThread();
446        HdmiCecLocalDeviceTv tv = tv();
447        if (tv == null) {
448            return null;
449        }
450        return tv.getDeviceInfo(logicalAddress);
451    }
452
453    /**
454     * Returns version of CEC.
455     */
456    int getCecVersion() {
457        return mCecController.getVersion();
458    }
459
460    /**
461     * Whether a device of the specified physical address is connected to ARC enabled port.
462     */
463    boolean isConnectedToArcPort(int physicalAddress) {
464        for (HdmiPortInfo portInfo : mPortInfo) {
465            if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
466                    && portInfo.isArcSupported()) {
467                return true;
468            }
469        }
470        return false;
471    }
472
473    void runOnServiceThread(Runnable runnable) {
474        mHandler.post(runnable);
475    }
476
477    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
478        mHandler.postAtFrontOfQueue(runnable);
479    }
480
481    private void assertRunOnServiceThread() {
482        if (Looper.myLooper() != mHandler.getLooper()) {
483            throw new IllegalStateException("Should run on service thread.");
484        }
485    }
486
487    /**
488     * Transmit a CEC command to CEC bus.
489     *
490     * @param command CEC command to send out
491     * @param callback interface used to the result of send command
492     */
493    @ServiceThreadOnly
494    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
495        assertRunOnServiceThread();
496        mCecController.sendCommand(command, callback);
497    }
498
499    @ServiceThreadOnly
500    void sendCecCommand(HdmiCecMessage command) {
501        assertRunOnServiceThread();
502        mCecController.sendCommand(command, null);
503    }
504
505    @ServiceThreadOnly
506    boolean handleCecCommand(HdmiCecMessage message) {
507        assertRunOnServiceThread();
508        if (!mMessageValidator.isValid(message)) {
509            return false;
510        }
511        return dispatchMessageToLocalDevice(message);
512    }
513
514    void setAudioReturnChannel(boolean enabled) {
515        mCecController.setAudioReturnChannel(enabled);
516    }
517
518    @ServiceThreadOnly
519    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
520        assertRunOnServiceThread();
521        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
522            if (device.dispatchMessage(message)
523                    && message.getDestination() != Constants.ADDR_BROADCAST) {
524                return true;
525            }
526        }
527
528        if (message.getDestination() != Constants.ADDR_BROADCAST) {
529            Slog.w(TAG, "Unhandled cec command:" + message);
530        }
531        return false;
532    }
533
534    /**
535     * Called when a new hotplug event is issued.
536     *
537     * @param portNo hdmi port number where hot plug event issued.
538     * @param connected whether to be plugged in or not
539     */
540    @ServiceThreadOnly
541    void onHotplug(int portNo, boolean connected) {
542        assertRunOnServiceThread();
543        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
544            device.onHotplug(portNo, connected);
545        }
546        announceHotplugEvent(portNo, connected);
547    }
548
549    /**
550     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
551     * devices.
552     *
553     * @param callback an interface used to get a list of all remote devices' address
554     * @param sourceAddress a logical address of source device where sends polling message
555     * @param pickStrategy strategy how to pick polling candidates
556     * @param retryCount the number of retry used to send polling message to remote devices
557     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
558     */
559    @ServiceThreadOnly
560    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
561            int retryCount) {
562        assertRunOnServiceThread();
563        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
564                retryCount);
565    }
566
567    private int checkPollStrategy(int pickStrategy) {
568        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
569        if (strategy == 0) {
570            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
571        }
572        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
573        if (iterationStrategy == 0) {
574            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
575        }
576        return strategy | iterationStrategy;
577    }
578
579    List<HdmiCecLocalDevice> getAllLocalDevices() {
580        assertRunOnServiceThread();
581        return mCecController.getLocalDeviceList();
582    }
583
584    Object getServiceLock() {
585        return mLock;
586    }
587
588    void setAudioStatus(boolean mute, int volume) {
589        AudioManager audioManager = getAudioManager();
590        boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
591        if (mute) {
592            if (!muted) {
593                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
594            }
595        } else {
596            if (muted) {
597                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
598            }
599            // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
600            // volume change notification back to hdmi control service.
601            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
602                    AudioManager.FLAG_SHOW_UI |
603                    AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
604        }
605    }
606
607    void announceSystemAudioModeChange(boolean enabled) {
608        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
609            invokeSystemAudioModeChange(listener, enabled);
610        }
611    }
612
613    private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
614        // TODO: find better name instead of model name.
615        String displayName = Build.MODEL;
616        return new HdmiCecDeviceInfo(logicalAddress,
617                getPhysicalAddress(), deviceType, getVendorId(), displayName);
618    }
619
620    // Record class that monitors the event of the caller of being killed. Used to clean up
621    // the listener list and record list accordingly.
622    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
623        private final IHdmiHotplugEventListener mListener;
624
625        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
626            mListener = listener;
627        }
628
629        @Override
630        public void binderDied() {
631            synchronized (mLock) {
632                mHotplugEventListenerRecords.remove(this);
633                mHotplugEventListeners.remove(mListener);
634            }
635        }
636    }
637
638    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
639        private final IHdmiDeviceEventListener mListener;
640
641        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
642            mListener = listener;
643        }
644
645        @Override
646        public void binderDied() {
647            synchronized (mLock) {
648                mDeviceEventListenerRecords.remove(this);
649                mDeviceEventListeners.remove(mListener);
650            }
651        }
652    }
653
654    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
655        private final IHdmiSystemAudioModeChangeListener mListener;
656
657        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
658            mListener = listener;
659        }
660
661        @Override
662        public void binderDied() {
663            synchronized (mLock) {
664                mSystemAudioModeChangeListenerRecords.remove(this);
665                mSystemAudioModeChangeListeners.remove(mListener);
666            }
667        }
668    }
669
670    class VendorCommandListenerRecord implements IBinder.DeathRecipient {
671        private final IHdmiVendorCommandListener mListener;
672        private final int mDeviceType;
673
674        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
675            mListener = listener;
676            mDeviceType = deviceType;
677        }
678
679        @Override
680        public void binderDied() {
681            synchronized (mLock) {
682                mVendorCommandListenerRecords.remove(this);
683            }
684        }
685    }
686
687    private void enforceAccessPermission() {
688        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
689    }
690
691    private final class BinderService extends IHdmiControlService.Stub {
692        @Override
693        public int[] getSupportedTypes() {
694            enforceAccessPermission();
695            // mLocalDevices is an unmodifiable list - no lock necesary.
696            int[] localDevices = new int[mLocalDevices.size()];
697            for (int i = 0; i < localDevices.length; ++i) {
698                localDevices[i] = mLocalDevices.get(i);
699            }
700            return localDevices;
701        }
702
703        @Override
704        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
705            enforceAccessPermission();
706            runOnServiceThread(new Runnable() {
707                @Override
708                public void run() {
709                    HdmiCecLocalDeviceTv tv = tv();
710                    if (tv == null) {
711                        Slog.w(TAG, "Local tv device not available");
712                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
713                        return;
714                    }
715                    tv.deviceSelect(logicalAddress, callback);
716                }
717            });
718        }
719
720        @Override
721        public void portSelect(final int portId, final IHdmiControlCallback callback) {
722            enforceAccessPermission();
723            runOnServiceThread(new Runnable() {
724                @Override
725                public void run() {
726                    HdmiCecLocalDeviceTv tv = tv();
727                    if (tv == null) {
728                        Slog.w(TAG, "Local tv device not available");
729                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
730                        return;
731                    }
732                    tv.doManualPortSwitching(portId, callback);
733                }
734            });
735        }
736
737        @Override
738        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
739            enforceAccessPermission();
740            runOnServiceThread(new Runnable() {
741                @Override
742                public void run() {
743                    HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
744                    if (localDevice == null) {
745                        Slog.w(TAG, "Local device not available");
746                        return;
747                    }
748                    localDevice.sendKeyEvent(keyCode, isPressed);
749                }
750            });
751        }
752
753        @Override
754        public void oneTouchPlay(final IHdmiControlCallback callback) {
755            enforceAccessPermission();
756            runOnServiceThread(new Runnable() {
757                @Override
758                public void run() {
759                    HdmiControlService.this.oneTouchPlay(callback);
760                }
761            });
762        }
763
764        @Override
765        public void queryDisplayStatus(final IHdmiControlCallback callback) {
766            enforceAccessPermission();
767            runOnServiceThread(new Runnable() {
768                @Override
769                public void run() {
770                    HdmiControlService.this.queryDisplayStatus(callback);
771                }
772            });
773        }
774
775        @Override
776        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
777            enforceAccessPermission();
778            runOnServiceThread(new Runnable() {
779                @Override
780                public void run() {
781                    HdmiControlService.this.addHotplugEventListener(listener);
782                }
783            });
784        }
785
786        @Override
787        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
788            enforceAccessPermission();
789            runOnServiceThread(new Runnable() {
790                @Override
791                public void run() {
792                    HdmiControlService.this.removeHotplugEventListener(listener);
793                }
794            });
795        }
796
797        @Override
798        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
799            enforceAccessPermission();
800            runOnServiceThread(new Runnable() {
801                @Override
802                public void run() {
803                    HdmiControlService.this.addDeviceEventListener(listener);
804                }
805            });
806        }
807
808        @Override
809        public List<HdmiPortInfo> getPortInfo() {
810            enforceAccessPermission();
811            return mPortInfo;
812        }
813
814        @Override
815        public boolean canChangeSystemAudioMode() {
816            enforceAccessPermission();
817            HdmiCecLocalDeviceTv tv = tv();
818            if (tv == null) {
819                return false;
820            }
821            return tv.hasSystemAudioDevice();
822        }
823
824        @Override
825        public boolean getSystemAudioMode() {
826            enforceAccessPermission();
827            HdmiCecLocalDeviceTv tv = tv();
828            if (tv == null) {
829                return false;
830            }
831            return tv.isSystemAudioActivated();
832        }
833
834        @Override
835        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
836            enforceAccessPermission();
837            runOnServiceThread(new Runnable() {
838                @Override
839                public void run() {
840                    HdmiCecLocalDeviceTv tv = tv();
841                    if (tv == null) {
842                        Slog.w(TAG, "Local tv device not available");
843                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
844                        return;
845                    }
846                    tv.changeSystemAudioMode(enabled, callback);
847                }
848            });
849        }
850
851        @Override
852        public void addSystemAudioModeChangeListener(
853                final IHdmiSystemAudioModeChangeListener listener) {
854            enforceAccessPermission();
855            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
856        }
857
858        @Override
859        public void removeSystemAudioModeChangeListener(
860                final IHdmiSystemAudioModeChangeListener listener) {
861            enforceAccessPermission();
862            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
863        }
864
865        @Override
866        public void setInputChangeListener(final IHdmiInputChangeListener listener) {
867            enforceAccessPermission();
868            HdmiControlService.this.setInputChangeListener(listener);
869        }
870
871        @Override
872        public List<HdmiCecDeviceInfo> getInputDevices() {
873            enforceAccessPermission();
874            // No need to hold the lock for obtaining TV device as the local device instance
875            // is preserved while the HDMI control is enabled.
876            HdmiCecLocalDeviceTv tv = tv();
877            if (tv == null) {
878                return Collections.emptyList();
879            }
880            return tv.getSafeExternalInputs();
881        }
882
883        @Override
884        public void setControlEnabled(final boolean enabled) {
885            enforceAccessPermission();
886            runOnServiceThread(new Runnable() {
887                @Override
888                public void run() {
889                    handleHdmiControlStatusChanged(enabled);
890
891                }
892            });
893        }
894
895        @Override
896        public void setSystemAudioVolume(final int oldIndex, final int newIndex,
897                final int maxIndex) {
898            enforceAccessPermission();
899            runOnServiceThread(new Runnable() {
900                @Override
901                public void run() {
902                    HdmiCecLocalDeviceTv tv = tv();
903                    if (tv == null) {
904                        Slog.w(TAG, "Local tv device not available");
905                        return;
906                    }
907                    tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
908                }
909            });
910        }
911
912        @Override
913        public void setSystemAudioMute(final boolean mute) {
914            enforceAccessPermission();
915            runOnServiceThread(new Runnable() {
916                @Override
917                public void run() {
918                    HdmiCecLocalDeviceTv tv = tv();
919                    if (tv == null) {
920                        Slog.w(TAG, "Local tv device not available");
921                        return;
922                    }
923                    tv.changeMute(mute);
924                }
925            });
926        }
927
928        @Override
929        public void setArcMode(final boolean enabled) {
930            enforceAccessPermission();
931            runOnServiceThread(new Runnable() {
932                @Override
933                public void run() {
934                    HdmiCecLocalDeviceTv tv = tv();
935                    if (tv == null) {
936                        Slog.w(TAG, "Local tv device not available to change arc mode.");
937                        return;
938                    }
939                }
940            });
941        }
942
943        @Override
944        public void setOption(final int key, final int value) {
945            enforceAccessPermission();
946            if (!isTvDevice()) {
947                return;
948            }
949            switch (key) {
950                case HdmiTvClient.OPTION_CEC_AUTO_WAKEUP:
951                    mCecController.setOption(key, value);
952                    break;
953                case HdmiTvClient.OPTION_CEC_AUTO_DEVICE_OFF:
954                    // No need to pass this option to HAL.
955                    tv().setAutoDeviceOff(value == HdmiTvClient.ENABLED);
956                    break;
957                case HdmiTvClient.OPTION_MHL_INPUT_SWITCHING:  // Fall through
958                case HdmiTvClient.OPTION_MHL_POWER_CHARGE:
959                    if (mMhlController != null) {
960                        mMhlController.setOption(key, value);
961                    }
962                    break;
963            }
964        }
965
966        private boolean isTvDevice() {
967            return tv() != null;
968        }
969
970        @Override
971        public void setProhibitMode(final boolean enabled) {
972            enforceAccessPermission();
973            if (!isTvDevice()) {
974                return;
975            }
976            HdmiControlService.this.setProhibitMode(enabled);
977        }
978
979        @Override
980        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
981                final int deviceType) {
982            enforceAccessPermission();
983            runOnServiceThread(new Runnable() {
984                @Override
985                public void run() {
986                    HdmiControlService.this.addVendorCommandListener(listener, deviceType);
987                }
988            });
989        }
990
991        @Override
992        public void sendVendorCommand(final int deviceType, final int targetAddress,
993                final byte[] params, final boolean hasVendorId) {
994            enforceAccessPermission();
995            runOnServiceThread(new Runnable() {
996                @Override
997                public void run() {
998                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
999                    if (device == null) {
1000                        Slog.w(TAG, "Local device not available");
1001                        return;
1002                    }
1003                    if (hasVendorId) {
1004                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1005                                device.getDeviceInfo().getLogicalAddress(), targetAddress,
1006                                getVendorId(), params));
1007                    } else {
1008                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1009                                device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1010                    }
1011                }
1012            });
1013         }
1014
1015        @Override
1016        public void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) {
1017            // TODO: implement this.
1018        }
1019
1020        @Override
1021        public void startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1022            // TODO: implement this.
1023        }
1024
1025        @Override
1026        public void startTimerRecording(int recorderAddress, byte[] recordSource) {
1027            // TODO: implement this.
1028        }
1029    }
1030
1031    @ServiceThreadOnly
1032    private void oneTouchPlay(final IHdmiControlCallback callback) {
1033        assertRunOnServiceThread();
1034        HdmiCecLocalDevicePlayback source = playback();
1035        if (source == null) {
1036            Slog.w(TAG, "Local playback device not available");
1037            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1038            return;
1039        }
1040        source.oneTouchPlay(callback);
1041    }
1042
1043    @ServiceThreadOnly
1044    private void queryDisplayStatus(final IHdmiControlCallback callback) {
1045        assertRunOnServiceThread();
1046        HdmiCecLocalDevicePlayback source = playback();
1047        if (source == null) {
1048            Slog.w(TAG, "Local playback device not available");
1049            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1050            return;
1051        }
1052        source.queryDisplayStatus(callback);
1053    }
1054
1055    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1056        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1057        try {
1058            listener.asBinder().linkToDeath(record, 0);
1059        } catch (RemoteException e) {
1060            Slog.w(TAG, "Listener already died");
1061            return;
1062        }
1063        synchronized (mLock) {
1064            mHotplugEventListenerRecords.add(record);
1065            mHotplugEventListeners.add(listener);
1066        }
1067    }
1068
1069    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1070        synchronized (mLock) {
1071            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1072                if (record.mListener.asBinder() == listener.asBinder()) {
1073                    listener.asBinder().unlinkToDeath(record, 0);
1074                    mHotplugEventListenerRecords.remove(record);
1075                    break;
1076                }
1077            }
1078            mHotplugEventListeners.remove(listener);
1079        }
1080    }
1081
1082    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1083        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1084        try {
1085            listener.asBinder().linkToDeath(record, 0);
1086        } catch (RemoteException e) {
1087            Slog.w(TAG, "Listener already died");
1088            return;
1089        }
1090        synchronized (mLock) {
1091            mDeviceEventListeners.add(listener);
1092            mDeviceEventListenerRecords.add(record);
1093        }
1094    }
1095
1096    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
1097        synchronized (mLock) {
1098            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
1099                try {
1100                    listener.onStatusChanged(device, activated);
1101                } catch (RemoteException e) {
1102                    Slog.e(TAG, "Failed to report device event:" + e);
1103                }
1104            }
1105        }
1106    }
1107
1108    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1109        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1110                listener);
1111        try {
1112            listener.asBinder().linkToDeath(record, 0);
1113        } catch (RemoteException e) {
1114            Slog.w(TAG, "Listener already died");
1115            return;
1116        }
1117        synchronized (mLock) {
1118            mSystemAudioModeChangeListeners.add(listener);
1119            mSystemAudioModeChangeListenerRecords.add(record);
1120        }
1121    }
1122
1123    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1124        synchronized (mLock) {
1125            for (SystemAudioModeChangeListenerRecord record :
1126                    mSystemAudioModeChangeListenerRecords) {
1127                if (record.mListener.asBinder() == listener) {
1128                    listener.asBinder().unlinkToDeath(record, 0);
1129                    mSystemAudioModeChangeListenerRecords.remove(record);
1130                    break;
1131                }
1132            }
1133            mSystemAudioModeChangeListeners.remove(listener);
1134        }
1135    }
1136
1137    private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1138        @Override
1139        public void binderDied() {
1140            synchronized (mLock) {
1141                mInputChangeListener = null;
1142            }
1143        }
1144    }
1145
1146    private void setInputChangeListener(IHdmiInputChangeListener listener) {
1147        synchronized (mLock) {
1148            mInputChangeListenerRecord = new InputChangeListenerRecord();
1149            try {
1150                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1151            } catch (RemoteException e) {
1152                Slog.w(TAG, "Listener already died");
1153                return;
1154            }
1155            mInputChangeListener = listener;
1156        }
1157    }
1158
1159    void invokeInputChangeListener(int activeAddress) {
1160        synchronized (mLock) {
1161            if (mInputChangeListener != null) {
1162                HdmiCecDeviceInfo activeSource = getDeviceInfo(activeAddress);
1163                try {
1164                    mInputChangeListener.onChanged(activeSource);
1165                } catch (RemoteException e) {
1166                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1167                }
1168            }
1169        }
1170    }
1171
1172    private void invokeCallback(IHdmiControlCallback callback, int result) {
1173        try {
1174            callback.onComplete(result);
1175        } catch (RemoteException e) {
1176            Slog.e(TAG, "Invoking callback failed:" + e);
1177        }
1178    }
1179
1180    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
1181            boolean enabled) {
1182        try {
1183            listener.onStatusChanged(enabled);
1184        } catch (RemoteException e) {
1185            Slog.e(TAG, "Invoking callback failed:" + e);
1186        }
1187    }
1188
1189    private void announceHotplugEvent(int portId, boolean connected) {
1190        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1191        synchronized (mLock) {
1192            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
1193                invokeHotplugEventListenerLocked(listener, event);
1194            }
1195        }
1196    }
1197
1198    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1199            HdmiHotplugEvent event) {
1200        try {
1201            listener.onReceived(event);
1202        } catch (RemoteException e) {
1203            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1204        }
1205    }
1206
1207    private static boolean hasSameTopPort(int path1, int path2) {
1208        return (path1 & Constants.ROUTING_PATH_TOP_MASK)
1209                == (path2 & Constants.ROUTING_PATH_TOP_MASK);
1210    }
1211
1212    private HdmiCecLocalDeviceTv tv() {
1213        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_TV);
1214    }
1215
1216    private HdmiCecLocalDevicePlayback playback() {
1217        return (HdmiCecLocalDevicePlayback)
1218                mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_PLAYBACK);
1219    }
1220
1221    AudioManager getAudioManager() {
1222        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1223    }
1224
1225    boolean isControlEnabled() {
1226        synchronized (mLock) {
1227            return mHdmiControlEnabled;
1228        }
1229    }
1230
1231    int getPowerStatus() {
1232        return mPowerStatus;
1233    }
1234
1235    boolean isPowerOnOrTransient() {
1236        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1237                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1238    }
1239
1240    boolean isPowerStandbyOrTransient() {
1241        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1242                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1243    }
1244
1245    boolean isPowerStandby() {
1246        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1247    }
1248
1249    @ServiceThreadOnly
1250    void wakeUp() {
1251        assertRunOnServiceThread();
1252        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1253        pm.wakeUp(SystemClock.uptimeMillis());
1254        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1255        // the intent, the sequence will continue at onWakeUp().
1256    }
1257
1258    @ServiceThreadOnly
1259    void standby() {
1260        assertRunOnServiceThread();
1261        mStandbyMessageReceived = true;
1262        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1263        pm.goToSleep(SystemClock.uptimeMillis());
1264        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1265        // the intent, the sequence will continue at onStandby().
1266    }
1267
1268    @ServiceThreadOnly
1269    private void onWakeUp() {
1270        assertRunOnServiceThread();
1271        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1272        if (mCecController != null) {
1273            if (mHdmiControlEnabled) {
1274                initializeCec(true);
1275            }
1276        } else {
1277            Slog.i(TAG, "Device does not support HDMI-CEC.");
1278        }
1279        // TODO: Initialize MHL local devices.
1280    }
1281
1282    @ServiceThreadOnly
1283    private void onStandby() {
1284        assertRunOnServiceThread();
1285        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1286
1287        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1288        disableDevices(new PendingActionClearedCallback() {
1289            @Override
1290            public void onCleared(HdmiCecLocalDevice device) {
1291                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1292                devices.remove(device);
1293                if (devices.isEmpty()) {
1294                    clearLocalDevices();
1295                    onStandbyCompleted();
1296                }
1297            }
1298        });
1299    }
1300
1301    private void disableDevices(PendingActionClearedCallback callback) {
1302        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1303            device.disableDevice(mStandbyMessageReceived, callback);
1304        }
1305    }
1306
1307    @ServiceThreadOnly
1308    private void clearLocalDevices() {
1309        assertRunOnServiceThread();
1310        if (mCecController == null) {
1311            return;
1312        }
1313        mCecController.clearLogicalAddress();
1314        mCecController.clearLocalDevices();
1315    }
1316
1317    @ServiceThreadOnly
1318    private void onStandbyCompleted() {
1319        assertRunOnServiceThread();
1320        Slog.v(TAG, "onStandbyCompleted");
1321
1322        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1323            return;
1324        }
1325        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1326        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1327            device.onStandby(mStandbyMessageReceived);
1328        }
1329        mStandbyMessageReceived = false;
1330        mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED);
1331    }
1332
1333    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1334        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1335        try {
1336            listener.asBinder().linkToDeath(record, 0);
1337        } catch (RemoteException e) {
1338            Slog.w(TAG, "Listener already died");
1339            return;
1340        }
1341        synchronized (mLock) {
1342            mVendorCommandListenerRecords.add(record);
1343        }
1344    }
1345
1346    void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1347            boolean hasVendorId) {
1348        synchronized (mLock) {
1349            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1350                if (record.mDeviceType != deviceType) {
1351                    continue;
1352                }
1353                try {
1354                    record.mListener.onReceived(srcAddress, params, hasVendorId);
1355                } catch (RemoteException e) {
1356                    Slog.e(TAG, "Failed to notify vendor command reception", e);
1357                }
1358            }
1359        }
1360    }
1361
1362    boolean isProhibitMode() {
1363        synchronized (mLock) {
1364            return mProhibitMode;
1365        }
1366    }
1367
1368    void setProhibitMode(boolean enabled) {
1369        synchronized (mLock) {
1370            mProhibitMode = enabled;
1371        }
1372    }
1373
1374    @ServiceThreadOnly
1375    private void handleHdmiControlStatusChanged(boolean enabled) {
1376        assertRunOnServiceThread();
1377
1378        int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED;
1379        mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value);
1380        if (mMhlController != null) {
1381            mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value);
1382        }
1383
1384        synchronized (mLock) {
1385            mHdmiControlEnabled = enabled;
1386        }
1387
1388        if (enabled) {
1389            initializeCec(false);
1390        } else {
1391            disableDevices(new PendingActionClearedCallback() {
1392                @Override
1393                public void onCleared(HdmiCecLocalDevice device) {
1394                    assertRunOnServiceThread();
1395                    clearLocalDevices();
1396                }
1397            });
1398        }
1399    }
1400}
1401