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