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