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