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