• 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.annotation.SuppressLint;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothSocket;
22 import android.bluetooth.SdpMasRecord;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.util.Log;
28 
29 import com.android.bluetooth.BluetoothObexTransport;
30 import com.android.bluetooth.ObexAppParameters;
31 import com.android.internal.util.StateMachine;
32 import com.android.obex.ClientSession;
33 import com.android.obex.HeaderSet;
34 import com.android.obex.ResponseCodes;
35 
36 import java.io.IOException;
37 import java.lang.ref.WeakReference;
38 
39 /* MasClient is a one time use connection to a server defined by the SDP record passed in at
40  * construction.  After use shutdown() must be called to properly clean up.
41  */
42 public class MasClient {
43     private static final String TAG = MasClient.class.getSimpleName();
44 
45     private static final int CONNECT = 0;
46     private static final int DISCONNECT = 1;
47     private static final int REQUEST = 2;
48     private static final byte[] BLUETOOTH_UUID_OBEX_MAS =
49             new byte[] {
50                 (byte) 0xbb,
51                 0x58,
52                 0x2b,
53                 0x40,
54                 0x42,
55                 0x0c,
56                 0x11,
57                 (byte) 0xdb,
58                 (byte) 0xb0,
59                 (byte) 0xde,
60                 0x08,
61                 0x00,
62                 0x20,
63                 0x0c,
64                 (byte) 0x9a,
65                 0x66
66             };
67     private static final byte OAP_TAGID_MAP_SUPPORTED_FEATURES = 0x29;
68     private static final int L2CAP_INVALID_PSM = -1;
69     private static final int MAP_FEATURE_NOTIFICATION_REGISTRATION = 0x00000001;
70     private static final int MAP_FEATURE_NOTIFICATION = 0x00000002;
71     private static final int MAP_FEATURE_BROWSING = 0x00000004;
72     private static final int MAP_FEATURE_UPLOADING = 0x00000008;
73     private static final int MAP_FEATURE_EXTENDED_EVENT_REPORT_1_1 = 0x00000040;
74     static final int MAP_SUPPORTED_FEATURES =
75             MAP_FEATURE_NOTIFICATION_REGISTRATION
76                     | MAP_FEATURE_NOTIFICATION
77                     | MAP_FEATURE_BROWSING
78                     | MAP_FEATURE_UPLOADING
79                     | MAP_FEATURE_EXTENDED_EVENT_REPORT_1_1;
80 
81     private final StateMachine mCallback;
82     private final Handler mHandler;
83     private BluetoothSocket mSocket;
84     private BluetoothObexTransport mTransport;
85     private final BluetoothDevice mRemoteDevice;
86     private ClientSession mSession;
87     private final HandlerThread mThread;
88     private boolean mConnected = false;
89     SdpMasRecord mSdpMasRecord;
90 
MasClient( BluetoothDevice remoteDevice, StateMachine callback, SdpMasRecord sdpMasRecord)91     public MasClient(
92             BluetoothDevice remoteDevice, StateMachine callback, SdpMasRecord sdpMasRecord) {
93         if (remoteDevice == null) {
94             throw new NullPointerException("Obex transport is null");
95         }
96         mRemoteDevice = remoteDevice;
97         mCallback = callback;
98         mSdpMasRecord = sdpMasRecord;
99         mThread = new HandlerThread("Client");
100         mThread.start();
101         /* This will block until the looper have started, hence it will be safe to use it,
102         when the constructor completes */
103         Looper looper = mThread.getLooper();
104         mHandler = new MasClientHandler(looper, this);
105 
106         mHandler.obtainMessage(CONNECT).sendToTarget();
107     }
108 
109     @SuppressLint("AndroidFrameworkRequiresPermission") // TODO: b/350563786
connect()110     private void connect() {
111         try {
112             int l2capSocket = mSdpMasRecord.getL2capPsm();
113 
114             if (l2capSocket != L2CAP_INVALID_PSM) {
115                 Log.d(TAG, "Connecting to OBEX on L2CAP channel " + l2capSocket);
116                 mSocket = mRemoteDevice.createL2capSocket(l2capSocket);
117             } else {
118                 Log.d(
119                         TAG,
120                         "Connecting to OBEX on RFCOMM channel "
121                                 + mSdpMasRecord.getRfcommCannelNumber());
122                 mSocket = mRemoteDevice.createRfcommSocket(mSdpMasRecord.getRfcommCannelNumber());
123             }
124             Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString());
125             mSocket.connect();
126             mTransport = new BluetoothObexTransport(mSocket);
127 
128             mSession = new ClientSession(mTransport);
129             HeaderSet headerset = new HeaderSet();
130             headerset.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_OBEX_MAS);
131             ObexAppParameters oap = new ObexAppParameters();
132 
133             oap.add(OAP_TAGID_MAP_SUPPORTED_FEATURES, MAP_SUPPORTED_FEATURES);
134 
135             oap.addToHeaderSet(headerset);
136 
137             headerset = mSession.connect(headerset);
138             Log.d(TAG, "Connection results" + headerset.getResponseCode());
139 
140             if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) {
141                 Log.d(TAG, "Connection Successful");
142                 mConnected = true;
143                 mCallback.sendMessage(MceStateMachine.MSG_MAS_CONNECTED);
144             } else {
145                 disconnect();
146             }
147 
148         } catch (IOException e) {
149             Log.e(TAG, "Caught an exception " + e.toString());
150             disconnect();
151         }
152     }
153 
disconnect()154     private void disconnect() {
155         if (mSession != null) {
156             try {
157                 mSession.disconnect(null);
158             } catch (IOException e) {
159                 Log.e(TAG, "Caught an exception while disconnecting:" + e.toString());
160             }
161 
162             try {
163                 mSession.close();
164             } catch (IOException e) {
165                 Log.e(TAG, "Caught an exception while closing:" + e.toString());
166             }
167         }
168 
169         mConnected = false;
170         mCallback.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED);
171     }
172 
executeRequest(Request request)173     private void executeRequest(Request request) {
174         try {
175             request.execute(mSession);
176             mCallback.sendMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, request);
177         } catch (IOException e) {
178             Log.d(TAG, "Request failed: " + request);
179             // Disconnect to cleanup.
180             disconnect();
181         }
182     }
183 
makeRequest(Request request)184     public boolean makeRequest(Request request) {
185         Log.d(TAG, "makeRequest called with: " + request);
186 
187         boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request));
188         if (!status) {
189             Log.e(TAG, "Adding messages failed, state: " + mConnected);
190             return false;
191         }
192         return true;
193     }
194 
195     /**
196      * Invokes {@link Request#abort} and removes it from the Handler's message queue.
197      *
198      * @param request The {@link Request} to abort.
199      */
abortRequest(Request request)200     public void abortRequest(Request request) {
201         Log.d(TAG, "abortRequest called with: " + request);
202 
203         request.abort();
204         mHandler.removeMessages(REQUEST, request);
205     }
206 
shutdown()207     public void shutdown() {
208         mHandler.obtainMessage(DISCONNECT).sendToTarget();
209         mThread.quitSafely();
210     }
211 
212     public enum CharsetType {
213         NATIVE,
214         UTF_8;
215     }
216 
getSdpMasRecord()217     SdpMasRecord getSdpMasRecord() {
218         return mSdpMasRecord;
219     }
220 
221     private static class MasClientHandler extends Handler {
222         final WeakReference<MasClient> mInst;
223 
MasClientHandler(Looper looper, MasClient inst)224         MasClientHandler(Looper looper, MasClient inst) {
225             super(looper);
226             mInst = new WeakReference<>(inst);
227         }
228 
229         @Override
handleMessage(Message msg)230         public void handleMessage(Message msg) {
231             MasClient inst = mInst.get();
232             switch (msg.what) {
233                 case CONNECT:
234                     if (!inst.mConnected) {
235                         inst.connect();
236                     }
237                     break;
238 
239                 case DISCONNECT:
240                     if (inst.mConnected) {
241                         inst.disconnect();
242                     }
243                     break;
244 
245                 case REQUEST:
246                     if (inst.mConnected) {
247                         inst.executeRequest((Request) msg.obj);
248                     }
249                     break;
250             }
251         }
252     }
253 }
254