HdmiControlService.java revision b6591b8e5399099dc6b7693e0fc719b613aba89c
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        private boolean isTvDevice() {
984            return tv() != null;
985        }
986
987        @Override
988        public void setProhibitMode(final boolean enabled) {
989            enforceAccessPermission();
990            if (!isTvDevice()) {
991                return;
992            }
993            HdmiControlService.this.setProhibitMode(enabled);
994        }
995
996        @Override
997        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
998                final int deviceType) {
999            enforceAccessPermission();
1000            runOnServiceThread(new Runnable() {
1001                @Override
1002                public void run() {
1003                    HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1004                }
1005            });
1006        }
1007
1008        @Override
1009        public void sendVendorCommand(final int deviceType, final int targetAddress,
1010                final byte[] params, final boolean hasVendorId) {
1011            enforceAccessPermission();
1012            runOnServiceThread(new Runnable() {
1013                @Override
1014                public void run() {
1015                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1016                    if (device == null) {
1017                        Slog.w(TAG, "Local device not available");
1018                        return;
1019                    }
1020                    if (hasVendorId) {
1021                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1022                                device.getDeviceInfo().getLogicalAddress(), targetAddress,
1023                                getVendorId(), params));
1024                    } else {
1025                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1026                                device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1027                    }
1028                }
1029            });
1030         }
1031
1032        @Override
1033        public void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) {
1034            HdmiControlService.this.setOneTouchRecordRequestListener(listener);
1035        }
1036
1037        @Override
1038        public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1039            runOnServiceThread(new Runnable() {
1040                @Override
1041                public void run() {
1042                    if (!isTvDevice()) {
1043                        Slog.w(TAG, "No TV is available.");
1044                        return;
1045                    }
1046                    tv().startOneTouchRecord(recorderAddress, recordSource);
1047                }
1048            });
1049        }
1050
1051        @Override
1052        public void stopOneTouchRecord(final int recorderAddress) {
1053            runOnServiceThread(new Runnable() {
1054                @Override
1055                public void run() {
1056                    if (!isTvDevice()) {
1057                        Slog.w(TAG, "No TV is available.");
1058                        return;
1059                    }
1060                    tv().stopOneTouchRecord(recorderAddress);
1061                }
1062            });
1063        }
1064
1065        @Override
1066        public void startTimerRecording(final int recorderAddress, final int sourceType,
1067                final byte[] recordSource) {
1068            runOnServiceThread(new Runnable() {
1069                @Override
1070                public void run() {
1071                    if (!isTvDevice()) {
1072                        Slog.w(TAG, "No TV is available.");
1073                        return;
1074                    }
1075                    tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1076                }
1077            });
1078        }
1079
1080        @Override
1081        public void clearTimerRecording(final int recorderAddress, final int sourceType,
1082                final byte[] recordSource) {
1083            runOnServiceThread(new Runnable() {
1084                @Override
1085                public void run() {
1086                    if (!isTvDevice()) {
1087                        Slog.w(TAG, "No TV is available.");
1088                        return;
1089                    }
1090                    tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1091                }
1092            });
1093        }
1094    }
1095
1096    @ServiceThreadOnly
1097    private void oneTouchPlay(final IHdmiControlCallback callback) {
1098        assertRunOnServiceThread();
1099        HdmiCecLocalDevicePlayback source = playback();
1100        if (source == null) {
1101            Slog.w(TAG, "Local playback device not available");
1102            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1103            return;
1104        }
1105        source.oneTouchPlay(callback);
1106    }
1107
1108    @ServiceThreadOnly
1109    private void queryDisplayStatus(final IHdmiControlCallback callback) {
1110        assertRunOnServiceThread();
1111        HdmiCecLocalDevicePlayback source = playback();
1112        if (source == null) {
1113            Slog.w(TAG, "Local playback device not available");
1114            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1115            return;
1116        }
1117        source.queryDisplayStatus(callback);
1118    }
1119
1120    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1121        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1122        try {
1123            listener.asBinder().linkToDeath(record, 0);
1124        } catch (RemoteException e) {
1125            Slog.w(TAG, "Listener already died");
1126            return;
1127        }
1128        synchronized (mLock) {
1129            mHotplugEventListenerRecords.add(record);
1130            mHotplugEventListeners.add(listener);
1131        }
1132    }
1133
1134    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1135        synchronized (mLock) {
1136            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1137                if (record.mListener.asBinder() == listener.asBinder()) {
1138                    listener.asBinder().unlinkToDeath(record, 0);
1139                    mHotplugEventListenerRecords.remove(record);
1140                    break;
1141                }
1142            }
1143            mHotplugEventListeners.remove(listener);
1144        }
1145    }
1146
1147    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1148        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1149        try {
1150            listener.asBinder().linkToDeath(record, 0);
1151        } catch (RemoteException e) {
1152            Slog.w(TAG, "Listener already died");
1153            return;
1154        }
1155        synchronized (mLock) {
1156            mDeviceEventListeners.add(listener);
1157            mDeviceEventListenerRecords.add(record);
1158        }
1159    }
1160
1161    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
1162        synchronized (mLock) {
1163            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
1164                try {
1165                    listener.onStatusChanged(device, activated);
1166                } catch (RemoteException e) {
1167                    Slog.e(TAG, "Failed to report device event:" + e);
1168                }
1169            }
1170        }
1171    }
1172
1173    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1174        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1175                listener);
1176        try {
1177            listener.asBinder().linkToDeath(record, 0);
1178        } catch (RemoteException e) {
1179            Slog.w(TAG, "Listener already died");
1180            return;
1181        }
1182        synchronized (mLock) {
1183            mSystemAudioModeChangeListeners.add(listener);
1184            mSystemAudioModeChangeListenerRecords.add(record);
1185        }
1186    }
1187
1188    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1189        synchronized (mLock) {
1190            for (SystemAudioModeChangeListenerRecord record :
1191                    mSystemAudioModeChangeListenerRecords) {
1192                if (record.mListener.asBinder() == listener) {
1193                    listener.asBinder().unlinkToDeath(record, 0);
1194                    mSystemAudioModeChangeListenerRecords.remove(record);
1195                    break;
1196                }
1197            }
1198            mSystemAudioModeChangeListeners.remove(listener);
1199        }
1200    }
1201
1202    private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1203        @Override
1204        public void binderDied() {
1205            synchronized (mLock) {
1206                mInputChangeListener = null;
1207            }
1208        }
1209    }
1210
1211    private void setInputChangeListener(IHdmiInputChangeListener listener) {
1212        synchronized (mLock) {
1213            mInputChangeListenerRecord = new InputChangeListenerRecord();
1214            try {
1215                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1216            } catch (RemoteException e) {
1217                Slog.w(TAG, "Listener already died");
1218                return;
1219            }
1220            mInputChangeListener = listener;
1221        }
1222    }
1223
1224    void invokeInputChangeListener(int activeAddress) {
1225        synchronized (mLock) {
1226            if (mInputChangeListener != null) {
1227                HdmiCecDeviceInfo activeSource = getDeviceInfo(activeAddress);
1228                try {
1229                    mInputChangeListener.onChanged(activeSource);
1230                } catch (RemoteException e) {
1231                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1232                }
1233            }
1234        }
1235    }
1236
1237    private void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) {
1238        synchronized (mLock) {
1239            mRecordRequestListenerRecord = new HdmiRecordRequestListenerRecord();
1240            try {
1241                listener.asBinder().linkToDeath(mRecordRequestListenerRecord, 0);
1242            } catch (RemoteException e) {
1243                Slog.w(TAG, "Listener already died", e);
1244                return;
1245            }
1246            mRecordRequestListener = listener;
1247        }
1248    }
1249
1250    byte[] invokeRecordRequestListener(int recorderAddress) {
1251        synchronized (mLock) {
1252            try {
1253                if (mRecordRequestListener != null) {
1254                    return mRecordRequestListener.onRecordRequestReceived(recorderAddress);
1255                }
1256            } catch (RemoteException e) {
1257                Slog.w(TAG, "Failed to start record.", e);
1258            }
1259            return EmptyArray.BYTE;
1260        }
1261    }
1262
1263    private void invokeCallback(IHdmiControlCallback callback, int result) {
1264        try {
1265            callback.onComplete(result);
1266        } catch (RemoteException e) {
1267            Slog.e(TAG, "Invoking callback failed:" + e);
1268        }
1269    }
1270
1271    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
1272            boolean enabled) {
1273        try {
1274            listener.onStatusChanged(enabled);
1275        } catch (RemoteException e) {
1276            Slog.e(TAG, "Invoking callback failed:" + e);
1277        }
1278    }
1279
1280    private void announceHotplugEvent(int portId, boolean connected) {
1281        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1282        synchronized (mLock) {
1283            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
1284                invokeHotplugEventListenerLocked(listener, event);
1285            }
1286        }
1287    }
1288
1289    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1290            HdmiHotplugEvent event) {
1291        try {
1292            listener.onReceived(event);
1293        } catch (RemoteException e) {
1294            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1295        }
1296    }
1297
1298    private static boolean hasSameTopPort(int path1, int path2) {
1299        return (path1 & Constants.ROUTING_PATH_TOP_MASK)
1300                == (path2 & Constants.ROUTING_PATH_TOP_MASK);
1301    }
1302
1303    private HdmiCecLocalDeviceTv tv() {
1304        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_TV);
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