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            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
993                    AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
994        }
995    }
996
997    void announceSystemAudioModeChange(boolean enabled) {
998        synchronized (mLock) {
999            for (SystemAudioModeChangeListenerRecord record :
1000                    mSystemAudioModeChangeListenerRecords) {
1001                invokeSystemAudioModeChangeLocked(record.mListener, enabled);
1002            }
1003        }
1004    }
1005
1006    private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
1007        // TODO: find better name instead of model name.
1008        String displayName = Build.MODEL;
1009        return new HdmiDeviceInfo(logicalAddress,
1010                getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
1011                getVendorId(), displayName);
1012    }
1013
1014    @ServiceThreadOnly
1015    void handleMhlHotplugEvent(int portId, boolean connected) {
1016        assertRunOnServiceThread();
1017        // Hotplug event is used to add/remove MHL devices as TV input.
1018        if (connected) {
1019            HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1020            HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
1021            if (oldDevice != null) {
1022                oldDevice.onDeviceRemoved();
1023                Slog.i(TAG, "Old device of port " + portId + " is removed");
1024            }
1025            invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1026            updateSafeMhlInput();
1027        } else {
1028            HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
1029            if (device != null) {
1030                device.onDeviceRemoved();
1031                invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1032                updateSafeMhlInput();
1033            } else {
1034                Slog.w(TAG, "No device to remove:[portId=" + portId);
1035            }
1036        }
1037        announceHotplugEvent(portId, connected);
1038    }
1039
1040    @ServiceThreadOnly
1041    void handleMhlBusModeChanged(int portId, int busmode) {
1042        assertRunOnServiceThread();
1043        HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1044        if (device != null) {
1045            device.setBusMode(busmode);
1046        } else {
1047            Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1048                    ", busmode:" + busmode + "]");
1049        }
1050    }
1051
1052    @ServiceThreadOnly
1053    void handleMhlBusOvercurrent(int portId, boolean on) {
1054        assertRunOnServiceThread();
1055        HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1056        if (device != null) {
1057            device.onBusOvercurrentDetected(on);
1058        } else {
1059            Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
1060        }
1061    }
1062
1063    @ServiceThreadOnly
1064    void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
1065        assertRunOnServiceThread();
1066        HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1067
1068        if (device != null) {
1069            device.setDeviceStatusChange(adopterId, deviceId);
1070        } else {
1071            Slog.w(TAG, "No mhl device exists for device status event[portId:"
1072                    + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1073        }
1074    }
1075
1076    @ServiceThreadOnly
1077    private void updateSafeMhlInput() {
1078        assertRunOnServiceThread();
1079        List<HdmiDeviceInfo> inputs = Collections.emptyList();
1080        SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
1081        for (int i = 0; i < devices.size(); ++i) {
1082            HdmiMhlLocalDeviceStub device = devices.valueAt(i);
1083            HdmiDeviceInfo info = device.getInfo();
1084            if (info != null) {
1085                if (inputs.isEmpty()) {
1086                    inputs = new ArrayList<>();
1087                }
1088                inputs.add(device.getInfo());
1089            }
1090        }
1091        synchronized (mLock) {
1092            mMhlDevices = inputs;
1093        }
1094    }
1095
1096    private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1097        return mMhlDevices;
1098    }
1099
1100    private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1101        private final IHdmiMhlVendorCommandListener mListener;
1102
1103        public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
1104            mListener = listener;
1105        }
1106
1107        @Override
1108        public void binderDied() {
1109            mMhlVendorCommandListenerRecords.remove(this);
1110        }
1111    }
1112
1113    // Record class that monitors the event of the caller of being killed. Used to clean up
1114    // the listener list and record list accordingly.
1115    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1116        private final IHdmiHotplugEventListener mListener;
1117
1118        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1119            mListener = listener;
1120        }
1121
1122        @Override
1123        public void binderDied() {
1124            synchronized (mLock) {
1125                mHotplugEventListenerRecords.remove(this);
1126            }
1127        }
1128
1129        @Override
1130        public boolean equals(Object obj) {
1131            if (!(obj instanceof HotplugEventListenerRecord)) return false;
1132            if (obj == this) return true;
1133            HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1134            return other.mListener == this.mListener;
1135        }
1136
1137        @Override
1138        public int hashCode() {
1139            return mListener.hashCode();
1140        }
1141    }
1142
1143    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1144        private final IHdmiDeviceEventListener mListener;
1145
1146        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1147            mListener = listener;
1148        }
1149
1150        @Override
1151        public void binderDied() {
1152            synchronized (mLock) {
1153                mDeviceEventListenerRecords.remove(this);
1154            }
1155        }
1156    }
1157
1158    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
1159        private final IHdmiSystemAudioModeChangeListener mListener;
1160
1161        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1162            mListener = listener;
1163        }
1164
1165        @Override
1166        public void binderDied() {
1167            synchronized (mLock) {
1168                mSystemAudioModeChangeListenerRecords.remove(this);
1169            }
1170        }
1171    }
1172
1173    class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1174        private final IHdmiVendorCommandListener mListener;
1175        private final int mDeviceType;
1176
1177        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1178            mListener = listener;
1179            mDeviceType = deviceType;
1180        }
1181
1182        @Override
1183        public void binderDied() {
1184            synchronized (mLock) {
1185                mVendorCommandListenerRecords.remove(this);
1186            }
1187        }
1188    }
1189
1190    private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
1191        private final IHdmiRecordListener mListener;
1192
1193        public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1194            mListener = listener;
1195        }
1196
1197        @Override
1198        public void binderDied() {
1199            synchronized (mLock) {
1200                if (mRecordListenerRecord == this) {
1201                    mRecordListenerRecord = null;
1202                }
1203            }
1204        }
1205    }
1206
1207    private void enforceAccessPermission() {
1208        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1209    }
1210
1211    private final class BinderService extends IHdmiControlService.Stub {
1212        @Override
1213        public int[] getSupportedTypes() {
1214            enforceAccessPermission();
1215            // mLocalDevices is an unmodifiable list - no lock necesary.
1216            int[] localDevices = new int[mLocalDevices.size()];
1217            for (int i = 0; i < localDevices.length; ++i) {
1218                localDevices[i] = mLocalDevices.get(i);
1219            }
1220            return localDevices;
1221        }
1222
1223        @Override
1224        public HdmiDeviceInfo getActiveSource() {
1225            enforceAccessPermission();
1226            HdmiCecLocalDeviceTv tv = tv();
1227            if (tv == null) {
1228                Slog.w(TAG, "Local tv device not available");
1229                return null;
1230            }
1231            ActiveSource activeSource = tv.getActiveSource();
1232            if (activeSource.isValid()) {
1233                return new HdmiDeviceInfo(activeSource.logicalAddress,
1234                        activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1235                        HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1236            }
1237            int activePath = tv.getActivePath();
1238            if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1239                HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
1240                return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
1241            }
1242            return null;
1243        }
1244
1245        @Override
1246        public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1247            enforceAccessPermission();
1248            runOnServiceThread(new Runnable() {
1249                @Override
1250                public void run() {
1251                    if (callback == null) {
1252                        Slog.e(TAG, "Callback cannot be null");
1253                        return;
1254                    }
1255                    HdmiCecLocalDeviceTv tv = tv();
1256                    if (tv == null) {
1257                        if (!mAddressAllocated) {
1258                            mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1259                                    HdmiControlService.this, deviceId, callback));
1260                            return;
1261                        }
1262                        Slog.w(TAG, "Local tv device not available");
1263                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1264                        return;
1265                    }
1266                    HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1267                    if (device != null) {
1268                        if (device.getPortId() == tv.getActivePortId()) {
1269                            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1270                            return;
1271                        }
1272                        // Upon selecting MHL device, we send RAP[Content On] to wake up
1273                        // the connected mobile device, start routing control to switch ports.
1274                        // callback is handled by MHL action.
1275                        device.turnOn(callback);
1276                        tv.doManualPortSwitching(device.getPortId(), null);
1277                        return;
1278                    }
1279                    tv.deviceSelect(deviceId, callback);
1280                }
1281            });
1282        }
1283
1284        @Override
1285        public void portSelect(final int portId, final IHdmiControlCallback callback) {
1286            enforceAccessPermission();
1287            runOnServiceThread(new Runnable() {
1288                @Override
1289                public void run() {
1290                    if (callback == null) {
1291                        Slog.e(TAG, "Callback cannot be null");
1292                        return;
1293                    }
1294                    HdmiCecLocalDeviceTv tv = tv();
1295                    if (tv == null) {
1296                        if (!mAddressAllocated) {
1297                            mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1298                                    HdmiControlService.this, portId, callback));
1299                            return;
1300                        }
1301                        Slog.w(TAG, "Local tv device not available");
1302                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1303                        return;
1304                    }
1305                    tv.doManualPortSwitching(portId, callback);
1306                }
1307            });
1308        }
1309
1310        @Override
1311        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1312            enforceAccessPermission();
1313            runOnServiceThread(new Runnable() {
1314                @Override
1315                public void run() {
1316                    HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1317                    if (device != null) {
1318                        device.sendKeyEvent(keyCode, isPressed);
1319                        return;
1320                    }
1321                    if (mCecController != null) {
1322                        HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1323                        if (localDevice == null) {
1324                            Slog.w(TAG, "Local device not available");
1325                            return;
1326                        }
1327                        localDevice.sendKeyEvent(keyCode, isPressed);
1328                    }
1329                }
1330            });
1331        }
1332
1333        @Override
1334        public void oneTouchPlay(final IHdmiControlCallback callback) {
1335            enforceAccessPermission();
1336            runOnServiceThread(new Runnable() {
1337                @Override
1338                public void run() {
1339                    HdmiControlService.this.oneTouchPlay(callback);
1340                }
1341            });
1342        }
1343
1344        @Override
1345        public void queryDisplayStatus(final IHdmiControlCallback callback) {
1346            enforceAccessPermission();
1347            runOnServiceThread(new Runnable() {
1348                @Override
1349                public void run() {
1350                    HdmiControlService.this.queryDisplayStatus(callback);
1351                }
1352            });
1353        }
1354
1355        @Override
1356        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1357            enforceAccessPermission();
1358            HdmiControlService.this.addHotplugEventListener(listener);
1359        }
1360
1361        @Override
1362        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1363            enforceAccessPermission();
1364            HdmiControlService.this.removeHotplugEventListener(listener);
1365        }
1366
1367        @Override
1368        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1369            enforceAccessPermission();
1370            HdmiControlService.this.addDeviceEventListener(listener);
1371        }
1372
1373        @Override
1374        public List<HdmiPortInfo> getPortInfo() {
1375            enforceAccessPermission();
1376            return HdmiControlService.this.getPortInfo();
1377        }
1378
1379        @Override
1380        public boolean canChangeSystemAudioMode() {
1381            enforceAccessPermission();
1382            HdmiCecLocalDeviceTv tv = tv();
1383            if (tv == null) {
1384                return false;
1385            }
1386            return tv.hasSystemAudioDevice();
1387        }
1388
1389        @Override
1390        public boolean getSystemAudioMode() {
1391            enforceAccessPermission();
1392            HdmiCecLocalDeviceTv tv = tv();
1393            if (tv == null) {
1394                return false;
1395            }
1396            return tv.isSystemAudioActivated();
1397        }
1398
1399        @Override
1400        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1401            enforceAccessPermission();
1402            runOnServiceThread(new Runnable() {
1403                @Override
1404                public void run() {
1405                    HdmiCecLocalDeviceTv tv = tv();
1406                    if (tv == null) {
1407                        Slog.w(TAG, "Local tv device not available");
1408                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1409                        return;
1410                    }
1411                    tv.changeSystemAudioMode(enabled, callback);
1412                }
1413            });
1414        }
1415
1416        @Override
1417        public void addSystemAudioModeChangeListener(
1418                final IHdmiSystemAudioModeChangeListener listener) {
1419            enforceAccessPermission();
1420            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1421        }
1422
1423        @Override
1424        public void removeSystemAudioModeChangeListener(
1425                final IHdmiSystemAudioModeChangeListener listener) {
1426            enforceAccessPermission();
1427            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1428        }
1429
1430        @Override
1431        public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1432            enforceAccessPermission();
1433            HdmiControlService.this.setInputChangeListener(listener);
1434        }
1435
1436        @Override
1437        public List<HdmiDeviceInfo> getInputDevices() {
1438            enforceAccessPermission();
1439            // No need to hold the lock for obtaining TV device as the local device instance
1440            // is preserved while the HDMI control is enabled.
1441            HdmiCecLocalDeviceTv tv = tv();
1442            synchronized (mLock) {
1443                List<HdmiDeviceInfo> cecDevices = (tv == null)
1444                        ? Collections.<HdmiDeviceInfo>emptyList()
1445                        : tv.getSafeExternalInputsLocked();
1446                return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1447            }
1448        }
1449
1450        // Returns all the CEC devices on the bus including system audio, switch,
1451        // even those of reserved type.
1452        @Override
1453        public List<HdmiDeviceInfo> getDeviceList() {
1454            enforceAccessPermission();
1455            HdmiCecLocalDeviceTv tv = tv();
1456            synchronized (mLock) {
1457                return (tv == null)
1458                        ? Collections.<HdmiDeviceInfo>emptyList()
1459                        : tv.getSafeCecDevicesLocked();
1460            }
1461        }
1462
1463        @Override
1464        public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1465                final int maxIndex) {
1466            enforceAccessPermission();
1467            runOnServiceThread(new Runnable() {
1468                @Override
1469                public void run() {
1470                    HdmiCecLocalDeviceTv tv = tv();
1471                    if (tv == null) {
1472                        Slog.w(TAG, "Local tv device not available");
1473                        return;
1474                    }
1475                    tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1476                }
1477            });
1478        }
1479
1480        @Override
1481        public void setSystemAudioMute(final boolean mute) {
1482            enforceAccessPermission();
1483            runOnServiceThread(new Runnable() {
1484                @Override
1485                public void run() {
1486                    HdmiCecLocalDeviceTv tv = tv();
1487                    if (tv == null) {
1488                        Slog.w(TAG, "Local tv device not available");
1489                        return;
1490                    }
1491                    tv.changeMute(mute);
1492                }
1493            });
1494        }
1495
1496        @Override
1497        public void setArcMode(final boolean enabled) {
1498            enforceAccessPermission();
1499            runOnServiceThread(new Runnable() {
1500                @Override
1501                public void run() {
1502                    HdmiCecLocalDeviceTv tv = tv();
1503                    if (tv == null) {
1504                        Slog.w(TAG, "Local tv device not available to change arc mode.");
1505                        return;
1506                    }
1507                }
1508            });
1509        }
1510
1511        @Override
1512        public void setProhibitMode(final boolean enabled) {
1513            enforceAccessPermission();
1514            if (!isTvDevice()) {
1515                return;
1516            }
1517            HdmiControlService.this.setProhibitMode(enabled);
1518        }
1519
1520        @Override
1521        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1522                final int deviceType) {
1523            enforceAccessPermission();
1524            HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1525        }
1526
1527        @Override
1528        public void sendVendorCommand(final int deviceType, final int targetAddress,
1529                final byte[] params, final boolean hasVendorId) {
1530            enforceAccessPermission();
1531            runOnServiceThread(new Runnable() {
1532                @Override
1533                public void run() {
1534                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1535                    if (device == null) {
1536                        Slog.w(TAG, "Local device not available");
1537                        return;
1538                    }
1539                    if (hasVendorId) {
1540                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1541                                device.getDeviceInfo().getLogicalAddress(), targetAddress,
1542                                getVendorId(), params));
1543                    } else {
1544                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1545                                device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1546                    }
1547                }
1548            });
1549        }
1550
1551        @Override
1552        public void sendStandby(final int deviceType, final int deviceId) {
1553            enforceAccessPermission();
1554            runOnServiceThread(new Runnable() {
1555                @Override
1556                public void run() {
1557                    HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1558                    if (mhlDevice != null) {
1559                        mhlDevice.sendStandby();
1560                        return;
1561                    }
1562                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1563                    if (device == null) {
1564                        Slog.w(TAG, "Local device not available");
1565                        return;
1566                    }
1567                    device.sendStandby(deviceId);
1568                }
1569            });
1570        }
1571
1572        @Override
1573        public void setHdmiRecordListener(IHdmiRecordListener listener) {
1574            enforceAccessPermission();
1575            HdmiControlService.this.setHdmiRecordListener(listener);
1576        }
1577
1578        @Override
1579        public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1580            enforceAccessPermission();
1581            runOnServiceThread(new Runnable() {
1582                @Override
1583                public void run() {
1584                    if (!isTvDeviceEnabled()) {
1585                        Slog.w(TAG, "TV device is not enabled.");
1586                        return;
1587                    }
1588                    tv().startOneTouchRecord(recorderAddress, recordSource);
1589                }
1590            });
1591        }
1592
1593        @Override
1594        public void stopOneTouchRecord(final int recorderAddress) {
1595            enforceAccessPermission();
1596            runOnServiceThread(new Runnable() {
1597                @Override
1598                public void run() {
1599                    if (!isTvDeviceEnabled()) {
1600                        Slog.w(TAG, "TV device is not enabled.");
1601                        return;
1602                    }
1603                    tv().stopOneTouchRecord(recorderAddress);
1604                }
1605            });
1606        }
1607
1608        @Override
1609        public void startTimerRecording(final int recorderAddress, final int sourceType,
1610                final byte[] recordSource) {
1611            enforceAccessPermission();
1612            runOnServiceThread(new Runnable() {
1613                @Override
1614                public void run() {
1615                    if (!isTvDeviceEnabled()) {
1616                        Slog.w(TAG, "TV device is not enabled.");
1617                        return;
1618                    }
1619                    tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1620                }
1621            });
1622        }
1623
1624        @Override
1625        public void clearTimerRecording(final int recorderAddress, final int sourceType,
1626                final byte[] recordSource) {
1627            enforceAccessPermission();
1628            runOnServiceThread(new Runnable() {
1629                @Override
1630                public void run() {
1631                    if (!isTvDeviceEnabled()) {
1632                        Slog.w(TAG, "TV device is not enabled.");
1633                        return;
1634                    }
1635                    tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1636                }
1637            });
1638        }
1639
1640        @Override
1641        public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1642                final byte[] data) {
1643            enforceAccessPermission();
1644            runOnServiceThread(new Runnable() {
1645                @Override
1646                public void run() {
1647                    if (!isControlEnabled()) {
1648                        Slog.w(TAG, "Hdmi control is disabled.");
1649                        return ;
1650                    }
1651                    HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1652                    if (device == null) {
1653                        Slog.w(TAG, "Invalid port id:" + portId);
1654                        return;
1655                    }
1656                    mMhlController.sendVendorCommand(portId, offset, length, data);
1657                }
1658            });
1659        }
1660
1661        @Override
1662        public void addHdmiMhlVendorCommandListener(
1663                IHdmiMhlVendorCommandListener listener) {
1664            enforceAccessPermission();
1665            HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1666        }
1667
1668        @Override
1669        public void setStandbyMode(final boolean isStandbyModeOn) {
1670            enforceAccessPermission();
1671            runOnServiceThread(new Runnable() {
1672                @Override
1673                public void run() {
1674                    HdmiControlService.this.setStandbyMode(isStandbyModeOn);
1675                }
1676            });
1677        }
1678
1679        @Override
1680        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1681            if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
1682            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1683
1684            pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1685            pw.println("mProhibitMode: " + mProhibitMode);
1686            if (mCecController != null) {
1687                pw.println("mCecController: ");
1688                pw.increaseIndent();
1689                mCecController.dump(pw);
1690                pw.decreaseIndent();
1691            }
1692
1693            pw.println("mMhlController: ");
1694            pw.increaseIndent();
1695            mMhlController.dump(pw);
1696            pw.decreaseIndent();
1697
1698            pw.println("mPortInfo: ");
1699            pw.increaseIndent();
1700            for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1701                pw.println("- " + hdmiPortInfo);
1702            }
1703            pw.decreaseIndent();
1704            pw.println("mPowerStatus: " + mPowerStatus);
1705        }
1706    }
1707
1708    @ServiceThreadOnly
1709    private void oneTouchPlay(final IHdmiControlCallback callback) {
1710        assertRunOnServiceThread();
1711        HdmiCecLocalDevicePlayback source = playback();
1712        if (source == null) {
1713            Slog.w(TAG, "Local playback device not available");
1714            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1715            return;
1716        }
1717        source.oneTouchPlay(callback);
1718    }
1719
1720    @ServiceThreadOnly
1721    private void queryDisplayStatus(final IHdmiControlCallback callback) {
1722        assertRunOnServiceThread();
1723        HdmiCecLocalDevicePlayback source = playback();
1724        if (source == null) {
1725            Slog.w(TAG, "Local playback device not available");
1726            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1727            return;
1728        }
1729        source.queryDisplayStatus(callback);
1730    }
1731
1732    private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1733        final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1734        try {
1735            listener.asBinder().linkToDeath(record, 0);
1736        } catch (RemoteException e) {
1737            Slog.w(TAG, "Listener already died");
1738            return;
1739        }
1740        synchronized (mLock) {
1741            mHotplugEventListenerRecords.add(record);
1742        }
1743
1744        // Inform the listener of the initial state of each HDMI port by generating
1745        // hotplug events.
1746        runOnServiceThread(new Runnable() {
1747            @Override
1748            public void run() {
1749                synchronized (mLock) {
1750                    if (!mHotplugEventListenerRecords.contains(record)) return;
1751                }
1752                for (HdmiPortInfo port : mPortInfo) {
1753                    HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
1754                            mCecController.isConnected(port.getId()));
1755                    synchronized (mLock) {
1756                        invokeHotplugEventListenerLocked(listener, event);
1757                    }
1758                }
1759            }
1760        });
1761    }
1762
1763    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1764        synchronized (mLock) {
1765            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1766                if (record.mListener.asBinder() == listener.asBinder()) {
1767                    listener.asBinder().unlinkToDeath(record, 0);
1768                    mHotplugEventListenerRecords.remove(record);
1769                    break;
1770                }
1771            }
1772        }
1773    }
1774
1775    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1776        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1777        try {
1778            listener.asBinder().linkToDeath(record, 0);
1779        } catch (RemoteException e) {
1780            Slog.w(TAG, "Listener already died");
1781            return;
1782        }
1783        synchronized (mLock) {
1784            mDeviceEventListenerRecords.add(record);
1785        }
1786    }
1787
1788    void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1789        synchronized (mLock) {
1790            for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1791                try {
1792                    record.mListener.onStatusChanged(device, status);
1793                } catch (RemoteException e) {
1794                    Slog.e(TAG, "Failed to report device event:" + e);
1795                }
1796            }
1797        }
1798    }
1799
1800    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1801        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1802                listener);
1803        try {
1804            listener.asBinder().linkToDeath(record, 0);
1805        } catch (RemoteException e) {
1806            Slog.w(TAG, "Listener already died");
1807            return;
1808        }
1809        synchronized (mLock) {
1810            mSystemAudioModeChangeListenerRecords.add(record);
1811        }
1812    }
1813
1814    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1815        synchronized (mLock) {
1816            for (SystemAudioModeChangeListenerRecord record :
1817                    mSystemAudioModeChangeListenerRecords) {
1818                if (record.mListener.asBinder() == listener) {
1819                    listener.asBinder().unlinkToDeath(record, 0);
1820                    mSystemAudioModeChangeListenerRecords.remove(record);
1821                    break;
1822                }
1823            }
1824        }
1825    }
1826
1827    private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1828        private final IHdmiInputChangeListener mListener;
1829
1830        public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1831            mListener = listener;
1832        }
1833
1834        @Override
1835        public void binderDied() {
1836            synchronized (mLock) {
1837                if (mInputChangeListenerRecord == this) {
1838                    mInputChangeListenerRecord = null;
1839                }
1840            }
1841        }
1842    }
1843
1844    private void setInputChangeListener(IHdmiInputChangeListener listener) {
1845        synchronized (mLock) {
1846            mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1847            try {
1848                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1849            } catch (RemoteException e) {
1850                Slog.w(TAG, "Listener already died");
1851                return;
1852            }
1853        }
1854    }
1855
1856    void invokeInputChangeListener(HdmiDeviceInfo info) {
1857        synchronized (mLock) {
1858            if (mInputChangeListenerRecord != null) {
1859                try {
1860                    mInputChangeListenerRecord.mListener.onChanged(info);
1861                } catch (RemoteException e) {
1862                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1863                }
1864            }
1865        }
1866    }
1867
1868    private void setHdmiRecordListener(IHdmiRecordListener listener) {
1869        synchronized (mLock) {
1870            mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1871            try {
1872                listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1873            } catch (RemoteException e) {
1874                Slog.w(TAG, "Listener already died.", e);
1875            }
1876        }
1877    }
1878
1879    byte[] invokeRecordRequestListener(int recorderAddress) {
1880        synchronized (mLock) {
1881            if (mRecordListenerRecord != null) {
1882                try {
1883                    return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1884                } catch (RemoteException e) {
1885                    Slog.w(TAG, "Failed to start record.", e);
1886                }
1887            }
1888            return EmptyArray.BYTE;
1889        }
1890    }
1891
1892    void invokeOneTouchRecordResult(int recorderAddress, int result) {
1893        synchronized (mLock) {
1894            if (mRecordListenerRecord != null) {
1895                try {
1896                    mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
1897                } catch (RemoteException e) {
1898                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1899                }
1900            }
1901        }
1902    }
1903
1904    void invokeTimerRecordingResult(int recorderAddress, int result) {
1905        synchronized (mLock) {
1906            if (mRecordListenerRecord != null) {
1907                try {
1908                    mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
1909                } catch (RemoteException e) {
1910                    Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1911                }
1912            }
1913        }
1914    }
1915
1916    void invokeClearTimerRecordingResult(int recorderAddress, int result) {
1917        synchronized (mLock) {
1918            if (mRecordListenerRecord != null) {
1919                try {
1920                    mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
1921                            result);
1922                } catch (RemoteException e) {
1923                    Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1924                }
1925            }
1926        }
1927    }
1928
1929    private void invokeCallback(IHdmiControlCallback callback, int result) {
1930        try {
1931            callback.onComplete(result);
1932        } catch (RemoteException e) {
1933            Slog.e(TAG, "Invoking callback failed:" + e);
1934        }
1935    }
1936
1937    private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1938            boolean enabled) {
1939        try {
1940            listener.onStatusChanged(enabled);
1941        } catch (RemoteException e) {
1942            Slog.e(TAG, "Invoking callback failed:" + e);
1943        }
1944    }
1945
1946    private void announceHotplugEvent(int portId, boolean connected) {
1947        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1948        synchronized (mLock) {
1949            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1950                invokeHotplugEventListenerLocked(record.mListener, event);
1951            }
1952        }
1953    }
1954
1955    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1956            HdmiHotplugEvent event) {
1957        try {
1958            listener.onReceived(event);
1959        } catch (RemoteException e) {
1960            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1961        }
1962    }
1963
1964    public HdmiCecLocalDeviceTv tv() {
1965        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1966    }
1967
1968    boolean isTvDevice() {
1969        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
1970    }
1971
1972    boolean isTvDeviceEnabled() {
1973        return isTvDevice() && tv() != null;
1974    }
1975
1976    private HdmiCecLocalDevicePlayback playback() {
1977        return (HdmiCecLocalDevicePlayback)
1978                mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1979    }
1980
1981    AudioManager getAudioManager() {
1982        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1983    }
1984
1985    boolean isControlEnabled() {
1986        synchronized (mLock) {
1987            return mHdmiControlEnabled;
1988        }
1989    }
1990
1991    @ServiceThreadOnly
1992    int getPowerStatus() {
1993        assertRunOnServiceThread();
1994        return mPowerStatus;
1995    }
1996
1997    @ServiceThreadOnly
1998    boolean isPowerOnOrTransient() {
1999        assertRunOnServiceThread();
2000        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
2001                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
2002    }
2003
2004    @ServiceThreadOnly
2005    boolean isPowerStandbyOrTransient() {
2006        assertRunOnServiceThread();
2007        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
2008                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
2009    }
2010
2011    @ServiceThreadOnly
2012    boolean isPowerStandby() {
2013        assertRunOnServiceThread();
2014        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
2015    }
2016
2017    @ServiceThreadOnly
2018    void wakeUp() {
2019        assertRunOnServiceThread();
2020        mWakeUpMessageReceived = true;
2021        mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
2022        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2023        // the intent, the sequence will continue at onWakeUp().
2024    }
2025
2026    @ServiceThreadOnly
2027    void standby() {
2028        assertRunOnServiceThread();
2029        if (!canGoToStandby()) {
2030            return;
2031        }
2032        mStandbyMessageReceived = true;
2033        mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2034        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2035        // the intent, the sequence will continue at onStandby().
2036    }
2037
2038    boolean isWakeUpMessageReceived() {
2039        return mWakeUpMessageReceived;
2040    }
2041
2042    @ServiceThreadOnly
2043    private void onWakeUp() {
2044        assertRunOnServiceThread();
2045        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
2046        if (mCecController != null) {
2047            if (mHdmiControlEnabled) {
2048                int startReason = INITIATED_BY_SCREEN_ON;
2049                if (mWakeUpMessageReceived) {
2050                    startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2051                }
2052                initializeCec(startReason);
2053            }
2054        } else {
2055            Slog.i(TAG, "Device does not support HDMI-CEC.");
2056        }
2057        // TODO: Initialize MHL local devices.
2058    }
2059
2060    @ServiceThreadOnly
2061    private void onStandby(final int standbyAction) {
2062        assertRunOnServiceThread();
2063        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
2064        invokeVendorCommandListenersOnControlStateChanged(false,
2065                HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
2066        if (!canGoToStandby()) {
2067            mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2068            return;
2069        }
2070
2071        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2072        disableDevices(new PendingActionClearedCallback() {
2073            @Override
2074            public void onCleared(HdmiCecLocalDevice device) {
2075                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2076                devices.remove(device);
2077                if (devices.isEmpty()) {
2078                    onStandbyCompleted(standbyAction);
2079                    // We will not clear local devices here, since some OEM/SOC will keep passing
2080                    // the received packets until the application processor enters to the sleep
2081                    // actually.
2082                }
2083            }
2084        });
2085    }
2086
2087    private boolean canGoToStandby() {
2088        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2089            if (!device.canGoToStandby()) return false;
2090        }
2091        return true;
2092    }
2093
2094    @ServiceThreadOnly
2095    private void onLanguageChanged(String language) {
2096        assertRunOnServiceThread();
2097        mLanguage = language;
2098
2099        if (isTvDeviceEnabled()) {
2100            tv().broadcastMenuLanguage(language);
2101            mCecController.setLanguage(language);
2102        }
2103    }
2104
2105    @ServiceThreadOnly
2106    String getLanguage() {
2107        assertRunOnServiceThread();
2108        return mLanguage;
2109    }
2110
2111    private void disableDevices(PendingActionClearedCallback callback) {
2112        if (mCecController != null) {
2113            for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2114                device.disableDevice(mStandbyMessageReceived, callback);
2115            }
2116        }
2117
2118        mMhlController.clearAllLocalDevices();
2119    }
2120
2121    @ServiceThreadOnly
2122    private void clearLocalDevices() {
2123        assertRunOnServiceThread();
2124        if (mCecController == null) {
2125            return;
2126        }
2127        mCecController.clearLogicalAddress();
2128        mCecController.clearLocalDevices();
2129    }
2130
2131    @ServiceThreadOnly
2132    private void onStandbyCompleted(int standbyAction) {
2133        assertRunOnServiceThread();
2134        Slog.v(TAG, "onStandbyCompleted");
2135
2136        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
2137            return;
2138        }
2139        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2140        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2141            device.onStandby(mStandbyMessageReceived, standbyAction);
2142        }
2143        mStandbyMessageReceived = false;
2144        mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
2145        mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
2146    }
2147
2148    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2149        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2150        try {
2151            listener.asBinder().linkToDeath(record, 0);
2152        } catch (RemoteException e) {
2153            Slog.w(TAG, "Listener already died");
2154            return;
2155        }
2156        synchronized (mLock) {
2157            mVendorCommandListenerRecords.add(record);
2158        }
2159    }
2160
2161    boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2162            byte[] params, boolean hasVendorId) {
2163        synchronized (mLock) {
2164            if (mVendorCommandListenerRecords.isEmpty()) {
2165                return false;
2166            }
2167            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2168                if (record.mDeviceType != deviceType) {
2169                    continue;
2170                }
2171                try {
2172                    record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
2173                } catch (RemoteException e) {
2174                    Slog.e(TAG, "Failed to notify vendor command reception", e);
2175                }
2176            }
2177            return true;
2178        }
2179    }
2180
2181    boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2182        synchronized (mLock) {
2183            if (mVendorCommandListenerRecords.isEmpty()) {
2184                return false;
2185            }
2186            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2187                try {
2188                    record.mListener.onControlStateChanged(enabled, reason);
2189                } catch (RemoteException e) {
2190                    Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2191                }
2192            }
2193            return true;
2194        }
2195    }
2196
2197    private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2198        HdmiMhlVendorCommandListenerRecord record =
2199                new HdmiMhlVendorCommandListenerRecord(listener);
2200        try {
2201            listener.asBinder().linkToDeath(record, 0);
2202        } catch (RemoteException e) {
2203            Slog.w(TAG, "Listener already died.");
2204            return;
2205        }
2206
2207        synchronized (mLock) {
2208            mMhlVendorCommandListenerRecords.add(record);
2209        }
2210    }
2211
2212    void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
2213        synchronized (mLock) {
2214            for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
2215                try {
2216                    record.mListener.onReceived(portId, offest, length, data);
2217                } catch (RemoteException e) {
2218                    Slog.e(TAG, "Failed to notify MHL vendor command", e);
2219                }
2220            }
2221        }
2222    }
2223
2224    void setStandbyMode(boolean isStandbyModeOn) {
2225        assertRunOnServiceThread();
2226        if (isPowerOnOrTransient() && isStandbyModeOn) {
2227            mPowerManager.goToSleep(SystemClock.uptimeMillis(),
2228                    PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2229            if (playback() != null) {
2230                playback().sendStandby(0 /* unused */);
2231            }
2232        } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
2233            mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
2234            if (playback() != null) {
2235                oneTouchPlay(new IHdmiControlCallback.Stub() {
2236                    @Override
2237                    public void onComplete(int result) {
2238                        if (result != HdmiControlManager.RESULT_SUCCESS) {
2239                            Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
2240                        }
2241                    }
2242                });
2243            }
2244        }
2245    }
2246
2247    boolean isProhibitMode() {
2248        synchronized (mLock) {
2249            return mProhibitMode;
2250        }
2251    }
2252
2253    void setProhibitMode(boolean enabled) {
2254        synchronized (mLock) {
2255            mProhibitMode = enabled;
2256        }
2257    }
2258
2259    @ServiceThreadOnly
2260    void setCecOption(int key, boolean value) {
2261        assertRunOnServiceThread();
2262        mCecController.setOption(key, value);
2263    }
2264
2265    @ServiceThreadOnly
2266    void setControlEnabled(boolean enabled) {
2267        assertRunOnServiceThread();
2268
2269        synchronized (mLock) {
2270            mHdmiControlEnabled = enabled;
2271        }
2272
2273        if (enabled) {
2274            enableHdmiControlService();
2275            return;
2276        }
2277        // Call the vendor handler before the service is disabled.
2278        invokeVendorCommandListenersOnControlStateChanged(false,
2279                HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2280        // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2281        // a chance to run.
2282        runOnServiceThread(new Runnable() {
2283            @Override
2284            public void run() {
2285                disableHdmiControlService();
2286            }
2287        });
2288        return;
2289    }
2290
2291    @ServiceThreadOnly
2292    private void enableHdmiControlService() {
2293        mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
2294        mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2295
2296        initializeCec(INITIATED_BY_ENABLE_CEC);
2297    }
2298
2299    @ServiceThreadOnly
2300    private void disableHdmiControlService() {
2301        disableDevices(new PendingActionClearedCallback() {
2302            @Override
2303            public void onCleared(HdmiCecLocalDevice device) {
2304                assertRunOnServiceThread();
2305                mCecController.flush(new Runnable() {
2306                    @Override
2307                    public void run() {
2308                        mCecController.setOption(OptionKey.ENABLE_CEC, false);
2309                        mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2310                        clearLocalDevices();
2311                    }
2312                });
2313            }
2314        });
2315    }
2316
2317    @ServiceThreadOnly
2318    void setActivePortId(int portId) {
2319        assertRunOnServiceThread();
2320        mActivePortId = portId;
2321
2322        // Resets last input for MHL, which stays valid only after the MHL device was selected,
2323        // and no further switching is done.
2324        setLastInputForMhl(Constants.INVALID_PORT_ID);
2325    }
2326
2327    @ServiceThreadOnly
2328    void setLastInputForMhl(int portId) {
2329        assertRunOnServiceThread();
2330        mLastInputMhl = portId;
2331    }
2332
2333    @ServiceThreadOnly
2334    int getLastInputForMhl() {
2335        assertRunOnServiceThread();
2336        return mLastInputMhl;
2337    }
2338
2339    /**
2340     * Performs input change, routing control for MHL device.
2341     *
2342     * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2343     * @param contentOn {@code true} if RAP data is content on; otherwise false
2344     */
2345    @ServiceThreadOnly
2346    void changeInputForMhl(int portId, boolean contentOn) {
2347        assertRunOnServiceThread();
2348        if (tv() == null) return;
2349        final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
2350        if (portId != Constants.INVALID_PORT_ID) {
2351            tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2352                @Override
2353                public void onComplete(int result) throws RemoteException {
2354                    // Keep the last input to switch back later when RAP[ContentOff] is received.
2355                    // This effectively sets the port to invalid one if the switching is for
2356                    // RAP[ContentOff].
2357                    setLastInputForMhl(lastInput);
2358                }
2359            });
2360        }
2361        // MHL device is always directly connected to the port. Update the active port ID to avoid
2362        // unnecessary post-routing control task.
2363        tv().setActivePortId(portId);
2364
2365        // The port is either the MHL-enabled port where the mobile device is connected, or
2366        // the last port to go back to when turnoff command is received. Note that the last port
2367        // may not be the MHL-enabled one. In this case the device info to be passed to
2368        // input change listener should be the one describing the corresponding HDMI port.
2369        HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
2370        HdmiDeviceInfo info = (device != null) ? device.getInfo()
2371                : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
2372        invokeInputChangeListener(info);
2373    }
2374
2375   void setMhlInputChangeEnabled(boolean enabled) {
2376       mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2377
2378        synchronized (mLock) {
2379            mMhlInputChangeEnabled = enabled;
2380        }
2381    }
2382
2383    boolean isMhlInputChangeEnabled() {
2384        synchronized (mLock) {
2385            return mMhlInputChangeEnabled;
2386        }
2387    }
2388
2389    @ServiceThreadOnly
2390    void displayOsd(int messageId) {
2391        assertRunOnServiceThread();
2392        Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2393        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2394        getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2395                HdmiControlService.PERMISSION);
2396    }
2397
2398    @ServiceThreadOnly
2399    void displayOsd(int messageId, int extra) {
2400        assertRunOnServiceThread();
2401        Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2402        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2403        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
2404        getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2405                HdmiControlService.PERMISSION);
2406    }
2407}
2408