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