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