blob: 79aa95dc7030d71f4969a820d59fcdb5ced4563d [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.pbapclient;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import android.util.Log;
import java.io.IOException;
import java.util.UUID;
class BluetoothPbapSession implements Callback {
//TODO consider cleaning file organization and naming.
private static final String TAG = "com.android.bluetooth.pbapclient.BluetoothPbapSession";
/* local use only */
private static final int RFCOMM_CONNECTED = 1;
private static final int RFCOMM_FAILED = 2;
/* to BluetoothPbapClient */
public static final int REQUEST_COMPLETED = 3;
public static final int REQUEST_FAILED = 4;
public static final int SESSION_CONNECTING = 5;
public static final int SESSION_CONNECTED = 6;
public static final int SESSION_DISCONNECTED = 7;
public static final int AUTH_REQUESTED = 8;
public static final int AUTH_TIMEOUT = 9;
public static final int ACTION_LISTING = 14;
public static final int ACTION_VCARD = 15;
public static final int ACTION_PHONEBOOK_SIZE = 16;
private static final String PBAP_UUID =
"0000112f-0000-1000-8000-00805f9b34fb";
private final BluetoothAdapter mAdapter;
private final BluetoothDevice mDevice;
private final Handler mParentHandler;
private final HandlerThread mHandlerThread;
private final Handler mSessionHandler;
private RfcommConnectThread mConnectThread;
private BluetoothPbapObexTransport mTransport;
private BluetoothPbapObexSession mObexSession;
private BluetoothPbapRequest mPendingRequest = null;
public BluetoothPbapSession(BluetoothDevice device, Handler handler) {
mAdapter = BluetoothAdapter.getDefaultAdapter();
if (mAdapter == null) {
throw new NullPointerException("No Bluetooth adapter in the system");
}
mDevice = device;
mParentHandler = handler;
mConnectThread = null;
mTransport = null;
mObexSession = null;
mHandlerThread = new HandlerThread("PBAP session handler",
Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mSessionHandler = new Handler(mHandlerThread.getLooper(), this);
}
@Override
public boolean handleMessage(Message msg) {
Log.d(TAG, "Handler: msg: " + msg.what);
switch (msg.what) {
case RFCOMM_FAILED:
mConnectThread = null;
mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
if (mPendingRequest != null) {
mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
mPendingRequest = null;
}
break;
case RFCOMM_CONNECTED:
mConnectThread = null;
mTransport = (BluetoothPbapObexTransport) msg.obj;
startObexSession();
break;
case BluetoothPbapObexSession.OBEX_SESSION_FAILED:
stopObexSession();
mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
if (mPendingRequest != null) {
mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
mPendingRequest = null;
}
break;
case BluetoothPbapObexSession.OBEX_SESSION_CONNECTED:
mParentHandler.obtainMessage(SESSION_CONNECTED).sendToTarget();
if (mPendingRequest != null) {
mObexSession.schedule(mPendingRequest);
mPendingRequest = null;
}
break;
case BluetoothPbapObexSession.OBEX_SESSION_DISCONNECTED:
mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
stopRfcomm();
break;
case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_COMPLETED:
/* send to parent, process there */
mParentHandler.obtainMessage(REQUEST_COMPLETED, msg.obj).sendToTarget();
break;
case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_FAILED:
/* send to parent, process there */
mParentHandler.obtainMessage(REQUEST_FAILED, msg.obj).sendToTarget();
break;
case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_REQUEST:
/* send to parent, process there */
mParentHandler.obtainMessage(AUTH_REQUESTED).sendToTarget();
mSessionHandler
.sendMessageDelayed(
mSessionHandler
.obtainMessage(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT),
30000);
break;
case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT:
/* stop authentication */
setAuthResponse(null);
mParentHandler.obtainMessage(AUTH_TIMEOUT).sendToTarget();
break;
default:
return false;
}
return true;
}
public void start() {
Log.d(TAG, "start");
startRfcomm();
}
public void stop() {
Log.d(TAG, "Stop");
stopObexSession();
stopRfcomm();
}
public void abort() {
Log.d(TAG, "abort");
/* fail pending request immediately */
if (mPendingRequest != null) {
mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
mPendingRequest = null;
}
if (mObexSession != null) {
mObexSession.abort();
}
}
public boolean makeRequest(BluetoothPbapRequest request) {
Log.v(TAG, "makeRequest: " + request.getClass().getSimpleName());
if (mPendingRequest != null) {
Log.w(TAG, "makeRequest: request already queued, exiting");
return false;
}
if (mObexSession == null) {
mPendingRequest = request;
/*
* since there is no pending request and no session it's safe to
* assume that RFCOMM does not exist either and we should start
* connecting it
*/
startRfcomm();
return true;
}
return mObexSession.schedule(request);
}
public boolean setAuthResponse(String key) {
Log.d(TAG, "setAuthResponse key=" + key);
mSessionHandler
.removeMessages(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT);
/* does not make sense to set auth response when OBEX session is down */
if (mObexSession == null) {
return false;
}
return mObexSession.setAuthReply(key);
}
private void startRfcomm() {
Log.d(TAG, "startRfcomm");
if (mConnectThread == null && mObexSession == null) {
mParentHandler.obtainMessage(SESSION_CONNECTING).sendToTarget();
mConnectThread = new RfcommConnectThread();
mConnectThread.start();
}
/*
* don't care if mConnectThread is not null - it means RFCOMM is being
* connected anyway
*/
}
private void stopRfcomm() {
Log.d(TAG, "stopRfcomm");
if (mConnectThread != null) {
try {
// Force close the socket in case the thread is stuck doing the connect()
// call.
mConnectThread.closeSocket();
// TODO: Add timed join if closeSocket does not clean up the state.
mConnectThread.join();
} catch (InterruptedException e) {
}
mConnectThread = null;
}
if (mTransport != null) {
try {
mTransport.close();
} catch (IOException e) {
}
mTransport = null;
}
}
private void startObexSession() {
Log.d(TAG, "startObexSession");
mObexSession = new BluetoothPbapObexSession(mTransport);
mObexSession.start(mSessionHandler);
}
private void stopObexSession() {
Log.d(TAG, "stopObexSession");
if (mObexSession != null) {
mObexSession.stop();
mObexSession = null;
}
}
private class RfcommConnectThread extends Thread {
private static final String TAG = "RfcommConnectThread";
private BluetoothSocket mSocket;
public RfcommConnectThread() {
super("RfcommConnectThread");
}
@Override
public void run() {
if (mAdapter.isDiscovering()) {
mAdapter.cancelDiscovery();
}
try {
mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(PBAP_UUID));
mSocket.connect();
BluetoothPbapObexTransport transport;
transport = new BluetoothPbapObexTransport(mSocket);
mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
} catch (IOException e) {
closeSocket();
mSessionHandler.obtainMessage(RFCOMM_FAILED).sendToTarget();
}
}
// This method may be called from outside the thread if the connect() call above is stuck.
public void closeSocket() {
try {
if (mSocket != null) {
mSocket.close();
}
} catch (IOException e) {
Log.e(TAG, "Error when closing socket", e);
}
}
}
}