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