BluetoothMnsObexClient.java revision 70be005a18a35ec5fcb46152f0dfbe82156efa3a
1/*
2* Copyright (C) 2013 Samsung System LSI
3* Licensed under the Apache License, Version 2.0 (the "License");
4* you may not use this file except in compliance with the License.
5* You may obtain a copy of the License at
6*
7*      http://www.apache.org/licenses/LICENSE-2.0
8*
9* Unless required by applicable law or agreed to in writing, software
10* distributed under the License is distributed on an "AS IS" BASIS,
11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12* See the License for the specific language governing permissions and
13* limitations under the License.
14*/
15package com.android.bluetooth.map;
16
17import android.bluetooth.BluetoothDevice;
18import android.bluetooth.BluetoothSocket;
19import android.content.Context;
20import android.os.Handler;
21import android.os.Looper;
22import android.os.Message;
23import android.os.ParcelUuid;
24import android.util.Log;
25
26import java.io.BufferedInputStream;
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.IOException;
30import java.io.OutputStream;
31
32import javax.obex.ApplicationParameter;
33import javax.obex.ClientOperation;
34import javax.obex.ClientSession;
35import javax.obex.HeaderSet;
36import javax.obex.ObexTransport;
37import javax.obex.ResponseCodes;
38
39/**
40 * The Message Notification Service class runs its own message handler thread,
41 * to avoid executing long operations on the MAP service Thread.
42 * This handler context is passed to the content observers,
43 * hence all call-backs (and thereby transmission of data) is executed
44 * from this thread.
45 */
46public class BluetoothMnsObexClient extends Thread{
47
48    private static final String TAG = "BluetoothMnsObexClient";
49    private static final boolean D = false;
50    private static final boolean V = false;
51
52    public final static int MSG_SESSION_ERROR = 1;
53    public final static int MSG_CONNECT_TIMEOUT = 2;
54
55    private ObexTransport mTransport;
56    private Context mContext;
57    public static Handler mHandler = null;
58    private volatile boolean mWaitingForRemote;
59    private static final String TYPE_EVENT = "x-bt/MAP-event-report";
60    private ClientSession mClientSession;
61    private boolean mConnected = false;
62    BluetoothDevice mRemoteDevice;
63    private static BluetoothMapContentObserver mObserver;
64    private boolean mObserverRegistered = false;
65
66    private Looper mLooper = null;
67    // Used by the MAS to forward notification registrations
68    public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1;
69
70
71    public static final ParcelUuid BluetoothUuid_ObexMns =
72            ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
73
74
75    public BluetoothMnsObexClient(Context context, BluetoothDevice remoteDevice) {
76        if (remoteDevice == null) {
77            throw new NullPointerException("Obex transport is null");
78        }
79        mContext = context;
80        mRemoteDevice = remoteDevice;
81    }
82
83    public static Handler getMessageHandler() {
84        // TODO: if mHandle is null, we should wait for it to be created.
85        return mHandler;
86    }
87
88    public static BluetoothMapContentObserver getContentObserver() {
89        return mObserver;
90    }
91
92    @Override
93    public void run() {
94        Looper.prepare();
95        mLooper = Looper.myLooper();
96
97
98        /* Create the context observer from within the thread to ensure the "content changed"
99         * events are handled in this thread. */
100        mObserver = new BluetoothMapContentObserver(mContext);
101        mObserver.init();
102
103        mHandler = new Handler() {
104            public void handleMessage(Message msg) {
105                switch (msg.what) {
106                case MSG_MNS_NOTIFICATION_REGISTRATION:
107                    handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/);
108                    break;
109                default:
110                    break;
111                }
112            }
113        };
114        Looper.loop();
115    }
116
117    public boolean isConnected() {
118        return mConnected;
119    }
120
121    public void disconnect() {
122        try {
123            if (mClientSession != null) {
124                mClientSession.disconnect(null);
125                if (D) Log.d(TAG, "OBEX session disconnected");
126            }
127        } catch (IOException e) {
128            Log.w(TAG, "OBEX session disconnect error " + e.getMessage());
129        }
130        try {
131            if (mClientSession != null) {
132                if (D) Log.d(TAG, "OBEX session close mClientSession");
133                mClientSession.close();
134                mClientSession = null;
135                if (D) Log.d(TAG, "OBEX session closed");
136            }
137        } catch (IOException e) {
138            Log.w(TAG, "OBEX session close error:" + e.getMessage());
139        }
140        if (mTransport != null) {
141            try {
142                if (D) Log.d(TAG, "Close Obex Transport");
143                mTransport.close();
144                mTransport = null;
145                mConnected = false;
146                if (D) Log.d(TAG, "Obex Transport Closed");
147            } catch (IOException e) {
148                Log.e(TAG, "mTransport.close error: " + e.getMessage());
149            }
150        }
151        if(mObserverRegistered) {
152            mObserver.unregisterObserver();
153            mObserverRegistered = false;
154        }
155
156        mObserver.deinit();
157        // Shut down the thread
158        if(mLooper != null)
159            mLooper.quit();
160        interrupt();
161        try {
162            join();
163        } catch (InterruptedException e) {
164            if(V) Log.w(TAG, "got interrupted. Probably a connection shutdown");
165        }
166        mHandler = null;
167        mObserver = null;
168    }
169
170    private HeaderSet hsConnect = null;
171
172    public void handleRegistration(int masId, int notificationStatus){
173        Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
174
175        if(isConnected() == false) {
176            Log.d(TAG, "handleRegistration: connect");
177            connect();
178        }
179
180        if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
181            // Unregister - should we disconnect, or keep the connection? - the spec. says nothing about this.
182            if(mObserverRegistered == true) {
183                mObserver.unregisterObserver();
184                mObserverRegistered = false;
185            }
186        } else if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
187            /* Connect if we do not have a connection, and start the content observers providing
188             * this thread as Handler.
189             */
190            if(mObserverRegistered == false) {
191                mObserver.registerObserver(this, masId);
192                mObserverRegistered = true;
193            }
194        }
195    }
196
197    public void connect() {
198        Log.d(TAG, "handleRegistration: connect 2");
199
200        BluetoothSocket btSocket = null;
201        try {
202            btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord(
203                    BluetoothUuid_ObexMns.getUuid());
204            btSocket.connect();
205        } catch (IOException e) {
206            Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e);
207            // TODO: do we need to report error somewhere?
208            return;
209        }
210
211        mTransport = new BluetoothMnsRfcommTransport(btSocket);
212
213        try {
214            mClientSession = new ClientSession(mTransport);
215            mConnected = true;
216        } catch (IOException e1) {
217            Log.e(TAG, "OBEX session create error " + e1.getMessage());
218        }
219        if (mConnected && mClientSession != null) {
220            mConnected = false;
221            HeaderSet hs = new HeaderSet();
222            // bb582b41-420c-11db-b0de-0800200c9a66
223            byte[] mnsTarget = { (byte) 0xbb, (byte) 0x58, (byte) 0x2b, (byte) 0x41,
224                                 (byte) 0x42, (byte) 0x0c, (byte) 0x11, (byte) 0xdb,
225                                 (byte) 0xb0, (byte) 0xde, (byte) 0x08, (byte) 0x00,
226                                 (byte) 0x20, (byte) 0x0c, (byte) 0x9a, (byte) 0x66 };
227            hs.setHeader(HeaderSet.TARGET, mnsTarget);
228
229            synchronized (this) {
230                mWaitingForRemote = true;
231            }
232            try {
233                hsConnect = mClientSession.connect(hs);
234                if (D) Log.d(TAG, "OBEX session created");
235                mConnected = true;
236            } catch (IOException e) {
237                Log.e(TAG, "OBEX session connect error " + e.getMessage());
238            }
239        }
240            synchronized (this) {
241                mWaitingForRemote = false;
242        }
243    }
244
245    public int sendEvent(byte[] eventBytes, int masInstanceId) {
246
247        boolean error = false;
248        int responseCode = -1;
249        HeaderSet request;
250        int maxChunkSize, bytesToWrite, bytesWritten = 0;
251        request = new HeaderSet();
252        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
253        appParams.setMasInstanceId(masInstanceId);
254
255        ClientOperation putOperation = null;
256        OutputStream outputStream = null;
257
258        try {
259            request.setHeader(HeaderSet.TYPE, TYPE_EVENT);
260            request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams());
261
262            request.mConnectionID = new byte[4];
263            System.arraycopy(hsConnect.mConnectionID, 0, request.mConnectionID, 0, 4);
264
265            synchronized (this) {
266                mWaitingForRemote = true;
267            }
268            // Send the header first and then the body
269            try {
270                if (V) Log.v(TAG, "Send headerset Event ");
271                putOperation = (ClientOperation)mClientSession.put(request);
272                // TODO - Should this be kept or Removed
273
274            } catch (IOException e) {
275                Log.e(TAG, "Error when put HeaderSet " + e.getMessage());
276                error = true;
277            }
278            synchronized (this) {
279                mWaitingForRemote = false;
280            }
281            if (!error) {
282                try {
283                    if (V) Log.v(TAG, "Send headerset Event ");
284                    outputStream = putOperation.openOutputStream();
285                } catch (IOException e) {
286                    Log.e(TAG, "Error when opening OutputStream " + e.getMessage());
287                    error = true;
288                }
289            }
290
291            if (!error) {
292
293                maxChunkSize = putOperation.getMaxPacketSize();
294
295                while (bytesWritten < eventBytes.length) {
296                    bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten);
297                    outputStream.write(eventBytes, bytesWritten, bytesToWrite);
298                    bytesWritten += bytesToWrite;
299                }
300
301                if (bytesWritten == eventBytes.length) {
302                    Log.i(TAG, "SendEvent finished send length" + eventBytes.length);
303                    outputStream.close();
304                } else {
305                    error = true;
306                    outputStream.close();
307                    putOperation.abort();
308                    Log.i(TAG, "SendEvent interrupted");
309                }
310            }
311        } catch (IOException e) {
312            handleSendException(e.toString());
313            error = true;
314        } catch (IndexOutOfBoundsException e) {
315            handleSendException(e.toString());
316            error = true;
317        } finally {
318            try {
319                if (!error) {
320                    responseCode = putOperation.getResponseCode();
321                    if (responseCode != -1) {
322                        if (V) Log.v(TAG, "Put response code " + responseCode);
323                        if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
324                            Log.i(TAG, "Response error code is " + responseCode);
325                        }
326                    }
327                }
328                if (putOperation != null) {
329                   putOperation.close();
330                }
331            } catch (IOException e) {
332                Log.e(TAG, "Error when closing stream after send " + e.getMessage());
333            }
334        }
335
336        return responseCode;
337    }
338
339    private void handleSendException(String exception) {
340        Log.e(TAG, "Error when sending event: " + exception);
341    }
342}
343