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