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