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