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