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