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