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