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