[go: nahoru, domu]

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