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