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