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