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