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