| /* |
| * Copyright (C) 2014 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 android.bluetooth.client.pbap; |
| |
| 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 { |
| private static final String TAG = "android.bluetooth.client.pbap.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 { |
| 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(); |
| } |
| |
| } |
| |
| private void closeSocket() { |
| try { |
| if (mSocket != null) { |
| mSocket.close(); |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "Error when closing socket", e); |
| } |
| } |
| } |
| } |