• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.bluetooth.mapclient;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothSocket;
21 import android.bluetooth.SdpMasRecord;
22 import android.os.Handler;
23 import android.os.HandlerThread;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.util.Log;
27 
28 import com.android.bluetooth.BluetoothObexTransport;
29 import com.android.internal.util.StateMachine;
30 
31 import java.io.IOException;
32 import java.lang.ref.WeakReference;
33 
34 import javax.obex.ClientSession;
35 import javax.obex.HeaderSet;
36 import javax.obex.ResponseCodes;
37 /* MasClient is a one time use connection to a server defined by the SDP record passed in at
38  * construction.  After use shutdown() must be called to properly clean up.
39  */
40 public class MasClient {
41     private static final int CONNECT = 0;
42     private static final int DISCONNECT = 1;
43     private static final int REQUEST = 2;
44     private static final String TAG = "MasClient";
45     private static final boolean DBG = MapClientService.DBG;
46     private static final boolean VDBG = MapClientService.VDBG;
47     private static final byte[] BLUETOOTH_UUID_OBEX_MAS = new byte[]{
48             (byte) 0xbb, 0x58, 0x2b, 0x40, 0x42, 0x0c, 0x11, (byte) 0xdb, (byte) 0xb0, (byte) 0xde,
49             0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
50     };
51     private static final byte OAP_TAGID_MAP_SUPPORTED_FEATURES = 0x29;
52     private static final int MAP_FEATURE_NOTIFICATION_REGISTRATION = 0x00000001;
53     private static final int MAP_SUPPORTED_FEATURES = MAP_FEATURE_NOTIFICATION_REGISTRATION;
54 
55     private final StateMachine mCallback;
56     private Handler mHandler;
57     private BluetoothSocket mSocket;
58     private BluetoothObexTransport mTransport;
59     private BluetoothDevice mRemoteDevice;
60     private ClientSession mSession;
61     private HandlerThread thread;
62     private boolean mConnected = false;
63     SdpMasRecord mSdpMasRecord;
64 
MasClient(BluetoothDevice remoteDevice, StateMachine callback, SdpMasRecord sdpMasRecord)65     public MasClient(BluetoothDevice remoteDevice,
66             StateMachine callback, SdpMasRecord sdpMasRecord) {
67         if (remoteDevice == null) {
68             throw new NullPointerException("Obex transport is null");
69         }
70         mRemoteDevice = remoteDevice;
71         mCallback = callback;
72         mSdpMasRecord = sdpMasRecord;
73         thread = new HandlerThread("Client");
74         thread.start();
75         /* This will block until the looper have started, hence it will be safe to use it,
76            when the constructor completes */
77         Looper looper = thread.getLooper();
78         mHandler = new MasClientHandler(looper, this);
79 
80         mHandler.obtainMessage(CONNECT).sendToTarget();
81     }
82 
connect()83     private void connect() {
84         try {
85             if (DBG) {
86                 Log.d(TAG, "Connecting to OBEX on RFCOM channel "
87                         + mSdpMasRecord.getRfcommCannelNumber());
88             }
89             mSocket = mRemoteDevice.createRfcommSocket(mSdpMasRecord.getRfcommCannelNumber());
90             Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString());
91             mSocket.connect();
92             mTransport = new BluetoothObexTransport(mSocket);
93 
94             mSession = new ClientSession(mTransport);
95             HeaderSet headerset = new HeaderSet();
96             headerset.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_OBEX_MAS);
97             ObexAppParameters oap = new ObexAppParameters();
98 
99             oap.add(OAP_TAGID_MAP_SUPPORTED_FEATURES,
100                     MAP_SUPPORTED_FEATURES);
101 
102             oap.addToHeaderSet(headerset);
103 
104             headerset = mSession.connect(headerset);
105             Log.d(TAG, "Connection results" + headerset.getResponseCode());
106 
107             if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) {
108                 if (DBG) Log.d(TAG, "Connection Successful");
109                 mConnected = true;
110                 mCallback.obtainMessage(
111                         MceStateMachine.MSG_MAS_CONNECTED).sendToTarget();
112             } else {
113                 disconnect();
114             }
115 
116         } catch (IOException e) {
117             Log.e(TAG, "Caught an exception " + e.toString());
118             disconnect();
119         }
120     }
121 
disconnect()122     private void disconnect() {
123         if (mSession != null) {
124             try {
125                 mSession.disconnect(null);
126             } catch (IOException e) {
127                 Log.e(TAG, "Caught an exception while disconnecting:" + e.toString());
128             }
129 
130             try {
131                 mSession.close();
132             } catch (IOException e) {
133                 Log.e(TAG, "Caught an exception while closing:" + e.toString());
134             }
135         }
136 
137         mConnected = false;
138         mCallback.obtainMessage(MceStateMachine.MSG_MAS_DISCONNECTED).sendToTarget();
139     }
140 
executeRequest(Request request)141     private void executeRequest(Request request) {
142         try {
143             request.execute(mSession);
144             mCallback.obtainMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
145                     request).sendToTarget();
146         } catch (IOException e) {
147             if (DBG) Log.d(TAG, "Request failed: " + request);
148             // Disconnect to cleanup.
149             disconnect();
150         }
151     }
152 
makeRequest(Request request)153     public boolean makeRequest(Request request) {
154         if (DBG) Log.d(TAG, "makeRequest called with: " + request);
155 
156         boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request));
157         if (!status) {
158             Log.e(TAG, "Adding messages failed, state: " + mConnected);
159             return false;
160         }
161         return true;
162     }
163 
shutdown()164     public void shutdown() {
165         mHandler.obtainMessage(DISCONNECT).sendToTarget();
166         thread.quitSafely();
167     }
168 
169     public enum CharsetType {
170         NATIVE, UTF_8;
171     }
172 
173     private static class MasClientHandler extends Handler {
174         WeakReference<MasClient> mInst;
175 
MasClientHandler(Looper looper, MasClient inst)176         MasClientHandler(Looper looper, MasClient inst) {
177             super(looper);
178             mInst = new WeakReference<>(inst);
179         }
180 
181         @Override
handleMessage(Message msg)182         public void handleMessage(Message msg) {
183             MasClient inst = mInst.get();
184             if (!inst.mConnected && msg.what != CONNECT) {
185                 Log.w(TAG, "Cannot execute " + msg + " when not CONNECTED.");
186                 return;
187             }
188 
189             switch (msg.what) {
190                 case CONNECT:
191                     inst.connect();
192                     break;
193 
194                 case DISCONNECT:
195                     inst.disconnect();
196                     break;
197 
198                 case REQUEST:
199                     inst.executeRequest((Request) msg.obj);
200                     break;
201             }
202         }
203     }
204 
205 }
206