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