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