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