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