HdmiControlService.java revision 7ecfbaed6e902aea151bc1919cf7771bbd868fc4
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.hdmi;
18
19import android.annotation.Nullable;
20import android.content.BroadcastReceiver;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.hardware.hdmi.HdmiCecDeviceInfo;
26import android.hardware.hdmi.HdmiControlManager;
27import android.hardware.hdmi.HdmiHotplugEvent;
28import android.hardware.hdmi.HdmiPortInfo;
29import android.hardware.hdmi.HdmiTvClient;
30import android.hardware.hdmi.IHdmiControlCallback;
31import android.hardware.hdmi.IHdmiControlService;
32import android.hardware.hdmi.IHdmiDeviceEventListener;
33import android.hardware.hdmi.IHdmiHotplugEventListener;
34import android.hardware.hdmi.IHdmiInputChangeListener;
35import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
36import android.hardware.hdmi.IHdmiVendorCommandListener;
37import android.media.AudioManager;
38import android.os.Build;
39import android.os.Handler;
40import android.os.HandlerThread;
41import android.os.IBinder;
42import android.os.Looper;
43import android.os.PowerManager;
44import android.os.RemoteException;
45import android.os.SystemClock;
46import android.provider.Settings.Global;
47import android.provider.Settings.SettingNotFoundException;
48import android.util.Slog;
49import android.util.SparseArray;
50import android.util.SparseIntArray;
51
52import com.android.internal.annotations.GuardedBy;
53import com.android.server.SystemService;
54import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
55import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
56import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
57
58import java.util.ArrayList;
59import java.util.Collections;
60import java.util.List;
61
62/**
63 * Provides a service for sending and processing HDMI control messages,
64 * HDMI-CEC and MHL control command, and providing the information on both standard.
65 */
66public final class HdmiControlService extends SystemService {
67    private static final String TAG = "HdmiControlService";
68
69    static final String PERMISSION = "android.permission.HDMI_CEC";
70
71    /**
72     * Interface to report send result.
73     */
74    interface SendMessageCallback {
75        /**
76         * Called when {@link HdmiControlService#sendCecCommand} is completed.
77         *
78         * @param error result of send request.
79         * <ul>
80         * <li>{@link Constants#SEND_RESULT_SUCCESS}
81         * <li>{@link Constants#SEND_RESULT_NAK}
82         * <li>{@link Constants#SEND_RESULT_FAILURE}
83         * </ul>
84         */
85        void onSendCompleted(int error);
86    }
87
88    /**
89     * Interface to get a list of available logical devices.
90     */
91    interface DevicePollingCallback {
92        /**
93         * Called when device polling is finished.
94         *
95         * @param ackedAddress a list of logical addresses of available devices
96         */
97        void onPollingFinished(List<Integer> ackedAddress);
98    }
99
100    private class PowerStateReceiver extends BroadcastReceiver {
101        @Override
102        public void onReceive(Context context, Intent intent) {
103            switch (intent.getAction()) {
104                case Intent.ACTION_SCREEN_OFF:
105                    if (isPowerOnOrTransient()) {
106                        onStandby();
107                    }
108                    break;
109                case Intent.ACTION_SCREEN_ON:
110                    if (isPowerStandbyOrTransient()) {
111                        onWakeUp();
112                    }
113                    break;
114            }
115        }
116    }
117
118    // A thread to handle synchronous IO of CEC and MHL control service.
119    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
120    // and sparse call it shares a thread to handle IO operations.
121    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
122
123    // Used to synchronize the access to the service.
124    private final Object mLock = new Object();
125
126    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
127    private final List<Integer> mLocalDevices;
128
129    // List of listeners registered by callers that want to get notified of
130    // hotplug events.
131    @GuardedBy("mLock")
132    private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
133
134    // List of records for hotplug event listener to handle the the caller killed in action.
135    @GuardedBy("mLock")
136    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
137            new ArrayList<>();
138
139    // List of listeners registered by callers that want to get notified of
140    // device status events.
141    @GuardedBy("mLock")
142    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
143
144    // List of records for device event listener to handle the the caller killed in action.
145    @GuardedBy("mLock")
146    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
147            new ArrayList<>();
148
149    // List of records for vendor command listener to handle the the caller killed in action.
150    @GuardedBy("mLock")
151    private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
152            new ArrayList<>();
153
154    @GuardedBy("mLock")
155    private IHdmiInputChangeListener mInputChangeListener;
156
157    @GuardedBy("mLock")
158    private InputChangeListenerRecord mInputChangeListenerRecord;
159
160    // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
161    // handling will be disabled and no request will be handled.
162    @GuardedBy("mLock")
163    private boolean mHdmiControlEnabled;
164
165    // Set to true while the service is in normal mode. While set to false, no input change is
166    // allowed. Used for situations where input change can confuse users such as channel auto-scan,
167    // system upgrade, etc., a.k.a. "prohibit mode".
168    @GuardedBy("mLock")
169    private boolean mProhibitMode;
170
171    // List of listeners registered by callers that want to get notified of
172    // system audio mode changes.
173    private final ArrayList<IHdmiSystemAudioModeChangeListener>
174            mSystemAudioModeChangeListeners = new ArrayList<>();
175    // List of records for system audio mode change to handle the the caller killed in action.
176    private final ArrayList<SystemAudioModeChangeListenerRecord>
177            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
178
179    // Handler used to run a task in service thread.
180    private final Handler mHandler = new Handler();
181
182    @Nullable
183    private HdmiCecController mCecController;
184
185    @Nullable
186    private HdmiMhlController mMhlController;
187
188    // HDMI port information. Stored in the unmodifiable list to keep the static information
189    // from being modified.
190    private List<HdmiPortInfo> mPortInfo;
191
192    private HdmiCecMessageValidator mMessageValidator;
193
194    private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver();
195
196    @ServiceThreadOnly
197    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
198
199    @ServiceThreadOnly
200    private boolean mStandbyMessageReceived = false;
201
202    public HdmiControlService(Context context) {
203        super(context);
204        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
205                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
206    }
207
208    @Override
209    public void onStart() {
210        mIoThread.start();
211        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
212        mProhibitMode = false;
213        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
214        mCecController = HdmiCecController.create(this);
215
216        if (mCecController != null) {
217            // TODO: Remove this as soon as OEM's HAL implementation is corrected.
218            mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE,
219                    HdmiTvClient.ENABLED);
220
221            mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL,
222                    HdmiTvClient.ENABLED);
223            initializeLocalDevices(mLocalDevices);
224        } else {
225            Slog.i(TAG, "Device does not support HDMI-CEC.");
226        }
227
228        mMhlController = HdmiMhlController.create(this);
229        if (mMhlController == null) {
230            Slog.i(TAG, "Device does not support MHL-control.");
231        }
232        mPortInfo = initPortInfo();
233        mMessageValidator = new HdmiCecMessageValidator(this);
234        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
235
236        // Register broadcast receiver for power state change.
237        if (mCecController != null || mMhlController != null) {
238            IntentFilter filter = new IntentFilter();
239            filter.addAction(Intent.ACTION_SCREEN_OFF);
240            filter.addAction(Intent.ACTION_SCREEN_ON);
241            getContext().registerReceiver(mPowerStateReceiver, filter);
242        }
243    }
244
245    boolean readBooleanSetting(String key, boolean defVal) {
246        ContentResolver cr = getContext().getContentResolver();
247        return Global.getInt(cr, key, defVal ? Constants.TRUE : Constants.FALSE) == Constants.TRUE;
248    }
249
250    void writeBooleanSetting(String key, boolean value) {
251        ContentResolver cr = getContext().getContentResolver();
252        Global.putInt(cr, key, value ? Constants.TRUE : Constants.FALSE);
253    }
254
255    @ServiceThreadOnly
256    private void initializeLocalDevices(final List<Integer> deviceTypes) {
257        assertRunOnServiceThread();
258        // A container for [Logical Address, Local device info].
259        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
260        final SparseIntArray finished = new SparseIntArray();
261        mCecController.clearLogicalAddress();
262        for (int type : deviceTypes) {
263            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
264            localDevice.init();
265            mCecController.allocateLogicalAddress(type,
266                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
267                @Override
268                public void onAllocated(int deviceType, int logicalAddress) {
269                    if (logicalAddress == Constants.ADDR_UNREGISTERED) {
270                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
271                    } else {
272                        HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType);
273                        localDevice.setDeviceInfo(deviceInfo);
274                        mCecController.addLocalDevice(deviceType, localDevice);
275                        mCecController.addLogicalAddress(logicalAddress);
276                        devices.append(logicalAddress, localDevice);
277                    }
278                    finished.append(deviceType, logicalAddress);
279
280                    // Address allocation completed for all devices. Notify each device.
281                    if (deviceTypes.size() == finished.size()) {
282                        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
283                            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
284                        }
285                        notifyAddressAllocated(devices);
286                    }
287                }
288            });
289        }
290    }
291
292    @ServiceThreadOnly
293    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) {
294        assertRunOnServiceThread();
295        for (int i = 0; i < devices.size(); ++i) {
296            int address = devices.keyAt(i);
297            HdmiCecLocalDevice device = devices.valueAt(i);
298            device.handleAddressAllocated(address);
299        }
300    }
301
302    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
303    // keep them in one place.
304    @ServiceThreadOnly
305    private List<HdmiPortInfo> initPortInfo() {
306        assertRunOnServiceThread();
307        HdmiPortInfo[] cecPortInfo = null;
308
309        // CEC HAL provides majority of the info while MHL does only MHL support flag for
310        // each port. Return empty array if CEC HAL didn't provide the info.
311        if (mCecController != null) {
312            cecPortInfo = mCecController.getPortInfos();
313        }
314        if (cecPortInfo == null) {
315            return Collections.emptyList();
316        }
317
318        HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0];
319        if (mMhlController != null) {
320            // TODO: Implement plumbing logic to get MHL port information.
321            // mhlPortInfo = mMhlController.getPortInfos();
322        }
323
324        // Use the id (port number) to find the matched info between CEC and MHL to combine them
325        // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found.
326        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
327        for (int i = 0; i < cecPortInfo.length; ++i) {
328            HdmiPortInfo cec = cecPortInfo[i];
329            int id = cec.getId();
330            boolean mhlInfoFound = false;
331            for (HdmiPortInfo mhl : mhlPortInfo) {
332                if (id == mhl.getId()) {
333                    result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(),
334                            cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported()));
335                    mhlInfoFound = true;
336                    break;
337                }
338            }
339            if (!mhlInfoFound) {
340                result.add(cec);
341            }
342        }
343
344        return Collections.unmodifiableList(result);
345    }
346
347    /**
348     * Returns HDMI port information for the given port id.
349     *
350     * @param portId HDMI port id
351     * @return {@link HdmiPortInfo} for the given port
352     */
353    HdmiPortInfo getPortInfo(int portId) {
354        // mPortInfo is an unmodifiable list and the only reference to its inner list.
355        // No lock is necessary.
356        for (HdmiPortInfo info : mPortInfo) {
357            if (portId == info.getId()) {
358                return info;
359            }
360        }
361        return null;
362    }
363
364    /**
365     * Returns the routing path (physical address) of the HDMI port for the given
366     * port id.
367     */
368    int portIdToPath(int portId) {
369        HdmiPortInfo portInfo = getPortInfo(portId);
370        if (portInfo == null) {
371            Slog.e(TAG, "Cannot find the port info: " + portId);
372            return Constants.INVALID_PHYSICAL_ADDRESS;
373        }
374        return portInfo.getAddress();
375    }
376
377    /**
378     * Returns the id of HDMI port located at the top of the hierarchy of
379     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
380     * the port id to be returned is the ID associated with the port address
381     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
382     */
383    int pathToPortId(int path) {
384        int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
385        for (HdmiPortInfo info : mPortInfo) {
386            if (portAddress == info.getAddress()) {
387                return info.getId();
388            }
389        }
390        return Constants.INVALID_PORT_ID;
391    }
392
393    /**
394     * Returns {@link Looper} for IO operation.
395     *
396     * <p>Declared as package-private.
397     */
398    Looper getIoLooper() {
399        return mIoThread.getLooper();
400    }
401
402    /**
403     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
404     * for tasks that are running on main service thread.
405     *
406     * <p>Declared as package-private.
407     */
408    Looper getServiceLooper() {
409        return mHandler.getLooper();
410    }
411
412    /**
413     * Returns physical address of the device.
414     */
415    int getPhysicalAddress() {
416        return mCecController.getPhysicalAddress();
417    }
418
419    /**
420     * Returns vendor id of CEC service.
421     */
422    int getVendorId() {
423        return mCecController.getVendorId();
424    }
425
426    @ServiceThreadOnly
427    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
428        assertRunOnServiceThread();
429        HdmiCecLocalDeviceTv tv = tv();
430        if (tv == null) {
431            return null;
432        }
433        return tv.getDeviceInfo(logicalAddress);
434    }
435
436    /**
437     * Returns version of CEC.
438     */
439    int getCecVersion() {
440        return mCecController.getVersion();
441    }
442
443    /**
444     * Whether a device of the specified physical address is connected to ARC enabled port.
445     */
446    boolean isConnectedToArcPort(int physicalAddress) {
447        for (HdmiPortInfo portInfo : mPortInfo) {
448            if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
449                    && portInfo.isArcSupported()) {
450                return true;
451            }
452        }
453        return false;
454    }
455
456    void runOnServiceThread(Runnable runnable) {
457        mHandler.post(runnable);
458    }
459
460    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
461        mHandler.postAtFrontOfQueue(runnable);
462    }
463
464    private void assertRunOnServiceThread() {
465        if (Looper.myLooper() != mHandler.getLooper()) {
466            throw new IllegalStateException("Should run on service thread.");
467        }
468    }
469
470    /**
471     * Transmit a CEC command to CEC bus.
472     *
473     * @param command CEC command to send out
474     * @param callback interface used to the result of send command
475     */
476    @ServiceThreadOnly
477    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
478        assertRunOnServiceThread();
479        mCecController.sendCommand(command, callback);
480    }
481
482    @ServiceThreadOnly
483    void sendCecCommand(HdmiCecMessage command) {
484        assertRunOnServiceThread();
485        mCecController.sendCommand(command, null);
486    }
487
488    @ServiceThreadOnly
489    boolean handleCecCommand(HdmiCecMessage message) {
490        assertRunOnServiceThread();
491        if (!mMessageValidator.isValid(message)) {
492            return false;
493        }
494        return dispatchMessageToLocalDevice(message);
495    }
496
497    void setAudioReturnChannel(boolean enabled) {
498        mCecController.setAudioReturnChannel(enabled);
499    }
500
501    @ServiceThreadOnly
502    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
503        assertRunOnServiceThread();
504        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
505            if (device.dispatchMessage(message)
506                    && message.getDestination() != Constants.ADDR_BROADCAST) {
507                return true;
508            }
509        }
510
511        if (message.getDestination() != Constants.ADDR_BROADCAST) {
512            Slog.w(TAG, "Unhandled cec command:" + message);
513        }
514        return false;
515    }
516
517    /**
518     * Called when a new hotplug event is issued.
519     *
520     * @param portNo hdmi port number where hot plug event issued.
521     * @param connected whether to be plugged in or not
522     */
523    @ServiceThreadOnly
524    void onHotplug(int portNo, boolean connected) {
525        assertRunOnServiceThread();
526        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
527            device.onHotplug(portNo, connected);
528        }
529        announceHotplugEvent(portNo, connected);
530    }
531
532    /**
533     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
534     * devices.
535     *
536     * @param callback an interface used to get a list of all remote devices' address
537     * @param sourceAddress a logical address of source device where sends polling message
538     * @param pickStrategy strategy how to pick polling candidates
539     * @param retryCount the number of retry used to send polling message to remote devices
540     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
541     */
542    @ServiceThreadOnly
543    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
544            int retryCount) {
545        assertRunOnServiceThread();
546        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
547                retryCount);
548    }
549
550    private int checkPollStrategy(int pickStrategy) {
551        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
552        if (strategy == 0) {
553            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
554        }
555        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
556        if (iterationStrategy == 0) {
557            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
558        }
559        return strategy | iterationStrategy;
560    }
561
562    List<HdmiCecLocalDevice> getAllLocalDevices() {
563        assertRunOnServiceThread();
564        return mCecController.getLocalDeviceList();
565    }
566
567    Object getServiceLock() {
568        return mLock;
569    }
570
571    void setAudioStatus(boolean mute, int volume) {
572        // TODO: Hook up with AudioManager.
573    }
574
575    void announceSystemAudioModeChange(boolean enabled) {
576        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
577            invokeSystemAudioModeChange(listener, enabled);
578        }
579    }
580
581    private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
582        // TODO: find better name instead of model name.
583        String displayName = Build.MODEL;
584        return new HdmiCecDeviceInfo(logicalAddress,
585                getPhysicalAddress(), deviceType, getVendorId(), displayName);
586    }
587
588    // Record class that monitors the event of the caller of being killed. Used to clean up
589    // the listener list and record list accordingly.
590    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
591        private final IHdmiHotplugEventListener mListener;
592
593        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
594            mListener = listener;
595        }
596
597        @Override
598        public void binderDied() {
599            synchronized (mLock) {
600                mHotplugEventListenerRecords.remove(this);
601                mHotplugEventListeners.remove(mListener);
602            }
603        }
604    }
605
606    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
607        private final IHdmiDeviceEventListener mListener;
608
609        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
610            mListener = listener;
611        }
612
613        @Override
614        public void binderDied() {
615            synchronized (mLock) {
616                mDeviceEventListenerRecords.remove(this);
617                mDeviceEventListeners.remove(mListener);
618            }
619        }
620    }
621
622    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
623        private final IHdmiSystemAudioModeChangeListener mListener;
624
625        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
626            mListener = listener;
627        }
628
629        @Override
630        public void binderDied() {
631            synchronized (mLock) {
632                mSystemAudioModeChangeListenerRecords.remove(this);
633                mSystemAudioModeChangeListeners.remove(mListener);
634            }
635        }
636    }
637
638    class VendorCommandListenerRecord implements IBinder.DeathRecipient {
639        private final IHdmiVendorCommandListener mListener;
640        private final int mDeviceType;
641
642        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
643            mListener = listener;
644            mDeviceType = deviceType;
645        }
646
647        @Override
648        public void binderDied() {
649            synchronized (mLock) {
650                mVendorCommandListenerRecords.remove(this);
651            }
652        }
653    }
654
655    private void enforceAccessPermission() {
656        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
657    }
658
659    private final class BinderService extends IHdmiControlService.Stub {
660        @Override
661        public int[] getSupportedTypes() {
662            enforceAccessPermission();
663            // mLocalDevices is an unmodifiable list - no lock necesary.
664            int[] localDevices = new int[mLocalDevices.size()];
665            for (int i = 0; i < localDevices.length; ++i) {
666                localDevices[i] = mLocalDevices.get(i);
667            }
668            return localDevices;
669        }
670
671        @Override
672        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
673            enforceAccessPermission();
674            runOnServiceThread(new Runnable() {
675                @Override
676                public void run() {
677                    HdmiCecLocalDeviceTv tv = tv();
678                    if (tv == null) {
679                        Slog.w(TAG, "Local tv device not available");
680                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
681                        return;
682                    }
683                    tv.deviceSelect(logicalAddress, callback);
684                }
685            });
686        }
687
688        @Override
689        public void portSelect(final int portId, final IHdmiControlCallback callback) {
690            enforceAccessPermission();
691            runOnServiceThread(new Runnable() {
692                @Override
693                public void run() {
694                    HdmiCecLocalDeviceTv tv = tv();
695                    if (tv == null) {
696                        Slog.w(TAG, "Local tv device not available");
697                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
698                        return;
699                    }
700                    tv.doManualPortSwitching(portId, callback);
701                }
702            });
703        }
704
705        @Override
706        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
707            enforceAccessPermission();
708            runOnServiceThread(new Runnable() {
709                @Override
710                public void run() {
711                    HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
712                    if (localDevice == null) {
713                        Slog.w(TAG, "Local device not available");
714                        return;
715                    }
716                    localDevice.sendKeyEvent(keyCode, isPressed);
717                }
718            });
719        }
720
721        @Override
722        public void oneTouchPlay(final IHdmiControlCallback callback) {
723            enforceAccessPermission();
724            runOnServiceThread(new Runnable() {
725                @Override
726                public void run() {
727                    HdmiControlService.this.oneTouchPlay(callback);
728                }
729            });
730        }
731
732        @Override
733        public void queryDisplayStatus(final IHdmiControlCallback callback) {
734            enforceAccessPermission();
735            runOnServiceThread(new Runnable() {
736                @Override
737                public void run() {
738                    HdmiControlService.this.queryDisplayStatus(callback);
739                }
740            });
741        }
742
743        @Override
744        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
745            enforceAccessPermission();
746            runOnServiceThread(new Runnable() {
747                @Override
748                public void run() {
749                    HdmiControlService.this.addHotplugEventListener(listener);
750                }
751            });
752        }
753
754        @Override
755        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
756            enforceAccessPermission();
757            runOnServiceThread(new Runnable() {
758                @Override
759                public void run() {
760                    HdmiControlService.this.removeHotplugEventListener(listener);
761                }
762            });
763        }
764
765        @Override
766        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
767            enforceAccessPermission();
768            runOnServiceThread(new Runnable() {
769                @Override
770                public void run() {
771                    HdmiControlService.this.addDeviceEventListener(listener);
772                }
773            });
774        }
775
776        @Override
777        public List<HdmiPortInfo> getPortInfo() {
778            enforceAccessPermission();
779            return mPortInfo;
780        }
781
782        @Override
783        public boolean canChangeSystemAudioMode() {
784            enforceAccessPermission();
785            HdmiCecLocalDeviceTv tv = tv();
786            if (tv == null) {
787                return false;
788            }
789            return tv.hasSystemAudioDevice();
790        }
791
792        @Override
793        public boolean getSystemAudioMode() {
794            enforceAccessPermission();
795            HdmiCecLocalDeviceTv tv = tv();
796            if (tv == null) {
797                return false;
798            }
799            return tv.getSystemAudioMode();
800        }
801
802        @Override
803        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
804            enforceAccessPermission();
805            runOnServiceThread(new Runnable() {
806                @Override
807                public void run() {
808                    HdmiCecLocalDeviceTv tv = tv();
809                    if (tv == null) {
810                        Slog.w(TAG, "Local tv device not available");
811                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
812                        return;
813                    }
814                    tv.changeSystemAudioMode(enabled, callback);
815                }
816            });
817        }
818
819        @Override
820        public void addSystemAudioModeChangeListener(
821                final IHdmiSystemAudioModeChangeListener listener) {
822            enforceAccessPermission();
823            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
824        }
825
826        @Override
827        public void removeSystemAudioModeChangeListener(
828                final IHdmiSystemAudioModeChangeListener listener) {
829            enforceAccessPermission();
830            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
831        }
832
833        @Override
834        public void setInputChangeListener(final IHdmiInputChangeListener listener) {
835            enforceAccessPermission();
836            HdmiControlService.this.setInputChangeListener(listener);
837        }
838
839        @Override
840        public List<HdmiCecDeviceInfo> getInputDevices() {
841            enforceAccessPermission();
842            // No need to hold the lock for obtaining TV device as the local device instance
843            // is preserved while the HDMI control is enabled.
844            HdmiCecLocalDeviceTv tv = tv();
845            if (tv == null) {
846                return Collections.emptyList();
847            }
848            return tv.getSafeExternalInputs();
849        }
850
851        @Override
852        public void setControlEnabled(final boolean enabled) {
853            enforceAccessPermission();
854            runOnServiceThread(new Runnable() {
855                @Override
856                public void run() {
857                    handleHdmiControlStatusChanged(enabled);
858
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
1239        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1240        disableDevices(new PendingActionClearedCallback() {
1241            @Override
1242            public void onCleared(HdmiCecLocalDevice device) {
1243                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1244                devices.remove(device);
1245                if (devices.isEmpty()) {
1246                    clearLocalDevices();
1247                    onStandbyCompleted();
1248                }
1249            }
1250        });
1251    }
1252
1253    private void disableDevices(PendingActionClearedCallback callback) {
1254        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1255            device.disableDevice(mStandbyMessageReceived, callback);
1256        }
1257    }
1258
1259    @ServiceThreadOnly
1260    private void clearLocalDevices() {
1261        assertRunOnServiceThread();
1262        if (mCecController == null) {
1263            return;
1264        }
1265        mCecController.clearLogicalAddress();
1266        mCecController.clearLocalDevices();
1267    }
1268
1269    @ServiceThreadOnly
1270    private void onStandbyCompleted() {
1271        assertRunOnServiceThread();
1272        Slog.v(TAG, "onStandbyCompleted");
1273
1274        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1275            return;
1276        }
1277        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1278        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1279            device.onStandby(mStandbyMessageReceived);
1280        }
1281        mStandbyMessageReceived = false;
1282        mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED);
1283    }
1284
1285    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1286        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1287        try {
1288            listener.asBinder().linkToDeath(record, 0);
1289        } catch (RemoteException e) {
1290            Slog.w(TAG, "Listener already died");
1291            return;
1292        }
1293        synchronized (mLock) {
1294            mVendorCommandListenerRecords.add(record);
1295        }
1296    }
1297
1298    void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1299            boolean hasVendorId) {
1300        synchronized (mLock) {
1301            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1302                if (record.mDeviceType != deviceType) {
1303                    continue;
1304                }
1305                try {
1306                    record.mListener.onReceived(srcAddress, params, hasVendorId);
1307                } catch (RemoteException e) {
1308                    Slog.e(TAG, "Failed to notify vendor command reception", e);
1309                }
1310            }
1311        }
1312    }
1313
1314    boolean isProhibitMode() {
1315        synchronized (mLock) {
1316            return mProhibitMode;
1317        }
1318    }
1319
1320    void setProhibitMode(boolean enabled) {
1321        synchronized (mLock) {
1322            mProhibitMode = enabled;
1323        }
1324    }
1325
1326    @ServiceThreadOnly
1327    private void handleHdmiControlStatusChanged(boolean enabled) {
1328        assertRunOnServiceThread();
1329
1330        int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED;
1331        mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value);
1332        if (mMhlController != null) {
1333            mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value);
1334        }
1335
1336        synchronized (mLock) {
1337            mHdmiControlEnabled = enabled;
1338        }
1339
1340        if (enabled) {
1341            // TODO: call initalizedLocalDevice with additional param once putting
1342            // it to address allocation result.
1343            HdmiCecLocalDeviceTv tv = tv();
1344            if (tv != null) {
1345                tv.launchRoutingControl(false);
1346            }
1347        } else {
1348            disableDevices(new PendingActionClearedCallback() {
1349                @Override
1350                public void onCleared(HdmiCecLocalDevice device) {
1351                    assertRunOnServiceThread();
1352                    clearLocalDevices();
1353                }
1354            });
1355        }
1356    }
1357}
1358