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