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