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