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