HdmiControlService.java revision 7df52862dae1fa33c84725c613b0d9b88c1b28b6
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 static com.android.server.hdmi.Constants.DISABLED;
20import static com.android.server.hdmi.Constants.ENABLED;
21import static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_DEVICE_OFF;
22import static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_WAKEUP;
23import static com.android.server.hdmi.Constants.OPTION_CEC_ENABLE;
24import static com.android.server.hdmi.Constants.OPTION_CEC_SERVICE_CONTROL;
25import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
26import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
27import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
28
29import android.annotation.Nullable;
30import android.content.BroadcastReceiver;
31import android.content.ContentResolver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.database.ContentObserver;
36import android.hardware.hdmi.HdmiDeviceInfo;
37import android.hardware.hdmi.HdmiControlManager;
38import android.hardware.hdmi.HdmiHotplugEvent;
39import android.hardware.hdmi.HdmiPortInfo;
40import android.hardware.hdmi.IHdmiControlCallback;
41import android.hardware.hdmi.IHdmiControlService;
42import android.hardware.hdmi.IHdmiDeviceEventListener;
43import android.hardware.hdmi.IHdmiHotplugEventListener;
44import android.hardware.hdmi.IHdmiInputChangeListener;
45import android.hardware.hdmi.IHdmiRecordListener;
46import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
47import android.hardware.hdmi.IHdmiVendorCommandListener;
48import android.media.AudioManager;
49import android.net.Uri;
50import android.os.Build;
51import android.os.Handler;
52import android.os.HandlerThread;
53import android.os.IBinder;
54import android.os.Looper;
55import android.os.PowerManager;
56import android.os.RemoteException;
57import android.os.SystemClock;
58import android.os.UserHandle;
59import android.provider.Settings.Global;
60import android.util.ArraySet;
61import android.util.Slog;
62import android.util.SparseArray;
63import android.util.SparseIntArray;
64
65import com.android.internal.annotations.GuardedBy;
66import com.android.server.SystemService;
67import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
68import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
69import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
70import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
71
72import libcore.util.EmptyArray;
73
74import java.util.ArrayList;
75import java.util.Arrays;
76import java.util.Collections;
77import java.util.List;
78
79/**
80 * Provides a service for sending and processing HDMI control messages,
81 * HDMI-CEC and MHL control command, and providing the information on both standard.
82 */
83public final class HdmiControlService extends SystemService {
84    private static final String TAG = "HdmiControlService";
85
86    static final String PERMISSION = "android.permission.HDMI_CEC";
87
88    // The reason code to initiate intializeCec().
89    static final int INITIATED_BY_ENABLE_CEC = 0;
90    static final int INITIATED_BY_BOOT_UP = 1;
91    static final int INITIATED_BY_SCREEN_ON = 2;
92    static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
93
94    /**
95     * Interface to report send result.
96     */
97    interface SendMessageCallback {
98        /**
99         * Called when {@link HdmiControlService#sendCecCommand} is completed.
100         *
101         * @param error result of send request.
102         * <ul>
103         * <li>{@link Constants#SEND_RESULT_SUCCESS}
104         * <li>{@link Constants#SEND_RESULT_NAK}
105         * <li>{@link Constants#SEND_RESULT_FAILURE}
106         * </ul>
107         */
108        void onSendCompleted(int error);
109    }
110
111    /**
112     * Interface to get a list of available logical devices.
113     */
114    interface DevicePollingCallback {
115        /**
116         * Called when device polling is finished.
117         *
118         * @param ackedAddress a list of logical addresses of available devices
119         */
120        void onPollingFinished(List<Integer> ackedAddress);
121    }
122
123    private class PowerStateReceiver extends BroadcastReceiver {
124        @Override
125        public void onReceive(Context context, Intent intent) {
126            switch (intent.getAction()) {
127                case Intent.ACTION_SCREEN_OFF:
128                    if (isPowerOnOrTransient()) {
129                        onStandby();
130                    }
131                    break;
132                case Intent.ACTION_SCREEN_ON:
133                    if (isPowerStandbyOrTransient()) {
134                        onWakeUp();
135                    }
136                    break;
137            }
138        }
139    }
140
141    // A thread to handle synchronous IO of CEC and MHL control service.
142    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
143    // and sparse call it shares a thread to handle IO operations.
144    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
145
146    // Used to synchronize the access to the service.
147    private final Object mLock = new Object();
148
149    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
150    private final List<Integer> mLocalDevices;
151
152    // List of listeners registered by callers that want to get notified of
153    // hotplug events.
154    @GuardedBy("mLock")
155    private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
156
157    // List of records for hotplug event listener to handle the the caller killed in action.
158    @GuardedBy("mLock")
159    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
160            new ArrayList<>();
161
162    // List of listeners registered by callers that want to get notified of
163    // device status events.
164    @GuardedBy("mLock")
165    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
166
167    // List of records for device event listener to handle the the caller killed in action.
168    @GuardedBy("mLock")
169    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
170            new ArrayList<>();
171
172    // List of records for vendor command listener to handle the the caller killed in action.
173    @GuardedBy("mLock")
174    private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
175            new ArrayList<>();
176
177    @GuardedBy("mLock")
178    private IHdmiInputChangeListener mInputChangeListener;
179
180    @GuardedBy("mLock")
181    private InputChangeListenerRecord mInputChangeListenerRecord;
182
183    @GuardedBy("mLock")
184    private IHdmiRecordListener mRecordListener;
185
186    @GuardedBy("mLock")
187    private HdmiRecordListenerRecord mRecordListenerRecord;
188
189    // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
190    // handling will be disabled and no request will be handled.
191    @GuardedBy("mLock")
192    private boolean mHdmiControlEnabled;
193
194    // Set to true while the service is in normal mode. While set to false, no input change is
195    // allowed. Used for situations where input change can confuse users such as channel auto-scan,
196    // system upgrade, etc., a.k.a. "prohibit mode".
197    @GuardedBy("mLock")
198    private boolean mProhibitMode;
199
200    // List of listeners registered by callers that want to get notified of
201    // system audio mode changes.
202    private final ArrayList<IHdmiSystemAudioModeChangeListener>
203            mSystemAudioModeChangeListeners = new ArrayList<>();
204    // List of records for system audio mode change to handle the the caller killed in action.
205    private final ArrayList<SystemAudioModeChangeListenerRecord>
206            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
207
208    // Handler used to run a task in service thread.
209    private final Handler mHandler = new Handler();
210
211    private final SettingsObserver mSettingsObserver;
212
213    @Nullable
214    private HdmiCecController mCecController;
215
216    @Nullable
217    private HdmiMhlController mMhlController;
218
219    // HDMI port information. Stored in the unmodifiable list to keep the static information
220    // from being modified.
221    private List<HdmiPortInfo> mPortInfo;
222
223    // Map from path(physical address) to port ID.
224    private UnmodifiableSparseIntArray mPortIdMap;
225
226    // Map from port ID to HdmiPortInfo.
227    private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
228
229    private HdmiCecMessageValidator mMessageValidator;
230
231    private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver();
232
233    @ServiceThreadOnly
234    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
235
236    @ServiceThreadOnly
237    private boolean mStandbyMessageReceived = false;
238
239    @ServiceThreadOnly
240    private boolean mWakeUpMessageReceived = false;
241
242    public HdmiControlService(Context context) {
243        super(context);
244        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
245                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
246        mSettingsObserver = new SettingsObserver(mHandler);
247    }
248
249    @Override
250    public void onStart() {
251        mIoThread.start();
252        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
253        mProhibitMode = false;
254        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
255
256        mCecController = HdmiCecController.create(this);
257        if (mCecController != null) {
258            // TODO: Remove this as soon as OEM's HAL implementation is corrected.
259            mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
260
261            // TODO: load value for mHdmiControlEnabled from preference.
262            if (mHdmiControlEnabled) {
263                initializeCec(INITIATED_BY_BOOT_UP);
264            }
265        } else {
266            Slog.i(TAG, "Device does not support HDMI-CEC.");
267        }
268
269        mMhlController = HdmiMhlController.create(this);
270        if (mMhlController == null) {
271            Slog.i(TAG, "Device does not support MHL-control.");
272        }
273        initPortInfo();
274        mMessageValidator = new HdmiCecMessageValidator(this);
275        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
276
277        // Register broadcast receiver for power state change.
278        if (mCecController != null || mMhlController != null) {
279            IntentFilter filter = new IntentFilter();
280            filter.addAction(Intent.ACTION_SCREEN_OFF);
281            filter.addAction(Intent.ACTION_SCREEN_ON);
282            getContext().registerReceiver(mPowerStateReceiver, filter);
283        }
284    }
285
286    /**
287     * Called when the initialization of local devices is complete.
288     */
289    private void onInitializeCecComplete() {
290        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
291            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
292        }
293        mWakeUpMessageReceived = false;
294
295        if (isTvDevice()) {
296            mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
297            registerContentObserver();
298        }
299    }
300
301
302    private void registerContentObserver() {
303        ContentResolver resolver = getContext().getContentResolver();
304        String[] settings = new String[] {
305                Global.HDMI_CONTROL_ENABLED,
306                Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
307                Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
308                Global.MHL_INPUT_SWITCHING_ENABLED,
309                Global.MHL_POWER_CHARGE_ENABLED
310        };
311        for (String s: settings) {
312            resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
313                    UserHandle.USER_ALL);
314        }
315    }
316
317    private class SettingsObserver extends ContentObserver {
318        public SettingsObserver(Handler handler) {
319            super(handler);
320        }
321
322        @Override
323        public void onChange(boolean selfChange, Uri uri) {
324            String option = uri.getLastPathSegment();
325            boolean enabled = readBooleanSetting(option, true);
326            switch (option) {
327                case Global.HDMI_CONTROL_ENABLED:
328                    setControlEnabled(enabled);
329                    break;
330                case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
331                    tv().setAutoWakeup(enabled);
332                    setOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
333                    break;
334                case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
335                    tv().setAutoDeviceOff(enabled);
336                    // No need to propagate to HAL.
337                    break;
338                case Global.MHL_INPUT_SWITCHING_ENABLED:
339                    setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
340                    break;
341                case Global.MHL_POWER_CHARGE_ENABLED:
342                    setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
343                    break;
344            }
345        }
346    }
347
348    private static int toInt(boolean enabled) {
349        return enabled ? ENABLED : DISABLED;
350    }
351
352    boolean readBooleanSetting(String key, boolean defVal) {
353        ContentResolver cr = getContext().getContentResolver();
354        return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
355    }
356
357    void writeBooleanSetting(String key, boolean value) {
358        ContentResolver cr = getContext().getContentResolver();
359        Global.putInt(cr, key, toInt(value));
360    }
361
362    private void unregisterSettingsObserver() {
363        getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
364    }
365
366    private void initializeCec(int initiatedBy) {
367        mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
368        initializeLocalDevices(mLocalDevices, initiatedBy);
369    }
370
371    @ServiceThreadOnly
372    private void initializeLocalDevices(final List<Integer> deviceTypes, final int initiatedBy) {
373        assertRunOnServiceThread();
374        // A container for [Logical Address, Local device info].
375        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
376        final int[] finished = new int[1];
377        clearLocalDevices();
378        for (int type : deviceTypes) {
379            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
380            localDevice.init();
381            mCecController.allocateLogicalAddress(type,
382                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
383                @Override
384                public void onAllocated(int deviceType, int logicalAddress) {
385                    if (logicalAddress == Constants.ADDR_UNREGISTERED) {
386                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
387                    } else {
388                        // Set POWER_STATUS_ON to all local devices because they share lifetime
389                        // with system.
390                        HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
391                                HdmiControlManager.POWER_STATUS_ON);
392                        localDevice.setDeviceInfo(deviceInfo);
393                        mCecController.addLocalDevice(deviceType, localDevice);
394                        mCecController.addLogicalAddress(logicalAddress);
395                        devices.append(logicalAddress, localDevice);
396                    }
397
398                    // Address allocation completed for all devices. Notify each device.
399                    if (deviceTypes.size() == ++finished[0]) {
400                        onInitializeCecComplete();
401                        notifyAddressAllocated(devices, initiatedBy);
402                    }
403                }
404            });
405        }
406    }
407
408    @ServiceThreadOnly
409    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices, int initiatedBy) {
410        assertRunOnServiceThread();
411        for (int i = 0; i < devices.size(); ++i) {
412            int address = devices.keyAt(i);
413            HdmiCecLocalDevice device = devices.valueAt(i);
414            device.handleAddressAllocated(address, initiatedBy);
415        }
416    }
417
418    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
419    // keep them in one place.
420    @ServiceThreadOnly
421    private void initPortInfo() {
422        assertRunOnServiceThread();
423        HdmiPortInfo[] cecPortInfo = null;
424
425        // CEC HAL provides majority of the info while MHL does only MHL support flag for
426        // each port. Return empty array if CEC HAL didn't provide the info.
427        if (mCecController != null) {
428            cecPortInfo = mCecController.getPortInfos();
429        }
430        if (cecPortInfo == null) {
431            return;
432        }
433
434        SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
435        SparseIntArray portIdMap = new SparseIntArray();
436        for (HdmiPortInfo info : cecPortInfo) {
437            portIdMap.put(info.getAddress(), info.getId());
438            portInfoMap.put(info.getId(), info);
439        }
440        mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
441        mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
442
443        if (mMhlController == null) {
444            mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
445            return;
446        } else {
447            HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
448            ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
449            for (HdmiPortInfo info : mhlPortInfo) {
450                if (info.isMhlSupported()) {
451                    mhlSupportedPorts.add(info.getId());
452                }
453            }
454
455            // Build HDMI port info list with CEC port info plus MHL supported flag.
456            ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
457            for (HdmiPortInfo info : cecPortInfo) {
458                if (mhlSupportedPorts.contains(info.getId())) {
459                    result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
460                            info.isCecSupported(), true, info.isArcSupported()));
461                } else {
462                    result.add(info);
463                }
464            }
465            mPortInfo = Collections.unmodifiableList(result);
466        }
467    }
468
469    /**
470     * Returns HDMI port information for the given port id.
471     *
472     * @param portId HDMI port id
473     * @return {@link HdmiPortInfo} for the given port
474     */
475    HdmiPortInfo getPortInfo(int portId) {
476        return mPortInfoMap.get(portId, null);
477    }
478
479    /**
480     * Returns the routing path (physical address) of the HDMI port for the given
481     * port id.
482     */
483    int portIdToPath(int portId) {
484        HdmiPortInfo portInfo = getPortInfo(portId);
485        if (portInfo == null) {
486            Slog.e(TAG, "Cannot find the port info: " + portId);
487            return Constants.INVALID_PHYSICAL_ADDRESS;
488        }
489        return portInfo.getAddress();
490    }
491
492    /**
493     * Returns the id of HDMI port located at the top of the hierarchy of
494     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
495     * the port id to be returned is the ID associated with the port address
496     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
497     */
498    int pathToPortId(int path) {
499        int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
500        return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
501    }
502
503    boolean isValidPortId(int portId) {
504        return getPortInfo(portId) != null;
505    }
506
507    /**
508     * Returns {@link Looper} for IO operation.
509     *
510     * <p>Declared as package-private.
511     */
512    Looper getIoLooper() {
513        return mIoThread.getLooper();
514    }
515
516    /**
517     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
518     * for tasks that are running on main service thread.
519     *
520     * <p>Declared as package-private.
521     */
522    Looper getServiceLooper() {
523        return mHandler.getLooper();
524    }
525
526    /**
527     * Returns physical address of the device.
528     */
529    int getPhysicalAddress() {
530        return mCecController.getPhysicalAddress();
531    }
532
533    /**
534     * Returns vendor id of CEC service.
535     */
536    int getVendorId() {
537        return mCecController.getVendorId();
538    }
539
540    @ServiceThreadOnly
541    HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
542        assertRunOnServiceThread();
543        HdmiCecLocalDeviceTv tv = tv();
544        if (tv == null) {
545            return null;
546        }
547        return tv.getDeviceInfo(logicalAddress);
548    }
549
550    /**
551     * Returns version of CEC.
552     */
553    int getCecVersion() {
554        return mCecController.getVersion();
555    }
556
557    /**
558     * Whether a device of the specified physical address is connected to ARC enabled port.
559     */
560    boolean isConnectedToArcPort(int physicalAddress) {
561        int portId = mPortIdMap.get(physicalAddress);
562        if (portId != Constants.INVALID_PORT_ID) {
563            return mPortInfoMap.get(portId).isArcSupported();
564        }
565        return false;
566    }
567
568    void runOnServiceThread(Runnable runnable) {
569        mHandler.post(runnable);
570    }
571
572    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
573        mHandler.postAtFrontOfQueue(runnable);
574    }
575
576    private void assertRunOnServiceThread() {
577        if (Looper.myLooper() != mHandler.getLooper()) {
578            throw new IllegalStateException("Should run on service thread.");
579        }
580    }
581
582    /**
583     * Transmit a CEC command to CEC bus.
584     *
585     * @param command CEC command to send out
586     * @param callback interface used to the result of send command
587     */
588    @ServiceThreadOnly
589    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
590        assertRunOnServiceThread();
591        if (mMessageValidator.isValid(command)) {
592            mCecController.sendCommand(command, callback);
593        } else {
594            Slog.e(TAG, "Invalid message type:" + command);
595            if (callback != null) {
596                callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
597            }
598        }
599    }
600
601    @ServiceThreadOnly
602    void sendCecCommand(HdmiCecMessage command) {
603        assertRunOnServiceThread();
604        sendCecCommand(command, null);
605    }
606
607    @ServiceThreadOnly
608    void sendMhlSubcommand(int portId, HdmiMhlSubcommand command) {
609        assertRunOnServiceThread();
610        sendMhlSubcommand(portId, command, null);
611    }
612
613    @ServiceThreadOnly
614    void sendMhlSubcommand(int portId, HdmiMhlSubcommand command, SendMessageCallback callback) {
615        assertRunOnServiceThread();
616        mMhlController.sendSubcommand(portId, command, callback);
617    }
618
619    /**
620     * Send <Feature Abort> command on the given CEC message if possible.
621     * If the aborted message is invalid, then it wont send the message.
622     * @param command original command to be aborted
623     * @param reason reason of feature abort
624     */
625    @ServiceThreadOnly
626    void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
627        assertRunOnServiceThread();
628        mCecController.maySendFeatureAbortCommand(command, reason);
629    }
630
631    @ServiceThreadOnly
632    boolean handleCecCommand(HdmiCecMessage message) {
633        assertRunOnServiceThread();
634        if (!mMessageValidator.isValid(message)) {
635            return false;
636        }
637        return dispatchMessageToLocalDevice(message);
638    }
639
640    void setAudioReturnChannel(boolean enabled) {
641        mCecController.setAudioReturnChannel(enabled);
642    }
643
644    @ServiceThreadOnly
645    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
646        assertRunOnServiceThread();
647        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
648            if (device.dispatchMessage(message)
649                    && message.getDestination() != Constants.ADDR_BROADCAST) {
650                return true;
651            }
652        }
653
654        if (message.getDestination() != Constants.ADDR_BROADCAST) {
655            Slog.w(TAG, "Unhandled cec command:" + message);
656        }
657        return false;
658    }
659
660    /**
661     * Called when a new hotplug event is issued.
662     *
663     * @param portNo hdmi port number where hot plug event issued.
664     * @param connected whether to be plugged in or not
665     */
666    @ServiceThreadOnly
667    void onHotplug(int portNo, boolean connected) {
668        assertRunOnServiceThread();
669        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
670            device.onHotplug(portNo, connected);
671        }
672        announceHotplugEvent(portNo, connected);
673    }
674
675    /**
676     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
677     * devices.
678     *
679     * @param callback an interface used to get a list of all remote devices' address
680     * @param sourceAddress a logical address of source device where sends polling message
681     * @param pickStrategy strategy how to pick polling candidates
682     * @param retryCount the number of retry used to send polling message to remote devices
683     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
684     */
685    @ServiceThreadOnly
686    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
687            int retryCount) {
688        assertRunOnServiceThread();
689        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
690                retryCount);
691    }
692
693    private int checkPollStrategy(int pickStrategy) {
694        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
695        if (strategy == 0) {
696            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
697        }
698        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
699        if (iterationStrategy == 0) {
700            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
701        }
702        return strategy | iterationStrategy;
703    }
704
705    List<HdmiCecLocalDevice> getAllLocalDevices() {
706        assertRunOnServiceThread();
707        return mCecController.getLocalDeviceList();
708    }
709
710    Object getServiceLock() {
711        return mLock;
712    }
713
714    void setAudioStatus(boolean mute, int volume) {
715        AudioManager audioManager = getAudioManager();
716        boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
717        if (mute) {
718            if (!muted) {
719                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
720            }
721        } else {
722            if (muted) {
723                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
724            }
725            // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
726            // volume change notification back to hdmi control service.
727            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
728                    AudioManager.FLAG_SHOW_UI |
729                    AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
730        }
731    }
732
733    void announceSystemAudioModeChange(boolean enabled) {
734        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
735            invokeSystemAudioModeChange(listener, enabled);
736        }
737    }
738
739    private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
740        // TODO: find better name instead of model name.
741        String displayName = Build.MODEL;
742        return new HdmiDeviceInfo(logicalAddress,
743                getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
744                getVendorId(), displayName);
745    }
746
747    @ServiceThreadOnly
748    boolean handleMhlSubcommand(int portId, HdmiMhlSubcommand message) {
749        assertRunOnServiceThread();
750
751        HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
752        if (device != null) {
753            return device.handleSubcommand(message);
754        }
755        Slog.w(TAG, "No mhl device exists[portId:" + portId + ", message:" + message);
756        return false;
757    }
758
759    @ServiceThreadOnly
760    void handleMhlHotplugEvent(int portId, boolean connected) {
761        assertRunOnServiceThread();
762        if (connected) {
763            HdmiMhlLocalDevice newDevice = new HdmiMhlLocalDevice(this, portId);
764            HdmiMhlLocalDevice oldDevice = mMhlController.addLocalDevice(newDevice);
765            if (oldDevice != null) {
766                oldDevice.onDeviceRemoved();
767                Slog.i(TAG, "Old device of port " + portId + " is removed");
768            }
769        } else {
770            HdmiMhlLocalDevice device = mMhlController.removeLocalDevice(portId);
771            if (device != null) {
772                device.onDeviceRemoved();
773            } else {
774                Slog.w(TAG, "No device to remove:[portId=" + portId);
775            }
776        }
777    }
778
779    @ServiceThreadOnly
780    void handleMhlCbusModeChanged(int portId, int cbusmode) {
781        assertRunOnServiceThread();
782        HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
783        if (device != null) {
784            device.setCbusMode(cbusmode);
785        } else {
786            Slog.w(TAG, "No mhl device exists for cbus mode change[portId:" + portId +
787                    ", cbusmode:" + cbusmode + "]");
788        }
789    }
790
791    @ServiceThreadOnly
792    void handleMhlVbusOvercurrent(int portId, boolean on) {
793        assertRunOnServiceThread();
794        HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
795        if (device != null) {
796            device.onVbusOvercurrentDetected(on);
797        } else {
798            Slog.w(TAG, "No mhl device exists for vbus overcurrent event[portId:" + portId + "]");
799        }
800    }
801
802    @ServiceThreadOnly
803    void handleCapabilityRegisterChanged(int portId, int adopterId, int deviceId) {
804        assertRunOnServiceThread();
805        HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
806        // Hot plug event should be called before capability register change event.
807        if (device != null) {
808            device.setCapabilityRegister(adopterId, deviceId);
809        } else {
810            Slog.w(TAG, "No mhl device exists for capability register change event[portId:"
811                    + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
812        }
813    }
814
815    // Record class that monitors the event of the caller of being killed. Used to clean up
816    // the listener list and record list accordingly.
817    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
818        private final IHdmiHotplugEventListener mListener;
819
820        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
821            mListener = listener;
822        }
823
824        @Override
825        public void binderDied() {
826            synchronized (mLock) {
827                mHotplugEventListenerRecords.remove(this);
828                mHotplugEventListeners.remove(mListener);
829            }
830        }
831    }
832
833    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
834        private final IHdmiDeviceEventListener mListener;
835
836        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
837            mListener = listener;
838        }
839
840        @Override
841        public void binderDied() {
842            synchronized (mLock) {
843                mDeviceEventListenerRecords.remove(this);
844                mDeviceEventListeners.remove(mListener);
845            }
846        }
847    }
848
849    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
850        private final IHdmiSystemAudioModeChangeListener mListener;
851
852        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
853            mListener = listener;
854        }
855
856        @Override
857        public void binderDied() {
858            synchronized (mLock) {
859                mSystemAudioModeChangeListenerRecords.remove(this);
860                mSystemAudioModeChangeListeners.remove(mListener);
861            }
862        }
863    }
864
865    class VendorCommandListenerRecord implements IBinder.DeathRecipient {
866        private final IHdmiVendorCommandListener mListener;
867        private final int mDeviceType;
868
869        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
870            mListener = listener;
871            mDeviceType = deviceType;
872        }
873
874        @Override
875        public void binderDied() {
876            synchronized (mLock) {
877                mVendorCommandListenerRecords.remove(this);
878            }
879        }
880    }
881
882    private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
883        @Override
884        public void binderDied() {
885            synchronized (mLock) {
886                mRecordListener = null;
887            }
888        }
889    }
890
891    private void enforceAccessPermission() {
892        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
893    }
894
895    private final class BinderService extends IHdmiControlService.Stub {
896        @Override
897        public int[] getSupportedTypes() {
898            enforceAccessPermission();
899            // mLocalDevices is an unmodifiable list - no lock necesary.
900            int[] localDevices = new int[mLocalDevices.size()];
901            for (int i = 0; i < localDevices.length; ++i) {
902                localDevices[i] = mLocalDevices.get(i);
903            }
904            return localDevices;
905        }
906
907        @Override
908        public HdmiDeviceInfo getActiveSource() {
909            HdmiCecLocalDeviceTv tv = tv();
910            if (tv == null) {
911                Slog.w(TAG, "Local tv device not available");
912                return null;
913            }
914            ActiveSource activeSource = tv.getActiveSource();
915            if (activeSource.isValid()) {
916                return new HdmiDeviceInfo(activeSource.logicalAddress,
917                        activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
918                        HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
919            }
920            int activePath = tv.getActivePath();
921            if (activePath != HdmiDeviceInfo.PATH_INVALID) {
922                return new HdmiDeviceInfo(activePath, tv.getActivePortId());
923            }
924            return null;
925        }
926
927        @Override
928        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
929            enforceAccessPermission();
930            runOnServiceThread(new Runnable() {
931                @Override
932                public void run() {
933                    if (callback == null) {
934                        Slog.e(TAG, "Callback cannot be null");
935                        return;
936                    }
937                    HdmiCecLocalDeviceTv tv = tv();
938                    if (tv == null) {
939                        Slog.w(TAG, "Local tv device not available");
940                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
941                        return;
942                    }
943                    tv.deviceSelect(logicalAddress, callback);
944                }
945            });
946        }
947
948        @Override
949        public void portSelect(final int portId, final IHdmiControlCallback callback) {
950            enforceAccessPermission();
951            runOnServiceThread(new Runnable() {
952                @Override
953                public void run() {
954                    if (callback == null) {
955                        Slog.e(TAG, "Callback cannot be null");
956                        return;
957                    }
958                    HdmiCecLocalDeviceTv tv = tv();
959                    if (tv == null) {
960                        Slog.w(TAG, "Local tv device not available");
961                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
962                        return;
963                    }
964                    tv.doManualPortSwitching(portId, callback);
965                }
966            });
967        }
968
969        @Override
970        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
971            enforceAccessPermission();
972            runOnServiceThread(new Runnable() {
973                @Override
974                public void run() {
975                    HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
976                    if (localDevice == null) {
977                        Slog.w(TAG, "Local device not available");
978                        return;
979                    }
980                    localDevice.sendKeyEvent(keyCode, isPressed);
981                }
982            });
983        }
984
985        @Override
986        public void oneTouchPlay(final IHdmiControlCallback callback) {
987            enforceAccessPermission();
988            runOnServiceThread(new Runnable() {
989                @Override
990                public void run() {
991                    HdmiControlService.this.oneTouchPlay(callback);
992                }
993            });
994        }
995
996        @Override
997        public void queryDisplayStatus(final IHdmiControlCallback callback) {
998            enforceAccessPermission();
999            runOnServiceThread(new Runnable() {
1000                @Override
1001                public void run() {
1002                    HdmiControlService.this.queryDisplayStatus(callback);
1003                }
1004            });
1005        }
1006
1007        @Override
1008        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1009            enforceAccessPermission();
1010            runOnServiceThread(new Runnable() {
1011                @Override
1012                public void run() {
1013                    HdmiControlService.this.addHotplugEventListener(listener);
1014                }
1015            });
1016        }
1017
1018        @Override
1019        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1020            enforceAccessPermission();
1021            runOnServiceThread(new Runnable() {
1022                @Override
1023                public void run() {
1024                    HdmiControlService.this.removeHotplugEventListener(listener);
1025                }
1026            });
1027        }
1028
1029        @Override
1030        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1031            enforceAccessPermission();
1032            runOnServiceThread(new Runnable() {
1033                @Override
1034                public void run() {
1035                    HdmiControlService.this.addDeviceEventListener(listener);
1036                }
1037            });
1038        }
1039
1040        @Override
1041        public List<HdmiPortInfo> getPortInfo() {
1042            enforceAccessPermission();
1043            return mPortInfo;
1044        }
1045
1046        @Override
1047        public boolean canChangeSystemAudioMode() {
1048            enforceAccessPermission();
1049            HdmiCecLocalDeviceTv tv = tv();
1050            if (tv == null) {
1051                return false;
1052            }
1053            return tv.hasSystemAudioDevice();
1054        }
1055
1056        @Override
1057        public boolean getSystemAudioMode() {
1058            enforceAccessPermission();
1059            HdmiCecLocalDeviceTv tv = tv();
1060            if (tv == null) {
1061                return false;
1062            }
1063            return tv.isSystemAudioActivated();
1064        }
1065
1066        @Override
1067        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1068            enforceAccessPermission();
1069            runOnServiceThread(new Runnable() {
1070                @Override
1071                public void run() {
1072                    HdmiCecLocalDeviceTv tv = tv();
1073                    if (tv == null) {
1074                        Slog.w(TAG, "Local tv device not available");
1075                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1076                        return;
1077                    }
1078                    tv.changeSystemAudioMode(enabled, callback);
1079                }
1080            });
1081        }
1082
1083        @Override
1084        public void addSystemAudioModeChangeListener(
1085                final IHdmiSystemAudioModeChangeListener listener) {
1086            enforceAccessPermission();
1087            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1088        }
1089
1090        @Override
1091        public void removeSystemAudioModeChangeListener(
1092                final IHdmiSystemAudioModeChangeListener listener) {
1093            enforceAccessPermission();
1094            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1095        }
1096
1097        @Override
1098        public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1099            enforceAccessPermission();
1100            HdmiControlService.this.setInputChangeListener(listener);
1101        }
1102
1103        @Override
1104        public List<HdmiDeviceInfo> getInputDevices() {
1105            enforceAccessPermission();
1106            // No need to hold the lock for obtaining TV device as the local device instance
1107            // is preserved while the HDMI control is enabled.
1108            HdmiCecLocalDeviceTv tv = tv();
1109            if (tv == null) {
1110                return Collections.emptyList();
1111            }
1112            return tv.getSafeExternalInputs();
1113        }
1114
1115        @Override
1116        public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1117                final int maxIndex) {
1118            enforceAccessPermission();
1119            runOnServiceThread(new Runnable() {
1120                @Override
1121                public void run() {
1122                    HdmiCecLocalDeviceTv tv = tv();
1123                    if (tv == null) {
1124                        Slog.w(TAG, "Local tv device not available");
1125                        return;
1126                    }
1127                    tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1128                }
1129            });
1130        }
1131
1132        @Override
1133        public void setSystemAudioMute(final boolean mute) {
1134            enforceAccessPermission();
1135            runOnServiceThread(new Runnable() {
1136                @Override
1137                public void run() {
1138                    HdmiCecLocalDeviceTv tv = tv();
1139                    if (tv == null) {
1140                        Slog.w(TAG, "Local tv device not available");
1141                        return;
1142                    }
1143                    tv.changeMute(mute);
1144                }
1145            });
1146        }
1147
1148        @Override
1149        public void setArcMode(final boolean enabled) {
1150            enforceAccessPermission();
1151            runOnServiceThread(new Runnable() {
1152                @Override
1153                public void run() {
1154                    HdmiCecLocalDeviceTv tv = tv();
1155                    if (tv == null) {
1156                        Slog.w(TAG, "Local tv device not available to change arc mode.");
1157                        return;
1158                    }
1159                }
1160            });
1161        }
1162
1163        @Override
1164        public void setProhibitMode(final boolean enabled) {
1165            enforceAccessPermission();
1166            if (!isTvDevice()) {
1167                return;
1168            }
1169            HdmiControlService.this.setProhibitMode(enabled);
1170        }
1171
1172        @Override
1173        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1174                final int deviceType) {
1175            enforceAccessPermission();
1176            runOnServiceThread(new Runnable() {
1177                @Override
1178                public void run() {
1179                    HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1180                }
1181            });
1182        }
1183
1184        @Override
1185        public void sendVendorCommand(final int deviceType, final int targetAddress,
1186                final byte[] params, final boolean hasVendorId) {
1187            enforceAccessPermission();
1188            runOnServiceThread(new Runnable() {
1189                @Override
1190                public void run() {
1191                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1192                    if (device == null) {
1193                        Slog.w(TAG, "Local device not available");
1194                        return;
1195                    }
1196                    if (hasVendorId) {
1197                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1198                                device.getDeviceInfo().getLogicalAddress(), targetAddress,
1199                                getVendorId(), params));
1200                    } else {
1201                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1202                                device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1203                    }
1204                }
1205            });
1206        }
1207
1208        @Override
1209        public void setHdmiRecordListener(IHdmiRecordListener listener) {
1210            HdmiControlService.this.setHdmiRecordListener(listener);
1211        }
1212
1213        @Override
1214        public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1215            runOnServiceThread(new Runnable() {
1216                @Override
1217                public void run() {
1218                    if (!isTvDevice()) {
1219                        Slog.w(TAG, "No TV is available.");
1220                        return;
1221                    }
1222                    tv().startOneTouchRecord(recorderAddress, recordSource);
1223                }
1224            });
1225        }
1226
1227        @Override
1228        public void stopOneTouchRecord(final int recorderAddress) {
1229            runOnServiceThread(new Runnable() {
1230                @Override
1231                public void run() {
1232                    if (!isTvDevice()) {
1233                        Slog.w(TAG, "No TV is available.");
1234                        return;
1235                    }
1236                    tv().stopOneTouchRecord(recorderAddress);
1237                }
1238            });
1239        }
1240
1241        @Override
1242        public void startTimerRecording(final int recorderAddress, final int sourceType,
1243                final byte[] recordSource) {
1244            runOnServiceThread(new Runnable() {
1245                @Override
1246                public void run() {
1247                    if (!isTvDevice()) {
1248                        Slog.w(TAG, "No TV is available.");
1249                        return;
1250                    }
1251                    tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1252                }
1253            });
1254        }
1255
1256        @Override
1257        public void clearTimerRecording(final int recorderAddress, final int sourceType,
1258                final byte[] recordSource) {
1259            runOnServiceThread(new Runnable() {
1260                @Override
1261                public void run() {
1262                    if (!isTvDevice()) {
1263                        Slog.w(TAG, "No TV is available.");
1264                        return;
1265                    }
1266                    tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1267                }
1268            });
1269        }
1270    }
1271
1272    @ServiceThreadOnly
1273    private void oneTouchPlay(final IHdmiControlCallback callback) {
1274        assertRunOnServiceThread();
1275        HdmiCecLocalDevicePlayback source = playback();
1276        if (source == null) {
1277            Slog.w(TAG, "Local playback device not available");
1278            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1279            return;
1280        }
1281        source.oneTouchPlay(callback);
1282    }
1283
1284    @ServiceThreadOnly
1285    private void queryDisplayStatus(final IHdmiControlCallback callback) {
1286        assertRunOnServiceThread();
1287        HdmiCecLocalDevicePlayback source = playback();
1288        if (source == null) {
1289            Slog.w(TAG, "Local playback device not available");
1290            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1291            return;
1292        }
1293        source.queryDisplayStatus(callback);
1294    }
1295
1296    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1297        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1298        try {
1299            listener.asBinder().linkToDeath(record, 0);
1300        } catch (RemoteException e) {
1301            Slog.w(TAG, "Listener already died");
1302            return;
1303        }
1304        synchronized (mLock) {
1305            mHotplugEventListenerRecords.add(record);
1306            mHotplugEventListeners.add(listener);
1307        }
1308    }
1309
1310    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1311        synchronized (mLock) {
1312            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1313                if (record.mListener.asBinder() == listener.asBinder()) {
1314                    listener.asBinder().unlinkToDeath(record, 0);
1315                    mHotplugEventListenerRecords.remove(record);
1316                    break;
1317                }
1318            }
1319            mHotplugEventListeners.remove(listener);
1320        }
1321    }
1322
1323    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1324        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1325        try {
1326            listener.asBinder().linkToDeath(record, 0);
1327        } catch (RemoteException e) {
1328            Slog.w(TAG, "Listener already died");
1329            return;
1330        }
1331        synchronized (mLock) {
1332            mDeviceEventListeners.add(listener);
1333            mDeviceEventListenerRecords.add(record);
1334        }
1335    }
1336
1337    void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1338        synchronized (mLock) {
1339            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
1340                try {
1341                    listener.onStatusChanged(device, status);
1342                } catch (RemoteException e) {
1343                    Slog.e(TAG, "Failed to report device event:" + e);
1344                }
1345            }
1346        }
1347    }
1348
1349    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1350        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1351                listener);
1352        try {
1353            listener.asBinder().linkToDeath(record, 0);
1354        } catch (RemoteException e) {
1355            Slog.w(TAG, "Listener already died");
1356            return;
1357        }
1358        synchronized (mLock) {
1359            mSystemAudioModeChangeListeners.add(listener);
1360            mSystemAudioModeChangeListenerRecords.add(record);
1361        }
1362    }
1363
1364    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1365        synchronized (mLock) {
1366            for (SystemAudioModeChangeListenerRecord record :
1367                    mSystemAudioModeChangeListenerRecords) {
1368                if (record.mListener.asBinder() == listener) {
1369                    listener.asBinder().unlinkToDeath(record, 0);
1370                    mSystemAudioModeChangeListenerRecords.remove(record);
1371                    break;
1372                }
1373            }
1374            mSystemAudioModeChangeListeners.remove(listener);
1375        }
1376    }
1377
1378    private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1379        @Override
1380        public void binderDied() {
1381            synchronized (mLock) {
1382                mInputChangeListener = null;
1383            }
1384        }
1385    }
1386
1387    private void setInputChangeListener(IHdmiInputChangeListener listener) {
1388        synchronized (mLock) {
1389            mInputChangeListenerRecord = new InputChangeListenerRecord();
1390            try {
1391                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1392            } catch (RemoteException e) {
1393                Slog.w(TAG, "Listener already died");
1394                return;
1395            }
1396            mInputChangeListener = listener;
1397        }
1398    }
1399
1400    void invokeInputChangeListener(HdmiDeviceInfo info) {
1401        synchronized (mLock) {
1402            if (mInputChangeListener != null) {
1403                try {
1404                    mInputChangeListener.onChanged(info);
1405                } catch (RemoteException e) {
1406                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1407                }
1408            }
1409        }
1410    }
1411
1412    private void setHdmiRecordListener(IHdmiRecordListener listener) {
1413        synchronized (mLock) {
1414            mRecordListenerRecord = new HdmiRecordListenerRecord();
1415            try {
1416                listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1417            } catch (RemoteException e) {
1418                Slog.w(TAG, "Listener already died.", e);
1419            }
1420            mRecordListener = listener;
1421        }
1422    }
1423
1424    byte[] invokeRecordRequestListener(int recorderAddress) {
1425        synchronized (mLock) {
1426            if (mRecordListener != null) {
1427                try {
1428                    return mRecordListener.getOneTouchRecordSource(recorderAddress);
1429                } catch (RemoteException e) {
1430                    Slog.w(TAG, "Failed to start record.", e);
1431                }
1432            }
1433            return EmptyArray.BYTE;
1434        }
1435    }
1436
1437    void invokeOneTouchRecordResult(int result) {
1438        synchronized (mLock) {
1439            if (mRecordListener != null) {
1440                try {
1441                    mRecordListener.onOneTouchRecordResult(result);
1442                } catch (RemoteException e) {
1443                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1444                }
1445            }
1446        }
1447    }
1448
1449    void invokeTimerRecordingResult(int result) {
1450        synchronized (mLock) {
1451            if (mRecordListener != null) {
1452                try {
1453                    mRecordListener.onTimerRecordingResult(result);
1454                } catch (RemoteException e) {
1455                    Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1456                }
1457            }
1458        }
1459    }
1460
1461    void invokeClearTimerRecordingResult(int result) {
1462        synchronized (mLock) {
1463            if (mRecordListener != null) {
1464                try {
1465                    mRecordListener.onClearTimerRecordingResult(result);
1466                } catch (RemoteException e) {
1467                    Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1468                }
1469            }
1470        }
1471    }
1472
1473    private void invokeCallback(IHdmiControlCallback callback, int result) {
1474        try {
1475            callback.onComplete(result);
1476        } catch (RemoteException e) {
1477            Slog.e(TAG, "Invoking callback failed:" + e);
1478        }
1479    }
1480
1481    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
1482            boolean enabled) {
1483        try {
1484            listener.onStatusChanged(enabled);
1485        } catch (RemoteException e) {
1486            Slog.e(TAG, "Invoking callback failed:" + e);
1487        }
1488    }
1489
1490    private void announceHotplugEvent(int portId, boolean connected) {
1491        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1492        synchronized (mLock) {
1493            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
1494                invokeHotplugEventListenerLocked(listener, event);
1495            }
1496        }
1497    }
1498
1499    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1500            HdmiHotplugEvent event) {
1501        try {
1502            listener.onReceived(event);
1503        } catch (RemoteException e) {
1504            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1505        }
1506    }
1507
1508    private HdmiCecLocalDeviceTv tv() {
1509        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1510    }
1511
1512    boolean isTvDevice() {
1513        return tv() != null;
1514    }
1515
1516    private HdmiCecLocalDevicePlayback playback() {
1517        return (HdmiCecLocalDevicePlayback)
1518                mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1519    }
1520
1521    AudioManager getAudioManager() {
1522        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1523    }
1524
1525    boolean isControlEnabled() {
1526        synchronized (mLock) {
1527            return mHdmiControlEnabled;
1528        }
1529    }
1530
1531    int getPowerStatus() {
1532        return mPowerStatus;
1533    }
1534
1535    boolean isPowerOnOrTransient() {
1536        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1537                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1538    }
1539
1540    boolean isPowerStandbyOrTransient() {
1541        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1542                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1543    }
1544
1545    boolean isPowerStandby() {
1546        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1547    }
1548
1549    @ServiceThreadOnly
1550    void wakeUp() {
1551        assertRunOnServiceThread();
1552        mWakeUpMessageReceived = true;
1553        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1554        pm.wakeUp(SystemClock.uptimeMillis());
1555        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1556        // the intent, the sequence will continue at onWakeUp().
1557    }
1558
1559    @ServiceThreadOnly
1560    void standby() {
1561        assertRunOnServiceThread();
1562        mStandbyMessageReceived = true;
1563        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1564        pm.goToSleep(SystemClock.uptimeMillis());
1565        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1566        // the intent, the sequence will continue at onStandby().
1567    }
1568
1569    void nap() {
1570        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1571        pm.nap(SystemClock.uptimeMillis());
1572    }
1573
1574    @ServiceThreadOnly
1575    private void onWakeUp() {
1576        assertRunOnServiceThread();
1577        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1578        if (mCecController != null) {
1579            if (mHdmiControlEnabled) {
1580                int startReason = INITIATED_BY_SCREEN_ON;
1581                if (mWakeUpMessageReceived) {
1582                    startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1583                }
1584                initializeCec(startReason);
1585            }
1586        } else {
1587            Slog.i(TAG, "Device does not support HDMI-CEC.");
1588        }
1589        // TODO: Initialize MHL local devices.
1590    }
1591
1592    @ServiceThreadOnly
1593    private void onStandby() {
1594        assertRunOnServiceThread();
1595        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1596
1597        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1598        disableDevices(new PendingActionClearedCallback() {
1599            @Override
1600            public void onCleared(HdmiCecLocalDevice device) {
1601                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1602                devices.remove(device);
1603                if (devices.isEmpty()) {
1604                    onStandbyCompleted();
1605                    // We will not clear local devices here, since some OEM/SOC will keep passing
1606                    // the received packets until the application processor enters to the sleep
1607                    // actually.
1608                }
1609            }
1610        });
1611    }
1612
1613    private void disableDevices(PendingActionClearedCallback callback) {
1614        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1615            device.disableDevice(mStandbyMessageReceived, callback);
1616        }
1617        if (isTvDevice()) {
1618            unregisterSettingsObserver();
1619        }
1620    }
1621
1622    @ServiceThreadOnly
1623    private void clearLocalDevices() {
1624        assertRunOnServiceThread();
1625        if (mCecController == null) {
1626            return;
1627        }
1628        mCecController.clearLogicalAddress();
1629        mCecController.clearLocalDevices();
1630    }
1631
1632    @ServiceThreadOnly
1633    private void onStandbyCompleted() {
1634        assertRunOnServiceThread();
1635        Slog.v(TAG, "onStandbyCompleted");
1636
1637        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1638            return;
1639        }
1640        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1641        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1642            device.onStandby(mStandbyMessageReceived);
1643        }
1644        mStandbyMessageReceived = false;
1645        mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
1646    }
1647
1648    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1649        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1650        try {
1651            listener.asBinder().linkToDeath(record, 0);
1652        } catch (RemoteException e) {
1653            Slog.w(TAG, "Listener already died");
1654            return;
1655        }
1656        synchronized (mLock) {
1657            mVendorCommandListenerRecords.add(record);
1658        }
1659    }
1660
1661    void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1662            boolean hasVendorId) {
1663        synchronized (mLock) {
1664            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1665                if (record.mDeviceType != deviceType) {
1666                    continue;
1667                }
1668                try {
1669                    record.mListener.onReceived(srcAddress, params, hasVendorId);
1670                } catch (RemoteException e) {
1671                    Slog.e(TAG, "Failed to notify vendor command reception", e);
1672                }
1673            }
1674        }
1675    }
1676
1677    boolean isProhibitMode() {
1678        synchronized (mLock) {
1679            return mProhibitMode;
1680        }
1681    }
1682
1683    void setProhibitMode(boolean enabled) {
1684        synchronized (mLock) {
1685            mProhibitMode = enabled;
1686        }
1687    }
1688
1689    @ServiceThreadOnly
1690    void setOption(int key, int value) {
1691        assertRunOnServiceThread();
1692        mCecController.setOption(key, value);
1693    }
1694
1695    @ServiceThreadOnly
1696    void setControlEnabled(boolean enabled) {
1697        assertRunOnServiceThread();
1698
1699        int value = toInt(enabled);
1700        mCecController.setOption(OPTION_CEC_ENABLE, value);
1701        if (mMhlController != null) {
1702            mMhlController.setOption(OPTION_MHL_ENABLE, value);
1703        }
1704
1705        synchronized (mLock) {
1706            mHdmiControlEnabled = enabled;
1707        }
1708
1709        if (enabled) {
1710            initializeCec(INITIATED_BY_ENABLE_CEC);
1711        } else {
1712            disableDevices(new PendingActionClearedCallback() {
1713                @Override
1714                public void onCleared(HdmiCecLocalDevice device) {
1715                    assertRunOnServiceThread();
1716                    clearLocalDevices();
1717                }
1718            });
1719        }
1720    }
1721}
1722