BluetoothMnsObexClient.java revision 824929471ee80476e6d6774eedac9f30c5623eb2
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    /**
116     * Disconnect the connection to MNS server.
117     * Call this when the MAS client requests a de-registration on events.
118     */
119    public void disconnect() {
120        try {
121            if (mClientSession != null) {
122                mClientSession.disconnect(null);
123                if (D) Log.d(TAG, "OBEX session disconnected");
124            }
125        } catch (IOException e) {
126            Log.w(TAG, "OBEX session disconnect error " + e.getMessage());
127        }
128        try {
129            if (mClientSession != null) {
130                if (D) Log.d(TAG, "OBEX session close mClientSession");
131                mClientSession.close();
132                mClientSession = null;
133                if (D) Log.d(TAG, "OBEX session closed");
134            }
135        } catch (IOException e) {
136            Log.w(TAG, "OBEX session close error:" + e.getMessage());
137        }
138        if (mTransport != null) {
139            try {
140                if (D) Log.d(TAG, "Close Obex Transport");
141                mTransport.close();
142                mTransport = null;
143                mConnected = false;
144                if (D) Log.d(TAG, "Obex Transport Closed");
145            } catch (IOException e) {
146                Log.e(TAG, "mTransport.close error: " + e.getMessage());
147            }
148        }
149    }
150
151    /**
152     * Shutdown the MNS.
153     */
154    public void shutdown() {
155        /* should shutdown handler thread first to make sure
156         * handleRegistration won't be called when disconnet
157         */
158        if (mHandler != null) {
159            // Shut down the thread
160            mHandler.removeCallbacksAndMessages(null);
161            Looper looper = mHandler.getLooper();
162            if (looper != null) {
163                looper.quit();
164            }
165            mHandler = null;
166        }
167
168        /* Disconnect if connected */
169        disconnect();
170
171        if(mObserverRegistered) {
172            mObserver.unregisterObserver();
173            mObserverRegistered = false;
174        }
175        if (mObserver != null) {
176            mObserver.deinit();
177            mObserver = null;
178        }
179    }
180
181    private HeaderSet hsConnect = null;
182
183    public void handleRegistration(int masId, int notificationStatus){
184        Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
185
186        if((isConnected() == false) &&
187           (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES)) {
188            Log.d(TAG, "handleRegistration: connect");
189            connect();
190        }
191
192        if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
193            // Unregister - should we disconnect, or keep the connection? - the spec. says nothing about this.
194            if(mObserverRegistered == true) {
195                mObserver.unregisterObserver();
196                mObserverRegistered = false;
197                disconnect();
198            }
199        } else if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
200            /* Connect if we do not have a connection, and start the content observers providing
201             * this thread as Handler.
202             */
203            if(mObserverRegistered == false) {
204                mObserver.registerObserver(this, masId);
205                mObserverRegistered = true;
206            }
207        }
208    }
209
210    public void connect() {
211        Log.d(TAG, "handleRegistration: connect 2");
212
213        BluetoothSocket btSocket = null;
214        try {
215            btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord(
216                    BluetoothUuid_ObexMns.getUuid());
217            btSocket.connect();
218        } catch (IOException e) {
219            Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e);
220            // TODO: do we need to report error somewhere?
221            return;
222        }
223
224        mTransport = new BluetoothMnsRfcommTransport(btSocket);
225
226        try {
227            mClientSession = new ClientSession(mTransport);
228            mConnected = true;
229        } catch (IOException e1) {
230            Log.e(TAG, "OBEX session create error " + e1.getMessage());
231        }
232        if (mConnected && mClientSession != null) {
233            mConnected = false;
234            HeaderSet hs = new HeaderSet();
235            // bb582b41-420c-11db-b0de-0800200c9a66
236            byte[] mnsTarget = { (byte) 0xbb, (byte) 0x58, (byte) 0x2b, (byte) 0x41,
237                                 (byte) 0x42, (byte) 0x0c, (byte) 0x11, (byte) 0xdb,
238                                 (byte) 0xb0, (byte) 0xde, (byte) 0x08, (byte) 0x00,
239                                 (byte) 0x20, (byte) 0x0c, (byte) 0x9a, (byte) 0x66 };
240            hs.setHeader(HeaderSet.TARGET, mnsTarget);
241
242            synchronized (this) {
243                mWaitingForRemote = true;
244            }
245            try {
246                hsConnect = mClientSession.connect(hs);
247                if (D) Log.d(TAG, "OBEX session created");
248                mConnected = true;
249            } catch (IOException e) {
250                Log.e(TAG, "OBEX session connect error " + e.getMessage());
251            }
252        }
253            synchronized (this) {
254                mWaitingForRemote = false;
255        }
256    }
257
258    public int sendEvent(byte[] eventBytes, int masInstanceId) {
259
260        boolean error = false;
261        int responseCode = -1;
262        HeaderSet request;
263        int maxChunkSize, bytesToWrite, bytesWritten = 0;
264        request = new HeaderSet();
265        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
266        appParams.setMasInstanceId(masInstanceId);
267
268        ClientOperation putOperation = null;
269        OutputStream outputStream = null;
270
271        try {
272            request.setHeader(HeaderSet.TYPE, TYPE_EVENT);
273            request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams());
274
275            request.mConnectionID = new byte[4];
276            System.arraycopy(hsConnect.mConnectionID, 0, request.mConnectionID, 0, 4);
277
278            synchronized (this) {
279                mWaitingForRemote = true;
280            }
281            // Send the header first and then the body
282            try {
283                if (V) Log.v(TAG, "Send headerset Event ");
284                putOperation = (ClientOperation)mClientSession.put(request);
285                // TODO - Should this be kept or Removed
286
287            } catch (IOException e) {
288                Log.e(TAG, "Error when put HeaderSet " + e.getMessage());
289                error = true;
290            }
291            synchronized (this) {
292                mWaitingForRemote = false;
293            }
294            if (!error) {
295                try {
296                    if (V) Log.v(TAG, "Send headerset Event ");
297                    outputStream = putOperation.openOutputStream();
298                } catch (IOException e) {
299                    Log.e(TAG, "Error when opening OutputStream " + e.getMessage());
300                    error = true;
301                }
302            }
303
304            if (!error) {
305
306                maxChunkSize = putOperation.getMaxPacketSize();
307
308                while (bytesWritten < eventBytes.length) {
309                    bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten);
310                    outputStream.write(eventBytes, bytesWritten, bytesToWrite);
311                    bytesWritten += bytesToWrite;
312                }
313
314                if (bytesWritten == eventBytes.length) {
315                    Log.i(TAG, "SendEvent finished send length" + eventBytes.length);
316                    outputStream.close();
317                } else {
318                    error = true;
319                    outputStream.close();
320                    putOperation.abort();
321                    Log.i(TAG, "SendEvent interrupted");
322                }
323            }
324        } catch (IOException e) {
325            handleSendException(e.toString());
326            error = true;
327        } catch (IndexOutOfBoundsException e) {
328            handleSendException(e.toString());
329            error = true;
330        } finally {
331            try {
332                if (!error) {
333                    responseCode = putOperation.getResponseCode();
334                    if (responseCode != -1) {
335                        if (V) Log.v(TAG, "Put response code " + responseCode);
336                        if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
337                            Log.i(TAG, "Response error code is " + responseCode);
338                        }
339                    }
340                }
341                if (putOperation != null) {
342                   putOperation.close();
343                }
344            } catch (IOException e) {
345                Log.e(TAG, "Error when closing stream after send " + e.getMessage());
346            }
347        }
348
349        return responseCode;
350    }
351
352    private void handleSendException(String exception) {
353        Log.e(TAG, "Error when sending event: " + exception);
354    }
355}
356