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