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