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