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