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