blob: 2966f2142be62e6bf4358b03193b96624c48dd5b [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.mapclient;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.SdpMasRecord;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.statemachine.StateMachine;
import java.io.IOException;
import java.lang.ref.WeakReference;
import javax.obex.ClientSession;
import javax.obex.HeaderSet;
import javax.obex.ResponseCodes;
/* MasClient is a one time use connection to a server defined by the SDP record passed in at
* construction. After use shutdown() must be called to properly clean up.
*/
public class MasClient {
private static final int CONNECT = 0;
private static final int DISCONNECT = 1;
private static final int REQUEST = 2;
private static final String TAG = "MasClient";
private static final boolean DBG = MapClientService.DBG;
private static final boolean VDBG = MapClientService.VDBG;
private static final byte[] BLUETOOTH_UUID_OBEX_MAS = new byte[]{
(byte) 0xbb,
0x58,
0x2b,
0x40,
0x42,
0x0c,
0x11,
(byte) 0xdb,
(byte) 0xb0,
(byte) 0xde,
0x08,
0x00,
0x20,
0x0c,
(byte) 0x9a,
0x66
};
private static final byte OAP_TAGID_MAP_SUPPORTED_FEATURES = 0x29;
private static final int MAP_FEATURE_NOTIFICATION_REGISTRATION = 0x00000001;
private static final int MAP_FEATURE_NOTIFICATION = 0x00000002;
static final int MAP_SUPPORTED_FEATURES =
MAP_FEATURE_NOTIFICATION_REGISTRATION | MAP_FEATURE_NOTIFICATION;
private final StateMachine mCallback;
private Handler mHandler;
private BluetoothSocket mSocket;
private BluetoothObexTransport mTransport;
private BluetoothDevice mRemoteDevice;
private ClientSession mSession;
private HandlerThread mThread;
private boolean mConnected = false;
SdpMasRecord mSdpMasRecord;
public MasClient(BluetoothDevice remoteDevice, StateMachine callback,
SdpMasRecord sdpMasRecord) {
if (remoteDevice == null) {
throw new NullPointerException("Obex transport is null");
}
mRemoteDevice = remoteDevice;
mCallback = callback;
mSdpMasRecord = sdpMasRecord;
mThread = new HandlerThread("Client");
mThread.start();
/* This will block until the looper have started, hence it will be safe to use it,
when the constructor completes */
Looper looper = mThread.getLooper();
mHandler = new MasClientHandler(looper, this);
mHandler.obtainMessage(CONNECT).sendToTarget();
}
private void connect() {
try {
if (DBG) {
Log.d(TAG, "Connecting to OBEX on RFCOM channel "
+ mSdpMasRecord.getRfcommCannelNumber());
}
mSocket = mRemoteDevice.createRfcommSocket(mSdpMasRecord.getRfcommCannelNumber());
if (DBG) Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString());
mSocket.connect();
mTransport = new BluetoothObexTransport(mSocket);
mSession = new ClientSession(mTransport);
HeaderSet headerset = new HeaderSet();
headerset.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_OBEX_MAS);
ObexAppParameters oap = new ObexAppParameters();
oap.add(OAP_TAGID_MAP_SUPPORTED_FEATURES, MAP_SUPPORTED_FEATURES);
oap.addToHeaderSet(headerset);
headerset = mSession.connect(headerset);
if (DBG) Log.d(TAG, "Connection results" + headerset.getResponseCode());
if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) {
if (DBG) {
Log.d(TAG, "Connection Successful");
}
mConnected = true;
mCallback.sendMessage(MceStateMachine.MSG_MAS_CONNECTED);
} else {
disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Caught an exception " + e.toString());
disconnect();
}
}
private void disconnect() {
if (mSession != null) {
try {
mSession.disconnect(null);
} catch (IOException e) {
Log.e(TAG, "Caught an exception while disconnecting:" + e.toString());
}
try {
mSession.close();
} catch (IOException e) {
Log.e(TAG, "Caught an exception while closing:" + e.toString());
}
}
mConnected = false;
mCallback.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED);
}
private void executeRequest(Request request) {
try {
request.execute(mSession);
mCallback.sendMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, request);
} catch (IOException e) {
if (DBG) {
Log.d(TAG, "Request failed: " + request);
}
// Disconnect to cleanup.
disconnect();
}
}
public boolean makeRequest(Request request) {
if (DBG) {
Log.d(TAG, "makeRequest called with: " + request);
}
boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request));
if (!status) {
Log.e(TAG, "Adding messages failed, state: " + mConnected);
return false;
}
return true;
}
public void shutdown() {
mHandler.obtainMessage(DISCONNECT).sendToTarget();
mThread.quitSafely();
}
public enum CharsetType {
NATIVE, UTF_8;
}
SdpMasRecord getSdpMasRecord() {
return mSdpMasRecord;
}
private static class MasClientHandler extends Handler {
WeakReference<MasClient> mInst;
MasClientHandler(Looper looper, MasClient inst) {
super(looper);
mInst = new WeakReference<>(inst);
}
@Override
public void handleMessage(Message msg) {
MasClient inst = mInst.get();
switch (msg.what) {
case CONNECT:
if (!inst.mConnected) {
inst.connect();
}
break;
case DISCONNECT:
if (inst.mConnected) {
inst.disconnect();
}
break;
case REQUEST:
if (inst.mConnected) {
inst.executeRequest((Request) msg.obj);
}
break;
}
}
}
}