HdmiControlService.java revision 544b62bb863788727587ee292596451e461fc0a7
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 android.annotation.Nullable;
20import android.content.BroadcastReceiver;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.hardware.hdmi.HdmiCecDeviceInfo;
26import android.hardware.hdmi.HdmiControlManager;
27import android.hardware.hdmi.HdmiHotplugEvent;
28import android.hardware.hdmi.HdmiPortInfo;
29import android.hardware.hdmi.HdmiTvClient;
30import android.hardware.hdmi.IHdmiControlCallback;
31import android.hardware.hdmi.IHdmiControlService;
32import android.hardware.hdmi.IHdmiDeviceEventListener;
33import android.hardware.hdmi.IHdmiHotplugEventListener;
34import android.hardware.hdmi.IHdmiInputChangeListener;
35import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
36import android.hardware.hdmi.IHdmiVendorCommandListener;
37import android.media.AudioManager;
38import android.os.Build;
39import android.os.Handler;
40import android.os.HandlerThread;
41import android.os.IBinder;
42import android.os.Looper;
43import android.os.PowerManager;
44import android.os.RemoteException;
45import android.os.SystemClock;
46import android.provider.Settings.Global;
47import android.util.Slog;
48import android.util.SparseArray;
49import android.util.SparseIntArray;
50
51import com.android.internal.annotations.GuardedBy;
52import com.android.server.SystemService;
53import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
54import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
55import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
56
57import java.util.ArrayList;
58import java.util.Collections;
59import java.util.List;
60
61/**
62 * Provides a service for sending and processing HDMI control messages,
63 * HDMI-CEC and MHL control command, and providing the information on both standard.
64 */
65public final class HdmiControlService extends SystemService {
66    private static final String TAG = "HdmiControlService";
67
68    static final String PERMISSION = "android.permission.HDMI_CEC";
69
70    /**
71     * Interface to report send result.
72     */
73    interface SendMessageCallback {
74        /**
75         * Called when {@link HdmiControlService#sendCecCommand} is completed.
76         *
77         * @param error result of send request.
78         * <ul>
79         * <li>{@link Constants#SEND_RESULT_SUCCESS}
80         * <li>{@link Constants#SEND_RESULT_NAK}
81         * <li>{@link Constants#SEND_RESULT_FAILURE}
82         * </ul>
83         */
84        void onSendCompleted(int error);
85    }
86
87    /**
88     * Interface to get a list of available logical devices.
89     */
90    interface DevicePollingCallback {
91        /**
92         * Called when device polling is finished.
93         *
94         * @param ackedAddress a list of logical addresses of available devices
95         */
96        void onPollingFinished(List<Integer> ackedAddress);
97    }
98
99    private class PowerStateReceiver extends BroadcastReceiver {
100        @Override
101        public void onReceive(Context context, Intent intent) {
102            switch (intent.getAction()) {
103                case Intent.ACTION_SCREEN_OFF:
104                    if (isPowerOnOrTransient()) {
105                        onStandby();
106                    }
107                    break;
108                case Intent.ACTION_SCREEN_ON:
109                    if (isPowerStandbyOrTransient()) {
110                        onWakeUp();
111                    }
112                    break;
113            }
114        }
115    }
116
117    // A thread to handle synchronous IO of CEC and MHL control service.
118    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
119    // and sparse call it shares a thread to handle IO operations.
120    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
121
122    // Used to synchronize the access to the service.
123    private final Object mLock = new Object();
124
125    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
126    private final List<Integer> mLocalDevices;
127
128    // List of listeners registered by callers that want to get notified of
129    // hotplug events.
130    @GuardedBy("mLock")
131    private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
132
133    // List of records for hotplug event listener to handle the the caller killed in action.
134    @GuardedBy("mLock")
135    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
136            new ArrayList<>();
137
138    // List of listeners registered by callers that want to get notified of
139    // device status events.
140    @GuardedBy("mLock")
141    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
142
143    // List of records for device event listener to handle the the caller killed in action.
144    @GuardedBy("mLock")
145    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
146            new ArrayList<>();
147
148    // List of records for vendor command listener to handle the the caller killed in action.
149    @GuardedBy("mLock")
150    private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
151            new ArrayList<>();
152
153    @GuardedBy("mLock")
154    private IHdmiInputChangeListener mInputChangeListener;
155
156    @GuardedBy("mLock")
157    private InputChangeListenerRecord mInputChangeListenerRecord;
158
159    // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
160    // handling will be disabled and no request will be handled.
161    @GuardedBy("mLock")
162    private boolean mHdmiControlEnabled;
163
164    // Set to true while the service is in normal mode. While set to false, no input change is
165    // allowed. Used for situations where input change can confuse users such as channel auto-scan,
166    // system upgrade, etc., a.k.a. "prohibit mode".
167    @GuardedBy("mLock")
168    private boolean mProhibitMode;
169
170    // List of listeners registered by callers that want to get notified of
171    // system audio mode changes.
172    private final ArrayList<IHdmiSystemAudioModeChangeListener>
173            mSystemAudioModeChangeListeners = new ArrayList<>();
174    // List of records for system audio mode change to handle the the caller killed in action.
175    private final ArrayList<SystemAudioModeChangeListenerRecord>
176            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
177
178    // Handler used to run a task in service thread.
179    private final Handler mHandler = new Handler();
180
181    @Nullable
182    private HdmiCecController mCecController;
183
184    @Nullable
185    private HdmiMhlController mMhlController;
186
187    // HDMI port information. Stored in the unmodifiable list to keep the static information
188    // from being modified.
189    private List<HdmiPortInfo> mPortInfo;
190
191    private HdmiCecMessageValidator mMessageValidator;
192
193    private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver();
194
195    @ServiceThreadOnly
196    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
197
198    @ServiceThreadOnly
199    private boolean mStandbyMessageReceived = false;
200
201    public HdmiControlService(Context context) {
202        super(context);
203        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
204                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
205    }
206
207    @Override
208    public void onStart() {
209        mIoThread.start();
210        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
211        mProhibitMode = false;
212        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
213
214        mCecController = HdmiCecController.create(this);
215        if (mCecController != null) {
216            // TODO: Remove this as soon as OEM's HAL implementation is corrected.
217            mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE,
218                    HdmiTvClient.ENABLED);
219
220            // TODO: load value for mHdmiControlEnabled from preference.
221            if (mHdmiControlEnabled) {
222                initializeCec(true);
223            }
224        } else {
225            Slog.i(TAG, "Device does not support HDMI-CEC.");
226        }
227
228        mMhlController = HdmiMhlController.create(this);
229        if (mMhlController == null) {
230            Slog.i(TAG, "Device does not support MHL-control.");
231        }
232        mPortInfo = initPortInfo();
233        mMessageValidator = new HdmiCecMessageValidator(this);
234        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
235
236        // Register broadcast receiver for power state change.
237        if (mCecController != null || mMhlController != null) {
238            IntentFilter filter = new IntentFilter();
239            filter.addAction(Intent.ACTION_SCREEN_OFF);
240            filter.addAction(Intent.ACTION_SCREEN_ON);
241            getContext().registerReceiver(mPowerStateReceiver, filter);
242        }
243    }
244
245    boolean readBooleanSetting(String key, boolean defVal) {
246        ContentResolver cr = getContext().getContentResolver();
247        return Global.getInt(cr, key, defVal ? Constants.TRUE : Constants.FALSE) == Constants.TRUE;
248    }
249
250    void writeBooleanSetting(String key, boolean value) {
251        ContentResolver cr = getContext().getContentResolver();
252        Global.putInt(cr, key, value ? Constants.TRUE : Constants.FALSE);
253    }
254
255    private void initializeCec(boolean fromBootup) {
256        mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL,
257                HdmiTvClient.ENABLED);
258        initializeLocalDevices(mLocalDevices, fromBootup);
259    }
260
261    @ServiceThreadOnly
262    private void initializeLocalDevices(final List<Integer> deviceTypes, final boolean fromBootup) {
263        assertRunOnServiceThread();
264        // A container for [Logical Address, Local device info].
265        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
266        final SparseIntArray finished = new SparseIntArray();
267        mCecController.clearLogicalAddress();
268        for (int type : deviceTypes) {
269            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
270            localDevice.init();
271            mCecController.allocateLogicalAddress(type,
272                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
273                @Override
274                public void onAllocated(int deviceType, int logicalAddress) {
275                    if (logicalAddress == Constants.ADDR_UNREGISTERED) {
276                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
277                    } else {
278                        HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType);
279                        localDevice.setDeviceInfo(deviceInfo);
280                        mCecController.addLocalDevice(deviceType, localDevice);
281                        mCecController.addLogicalAddress(logicalAddress);
282                        devices.append(logicalAddress, localDevice);
283                    }
284                    finished.append(deviceType, logicalAddress);
285
286                    // Address allocation completed for all devices. Notify each device.
287                    if (deviceTypes.size() == finished.size()) {
288                        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
289                            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
290                        }
291                        notifyAddressAllocated(devices, fromBootup);
292                    }
293                }
294            });
295        }
296    }
297
298    @ServiceThreadOnly
299    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices,
300            boolean fromBootup) {
301        assertRunOnServiceThread();
302        for (int i = 0; i < devices.size(); ++i) {
303            int address = devices.keyAt(i);
304            HdmiCecLocalDevice device = devices.valueAt(i);
305            device.handleAddressAllocated(address, fromBootup);
306        }
307    }
308
309    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
310    // keep them in one place.
311    @ServiceThreadOnly
312    private List<HdmiPortInfo> initPortInfo() {
313        assertRunOnServiceThread();
314        HdmiPortInfo[] cecPortInfo = null;
315
316        // CEC HAL provides majority of the info while MHL does only MHL support flag for
317        // each port. Return empty array if CEC HAL didn't provide the info.
318        if (mCecController != null) {
319            cecPortInfo = mCecController.getPortInfos();
320        }
321        if (cecPortInfo == null) {
322            return Collections.emptyList();
323        }
324
325        HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0];
326        if (mMhlController != null) {
327            // TODO: Implement plumbing logic to get MHL port information.
328            // mhlPortInfo = mMhlController.getPortInfos();
329        }
330
331        // Use the id (port number) to find the matched info between CEC and MHL to combine them
332        // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found.
333        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
334        for (int i = 0; i < cecPortInfo.length; ++i) {
335            HdmiPortInfo cec = cecPortInfo[i];
336            int id = cec.getId();
337            boolean mhlInfoFound = false;
338            for (HdmiPortInfo mhl : mhlPortInfo) {
339                if (id == mhl.getId()) {
340                    result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(),
341                            cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported()));
342                    mhlInfoFound = true;
343                    break;
344                }
345            }
346            if (!mhlInfoFound) {
347                result.add(cec);
348            }
349        }
350
351        return Collections.unmodifiableList(result);
352    }
353
354    /**
355     * Returns HDMI port information for the given port id.
356     *
357     * @param portId HDMI port id
358     * @return {@link HdmiPortInfo} for the given port
359     */
360    HdmiPortInfo getPortInfo(int portId) {
361        // mPortInfo is an unmodifiable list and the only reference to its inner list.
362        // No lock is necessary.
363        for (HdmiPortInfo info : mPortInfo) {
364            if (portId == info.getId()) {
365                return info;
366            }
367        }
368        return null;
369    }
370
371    /**
372     * Returns the routing path (physical address) of the HDMI port for the given
373     * port id.
374     */
375    int portIdToPath(int portId) {
376        HdmiPortInfo portInfo = getPortInfo(portId);
377        if (portInfo == null) {
378            Slog.e(TAG, "Cannot find the port info: " + portId);
379            return Constants.INVALID_PHYSICAL_ADDRESS;
380        }
381        return portInfo.getAddress();
382    }
383
384    /**
385     * Returns the id of HDMI port located at the top of the hierarchy of
386     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
387     * the port id to be returned is the ID associated with the port address
388     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
389     */
390    int pathToPortId(int path) {
391        int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
392        for (HdmiPortInfo info : mPortInfo) {
393            if (portAddress == info.getAddress()) {
394                return info.getId();
395            }
396        }
397        return Constants.INVALID_PORT_ID;
398    }
399
400    boolean isValidPortId(int portId) {
401        for (HdmiPortInfo info : mPortInfo) {
402            if (portId == info.getId()) {
403                return true;
404            }
405        }
406        return false;
407    }
408
409    /**
410     * Returns {@link Looper} for IO operation.
411     *
412     * <p>Declared as package-private.
413     */
414    Looper getIoLooper() {
415        return mIoThread.getLooper();
416    }
417
418    /**
419     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
420     * for tasks that are running on main service thread.
421     *
422     * <p>Declared as package-private.
423     */
424    Looper getServiceLooper() {
425        return mHandler.getLooper();
426    }
427
428    /**
429     * Returns physical address of the device.
430     */
431    int getPhysicalAddress() {
432        return mCecController.getPhysicalAddress();
433    }
434
435    /**
436     * Returns vendor id of CEC service.
437     */
438    int getVendorId() {
439        return mCecController.getVendorId();
440    }
441
442    @ServiceThreadOnly
443    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
444        assertRunOnServiceThread();
445        HdmiCecLocalDeviceTv tv = tv();
446        if (tv == null) {
447            return null;
448        }
449        return tv.getDeviceInfo(logicalAddress);
450    }
451
452    /**
453     * Returns version of CEC.
454     */
455    int getCecVersion() {
456        return mCecController.getVersion();
457    }
458
459    /**
460     * Whether a device of the specified physical address is connected to ARC enabled port.
461     */
462    boolean isConnectedToArcPort(int physicalAddress) {
463        for (HdmiPortInfo portInfo : mPortInfo) {
464            if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
465                    && portInfo.isArcSupported()) {
466                return true;
467            }
468        }
469        return false;
470    }
471
472    void runOnServiceThread(Runnable runnable) {
473        mHandler.post(runnable);
474    }
475
476    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
477        mHandler.postAtFrontOfQueue(runnable);
478    }
479
480    private void assertRunOnServiceThread() {
481        if (Looper.myLooper() != mHandler.getLooper()) {
482            throw new IllegalStateException("Should run on service thread.");
483        }
484    }
485
486    /**
487     * Transmit a CEC command to CEC bus.
488     *
489     * @param command CEC command to send out
490     * @param callback interface used to the result of send command
491     */
492    @ServiceThreadOnly
493    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
494        assertRunOnServiceThread();
495        mCecController.sendCommand(command, callback);
496    }
497
498    @ServiceThreadOnly
499    void sendCecCommand(HdmiCecMessage command) {
500        assertRunOnServiceThread();
501        mCecController.sendCommand(command, null);
502    }
503
504    @ServiceThreadOnly
505    boolean handleCecCommand(HdmiCecMessage message) {
506        assertRunOnServiceThread();
507        if (!mMessageValidator.isValid(message)) {
508            return false;
509        }
510        return dispatchMessageToLocalDevice(message);
511    }
512
513    void setAudioReturnChannel(boolean enabled) {
514        mCecController.setAudioReturnChannel(enabled);
515    }
516
517    @ServiceThreadOnly
518    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
519        assertRunOnServiceThread();
520        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
521            if (device.dispatchMessage(message)
522                    && message.getDestination() != Constants.ADDR_BROADCAST) {
523                return true;
524            }
525        }
526
527        if (message.getDestination() != Constants.ADDR_BROADCAST) {
528            Slog.w(TAG, "Unhandled cec command:" + message);
529        }
530        return false;
531    }
532
533    /**
534     * Called when a new hotplug event is issued.
535     *
536     * @param portNo hdmi port number where hot plug event issued.
537     * @param connected whether to be plugged in or not
538     */
539    @ServiceThreadOnly
540    void onHotplug(int portNo, boolean connected) {
541        assertRunOnServiceThread();
542        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
543            device.onHotplug(portNo, connected);
544        }
545        announceHotplugEvent(portNo, connected);
546    }
547
548    /**
549     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
550     * devices.
551     *
552     * @param callback an interface used to get a list of all remote devices' address
553     * @param sourceAddress a logical address of source device where sends polling message
554     * @param pickStrategy strategy how to pick polling candidates
555     * @param retryCount the number of retry used to send polling message to remote devices
556     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
557     */
558    @ServiceThreadOnly
559    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
560            int retryCount) {
561        assertRunOnServiceThread();
562        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
563                retryCount);
564    }
565
566    private int checkPollStrategy(int pickStrategy) {
567        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
568        if (strategy == 0) {
569            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
570        }
571        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
572        if (iterationStrategy == 0) {
573            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
574        }
575        return strategy | iterationStrategy;
576    }
577
578    List<HdmiCecLocalDevice> getAllLocalDevices() {
579        assertRunOnServiceThread();
580        return mCecController.getLocalDeviceList();
581    }
582
583    Object getServiceLock() {
584        return mLock;
585    }
586
587    void setAudioStatus(boolean mute, int volume) {
588        AudioManager audioManager = getAudioManager();
589        boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
590        if (mute) {
591            if (!muted) {
592                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
593            }
594        } else {
595            if (muted) {
596                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
597            }
598            // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
599            // volume change notification back to hdmi control service.
600            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
601                    AudioManager.FLAG_SHOW_UI |
602                    AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
603        }
604    }
605
606    void announceSystemAudioModeChange(boolean enabled) {
607        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
608            invokeSystemAudioModeChange(listener, enabled);
609        }
610    }
611
612    private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
613        // TODO: find better name instead of model name.
614        String displayName = Build.MODEL;
615        return new HdmiCecDeviceInfo(logicalAddress,
616                getPhysicalAddress(), deviceType, getVendorId(), displayName);
617    }
618
619    // Record class that monitors the event of the caller of being killed. Used to clean up
620    // the listener list and record list accordingly.
621    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
622        private final IHdmiHotplugEventListener mListener;
623
624        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
625            mListener = listener;
626        }
627
628        @Override
629        public void binderDied() {
630            synchronized (mLock) {
631                mHotplugEventListenerRecords.remove(this);
632                mHotplugEventListeners.remove(mListener);
633            }
634        }
635    }
636
637    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
638        private final IHdmiDeviceEventListener mListener;
639
640        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
641            mListener = listener;
642        }
643
644        @Override
645        public void binderDied() {
646            synchronized (mLock) {
647                mDeviceEventListenerRecords.remove(this);
648                mDeviceEventListeners.remove(mListener);
649            }
650        }
651    }
652
653    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
654        private final IHdmiSystemAudioModeChangeListener mListener;
655
656        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
657            mListener = listener;
658        }
659
660        @Override
661        public void binderDied() {
662            synchronized (mLock) {
663                mSystemAudioModeChangeListenerRecords.remove(this);
664                mSystemAudioModeChangeListeners.remove(mListener);
665            }
666        }
667    }
668
669    class VendorCommandListenerRecord implements IBinder.DeathRecipient {
670        private final IHdmiVendorCommandListener mListener;
671        private final int mDeviceType;
672
673        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
674            mListener = listener;
675            mDeviceType = deviceType;
676        }
677
678        @Override
679        public void binderDied() {
680            synchronized (mLock) {
681                mVendorCommandListenerRecords.remove(this);
682            }
683        }
684    }
685
686    private void enforceAccessPermission() {
687        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
688    }
689
690    private final class BinderService extends IHdmiControlService.Stub {
691        @Override
692        public int[] getSupportedTypes() {
693            enforceAccessPermission();
694            // mLocalDevices is an unmodifiable list - no lock necesary.
695            int[] localDevices = new int[mLocalDevices.size()];
696            for (int i = 0; i < localDevices.length; ++i) {
697                localDevices[i] = mLocalDevices.get(i);
698            }
699            return localDevices;
700        }
701
702        @Override
703        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
704            enforceAccessPermission();
705            runOnServiceThread(new Runnable() {
706                @Override
707                public void run() {
708                    HdmiCecLocalDeviceTv tv = tv();
709                    if (tv == null) {
710                        Slog.w(TAG, "Local tv device not available");
711                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
712                        return;
713                    }
714                    tv.deviceSelect(logicalAddress, callback);
715                }
716            });
717        }
718
719        @Override
720        public void portSelect(final int portId, final IHdmiControlCallback callback) {
721            enforceAccessPermission();
722            runOnServiceThread(new Runnable() {
723                @Override
724                public void run() {
725                    HdmiCecLocalDeviceTv tv = tv();
726                    if (tv == null) {
727                        Slog.w(TAG, "Local tv device not available");
728                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
729                        return;
730                    }
731                    tv.doManualPortSwitching(portId, callback);
732                }
733            });
734        }
735
736        @Override
737        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
738            enforceAccessPermission();
739            runOnServiceThread(new Runnable() {
740                @Override
741                public void run() {
742                    HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
743                    if (localDevice == null) {
744                        Slog.w(TAG, "Local device not available");
745                        return;
746                    }
747                    localDevice.sendKeyEvent(keyCode, isPressed);
748                }
749            });
750        }
751
752        @Override
753        public void oneTouchPlay(final IHdmiControlCallback callback) {
754            enforceAccessPermission();
755            runOnServiceThread(new Runnable() {
756                @Override
757                public void run() {
758                    HdmiControlService.this.oneTouchPlay(callback);
759                }
760            });
761        }
762
763        @Override
764        public void queryDisplayStatus(final IHdmiControlCallback callback) {
765            enforceAccessPermission();
766            runOnServiceThread(new Runnable() {
767                @Override
768                public void run() {
769                    HdmiControlService.this.queryDisplayStatus(callback);
770                }
771            });
772        }
773
774        @Override
775        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
776            enforceAccessPermission();
777            runOnServiceThread(new Runnable() {
778                @Override
779                public void run() {
780                    HdmiControlService.this.addHotplugEventListener(listener);
781                }
782            });
783        }
784
785        @Override
786        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
787            enforceAccessPermission();
788            runOnServiceThread(new Runnable() {
789                @Override
790                public void run() {
791                    HdmiControlService.this.removeHotplugEventListener(listener);
792                }
793            });
794        }
795
796        @Override
797        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
798            enforceAccessPermission();
799            runOnServiceThread(new Runnable() {
800                @Override
801                public void run() {
802                    HdmiControlService.this.addDeviceEventListener(listener);
803                }
804            });
805        }
806
807        @Override
808        public List<HdmiPortInfo> getPortInfo() {
809            enforceAccessPermission();
810            return mPortInfo;
811        }
812
813        @Override
814        public boolean canChangeSystemAudioMode() {
815            enforceAccessPermission();
816            HdmiCecLocalDeviceTv tv = tv();
817            if (tv == null) {
818                return false;
819            }
820            return tv.hasSystemAudioDevice();
821        }
822
823        @Override
824        public boolean getSystemAudioMode() {
825            enforceAccessPermission();
826            HdmiCecLocalDeviceTv tv = tv();
827            if (tv == null) {
828                return false;
829            }
830            return tv.getSystemAudioMode();
831        }
832
833        @Override
834        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
835            enforceAccessPermission();
836            runOnServiceThread(new Runnable() {
837                @Override
838                public void run() {
839                    HdmiCecLocalDeviceTv tv = tv();
840                    if (tv == null) {
841                        Slog.w(TAG, "Local tv device not available");
842                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
843                        return;
844                    }
845                    tv.changeSystemAudioMode(enabled, callback);
846                }
847            });
848        }
849
850        @Override
851        public void addSystemAudioModeChangeListener(
852                final IHdmiSystemAudioModeChangeListener listener) {
853            enforceAccessPermission();
854            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
855        }
856
857        @Override
858        public void removeSystemAudioModeChangeListener(
859                final IHdmiSystemAudioModeChangeListener listener) {
860            enforceAccessPermission();
861            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
862        }
863
864        @Override
865        public void setInputChangeListener(final IHdmiInputChangeListener listener) {
866            enforceAccessPermission();
867            HdmiControlService.this.setInputChangeListener(listener);
868        }
869
870        @Override
871        public List<HdmiCecDeviceInfo> getInputDevices() {
872            enforceAccessPermission();
873            // No need to hold the lock for obtaining TV device as the local device instance
874            // is preserved while the HDMI control is enabled.
875            HdmiCecLocalDeviceTv tv = tv();
876            if (tv == null) {
877                return Collections.emptyList();
878            }
879            return tv.getSafeExternalInputs();
880        }
881
882        @Override
883        public void setControlEnabled(final boolean enabled) {
884            enforceAccessPermission();
885            runOnServiceThread(new Runnable() {
886                @Override
887                public void run() {
888                    handleHdmiControlStatusChanged(enabled);
889
890                }
891            });
892        }
893
894        @Override
895        public void setSystemAudioVolume(final int oldIndex, final int newIndex,
896                final int maxIndex) {
897            enforceAccessPermission();
898            runOnServiceThread(new Runnable() {
899                @Override
900                public void run() {
901                    HdmiCecLocalDeviceTv tv = tv();
902                    if (tv == null) {
903                        Slog.w(TAG, "Local tv device not available");
904                        return;
905                    }
906                    tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
907                }
908            });
909        }
910
911        @Override
912        public void setSystemAudioMute(final boolean mute) {
913            enforceAccessPermission();
914            runOnServiceThread(new Runnable() {
915                @Override
916                public void run() {
917                    HdmiCecLocalDeviceTv tv = tv();
918                    if (tv == null) {
919                        Slog.w(TAG, "Local tv device not available");
920                        return;
921                    }
922                    tv.changeMute(mute);
923                }
924            });
925        }
926
927        @Override
928        public void setArcMode(final boolean enabled) {
929            enforceAccessPermission();
930            runOnServiceThread(new Runnable() {
931                @Override
932                public void run() {
933                    HdmiCecLocalDeviceTv tv = tv();
934                    if (tv == null) {
935                        Slog.w(TAG, "Local tv device not available to change arc mode.");
936                        return;
937                    }
938                }
939            });
940        }
941
942        @Override
943        public void setOption(final int key, final int value) {
944            enforceAccessPermission();
945            if (!isTvDevice()) {
946                return;
947            }
948            switch (key) {
949                case HdmiTvClient.OPTION_CEC_AUTO_WAKEUP:
950                    mCecController.setOption(key, value);
951                    break;
952                case HdmiTvClient.OPTION_CEC_AUTO_DEVICE_OFF:
953                    // No need to pass this option to HAL.
954                    tv().setAutoDeviceOff(value == HdmiTvClient.ENABLED);
955                    break;
956                case HdmiTvClient.OPTION_MHL_INPUT_SWITCHING:  // Fall through
957                case HdmiTvClient.OPTION_MHL_POWER_CHARGE:
958                    if (mMhlController != null) {
959                        mMhlController.setOption(key, value);
960                    }
961                    break;
962            }
963        }
964
965        private boolean isTvDevice() {
966            return tv() != null;
967        }
968
969        @Override
970        public void setProhibitMode(final boolean enabled) {
971            enforceAccessPermission();
972            if (!isTvDevice()) {
973                return;
974            }
975            HdmiControlService.this.setProhibitMode(enabled);
976        }
977
978        @Override
979        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
980                final int deviceType) {
981            enforceAccessPermission();
982            runOnServiceThread(new Runnable() {
983                @Override
984                public void run() {
985                    HdmiControlService.this.addVendorCommandListener(listener, deviceType);
986                }
987            });
988        }
989
990        @Override
991        public void sendVendorCommand(final int deviceType, final int targetAddress,
992                final byte[] params, final boolean hasVendorId) {
993            enforceAccessPermission();
994            runOnServiceThread(new Runnable() {
995                @Override
996                public void run() {
997                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
998                    if (device == null) {
999                        Slog.w(TAG, "Local device not available");
1000                        return;
1001                    }
1002                    if (hasVendorId) {
1003                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1004                                device.getDeviceInfo().getLogicalAddress(), targetAddress,
1005                                getVendorId(), params));
1006                    } else {
1007                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1008                                device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1009                    }
1010                }
1011            });
1012         }
1013    }
1014
1015    @ServiceThreadOnly
1016    private void oneTouchPlay(final IHdmiControlCallback callback) {
1017        assertRunOnServiceThread();
1018        HdmiCecLocalDevicePlayback source = playback();
1019        if (source == null) {
1020            Slog.w(TAG, "Local playback device not available");
1021            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1022            return;
1023        }
1024        source.oneTouchPlay(callback);
1025    }
1026
1027    @ServiceThreadOnly
1028    private void queryDisplayStatus(final IHdmiControlCallback callback) {
1029        assertRunOnServiceThread();
1030        HdmiCecLocalDevicePlayback source = playback();
1031        if (source == null) {
1032            Slog.w(TAG, "Local playback device not available");
1033            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1034            return;
1035        }
1036        source.queryDisplayStatus(callback);
1037    }
1038
1039    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1040        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1041        try {
1042            listener.asBinder().linkToDeath(record, 0);
1043        } catch (RemoteException e) {
1044            Slog.w(TAG, "Listener already died");
1045            return;
1046        }
1047        synchronized (mLock) {
1048            mHotplugEventListenerRecords.add(record);
1049            mHotplugEventListeners.add(listener);
1050        }
1051    }
1052
1053    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1054        synchronized (mLock) {
1055            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1056                if (record.mListener.asBinder() == listener.asBinder()) {
1057                    listener.asBinder().unlinkToDeath(record, 0);
1058                    mHotplugEventListenerRecords.remove(record);
1059                    break;
1060                }
1061            }
1062            mHotplugEventListeners.remove(listener);
1063        }
1064    }
1065
1066    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1067        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1068        try {
1069            listener.asBinder().linkToDeath(record, 0);
1070        } catch (RemoteException e) {
1071            Slog.w(TAG, "Listener already died");
1072            return;
1073        }
1074        synchronized (mLock) {
1075            mDeviceEventListeners.add(listener);
1076            mDeviceEventListenerRecords.add(record);
1077        }
1078    }
1079
1080    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
1081        synchronized (mLock) {
1082            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
1083                try {
1084                    listener.onStatusChanged(device, activated);
1085                } catch (RemoteException e) {
1086                    Slog.e(TAG, "Failed to report device event:" + e);
1087                }
1088            }
1089        }
1090    }
1091
1092    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1093        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1094                listener);
1095        try {
1096            listener.asBinder().linkToDeath(record, 0);
1097        } catch (RemoteException e) {
1098            Slog.w(TAG, "Listener already died");
1099            return;
1100        }
1101        synchronized (mLock) {
1102            mSystemAudioModeChangeListeners.add(listener);
1103            mSystemAudioModeChangeListenerRecords.add(record);
1104        }
1105    }
1106
1107    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1108        synchronized (mLock) {
1109            for (SystemAudioModeChangeListenerRecord record :
1110                    mSystemAudioModeChangeListenerRecords) {
1111                if (record.mListener.asBinder() == listener) {
1112                    listener.asBinder().unlinkToDeath(record, 0);
1113                    mSystemAudioModeChangeListenerRecords.remove(record);
1114                    break;
1115                }
1116            }
1117            mSystemAudioModeChangeListeners.remove(listener);
1118        }
1119    }
1120
1121    private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1122        @Override
1123        public void binderDied() {
1124            synchronized (mLock) {
1125                mInputChangeListener = null;
1126            }
1127        }
1128    }
1129
1130    private void setInputChangeListener(IHdmiInputChangeListener listener) {
1131        synchronized (mLock) {
1132            mInputChangeListenerRecord = new InputChangeListenerRecord();
1133            try {
1134                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1135            } catch (RemoteException e) {
1136                Slog.w(TAG, "Listener already died");
1137                return;
1138            }
1139            mInputChangeListener = listener;
1140        }
1141    }
1142
1143    void invokeInputChangeListener(int activeAddress) {
1144        synchronized (mLock) {
1145            if (mInputChangeListener != null) {
1146                HdmiCecDeviceInfo activeSource = getDeviceInfo(activeAddress);
1147                try {
1148                    mInputChangeListener.onChanged(activeSource);
1149                } catch (RemoteException e) {
1150                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1151                }
1152            }
1153        }
1154    }
1155
1156    private void invokeCallback(IHdmiControlCallback callback, int result) {
1157        try {
1158            callback.onComplete(result);
1159        } catch (RemoteException e) {
1160            Slog.e(TAG, "Invoking callback failed:" + e);
1161        }
1162    }
1163
1164    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
1165            boolean enabled) {
1166        try {
1167            listener.onStatusChanged(enabled);
1168        } catch (RemoteException e) {
1169            Slog.e(TAG, "Invoking callback failed:" + e);
1170        }
1171    }
1172
1173    private void announceHotplugEvent(int portId, boolean connected) {
1174        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1175        synchronized (mLock) {
1176            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
1177                invokeHotplugEventListenerLocked(listener, event);
1178            }
1179        }
1180    }
1181
1182    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1183            HdmiHotplugEvent event) {
1184        try {
1185            listener.onReceived(event);
1186        } catch (RemoteException e) {
1187            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1188        }
1189    }
1190
1191    private static boolean hasSameTopPort(int path1, int path2) {
1192        return (path1 & Constants.ROUTING_PATH_TOP_MASK)
1193                == (path2 & Constants.ROUTING_PATH_TOP_MASK);
1194    }
1195
1196    private HdmiCecLocalDeviceTv tv() {
1197        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_TV);
1198    }
1199
1200    private HdmiCecLocalDevicePlayback playback() {
1201        return (HdmiCecLocalDevicePlayback)
1202                mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_PLAYBACK);
1203    }
1204
1205    AudioManager getAudioManager() {
1206        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1207    }
1208
1209    boolean isControlEnabled() {
1210        synchronized (mLock) {
1211            return mHdmiControlEnabled;
1212        }
1213    }
1214
1215    int getPowerStatus() {
1216        return mPowerStatus;
1217    }
1218
1219    boolean isPowerOnOrTransient() {
1220        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1221                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1222    }
1223
1224    boolean isPowerStandbyOrTransient() {
1225        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1226                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1227    }
1228
1229    boolean isPowerStandby() {
1230        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1231    }
1232
1233    @ServiceThreadOnly
1234    void wakeUp() {
1235        assertRunOnServiceThread();
1236        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1237        pm.wakeUp(SystemClock.uptimeMillis());
1238        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1239        // the intent, the sequence will continue at onWakeUp().
1240    }
1241
1242    @ServiceThreadOnly
1243    void standby() {
1244        assertRunOnServiceThread();
1245        mStandbyMessageReceived = true;
1246        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1247        pm.goToSleep(SystemClock.uptimeMillis());
1248        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1249        // the intent, the sequence will continue at onStandby().
1250    }
1251
1252    @ServiceThreadOnly
1253    private void onWakeUp() {
1254        assertRunOnServiceThread();
1255        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1256        if (mCecController != null) {
1257            if (mHdmiControlEnabled) {
1258                initializeCec(true);
1259            }
1260        } else {
1261            Slog.i(TAG, "Device does not support HDMI-CEC.");
1262        }
1263        // TODO: Initialize MHL local devices.
1264    }
1265
1266    @ServiceThreadOnly
1267    private void onStandby() {
1268        assertRunOnServiceThread();
1269        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1270
1271        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1272        disableDevices(new PendingActionClearedCallback() {
1273            @Override
1274            public void onCleared(HdmiCecLocalDevice device) {
1275                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1276                devices.remove(device);
1277                if (devices.isEmpty()) {
1278                    clearLocalDevices();
1279                    onStandbyCompleted();
1280                }
1281            }
1282        });
1283    }
1284
1285    private void disableDevices(PendingActionClearedCallback callback) {
1286        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1287            device.disableDevice(mStandbyMessageReceived, callback);
1288        }
1289    }
1290
1291    @ServiceThreadOnly
1292    private void clearLocalDevices() {
1293        assertRunOnServiceThread();
1294        if (mCecController == null) {
1295            return;
1296        }
1297        mCecController.clearLogicalAddress();
1298        mCecController.clearLocalDevices();
1299    }
1300
1301    @ServiceThreadOnly
1302    private void onStandbyCompleted() {
1303        assertRunOnServiceThread();
1304        Slog.v(TAG, "onStandbyCompleted");
1305
1306        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1307            return;
1308        }
1309        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1310        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1311            device.onStandby(mStandbyMessageReceived);
1312        }
1313        mStandbyMessageReceived = false;
1314        mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED);
1315    }
1316
1317    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1318        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1319        try {
1320            listener.asBinder().linkToDeath(record, 0);
1321        } catch (RemoteException e) {
1322            Slog.w(TAG, "Listener already died");
1323            return;
1324        }
1325        synchronized (mLock) {
1326            mVendorCommandListenerRecords.add(record);
1327        }
1328    }
1329
1330    void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1331            boolean hasVendorId) {
1332        synchronized (mLock) {
1333            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1334                if (record.mDeviceType != deviceType) {
1335                    continue;
1336                }
1337                try {
1338                    record.mListener.onReceived(srcAddress, params, hasVendorId);
1339                } catch (RemoteException e) {
1340                    Slog.e(TAG, "Failed to notify vendor command reception", e);
1341                }
1342            }
1343        }
1344    }
1345
1346    boolean isProhibitMode() {
1347        synchronized (mLock) {
1348            return mProhibitMode;
1349        }
1350    }
1351
1352    void setProhibitMode(boolean enabled) {
1353        synchronized (mLock) {
1354            mProhibitMode = enabled;
1355        }
1356    }
1357
1358    @ServiceThreadOnly
1359    private void handleHdmiControlStatusChanged(boolean enabled) {
1360        assertRunOnServiceThread();
1361
1362        int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED;
1363        mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value);
1364        if (mMhlController != null) {
1365            mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value);
1366        }
1367
1368        synchronized (mLock) {
1369            mHdmiControlEnabled = enabled;
1370        }
1371
1372        if (enabled) {
1373            initializeCec(false);
1374        } else {
1375            disableDevices(new PendingActionClearedCallback() {
1376                @Override
1377                public void onCleared(HdmiCecLocalDevice device) {
1378                    assertRunOnServiceThread();
1379                    clearLocalDevices();
1380                }
1381            });
1382        }
1383    }
1384}
1385