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