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