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