HidDeviceService.java revision 5fed27a96f263473e8f4c2d2f2a0b29e5e54aa79
1/*
2 * Copyright (C) 2016 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.bluetooth.hid;
18
19import android.bluetooth.BluetoothDevice;
20import android.bluetooth.BluetoothHidDevice;
21import android.bluetooth.BluetoothHidDeviceAppConfiguration;
22import android.bluetooth.BluetoothHidDeviceAppQosSettings;
23import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
24import android.bluetooth.BluetoothProfile;
25import android.bluetooth.IBluetoothHidDevice;
26import android.bluetooth.IBluetoothHidDeviceCallback;
27import android.content.Intent;
28import android.os.Binder;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Message;
32import android.os.Process;
33import android.os.RemoteException;
34import android.util.Log;
35
36import com.android.bluetooth.Utils;
37import com.android.bluetooth.btservice.ProfileService;
38import com.android.internal.annotations.VisibleForTesting;
39
40import java.nio.ByteBuffer;
41import java.util.ArrayList;
42import java.util.Arrays;
43import java.util.List;
44import java.util.NoSuchElementException;
45
46/** @hide */
47public class HidDeviceService extends ProfileService {
48    private static final boolean DBG = false;
49    private static final String TAG = HidDeviceService.class.getSimpleName();
50
51    private static final int MESSAGE_APPLICATION_STATE_CHANGED = 1;
52    private static final int MESSAGE_CONNECT_STATE_CHANGED = 2;
53    private static final int MESSAGE_GET_REPORT = 3;
54    private static final int MESSAGE_SET_REPORT = 4;
55    private static final int MESSAGE_SET_PROTOCOL = 5;
56    private static final int MESSAGE_INTR_DATA = 6;
57    private static final int MESSAGE_VC_UNPLUG = 7;
58
59    private static HidDeviceService sHidDeviceService;
60
61    private HidDeviceNativeInterface mHidDeviceNativeInterface;
62
63    private boolean mNativeAvailable = false;
64    private BluetoothDevice mHidDevice;
65    private int mHidDeviceState = BluetoothHidDevice.STATE_DISCONNECTED;
66    private int mUserUid = 0;
67    private IBluetoothHidDeviceCallback mCallback;
68    private BluetoothHidDeviceDeathRecipient mDeathRcpt;
69
70    private HidDeviceServiceHandler mHandler;
71
72    public HidDeviceService() {
73        mHidDeviceNativeInterface = HidDeviceNativeInterface.getInstance();
74    }
75
76    private class HidDeviceServiceHandler extends Handler {
77        @Override
78        public void handleMessage(Message msg) {
79            if (DBG) {
80                Log.d(TAG, "handleMessage(): msg.what=" + msg.what);
81            }
82
83            switch (msg.what) {
84                case MESSAGE_APPLICATION_STATE_CHANGED: {
85                    BluetoothDevice device = msg.obj != null ? (BluetoothDevice) msg.obj : null;
86                    boolean success = (msg.arg1 != 0);
87
88                    if (success) {
89                        Log.d(TAG, "App registered, set device to: " + device);
90                        mHidDevice = device;
91                    } else {
92                        mHidDevice = null;
93                    }
94
95                    try {
96                        if (mCallback != null) {
97                            // TODO(hsz) remove AppConfig in frameworks/base in the follow-up CL
98                            mCallback.onAppStatusChanged(device, null, success);
99                        } else {
100                            break;
101                        }
102                    } catch (RemoteException e) {
103                        Log.e(TAG, "e=" + e.toString());
104                        e.printStackTrace();
105                    }
106
107                    if (success) {
108                        mDeathRcpt = new BluetoothHidDeviceDeathRecipient(HidDeviceService.this);
109                        if (mCallback != null) {
110                            IBinder binder = mCallback.asBinder();
111                            try {
112                                binder.linkToDeath(mDeathRcpt, 0);
113                                Log.i(TAG, "IBinder.linkToDeath() ok");
114                            } catch (RemoteException e) {
115                                e.printStackTrace();
116                            }
117                        }
118                    } else if (mDeathRcpt != null) {
119                        if (mCallback != null) {
120                            IBinder binder = mCallback.asBinder();
121                            try {
122                                binder.unlinkToDeath(mDeathRcpt, 0);
123                                Log.i(TAG, "IBinder.unlinkToDeath() ok");
124                            } catch (NoSuchElementException e) {
125                                e.printStackTrace();
126                            }
127                            mDeathRcpt.cleanup();
128                            mDeathRcpt = null;
129                        }
130                    }
131
132                    if (!success) {
133                        mCallback = null;
134                    }
135
136                    break;
137                }
138
139                case MESSAGE_CONNECT_STATE_CHANGED: {
140                    BluetoothDevice device = (BluetoothDevice) msg.obj;
141                    int halState = msg.arg1;
142                    int state = convertHalState(halState);
143
144                    if (state != BluetoothHidDevice.STATE_DISCONNECTED) {
145                        mHidDevice = device;
146                    }
147
148                    setAndBroadcastConnectionState(device, state);
149
150                    try {
151                        if (mCallback != null) {
152                            mCallback.onConnectionStateChanged(device, state);
153                        }
154                    } catch (RemoteException e) {
155                        e.printStackTrace();
156                    }
157                    break;
158                }
159
160                case MESSAGE_GET_REPORT:
161                    byte type = (byte) msg.arg1;
162                    byte id = (byte) msg.arg2;
163                    int bufferSize = msg.obj == null ? 0 : ((Integer) msg.obj).intValue();
164
165                    try {
166                        if (mCallback != null) {
167                            mCallback.onGetReport(mHidDevice, type, id, bufferSize);
168                        }
169                    } catch (RemoteException e) {
170                        e.printStackTrace();
171                    }
172                    break;
173
174                case MESSAGE_SET_REPORT: {
175                    byte reportType = (byte) msg.arg1;
176                    byte reportId = (byte) msg.arg2;
177                    byte[] data = ((ByteBuffer) msg.obj).array();
178
179                    try {
180                        if (mCallback != null) {
181                            mCallback.onSetReport(mHidDevice, reportType, reportId, data);
182                        }
183                    } catch (RemoteException e) {
184                        e.printStackTrace();
185                    }
186                    break;
187                }
188
189                case MESSAGE_SET_PROTOCOL:
190                    byte protocol = (byte) msg.arg1;
191
192                    try {
193                        if (mCallback != null) {
194                            mCallback.onSetProtocol(mHidDevice, protocol);
195                        }
196                    } catch (RemoteException e) {
197                        e.printStackTrace();
198                    }
199                    break;
200
201                case MESSAGE_INTR_DATA:
202                    byte reportId = (byte) msg.arg1;
203                    byte[] data = ((ByteBuffer) msg.obj).array();
204
205                    try {
206                        if (mCallback != null) {
207                            mCallback.onIntrData(mHidDevice, reportId, data);
208                        }
209                    } catch (RemoteException e) {
210                        e.printStackTrace();
211                    }
212                    break;
213
214                case MESSAGE_VC_UNPLUG:
215                    try {
216                        if (mCallback != null) {
217                            mCallback.onVirtualCableUnplug(mHidDevice);
218                        }
219                    } catch (RemoteException e) {
220                        e.printStackTrace();
221                    }
222                    mHidDevice = null;
223                    break;
224            }
225        }
226    };
227
228    private static class BluetoothHidDeviceDeathRecipient implements IBinder.DeathRecipient {
229        private HidDeviceService mService;
230
231        BluetoothHidDeviceDeathRecipient(HidDeviceService service) {
232            mService = service;
233        }
234
235        @Override
236        public void binderDied() {
237            Log.w(TAG, "Binder died, need to unregister app :(");
238            mService.unregisterApp();
239        }
240
241        public void cleanup() {
242            mService = null;
243        }
244    }
245
246    @VisibleForTesting
247    static class BluetoothHidDeviceBinder extends IBluetoothHidDevice.Stub
248            implements IProfileServiceBinder {
249
250        private static final String TAG = BluetoothHidDeviceBinder.class.getSimpleName();
251
252        private HidDeviceService mService;
253
254        BluetoothHidDeviceBinder(HidDeviceService service) {
255            mService = service;
256        }
257
258        @VisibleForTesting
259        HidDeviceService getServiceForTesting() {
260            if (mService != null && mService.isAvailable()) {
261                return mService;
262            }
263            return null;
264        }
265
266        @Override
267        public boolean cleanup() {
268            mService = null;
269            return true;
270        }
271
272        private HidDeviceService getService() {
273            if (!Utils.checkCaller()) {
274                Log.w(TAG, "HidDevice call not allowed for non-active user");
275                return null;
276            }
277
278            if (mService != null && mService.isAvailable()) {
279                return mService;
280            }
281
282            return null;
283        }
284
285        @Override
286        public boolean registerApp(BluetoothHidDeviceAppConfiguration config,
287                BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos,
288                BluetoothHidDeviceAppQosSettings outQos, IBluetoothHidDeviceCallback callback) {
289            if (DBG) {
290                Log.d(TAG, "registerApp()");
291            }
292
293            HidDeviceService service = getService();
294            if (service == null) {
295                return false;
296            }
297
298            return service.registerApp(sdp, inQos, outQos, callback);
299        }
300
301        @Override
302        public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
303            if (DBG) {
304                Log.d(TAG, "unregisterApp()");
305            }
306
307            HidDeviceService service = getService();
308            if (service == null) {
309                return false;
310            }
311
312            return service.unregisterApp();
313        }
314
315        @Override
316        public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
317            if (DBG) {
318                Log.d(TAG, "sendReport(): device=" + device + "  id=" + id);
319            }
320
321            HidDeviceService service = getService();
322            if (service == null) {
323                return false;
324            }
325
326            return service.sendReport(device, id, data);
327        }
328
329        @Override
330        public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
331            if (DBG) {
332                Log.d(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
333            }
334
335            HidDeviceService service = getService();
336            if (service == null) {
337                return false;
338            }
339
340            return service.replyReport(device, type, id, data);
341        }
342
343        @Override
344        public boolean unplug(BluetoothDevice device) {
345            if (DBG) {
346                Log.d(TAG, "unplug(): device=" + device);
347            }
348
349            HidDeviceService service = getService();
350            if (service == null) {
351                return false;
352            }
353
354            return service.unplug(device);
355        }
356
357        @Override
358        public boolean connect(BluetoothDevice device) {
359            if (DBG) {
360                Log.d(TAG, "connect(): device=" + device);
361            }
362
363            HidDeviceService service = getService();
364            if (service == null) {
365                return false;
366            }
367
368            return service.connect(device);
369        }
370
371        @Override
372        public boolean disconnect(BluetoothDevice device) {
373            if (DBG) {
374                Log.d(TAG, "disconnect(): device=" + device);
375            }
376
377            HidDeviceService service = getService();
378            if (service == null) {
379                return false;
380            }
381
382            return service.disconnect(device);
383        }
384
385        @Override
386        public boolean reportError(BluetoothDevice device, byte error) {
387            if (DBG) {
388                Log.d(TAG, "reportError(): device=" + device + " error=" + error);
389            }
390
391            HidDeviceService service = getService();
392            if (service == null) {
393                return false;
394            }
395
396            return service.reportError(device, error);
397        }
398
399        @Override
400        public int getConnectionState(BluetoothDevice device) {
401            if (DBG) {
402                Log.d(TAG, "getConnectionState(): device=" + device);
403            }
404
405            HidDeviceService service = getService();
406            if (service == null) {
407                return BluetoothHidDevice.STATE_DISCONNECTED;
408            }
409
410            return service.getConnectionState(device);
411        }
412
413        @Override
414        public List<BluetoothDevice> getConnectedDevices() {
415            if (DBG) {
416                Log.d(TAG, "getConnectedDevices()");
417            }
418
419            return getDevicesMatchingConnectionStates(new int[]{BluetoothProfile.STATE_CONNECTED});
420        }
421
422        @Override
423        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
424            if (DBG) {
425                Log.d(TAG,
426                        "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
427            }
428
429            HidDeviceService service = getService();
430            if (service == null) {
431                return new ArrayList<BluetoothDevice>(0);
432            }
433
434            return service.getDevicesMatchingConnectionStates(states);
435        }
436    }
437
438    @Override
439    protected IProfileServiceBinder initBinder() {
440        return new BluetoothHidDeviceBinder(this);
441    }
442
443    private boolean checkDevice(BluetoothDevice device) {
444        if (mHidDevice == null || !mHidDevice.equals(device)) {
445            Log.w(TAG, "Unknown device: " + device);
446            return false;
447        }
448        return true;
449    }
450
451    private boolean checkCallingUid() {
452        int callingUid = Binder.getCallingUid();
453        if (callingUid != mUserUid) {
454            Log.w(TAG, "checkCallingUid(): caller UID doesn't match registered user UID");
455            return false;
456        }
457        return true;
458    }
459
460    synchronized boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
461            BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
462            IBluetoothHidDeviceCallback callback) {
463        if (mUserUid != 0) {
464            Log.w(TAG, "registerApp(): failed because another app is registered");
465            return false;
466        }
467
468        mUserUid = Binder.getCallingUid();
469        if (DBG) {
470            Log.d(TAG, "registerApp(): calling uid=" + mUserUid);
471        }
472        mCallback = callback;
473
474        return mHidDeviceNativeInterface.registerApp(sdp.name, sdp.description, sdp.provider,
475                sdp.subclass, sdp.descriptors, inQos == null ? null : inQos.toArray(),
476                outQos == null ? null : outQos.toArray());
477    }
478
479    synchronized boolean unregisterApp() {
480        if (DBG) {
481            Log.d(TAG, "unregisterApp()");
482        }
483
484        int callingUid = Binder.getCallingUid();
485        if (callingUid == mUserUid || callingUid < Process.FIRST_APPLICATION_UID) {
486            mUserUid = 0;
487            return mHidDeviceNativeInterface.unregisterApp();
488        }
489        Log.w(TAG, "unregisterApp(): caller UID doesn't match user UID");
490        return false;
491    }
492
493    synchronized boolean sendReport(BluetoothDevice device, int id, byte[] data) {
494        if (DBG) {
495            Log.d(TAG, "sendReport(): device=" + device + " id=" + id);
496        }
497
498        return checkDevice(device) && checkCallingUid()
499                && mHidDeviceNativeInterface.sendReport(id, data);
500    }
501
502    synchronized boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
503        if (DBG) {
504            Log.d(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
505        }
506
507        return checkDevice(device) && checkCallingUid()
508                && mHidDeviceNativeInterface.replyReport(type, id, data);
509    }
510
511    synchronized boolean unplug(BluetoothDevice device) {
512        if (DBG) {
513            Log.d(TAG, "unplug(): device=" + device);
514        }
515
516        return checkDevice(device) && checkCallingUid()
517                && mHidDeviceNativeInterface.unplug();
518    }
519
520    synchronized boolean connect(BluetoothDevice device) {
521        if (DBG) {
522            Log.d(TAG, "connect(): device=" + device);
523        }
524
525        return checkCallingUid() && mHidDeviceNativeInterface.connect(device);
526    }
527
528    synchronized boolean disconnect(BluetoothDevice device) {
529        if (DBG) {
530            Log.d(TAG, "disconnect(): device=" + device);
531        }
532
533        int callingUid = Binder.getCallingUid();
534        if (callingUid != mUserUid && callingUid >= Process.FIRST_APPLICATION_UID) {
535            Log.w(TAG, "disconnect(): caller UID doesn't match user UID");
536            return false;
537        }
538        return checkDevice(device) && mHidDeviceNativeInterface.disconnect();
539    }
540
541    synchronized boolean reportError(BluetoothDevice device, byte error) {
542        if (DBG) {
543            Log.d(TAG, "reportError(): device=" + device + " error=" + error);
544        }
545
546        return checkDevice(device) && checkCallingUid()
547                && mHidDeviceNativeInterface.reportError(error);
548    }
549
550    @Override
551    protected boolean start() {
552        if (DBG) {
553            Log.d(TAG, "start()");
554        }
555
556        mHandler = new HidDeviceServiceHandler();
557        setHidDeviceService(this);
558        mHidDeviceNativeInterface.init();
559        mNativeAvailable = true;
560        return true;
561    }
562
563    @Override
564    protected boolean stop() {
565        if (DBG) {
566            Log.d(TAG, "stop()");
567        }
568
569        return true;
570    }
571
572    @Override
573    protected boolean cleanup() {
574        if (DBG) {
575            Log.d(TAG, "cleanup()");
576        }
577
578        if (mNativeAvailable) {
579            mHidDeviceNativeInterface.cleanup();
580            mNativeAvailable = false;
581        }
582
583        return true;
584    }
585
586    @Override
587    public boolean onUnbind(Intent intent) {
588        Log.d(TAG, "Need to unregister app");
589        unregisterApp();
590        return super.onUnbind(intent);
591    }
592
593    /**
594     * Get the HID Device Service instance
595     * @return HID Device Service instance
596     */
597    public static synchronized HidDeviceService getHidDeviceService() {
598        if (sHidDeviceService == null) {
599            Log.d(TAG, "getHidDeviceService(): service is NULL");
600            return null;
601        }
602        if (!sHidDeviceService.isAvailable()) {
603            Log.d(TAG, "getHidDeviceService(): service is not available");
604            return null;
605        }
606        return sHidDeviceService;
607    }
608
609    private static synchronized void setHidDeviceService(HidDeviceService instance) {
610        sHidDeviceService = instance;
611    }
612
613    int getConnectionState(BluetoothDevice device) {
614        if (mHidDevice != null && mHidDevice.equals(device)) {
615            return mHidDeviceState;
616        }
617        return BluetoothHidDevice.STATE_DISCONNECTED;
618    }
619
620    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
621        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
622        List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
623
624        if (mHidDevice != null) {
625            for (int state : states) {
626                if (state == mHidDeviceState) {
627                    inputDevices.add(mHidDevice);
628                    break;
629                }
630            }
631        }
632        return inputDevices;
633    }
634
635    synchronized void onApplicationStateChangedFromNative(BluetoothDevice device,
636            boolean registered) {
637        if (DBG) {
638            Log.d(TAG, "onApplicationStateChanged(): registered=" + registered);
639        }
640
641        Message msg = mHandler.obtainMessage(MESSAGE_APPLICATION_STATE_CHANGED);
642        msg.obj = device;
643        msg.arg1 = registered ? 1 : 0;
644        mHandler.sendMessage(msg);
645    }
646
647    synchronized void onConnectStateChangedFromNative(BluetoothDevice device, int state) {
648        if (DBG) {
649            Log.d(TAG, "onConnectStateChanged(): device="
650                    + device + " state=" + state);
651        }
652
653        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
654        msg.obj = device;
655        msg.arg1 = state;
656        mHandler.sendMessage(msg);
657    }
658
659    synchronized void onGetReportFromNative(byte type, byte id, short bufferSize) {
660        if (DBG) {
661            Log.d(TAG, "onGetReport(): type=" + type + " id=" + id + " bufferSize=" + bufferSize);
662        }
663
664        Message msg = mHandler.obtainMessage(MESSAGE_GET_REPORT);
665        msg.obj = bufferSize > 0 ? new Integer(bufferSize) : null;
666        msg.arg1 = type;
667        msg.arg2 = id;
668        mHandler.sendMessage(msg);
669    }
670
671    synchronized void onSetReportFromNative(byte reportType, byte reportId, byte[] data) {
672        if (DBG) {
673            Log.d(TAG, "onSetReport(): reportType=" + reportType + " reportId=" + reportId);
674        }
675
676        ByteBuffer bb = ByteBuffer.wrap(data);
677
678        Message msg = mHandler.obtainMessage(MESSAGE_SET_REPORT);
679        msg.arg1 = reportType;
680        msg.arg2 = reportId;
681        msg.obj = bb;
682        mHandler.sendMessage(msg);
683    }
684
685    synchronized void onSetProtocolFromNative(byte protocol) {
686        if (DBG) {
687            Log.d(TAG, "onSetProtocol(): protocol=" + protocol);
688        }
689
690        Message msg = mHandler.obtainMessage(MESSAGE_SET_PROTOCOL);
691        msg.arg1 = protocol;
692        mHandler.sendMessage(msg);
693    }
694
695    synchronized void onIntrDataFromNative(byte reportId, byte[] data) {
696        if (DBG) {
697            Log.d(TAG, "onIntrData(): reportId=" + reportId);
698        }
699
700        ByteBuffer bb = ByteBuffer.wrap(data);
701
702        Message msg = mHandler.obtainMessage(MESSAGE_INTR_DATA);
703        msg.arg1 = reportId;
704        msg.obj = bb;
705        mHandler.sendMessage(msg);
706    }
707
708    synchronized void onVirtualCableUnplugFromNative() {
709        if (DBG) {
710            Log.d(TAG, "onVirtualCableUnplug()");
711        }
712
713        Message msg = mHandler.obtainMessage(MESSAGE_VC_UNPLUG);
714        mHandler.sendMessage(msg);
715    }
716
717    private void setAndBroadcastConnectionState(BluetoothDevice device, int newState) {
718        if (DBG) {
719            Log.d(TAG, "setAndBroadcastConnectionState(): device=" + device.getAddress()
720                    + " oldState=" + mHidDeviceState + " newState=" + newState);
721        }
722
723        if (mHidDevice != null && !mHidDevice.equals(device)) {
724            Log.w(TAG, "Connection state changed for unknown device, ignoring");
725            return;
726        }
727
728        int prevState = mHidDeviceState;
729        mHidDeviceState = newState;
730
731        if (prevState == newState) {
732            Log.w(TAG, "Connection state is unchanged, ignoring");
733            return;
734        }
735
736        Intent intent = new Intent(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
737        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
738        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
739        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
740        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
741        sendBroadcast(intent, BLUETOOTH_PERM);
742    }
743
744    private static int convertHalState(int halState) {
745        switch (halState) {
746            case CONN_STATE_CONNECTED:
747                return BluetoothProfile.STATE_CONNECTED;
748            case CONN_STATE_CONNECTING:
749                return BluetoothProfile.STATE_CONNECTING;
750            case CONN_STATE_DISCONNECTED:
751                return BluetoothProfile.STATE_DISCONNECTED;
752            case CONN_STATE_DISCONNECTING:
753                return BluetoothProfile.STATE_DISCONNECTING;
754            default:
755                return BluetoothProfile.STATE_DISCONNECTED;
756        }
757    }
758
759    private static final int CONN_STATE_CONNECTED = 0;
760    private static final int CONN_STATE_CONNECTING = 1;
761    private static final int CONN_STATE_DISCONNECTED = 2;
762    private static final int CONN_STATE_DISCONNECTING = 3;
763}
764