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