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