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