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