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