blob: 40922842e809df76bf0c121536331a666c6f908f [file] [log] [blame]
package com.android.bluetooth.sap;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothSap;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothSap;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.Log;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.sdp.SdpManager;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@TargetApi(Build.VERSION_CODES.ECLAIR)
public class SapService extends ProfileService {
private static final String SDP_SAP_SERVICE_NAME = "SIM Access";
private static final int SDP_SAP_VERSION = 0x0102;
private static final String TAG = "SapService";
public static final boolean DEBUG = false;
public static final boolean VERBOSE = false;
/* Message ID's */
private static final int START_LISTENER = 1;
private static final int USER_TIMEOUT = 2;
private static final int SHUTDOWN = 3;
public static final int MSG_SERVERSESSION_CLOSE = 5000;
public static final int MSG_SESSION_ESTABLISHED = 5001;
public static final int MSG_SESSION_DISCONNECTED = 5002;
public static final int MSG_ACQUIRE_WAKE_LOCK = 5005;
public static final int MSG_RELEASE_WAKE_LOCK = 5006;
public static final int MSG_CHANGE_STATE = 5007;
/* Each time a transaction between the SIM and the BT Client is detected a wakelock is taken.
* After an idle period of RELEASE_WAKE_LOCK_DELAY ms the wakelock is released.
*
* NOTE: While connected the the Nokia 616 car-kit it was noticed that the carkit do
* TRANSFER_APDU_REQ with 20-30 seconds interval, and it sends no requests less than 1 sec
* apart. Additionally the responses from the RIL seems to come within 100 ms, hence a
* one second timeout should be enough.
*/
private static final int RELEASE_WAKE_LOCK_DELAY = 1000;
/* Intent indicating timeout for user confirmation. */
public static final String USER_CONFIRM_TIMEOUT_ACTION =
"com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT";
private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000;
private PowerManager.WakeLock mWakeLock = null;
private BluetoothAdapter mAdapter;
private SocketAcceptThread mAcceptThread = null;
private BluetoothServerSocket mServerSocket = null;
private int mSdpHandle = -1;
private BluetoothSocket mConnSocket = null;
private BluetoothDevice mRemoteDevice = null;
private static String sRemoteDeviceName = null;
private volatile boolean mInterrupted;
private int mState;
private SapServer mSapServer = null;
private AlarmManager mAlarmManager = null;
private boolean mRemoveTimeoutMsg = false;
private boolean mIsWaitingAuthorization = false;
private boolean mIsRegistered = false;
private static SapService sSapService;
private static final ParcelUuid[] SAP_UUIDS = {
BluetoothUuid.SAP,
};
public SapService() {
mState = BluetoothSap.STATE_DISCONNECTED;
}
/***
* Call this when ever an activity is detected to renew the wakelock
*
* @param messageHandler reference to the handler to notify
* - typically mSessionStatusHandler, but it cannot be accessed in a static manner.
*/
public static void notifyUpdateWakeLock(Handler messageHandler) {
if (messageHandler != null) {
Message msg = Message.obtain(messageHandler);
msg.what = MSG_ACQUIRE_WAKE_LOCK;
msg.sendToTarget();
}
}
private void removeSdpRecord() {
if (mAdapter != null && mSdpHandle >= 0 && SdpManager.getDefaultManager() != null) {
if (VERBOSE) {
Log.d(TAG, "Removing SDP record handle: " + mSdpHandle);
}
boolean status = SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
mSdpHandle = -1;
}
}
private void startRfcommSocketListener() {
if (VERBOSE) {
Log.v(TAG, "Sap Service startRfcommSocketListener");
}
if (mAcceptThread == null) {
mAcceptThread = new SocketAcceptThread();
mAcceptThread.setName("SapAcceptThread");
mAcceptThread.start();
}
}
private static final int CREATE_RETRY_TIME = 10;
private boolean initSocket() {
if (VERBOSE) {
Log.v(TAG, "Sap Service initSocket");
}
boolean initSocketOK = false;
// It's possible that create will fail in some cases. retry for 10 times
for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
initSocketOK = true;
try {
// It is mandatory for MSE to support initiation of bonding and encryption.
// TODO: Consider reusing the mServerSocket - it is indented to be reused
// for multiple connections.
mServerSocket = mAdapter.listenUsingRfcommOn(
BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true, true);
removeSdpRecord();
mSdpHandle = SdpManager.getDefaultManager()
.createSapsRecord(SDP_SAP_SERVICE_NAME, mServerSocket.getChannel(),
SDP_SAP_VERSION);
} catch (IOException e) {
Log.e(TAG, "Error create RfcommServerSocket ", e);
initSocketOK = false;
}
if (!initSocketOK) {
// Need to break out of this loop if BT is being turned off.
if (mAdapter == null) {
break;
}
int state = mAdapter.getState();
if ((state != BluetoothAdapter.STATE_TURNING_ON) && (state
!= BluetoothAdapter.STATE_ON)) {
Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
break;
}
try {
if (VERBOSE) {
Log.v(TAG, "wait 300 ms");
}
Thread.sleep(300);
} catch (InterruptedException e) {
Log.e(TAG, "socketAcceptThread thread was interrupted (3)", e);
}
} else {
break;
}
}
if (initSocketOK) {
if (VERBOSE) {
Log.v(TAG, "Succeed to create listening socket ");
}
} else {
Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
}
return initSocketOK;
}
private synchronized void closeServerSocket() {
// exit SocketAcceptThread early
if (mServerSocket != null) {
try {
// this will cause mServerSocket.accept() return early with IOException
mServerSocket.close();
mServerSocket = null;
} catch (IOException ex) {
Log.e(TAG, "Close Server Socket error: ", ex);
}
}
}
private synchronized void closeConnectionSocket() {
if (mConnSocket != null) {
try {
mConnSocket.close();
mConnSocket = null;
} catch (IOException e) {
Log.e(TAG, "Close Connection Socket error: ", e);
}
}
}
private void closeService() {
if (VERBOSE) {
Log.v(TAG, "SAP Service closeService in");
}
// exit initSocket early
mInterrupted = true;
closeServerSocket();
if (mAcceptThread != null) {
try {
mAcceptThread.shutdown();
mAcceptThread.join();
mAcceptThread = null;
} catch (InterruptedException ex) {
Log.w(TAG, "mAcceptThread close error", ex);
}
}
if (mWakeLock != null) {
mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
mWakeLock.release();
mWakeLock = null;
}
closeConnectionSocket();
if (VERBOSE) {
Log.v(TAG, "SAP Service closeService out");
}
}
private void startSapServerSession() throws IOException {
if (VERBOSE) {
Log.v(TAG, "Sap Service startSapServerSession");
}
// acquire the wakeLock before start SAP transaction thread
if (mWakeLock == null) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StartingSapTransaction");
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
}
/* Start the SAP I/O thread and associate with message handler */
mSapServer = new SapServer(mSessionStatusHandler, this, mConnSocket.getInputStream(),
mConnSocket.getOutputStream());
mSapServer.start();
/* Warning: at this point we most likely have already handled the initial connect
* request from the SAP client, hence we need to be prepared to handle the
* response. (the SapHandler should have been started before this point)*/
mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
mSessionStatusHandler.sendMessageDelayed(
mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
RELEASE_WAKE_LOCK_DELAY);
if (VERBOSE) {
Log.v(TAG, "startSapServerSession() success!");
}
}
private void stopSapServerSession() {
/* When we reach this point, the SapServer is closed down, and the client is
* supposed to close the RFCOMM connection. */
if (VERBOSE) {
Log.v(TAG, "SAP Service stopSapServerSession");
}
mAcceptThread = null;
closeConnectionSocket();
closeServerSocket();
setState(BluetoothSap.STATE_DISCONNECTED);
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
}
// Last SAP transaction is finished, we start to listen for incoming
// rfcomm connection again
if (mAdapter.isEnabled()) {
startRfcommSocketListener();
}
}
/**
* A thread that runs in the background waiting for remote rfcomm
* connect.Once a remote socket connected, this thread shall be
* shutdown.When the remote disconnect,this thread shall run again waiting
* for next request.
*/
private class SocketAcceptThread extends Thread {
private boolean mStopped = false;
@Override
public void run() {
BluetoothServerSocket serverSocket;
if (mServerSocket == null) {
if (!initSocket()) {
return;
}
}
while (!mStopped) {
try {
if (VERBOSE) {
Log.v(TAG, "Accepting socket connection...");
}
serverSocket = mServerSocket;
if (serverSocket == null) {
Log.w(TAG, "mServerSocket is null");
break;
}
mConnSocket = mServerSocket.accept();
if (VERBOSE) {
Log.v(TAG, "Accepted socket connection...");
}
synchronized (SapService.this) {
if (mConnSocket == null) {
Log.w(TAG, "mConnSocket is null");
break;
}
mRemoteDevice = mConnSocket.getRemoteDevice();
}
if (mRemoteDevice == null) {
Log.i(TAG, "getRemoteDevice() = null");
break;
}
sRemoteDeviceName = mRemoteDevice.getName();
// In case getRemoteName failed and return null
if (TextUtils.isEmpty(sRemoteDeviceName)) {
sRemoteDeviceName = getString(R.string.defaultname);
}
int permission = mRemoteDevice.getSimAccessPermission();
if (VERBOSE) {
Log.v(TAG, "getSimAccessPermission() = " + permission);
}
if (permission == BluetoothDevice.ACCESS_ALLOWED) {
try {
if (VERBOSE) {
Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName
+ " automatically as trusted device");
}
startSapServerSession();
} catch (IOException ex) {
Log.e(TAG, "catch exception starting obex server session", ex);
}
} else if (permission != BluetoothDevice.ACCESS_REJECTED) {
Intent intent =
new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
intent.setPackage(getString(R.string.pairing_ui_package));
intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
mIsWaitingAuthorization = true;
setUserTimeoutAlarm();
sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
if (VERBOSE) {
Log.v(TAG, "waiting for authorization for connection from: "
+ sRemoteDeviceName);
}
} else {
// Close RFCOMM socket for current connection and start listening
// again for new connections.
Log.w(TAG, "Can't connect with " + sRemoteDeviceName
+ " as access is rejected");
if (mSessionStatusHandler != null) {
mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
}
}
mStopped = true; // job done ,close this thread;
} catch (IOException ex) {
mStopped = true;
if (VERBOSE) {
Log.v(TAG, "Accept exception: ", ex);
}
}
}
}
void shutdown() {
mStopped = true;
interrupt();
}
}
private final Handler mSessionStatusHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (VERBOSE) {
Log.v(TAG, "Handler(): got msg=" + msg.what);
}
switch (msg.what) {
case START_LISTENER:
if (mAdapter.isEnabled()) {
startRfcommSocketListener();
}
break;
case USER_TIMEOUT:
if (mIsWaitingAuthorization) {
sendCancelUserConfirmationIntent(mRemoteDevice);
cancelUserTimeoutAlarm();
mIsWaitingAuthorization = false;
stopSapServerSession(); // And restart RfcommListener if needed
}
break;
case MSG_SERVERSESSION_CLOSE:
stopSapServerSession();
break;
case MSG_SESSION_ESTABLISHED:
break;
case MSG_SESSION_DISCONNECTED:
// handled elsewhere
break;
case MSG_ACQUIRE_WAKE_LOCK:
if (VERBOSE) {
Log.i(TAG, "Acquire Wake Lock request message");
}
if (mWakeLock == null) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"StartingObexMapTransaction");
mWakeLock.setReferenceCounted(false);
}
if (!mWakeLock.isHeld()) {
mWakeLock.acquire();
if (DEBUG) {
Log.i(TAG, " Acquired Wake Lock by message");
}
}
mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
mSessionStatusHandler.sendMessageDelayed(
mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
RELEASE_WAKE_LOCK_DELAY);
break;
case MSG_RELEASE_WAKE_LOCK:
if (VERBOSE) {
Log.i(TAG, "Release Wake Lock request message");
}
if (mWakeLock != null) {
mWakeLock.release();
if (DEBUG) {
Log.i(TAG, " Released Wake Lock by message");
}
}
break;
case MSG_CHANGE_STATE:
if (DEBUG) {
Log.d(TAG, "change state message: newState = " + msg.arg1);
}
setState(msg.arg1);
break;
case SHUTDOWN:
/* Ensure to call close from this handler to avoid starting new stuff
because of pending messages */
closeService();
break;
default:
break;
}
}
};
private void setState(int state) {
setState(state, BluetoothSap.RESULT_SUCCESS);
}
private synchronized void setState(int state, int result) {
if (state != mState) {
if (DEBUG) {
Log.d(TAG, "Sap state " + mState + " -> " + state + ", result = " + result);
}
if (state == BluetoothProfile.STATE_CONNECTED) {
MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.SAP);
}
int prevState = mState;
mState = state;
Intent intent = new Intent(BluetoothSap.ACTION_CONNECTION_STATE_CHANGED);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
sendBroadcast(intent, BLUETOOTH_PERM);
}
}
public int getState() {
return mState;
}
public BluetoothDevice getRemoteDevice() {
return mRemoteDevice;
}
public static String getRemoteDeviceName() {
return sRemoteDeviceName;
}
public boolean disconnect(BluetoothDevice device) {
boolean result = false;
synchronized (SapService.this) {
if (mRemoteDevice != null && mRemoteDevice.equals(device)) {
switch (mState) {
case BluetoothSap.STATE_CONNECTED:
closeConnectionSocket();
setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
result = true;
break;
default:
break;
}
}
}
return result;
}
public List<BluetoothDevice> getConnectedDevices() {
List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
synchronized (this) {
if (mState == BluetoothSap.STATE_CONNECTED && mRemoteDevice != null) {
devices.add(mRemoteDevice);
}
}
return devices;
}
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
int connectionState;
synchronized (this) {
for (BluetoothDevice device : bondedDevices) {
ParcelUuid[] featureUuids = device.getUuids();
if (!BluetoothUuid.containsAnyUuid(featureUuids, SAP_UUIDS)) {
continue;
}
connectionState = getConnectionState(device);
for (int i = 0; i < states.length; i++) {
if (connectionState == states[i]) {
deviceList.add(device);
}
}
}
}
return deviceList;
}
public int getConnectionState(BluetoothDevice device) {
synchronized (this) {
if (getState() == BluetoothSap.STATE_CONNECTED && getRemoteDevice().equals(device)) {
return BluetoothProfile.STATE_CONNECTED;
} else {
return BluetoothProfile.STATE_DISCONNECTED;
}
}
}
/**
* Set connection policy of the profile and disconnects it if connectionPolicy is
* {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
*
* <p> The device should already be paired.
* Connection policy can be one of:
* {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
* {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
* {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
*
* @param device Paired bluetooth device
* @param connectionPolicy is the connection policy to set to for this profile
* @return true if connectionPolicy is set, false on error
*/
public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
if (DEBUG) {
Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
"Need BLUETOOTH_PRIVILEGED permission");
AdapterService.getAdapterService().getDatabase()
.setProfileConnectionPolicy(device, BluetoothProfile.SAP, connectionPolicy);
if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
disconnect(device);
}
return true;
}
/**
* Get the connection policy of the profile.
*
* <p> The connection policy can be any of:
* {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
* {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
* {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
*
* @param device Bluetooth device
* @return connection policy of the device
* @hide
*/
public int getConnectionPolicy(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
"Need BLUETOOTH_PRIVILEGED permission");
return AdapterService.getAdapterService().getDatabase()
.getProfileConnectionPolicy(device, BluetoothProfile.SAP);
}
@Override
protected IProfileServiceBinder initBinder() {
return new SapBinder(this);
}
@Override
protected boolean start() {
Log.v(TAG, "start()");
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
filter.addAction(USER_CONFIRM_TIMEOUT_ACTION);
try {
registerReceiver(mSapReceiver, filter);
mIsRegistered = true;
} catch (Exception e) {
Log.w(TAG, "Unable to register sap receiver", e);
}
mInterrupted = false;
mAdapter = BluetoothAdapter.getDefaultAdapter();
// start RFCOMM listener
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
setSapService(this);
return true;
}
@Override
protected boolean stop() {
Log.v(TAG, "stop()");
if (!mIsRegistered) {
Log.i(TAG, "Avoid unregister when receiver it is not registered");
return true;
}
setSapService(null);
try {
mIsRegistered = false;
unregisterReceiver(mSapReceiver);
} catch (Exception e) {
Log.w(TAG, "Unable to unregister sap receiver", e);
}
setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
sendShutdownMessage();
return true;
}
@Override
public void cleanup() {
setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
closeService();
if (mSessionStatusHandler != null) {
mSessionStatusHandler.removeCallbacksAndMessages(null);
}
}
/**
* Get the current instance of {@link SapService}
*
* @return current instance of {@link SapService}
*/
@VisibleForTesting
public static synchronized SapService getSapService() {
if (sSapService == null) {
Log.w(TAG, "getSapService(): service is null");
return null;
}
if (!sSapService.isAvailable()) {
Log.w(TAG, "getSapService(): service is not available");
return null;
}
return sSapService;
}
private static synchronized void setSapService(SapService instance) {
if (DEBUG) {
Log.d(TAG, "setSapService(): set to: " + instance);
}
sSapService = instance;
}
private void setUserTimeoutAlarm() {
if (DEBUG) {
Log.d(TAG, "setUserTimeOutAlarm()");
}
cancelUserTimeoutAlarm();
mRemoveTimeoutMsg = true;
Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
mAlarmManager.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + USER_CONFIRM_TIMEOUT_VALUE, pIntent);
}
private void cancelUserTimeoutAlarm() {
if (DEBUG) {
Log.d(TAG, "cancelUserTimeOutAlarm()");
}
if (mAlarmManager == null) {
mAlarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
}
if (mRemoveTimeoutMsg) {
Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
PendingIntent sender = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
mAlarmManager.cancel(sender);
mRemoveTimeoutMsg = false;
}
}
private void sendCancelUserConfirmationIntent(BluetoothDevice device) {
Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
intent.setPackage(getString(R.string.pairing_ui_package));
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
sendBroadcast(intent, BLUETOOTH_PERM);
}
private void sendShutdownMessage() {
/* Any pending messages are no longer valid.
To speed up things, simply delete them. */
if (mRemoveTimeoutMsg) {
Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
mIsWaitingAuthorization = false;
cancelUserTimeoutAlarm();
}
removeSdpRecord();
mSessionStatusHandler.removeCallbacksAndMessages(null);
// Request release of all resources
mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
}
private void sendConnectTimeoutMessage() {
if (DEBUG) {
Log.d(TAG, "sendConnectTimeoutMessage()");
}
if (mSessionStatusHandler != null) {
Message msg = mSessionStatusHandler.obtainMessage(USER_TIMEOUT);
msg.sendToTarget();
} // Can only be null during shutdown
}
private SapBroadcastReceiver mSapReceiver = new SapBroadcastReceiver();
private class SapBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (VERBOSE) {
Log.v(TAG, "onReceive");
}
String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state =
intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if (state == BluetoothAdapter.STATE_TURNING_OFF) {
if (DEBUG) {
Log.d(TAG, "STATE_TURNING_OFF");
}
sendShutdownMessage();
} else if (state == BluetoothAdapter.STATE_ON) {
if (DEBUG) {
Log.d(TAG, "STATE_ON");
}
// start RFCOMM listener
mSessionStatusHandler.sendMessage(
mSessionStatusHandler.obtainMessage(START_LISTENER));
}
return;
}
if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
Log.v(TAG, " - Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, -1);
if (requestType != BluetoothDevice.REQUEST_TYPE_SIM_ACCESS) {
return;
}
mIsWaitingAuthorization = false;
if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
BluetoothDevice.CONNECTION_ACCESS_NO)
== BluetoothDevice.CONNECTION_ACCESS_YES) {
// bluetooth connection accepted by user
if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
boolean result = mRemoteDevice.setSimAccessPermission(
BluetoothDevice.ACCESS_ALLOWED);
if (VERBOSE) {
Log.v(TAG, "setSimAccessPermission(ACCESS_ALLOWED) result=" + result);
}
}
boolean result = setConnectionPolicy(mRemoteDevice,
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
Log.d(TAG, "setConnectionPolicy ALLOWED, result = " + result);
try {
if (mConnSocket != null) {
// start obex server and rfcomm connection
startSapServerSession();
} else {
stopSapServerSession();
}
} catch (IOException ex) {
Log.e(TAG, "Caught the error: ", ex);
}
} else {
if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
boolean result = mRemoteDevice.setSimAccessPermission(
BluetoothDevice.ACCESS_REJECTED);
if (VERBOSE) {
Log.v(TAG, "setSimAccessPermission(ACCESS_REJECTED) result=" + result);
}
}
boolean result = setConnectionPolicy(mRemoteDevice,
BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
Log.d(TAG, "setConnectionPolicy FORBIDDEN, result = " + result);
// Ensure proper cleanup, and prepare for new connect.
mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
}
return;
}
if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)) {
if (DEBUG) {
Log.d(TAG, "USER_CONFIRM_TIMEOUT_ACTION Received.");
}
// send us self a message about the timeout.
sendConnectTimeoutMessage();
return;
}
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (mRemoteDevice == null || device == null) {
Log.i(TAG, "Unexpected error!");
return;
}
if (DEBUG) {
Log.d(TAG, "ACL disconnected for " + device);
}
if (mRemoteDevice.equals(device)) {
if (mRemoveTimeoutMsg) {
// Send any pending timeout now, as ACL got disconnected.
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
}
setState(BluetoothSap.STATE_DISCONNECTED);
// Ensure proper cleanup, and prepare for new connect.
mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
}
}
}
}
;
//Binder object: Must be static class or memory leak may occur
/**
* This class implements the IBluetoothSap interface - or actually it validates the
* preconditions for calling the actual functionality in the SapService, and calls it.
*/
private static class SapBinder extends IBluetoothSap.Stub implements IProfileServiceBinder {
private SapService mService;
private SapService getService() {
if (!Utils.checkCaller()) {
Log.w(TAG, "call not allowed for non-active user");
return null;
}
if (mService != null && mService.isAvailable()) {
mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
"Need BLUETOOTH permission");
return mService;
}
return null;
}
SapBinder(SapService service) {
Log.v(TAG, "SapBinder()");
mService = service;
}
@Override
public void cleanup() {
mService = null;
}
@Override
public int getState() {
Log.v(TAG, "getState()");
SapService service = getService();
if (service == null) {
return BluetoothSap.STATE_DISCONNECTED;
}
return getService().getState();
}
@Override
public BluetoothDevice getClient() {
Log.v(TAG, "getClient()");
SapService service = getService();
if (service == null) {
return null;
}
Log.v(TAG, "getClient() - returning " + service.getRemoteDevice());
return service.getRemoteDevice();
}
@Override
public boolean isConnected(BluetoothDevice device) {
Log.v(TAG, "isConnected()");
SapService service = getService();
if (service == null) {
return false;
}
return (service.getState() == BluetoothSap.STATE_CONNECTED && service.getRemoteDevice()
.equals(device));
}
@Override
public boolean connect(BluetoothDevice device) {
Log.v(TAG, "connect()");
SapService service = getService();
if (service == null) {
return false;
}
return false;
}
@Override
public boolean disconnect(BluetoothDevice device) {
Log.v(TAG, "disconnect()");
SapService service = getService();
if (service == null) {
return false;
}
return service.disconnect(device);
}
@Override
public List<BluetoothDevice> getConnectedDevices() {
Log.v(TAG, "getConnectedDevices()");
SapService service = getService();
if (service == null) {
return new ArrayList<BluetoothDevice>(0);
}
return service.getConnectedDevices();
}
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Log.v(TAG, "getDevicesMatchingConnectionStates()");
SapService service = getService();
if (service == null) {
return new ArrayList<BluetoothDevice>(0);
}
return service.getDevicesMatchingConnectionStates(states);
}
@Override
public int getConnectionState(BluetoothDevice device) {
Log.v(TAG, "getConnectionState()");
SapService service = getService();
if (service == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
return service.getConnectionState(device);
}
@Override
public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
SapService service = getService();
if (service == null) {
return false;
}
return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
public int getConnectionPolicy(BluetoothDevice device) {
SapService service = getService();
if (service == null) {
return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
return service.getConnectionPolicy(device);
}
}
}