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