HdmiControlService.java revision 30c74d9ba60a7208a587ecd6dcdfef1cfbd7fce7
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.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                        HdmiCecDeviceInfo 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    HdmiCecDeviceInfo 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        mCecController.sendCommand(command, callback);
521    }
522
523    @ServiceThreadOnly
524    void sendCecCommand(HdmiCecMessage command) {
525        assertRunOnServiceThread();
526        mCecController.sendCommand(command, null);
527    }
528
529    /**
530     * Send <Feature Abort> command on the given CEC message if possible.
531     * If the aborted message is invalid, then it wont send the message.
532     * @param command original command to be aborted
533     * @param reason reason of feature abort
534     */
535    @ServiceThreadOnly
536    void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
537        assertRunOnServiceThread();
538        mCecController.maySendFeatureAbortCommand(command, reason);
539    }
540
541    @ServiceThreadOnly
542    boolean handleCecCommand(HdmiCecMessage message) {
543        assertRunOnServiceThread();
544        if (!mMessageValidator.isValid(message)) {
545            return false;
546        }
547        return dispatchMessageToLocalDevice(message);
548    }
549
550    void setAudioReturnChannel(boolean enabled) {
551        mCecController.setAudioReturnChannel(enabled);
552    }
553
554    @ServiceThreadOnly
555    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
556        assertRunOnServiceThread();
557        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
558            if (device.dispatchMessage(message)
559                    && message.getDestination() != Constants.ADDR_BROADCAST) {
560                return true;
561            }
562        }
563
564        if (message.getDestination() != Constants.ADDR_BROADCAST) {
565            Slog.w(TAG, "Unhandled cec command:" + message);
566        }
567        return false;
568    }
569
570    /**
571     * Called when a new hotplug event is issued.
572     *
573     * @param portNo hdmi port number where hot plug event issued.
574     * @param connected whether to be plugged in or not
575     */
576    @ServiceThreadOnly
577    void onHotplug(int portNo, boolean connected) {
578        assertRunOnServiceThread();
579        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
580            device.onHotplug(portNo, connected);
581        }
582        announceHotplugEvent(portNo, connected);
583    }
584
585    /**
586     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
587     * devices.
588     *
589     * @param callback an interface used to get a list of all remote devices' address
590     * @param sourceAddress a logical address of source device where sends polling message
591     * @param pickStrategy strategy how to pick polling candidates
592     * @param retryCount the number of retry used to send polling message to remote devices
593     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
594     */
595    @ServiceThreadOnly
596    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
597            int retryCount) {
598        assertRunOnServiceThread();
599        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
600                retryCount);
601    }
602
603    private int checkPollStrategy(int pickStrategy) {
604        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
605        if (strategy == 0) {
606            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
607        }
608        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
609        if (iterationStrategy == 0) {
610            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
611        }
612        return strategy | iterationStrategy;
613    }
614
615    List<HdmiCecLocalDevice> getAllLocalDevices() {
616        assertRunOnServiceThread();
617        return mCecController.getLocalDeviceList();
618    }
619
620    Object getServiceLock() {
621        return mLock;
622    }
623
624    void setAudioStatus(boolean mute, int volume) {
625        AudioManager audioManager = getAudioManager();
626        boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
627        if (mute) {
628            if (!muted) {
629                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
630            }
631        } else {
632            if (muted) {
633                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
634            }
635            // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
636            // volume change notification back to hdmi control service.
637            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
638                    AudioManager.FLAG_SHOW_UI |
639                    AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
640        }
641    }
642
643    void announceSystemAudioModeChange(boolean enabled) {
644        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
645            invokeSystemAudioModeChange(listener, enabled);
646        }
647    }
648
649    private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
650        // TODO: find better name instead of model name.
651        String displayName = Build.MODEL;
652        return new HdmiCecDeviceInfo(logicalAddress,
653                getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
654                getVendorId(), displayName);
655    }
656
657    // Record class that monitors the event of the caller of being killed. Used to clean up
658    // the listener list and record list accordingly.
659    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
660        private final IHdmiHotplugEventListener mListener;
661
662        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
663            mListener = listener;
664        }
665
666        @Override
667        public void binderDied() {
668            synchronized (mLock) {
669                mHotplugEventListenerRecords.remove(this);
670                mHotplugEventListeners.remove(mListener);
671            }
672        }
673    }
674
675    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
676        private final IHdmiDeviceEventListener mListener;
677
678        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
679            mListener = listener;
680        }
681
682        @Override
683        public void binderDied() {
684            synchronized (mLock) {
685                mDeviceEventListenerRecords.remove(this);
686                mDeviceEventListeners.remove(mListener);
687            }
688        }
689    }
690
691    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
692        private final IHdmiSystemAudioModeChangeListener mListener;
693
694        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
695            mListener = listener;
696        }
697
698        @Override
699        public void binderDied() {
700            synchronized (mLock) {
701                mSystemAudioModeChangeListenerRecords.remove(this);
702                mSystemAudioModeChangeListeners.remove(mListener);
703            }
704        }
705    }
706
707    class VendorCommandListenerRecord implements IBinder.DeathRecipient {
708        private final IHdmiVendorCommandListener mListener;
709        private final int mDeviceType;
710
711        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
712            mListener = listener;
713            mDeviceType = deviceType;
714        }
715
716        @Override
717        public void binderDied() {
718            synchronized (mLock) {
719                mVendorCommandListenerRecords.remove(this);
720            }
721        }
722    }
723
724    private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
725        @Override
726        public void binderDied() {
727            synchronized (mLock) {
728                mRecordListener = null;
729            }
730        }
731    }
732
733    private void enforceAccessPermission() {
734        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
735    }
736
737    private final class BinderService extends IHdmiControlService.Stub {
738        @Override
739        public int[] getSupportedTypes() {
740            enforceAccessPermission();
741            // mLocalDevices is an unmodifiable list - no lock necesary.
742            int[] localDevices = new int[mLocalDevices.size()];
743            for (int i = 0; i < localDevices.length; ++i) {
744                localDevices[i] = mLocalDevices.get(i);
745            }
746            return localDevices;
747        }
748
749        @Override
750        public HdmiCecDeviceInfo getActiveSource() {
751            HdmiCecLocalDeviceTv tv = tv();
752            if (tv == null) {
753                Slog.w(TAG, "Local tv device not available");
754                return null;
755            }
756            ActiveSource activeSource = tv.getActiveSource();
757            if (activeSource.isValid()) {
758                return new HdmiCecDeviceInfo(activeSource.logicalAddress,
759                        activeSource.physicalAddress, HdmiCecDeviceInfo.PORT_INVALID,
760                        HdmiCecDeviceInfo.DEVICE_INACTIVE, 0, "");
761            }
762            int activePath = tv.getActivePath();
763            if (activePath != HdmiCecDeviceInfo.PATH_INVALID) {
764                return new HdmiCecDeviceInfo(activePath, tv.getActivePortId());
765            }
766            return null;
767        }
768
769        @Override
770        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
771            enforceAccessPermission();
772            runOnServiceThread(new Runnable() {
773                @Override
774                public void run() {
775                    if (callback == null) {
776                        Slog.e(TAG, "Callback cannot be null");
777                        return;
778                    }
779                    HdmiCecLocalDeviceTv tv = tv();
780                    if (tv == null) {
781                        Slog.w(TAG, "Local tv device not available");
782                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
783                        return;
784                    }
785                    tv.deviceSelect(logicalAddress, callback);
786                }
787            });
788        }
789
790        @Override
791        public void portSelect(final int portId, final IHdmiControlCallback callback) {
792            enforceAccessPermission();
793            runOnServiceThread(new Runnable() {
794                @Override
795                public void run() {
796                    if (callback == null) {
797                        Slog.e(TAG, "Callback cannot be null");
798                        return;
799                    }
800                    HdmiCecLocalDeviceTv tv = tv();
801                    if (tv == null) {
802                        Slog.w(TAG, "Local tv device not available");
803                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
804                        return;
805                    }
806                    tv.doManualPortSwitching(portId, callback);
807                }
808            });
809        }
810
811        @Override
812        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
813            enforceAccessPermission();
814            runOnServiceThread(new Runnable() {
815                @Override
816                public void run() {
817                    HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
818                    if (localDevice == null) {
819                        Slog.w(TAG, "Local device not available");
820                        return;
821                    }
822                    localDevice.sendKeyEvent(keyCode, isPressed);
823                }
824            });
825        }
826
827        @Override
828        public void oneTouchPlay(final IHdmiControlCallback callback) {
829            enforceAccessPermission();
830            runOnServiceThread(new Runnable() {
831                @Override
832                public void run() {
833                    HdmiControlService.this.oneTouchPlay(callback);
834                }
835            });
836        }
837
838        @Override
839        public void queryDisplayStatus(final IHdmiControlCallback callback) {
840            enforceAccessPermission();
841            runOnServiceThread(new Runnable() {
842                @Override
843                public void run() {
844                    HdmiControlService.this.queryDisplayStatus(callback);
845                }
846            });
847        }
848
849        @Override
850        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
851            enforceAccessPermission();
852            runOnServiceThread(new Runnable() {
853                @Override
854                public void run() {
855                    HdmiControlService.this.addHotplugEventListener(listener);
856                }
857            });
858        }
859
860        @Override
861        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
862            enforceAccessPermission();
863            runOnServiceThread(new Runnable() {
864                @Override
865                public void run() {
866                    HdmiControlService.this.removeHotplugEventListener(listener);
867                }
868            });
869        }
870
871        @Override
872        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
873            enforceAccessPermission();
874            runOnServiceThread(new Runnable() {
875                @Override
876                public void run() {
877                    HdmiControlService.this.addDeviceEventListener(listener);
878                }
879            });
880        }
881
882        @Override
883        public List<HdmiPortInfo> getPortInfo() {
884            enforceAccessPermission();
885            return mPortInfo;
886        }
887
888        @Override
889        public boolean canChangeSystemAudioMode() {
890            enforceAccessPermission();
891            HdmiCecLocalDeviceTv tv = tv();
892            if (tv == null) {
893                return false;
894            }
895            return tv.hasSystemAudioDevice();
896        }
897
898        @Override
899        public boolean getSystemAudioMode() {
900            enforceAccessPermission();
901            HdmiCecLocalDeviceTv tv = tv();
902            if (tv == null) {
903                return false;
904            }
905            return tv.isSystemAudioActivated();
906        }
907
908        @Override
909        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
910            enforceAccessPermission();
911            runOnServiceThread(new Runnable() {
912                @Override
913                public void run() {
914                    HdmiCecLocalDeviceTv tv = tv();
915                    if (tv == null) {
916                        Slog.w(TAG, "Local tv device not available");
917                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
918                        return;
919                    }
920                    tv.changeSystemAudioMode(enabled, callback);
921                }
922            });
923        }
924
925        @Override
926        public void addSystemAudioModeChangeListener(
927                final IHdmiSystemAudioModeChangeListener listener) {
928            enforceAccessPermission();
929            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
930        }
931
932        @Override
933        public void removeSystemAudioModeChangeListener(
934                final IHdmiSystemAudioModeChangeListener listener) {
935            enforceAccessPermission();
936            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
937        }
938
939        @Override
940        public void setInputChangeListener(final IHdmiInputChangeListener listener) {
941            enforceAccessPermission();
942            HdmiControlService.this.setInputChangeListener(listener);
943        }
944
945        @Override
946        public List<HdmiCecDeviceInfo> getInputDevices() {
947            enforceAccessPermission();
948            // No need to hold the lock for obtaining TV device as the local device instance
949            // is preserved while the HDMI control is enabled.
950            HdmiCecLocalDeviceTv tv = tv();
951            if (tv == null) {
952                return Collections.emptyList();
953            }
954            return tv.getSafeExternalInputs();
955        }
956
957        @Override
958        public void setControlEnabled(final boolean enabled) {
959            enforceAccessPermission();
960            runOnServiceThread(new Runnable() {
961                @Override
962                public void run() {
963                    handleHdmiControlStatusChanged(enabled);
964
965                }
966            });
967        }
968
969        @Override
970        public void setSystemAudioVolume(final int oldIndex, final int newIndex,
971                final int maxIndex) {
972            enforceAccessPermission();
973            runOnServiceThread(new Runnable() {
974                @Override
975                public void run() {
976                    HdmiCecLocalDeviceTv tv = tv();
977                    if (tv == null) {
978                        Slog.w(TAG, "Local tv device not available");
979                        return;
980                    }
981                    tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
982                }
983            });
984        }
985
986        @Override
987        public void setSystemAudioMute(final boolean mute) {
988            enforceAccessPermission();
989            runOnServiceThread(new Runnable() {
990                @Override
991                public void run() {
992                    HdmiCecLocalDeviceTv tv = tv();
993                    if (tv == null) {
994                        Slog.w(TAG, "Local tv device not available");
995                        return;
996                    }
997                    tv.changeMute(mute);
998                }
999            });
1000        }
1001
1002        @Override
1003        public void setArcMode(final boolean enabled) {
1004            enforceAccessPermission();
1005            runOnServiceThread(new Runnable() {
1006                @Override
1007                public void run() {
1008                    HdmiCecLocalDeviceTv tv = tv();
1009                    if (tv == null) {
1010                        Slog.w(TAG, "Local tv device not available to change arc mode.");
1011                        return;
1012                    }
1013                }
1014            });
1015        }
1016
1017        @Override
1018        public void setOption(final int key, final int value) {
1019            enforceAccessPermission();
1020            if (!isTvDevice()) {
1021                return;
1022            }
1023            switch (key) {
1024                case HdmiTvClient.OPTION_CEC_AUTO_WAKEUP:
1025                    tv().setAutoWakeup(value == HdmiTvClient.ENABLED);
1026                    mCecController.setOption(key, value);
1027                    break;
1028                case HdmiTvClient.OPTION_CEC_AUTO_DEVICE_OFF:
1029                    // No need to pass this option to HAL.
1030                    tv().setAutoDeviceOff(value == HdmiTvClient.ENABLED);
1031                    break;
1032                case HdmiTvClient.OPTION_MHL_INPUT_SWITCHING:  // Fall through
1033                case HdmiTvClient.OPTION_MHL_POWER_CHARGE:
1034                    if (mMhlController != null) {
1035                        mMhlController.setOption(key, value);
1036                    }
1037                    break;
1038            }
1039        }
1040
1041        @Override
1042        public void setProhibitMode(final boolean enabled) {
1043            enforceAccessPermission();
1044            if (!isTvDevice()) {
1045                return;
1046            }
1047            HdmiControlService.this.setProhibitMode(enabled);
1048        }
1049
1050        @Override
1051        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1052                final int deviceType) {
1053            enforceAccessPermission();
1054            runOnServiceThread(new Runnable() {
1055                @Override
1056                public void run() {
1057                    HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1058                }
1059            });
1060        }
1061
1062        @Override
1063        public void sendVendorCommand(final int deviceType, final int targetAddress,
1064                final byte[] params, final boolean hasVendorId) {
1065            enforceAccessPermission();
1066            runOnServiceThread(new Runnable() {
1067                @Override
1068                public void run() {
1069                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1070                    if (device == null) {
1071                        Slog.w(TAG, "Local device not available");
1072                        return;
1073                    }
1074                    if (hasVendorId) {
1075                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1076                                device.getDeviceInfo().getLogicalAddress(), targetAddress,
1077                                getVendorId(), params));
1078                    } else {
1079                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1080                                device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1081                    }
1082                }
1083            });
1084        }
1085
1086        @Override
1087        public void setHdmiRecordListener(IHdmiRecordListener listener) {
1088            HdmiControlService.this.setHdmiRecordListener(listener);
1089        }
1090
1091        @Override
1092        public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1093            runOnServiceThread(new Runnable() {
1094                @Override
1095                public void run() {
1096                    if (!isTvDevice()) {
1097                        Slog.w(TAG, "No TV is available.");
1098                        return;
1099                    }
1100                    tv().startOneTouchRecord(recorderAddress, recordSource);
1101                }
1102            });
1103        }
1104
1105        @Override
1106        public void stopOneTouchRecord(final int recorderAddress) {
1107            runOnServiceThread(new Runnable() {
1108                @Override
1109                public void run() {
1110                    if (!isTvDevice()) {
1111                        Slog.w(TAG, "No TV is available.");
1112                        return;
1113                    }
1114                    tv().stopOneTouchRecord(recorderAddress);
1115                }
1116            });
1117        }
1118
1119        @Override
1120        public void startTimerRecording(final int recorderAddress, final int sourceType,
1121                final byte[] recordSource) {
1122            runOnServiceThread(new Runnable() {
1123                @Override
1124                public void run() {
1125                    if (!isTvDevice()) {
1126                        Slog.w(TAG, "No TV is available.");
1127                        return;
1128                    }
1129                    tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1130                }
1131            });
1132        }
1133
1134        @Override
1135        public void clearTimerRecording(final int recorderAddress, final int sourceType,
1136                final byte[] recordSource) {
1137            runOnServiceThread(new Runnable() {
1138                @Override
1139                public void run() {
1140                    if (!isTvDevice()) {
1141                        Slog.w(TAG, "No TV is available.");
1142                        return;
1143                    }
1144                    tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1145                }
1146            });
1147        }
1148    }
1149
1150    @ServiceThreadOnly
1151    private void oneTouchPlay(final IHdmiControlCallback callback) {
1152        assertRunOnServiceThread();
1153        HdmiCecLocalDevicePlayback source = playback();
1154        if (source == null) {
1155            Slog.w(TAG, "Local playback device not available");
1156            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1157            return;
1158        }
1159        source.oneTouchPlay(callback);
1160    }
1161
1162    @ServiceThreadOnly
1163    private void queryDisplayStatus(final IHdmiControlCallback callback) {
1164        assertRunOnServiceThread();
1165        HdmiCecLocalDevicePlayback source = playback();
1166        if (source == null) {
1167            Slog.w(TAG, "Local playback device not available");
1168            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1169            return;
1170        }
1171        source.queryDisplayStatus(callback);
1172    }
1173
1174    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1175        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1176        try {
1177            listener.asBinder().linkToDeath(record, 0);
1178        } catch (RemoteException e) {
1179            Slog.w(TAG, "Listener already died");
1180            return;
1181        }
1182        synchronized (mLock) {
1183            mHotplugEventListenerRecords.add(record);
1184            mHotplugEventListeners.add(listener);
1185        }
1186    }
1187
1188    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1189        synchronized (mLock) {
1190            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1191                if (record.mListener.asBinder() == listener.asBinder()) {
1192                    listener.asBinder().unlinkToDeath(record, 0);
1193                    mHotplugEventListenerRecords.remove(record);
1194                    break;
1195                }
1196            }
1197            mHotplugEventListeners.remove(listener);
1198        }
1199    }
1200
1201    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1202        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1203        try {
1204            listener.asBinder().linkToDeath(record, 0);
1205        } catch (RemoteException e) {
1206            Slog.w(TAG, "Listener already died");
1207            return;
1208        }
1209        synchronized (mLock) {
1210            mDeviceEventListeners.add(listener);
1211            mDeviceEventListenerRecords.add(record);
1212        }
1213    }
1214
1215    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
1216        synchronized (mLock) {
1217            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
1218                try {
1219                    listener.onStatusChanged(device, activated);
1220                } catch (RemoteException e) {
1221                    Slog.e(TAG, "Failed to report device event:" + e);
1222                }
1223            }
1224        }
1225    }
1226
1227    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1228        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1229                listener);
1230        try {
1231            listener.asBinder().linkToDeath(record, 0);
1232        } catch (RemoteException e) {
1233            Slog.w(TAG, "Listener already died");
1234            return;
1235        }
1236        synchronized (mLock) {
1237            mSystemAudioModeChangeListeners.add(listener);
1238            mSystemAudioModeChangeListenerRecords.add(record);
1239        }
1240    }
1241
1242    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1243        synchronized (mLock) {
1244            for (SystemAudioModeChangeListenerRecord record :
1245                    mSystemAudioModeChangeListenerRecords) {
1246                if (record.mListener.asBinder() == listener) {
1247                    listener.asBinder().unlinkToDeath(record, 0);
1248                    mSystemAudioModeChangeListenerRecords.remove(record);
1249                    break;
1250                }
1251            }
1252            mSystemAudioModeChangeListeners.remove(listener);
1253        }
1254    }
1255
1256    private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1257        @Override
1258        public void binderDied() {
1259            synchronized (mLock) {
1260                mInputChangeListener = null;
1261            }
1262        }
1263    }
1264
1265    private void setInputChangeListener(IHdmiInputChangeListener listener) {
1266        synchronized (mLock) {
1267            mInputChangeListenerRecord = new InputChangeListenerRecord();
1268            try {
1269                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1270            } catch (RemoteException e) {
1271                Slog.w(TAG, "Listener already died");
1272                return;
1273            }
1274            mInputChangeListener = listener;
1275        }
1276    }
1277
1278    void invokeInputChangeListener(HdmiCecDeviceInfo info) {
1279        synchronized (mLock) {
1280            if (mInputChangeListener != null) {
1281                try {
1282                    mInputChangeListener.onChanged(info);
1283                } catch (RemoteException e) {
1284                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1285                }
1286            }
1287        }
1288    }
1289
1290    private void setHdmiRecordListener(IHdmiRecordListener listener) {
1291        synchronized (mLock) {
1292            mRecordListenerRecord = new HdmiRecordListenerRecord();
1293            try {
1294                listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1295            } catch (RemoteException e) {
1296                Slog.w(TAG, "Listener already died.", e);
1297            }
1298            mRecordListener = listener;
1299        }
1300    }
1301
1302    byte[] invokeRecordRequestListener(int recorderAddress) {
1303        synchronized (mLock) {
1304            if (mRecordListener != null) {
1305                try {
1306                    return mRecordListener.getOneTouchRecordSource(recorderAddress);
1307                } catch (RemoteException e) {
1308                    Slog.w(TAG, "Failed to start record.", e);
1309                }
1310            }
1311            return EmptyArray.BYTE;
1312        }
1313    }
1314
1315    void invokeOneTouchRecordResult(int result) {
1316        synchronized (mLock) {
1317            if (mRecordListener != null) {
1318                try {
1319                    mRecordListener.onOneTouchRecordResult(result);
1320                } catch (RemoteException e) {
1321                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1322                }
1323            }
1324        }
1325    }
1326
1327    void invokeTimerRecordingResult(int result) {
1328        synchronized (mLock) {
1329            if (mRecordListener != null) {
1330                try {
1331                    mRecordListener.onTimerRecordingResult(result);
1332                } catch (RemoteException e) {
1333                    Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1334                }
1335            }
1336        }
1337    }
1338
1339    void invokeClearTimerRecordingResult(int result) {
1340        synchronized (mLock) {
1341            if (mRecordListener != null) {
1342                try {
1343                    mRecordListener.onClearTimerRecordingResult(result);
1344                } catch (RemoteException e) {
1345                    Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1346                }
1347            }
1348        }
1349    }
1350
1351    private void invokeCallback(IHdmiControlCallback callback, int result) {
1352        try {
1353            callback.onComplete(result);
1354        } catch (RemoteException e) {
1355            Slog.e(TAG, "Invoking callback failed:" + e);
1356        }
1357    }
1358
1359    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
1360            boolean enabled) {
1361        try {
1362            listener.onStatusChanged(enabled);
1363        } catch (RemoteException e) {
1364            Slog.e(TAG, "Invoking callback failed:" + e);
1365        }
1366    }
1367
1368    private void announceHotplugEvent(int portId, boolean connected) {
1369        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1370        synchronized (mLock) {
1371            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
1372                invokeHotplugEventListenerLocked(listener, event);
1373            }
1374        }
1375    }
1376
1377    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1378            HdmiHotplugEvent event) {
1379        try {
1380            listener.onReceived(event);
1381        } catch (RemoteException e) {
1382            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1383        }
1384    }
1385
1386    private HdmiCecLocalDeviceTv tv() {
1387        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_TV);
1388    }
1389
1390    boolean isTvDevice() {
1391        return tv() != null;
1392    }
1393
1394    private HdmiCecLocalDevicePlayback playback() {
1395        return (HdmiCecLocalDevicePlayback)
1396                mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_PLAYBACK);
1397    }
1398
1399    AudioManager getAudioManager() {
1400        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1401    }
1402
1403    boolean isControlEnabled() {
1404        synchronized (mLock) {
1405            return mHdmiControlEnabled;
1406        }
1407    }
1408
1409    int getPowerStatus() {
1410        return mPowerStatus;
1411    }
1412
1413    boolean isPowerOnOrTransient() {
1414        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1415                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1416    }
1417
1418    boolean isPowerStandbyOrTransient() {
1419        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1420                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1421    }
1422
1423    boolean isPowerStandby() {
1424        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1425    }
1426
1427    @ServiceThreadOnly
1428    void wakeUp() {
1429        assertRunOnServiceThread();
1430        mWakeUpMessageReceived = true;
1431        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1432        pm.wakeUp(SystemClock.uptimeMillis());
1433        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1434        // the intent, the sequence will continue at onWakeUp().
1435    }
1436
1437    @ServiceThreadOnly
1438    void standby() {
1439        assertRunOnServiceThread();
1440        mStandbyMessageReceived = true;
1441        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1442        pm.goToSleep(SystemClock.uptimeMillis());
1443        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1444        // the intent, the sequence will continue at onStandby().
1445    }
1446
1447    @ServiceThreadOnly
1448    private void onWakeUp() {
1449        assertRunOnServiceThread();
1450        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1451        if (mCecController != null) {
1452            if (mHdmiControlEnabled) {
1453                int startReason = INITIATED_BY_SCREEN_ON;
1454                if (mWakeUpMessageReceived) {
1455                    startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1456                }
1457                initializeCec(startReason);
1458            }
1459        } else {
1460            Slog.i(TAG, "Device does not support HDMI-CEC.");
1461        }
1462        // TODO: Initialize MHL local devices.
1463    }
1464
1465    @ServiceThreadOnly
1466    private void onStandby() {
1467        assertRunOnServiceThread();
1468        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1469
1470        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1471        disableDevices(new PendingActionClearedCallback() {
1472            @Override
1473            public void onCleared(HdmiCecLocalDevice device) {
1474                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1475                devices.remove(device);
1476                if (devices.isEmpty()) {
1477                    onStandbyCompleted();
1478                    // We will not clear local devices here, since some OEM/SOC will keep passing
1479                    // the received packets until the application processor enters to the sleep
1480                    // actually.
1481                }
1482            }
1483        });
1484    }
1485
1486    private void disableDevices(PendingActionClearedCallback callback) {
1487        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1488            device.disableDevice(mStandbyMessageReceived, callback);
1489        }
1490    }
1491
1492    @ServiceThreadOnly
1493    private void clearLocalDevices() {
1494        assertRunOnServiceThread();
1495        if (mCecController == null) {
1496            return;
1497        }
1498        mCecController.clearLogicalAddress();
1499        mCecController.clearLocalDevices();
1500    }
1501
1502    @ServiceThreadOnly
1503    private void onStandbyCompleted() {
1504        assertRunOnServiceThread();
1505        Slog.v(TAG, "onStandbyCompleted");
1506
1507        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1508            return;
1509        }
1510        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1511        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1512            device.onStandby(mStandbyMessageReceived);
1513        }
1514        mStandbyMessageReceived = false;
1515        mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED);
1516    }
1517
1518    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1519        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1520        try {
1521            listener.asBinder().linkToDeath(record, 0);
1522        } catch (RemoteException e) {
1523            Slog.w(TAG, "Listener already died");
1524            return;
1525        }
1526        synchronized (mLock) {
1527            mVendorCommandListenerRecords.add(record);
1528        }
1529    }
1530
1531    void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1532            boolean hasVendorId) {
1533        synchronized (mLock) {
1534            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1535                if (record.mDeviceType != deviceType) {
1536                    continue;
1537                }
1538                try {
1539                    record.mListener.onReceived(srcAddress, params, hasVendorId);
1540                } catch (RemoteException e) {
1541                    Slog.e(TAG, "Failed to notify vendor command reception", e);
1542                }
1543            }
1544        }
1545    }
1546
1547    boolean isProhibitMode() {
1548        synchronized (mLock) {
1549            return mProhibitMode;
1550        }
1551    }
1552
1553    void setProhibitMode(boolean enabled) {
1554        synchronized (mLock) {
1555            mProhibitMode = enabled;
1556        }
1557    }
1558
1559    @ServiceThreadOnly
1560    private void handleHdmiControlStatusChanged(boolean enabled) {
1561        assertRunOnServiceThread();
1562
1563        int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED;
1564        mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value);
1565        if (mMhlController != null) {
1566            mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value);
1567        }
1568
1569        synchronized (mLock) {
1570            mHdmiControlEnabled = enabled;
1571        }
1572
1573        if (enabled) {
1574            initializeCec(INITIATED_BY_ENABLE_CEC);
1575        } else {
1576            disableDevices(new PendingActionClearedCallback() {
1577                @Override
1578                public void onCleared(HdmiCecLocalDevice device) {
1579                    assertRunOnServiceThread();
1580                    clearLocalDevices();
1581                }
1582            });
1583        }
1584    }
1585}
1586