blob: 54bf4814df662b74902303199747e4c372aa0f3a [file] [log] [blame]
/*
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of the Motorola, Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.android.bluetooth.pbap;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothPbap;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothPbap;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
import java.io.IOException;
import javax.obex.ServerSession;
public class BluetoothPbapService extends ProfileService {
private static final String TAG = "BluetoothPbapService";
/**
* To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
* restart com.android.bluetooth process. only enable DEBUG log:
* "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and
* DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE"
*/
public static final boolean DEBUG = true;
public static final boolean VERBOSE = false;
/**
* Intent indicating incoming obex authentication request which is from
* PCE(Carkit)
*/
public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall";
/**
* Intent indicating obex session key input complete by user which is sent
* from BluetoothPbapActivity
*/
public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse";
/**
* Intent indicating user canceled obex authentication session key input
* which is sent from BluetoothPbapActivity
*/
public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled";
/**
* Intent indicating timeout for user confirmation, which is sent to
* BluetoothPbapActivity
*/
public static final String USER_CONFIRM_TIMEOUT_ACTION =
"com.android.bluetooth.pbap.userconfirmtimeout";
/**
* Intent Extra name indicating session key which is sent from
* BluetoothPbapActivity
*/
public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey";
public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
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_OBEX_AUTH_CHALL = 5003;
public static final int MSG_ACQUIRE_WAKE_LOCK = 5004;
public static final int MSG_RELEASE_WAKE_LOCK = 5005;
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
private static final int START_LISTENER = 1;
private static final int USER_TIMEOUT = 2;
private static final int AUTH_TIMEOUT = 3;
private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
// Ensure not conflict with Opp notification ID
private static final int NOTIFICATION_ID_ACCESS = -1000001;
private static final int NOTIFICATION_ID_AUTH = -1000002;
private PowerManager.WakeLock mWakeLock = null;
private BluetoothAdapter mAdapter;
private SocketAcceptThread mAcceptThread = null;
private BluetoothPbapAuthenticator mAuth = null;
private BluetoothPbapObexServer mPbapServer;
private ServerSession mServerSession = null;
private BluetoothServerSocket mServerSocket = null;
private BluetoothSocket mConnSocket = null;
private BluetoothDevice mRemoteDevice = null;
private static String sLocalPhoneNum = null;
private static String sLocalPhoneName = null;
private static String sRemoteDeviceName = null;
private boolean mHasStarted = false;
private volatile boolean mInterrupted;
private int mState;
private int mStartId = -1;
private boolean mIsWaitingAuthorization = false;
private boolean mIsRegistered = false;
public BluetoothPbapService() {
mState = BluetoothPbap.STATE_DISCONNECTED;
}
// process the intent from receiver
private void parseIntent(final Intent intent) {
String action = intent.getAction();
if (action == null) return; // Nothing to do
if (VERBOSE) Log.v(TAG, "action: " + action);
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if (VERBOSE) Log.v(TAG, "state: " + state);
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
if (state == BluetoothAdapter.STATE_TURNING_OFF) {
// Send any pending timeout now, as this service will be destroyed.
if (mSessionStatusHandler.hasMessages(USER_TIMEOUT)) {
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
}
// Release all resources
closeService();
return;
} else if (state == BluetoothAdapter.STATE_ON) {
// start RFCOMM listener
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
}
}
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (mRemoteDevice == null) return;
if (DEBUG) Log.d(TAG,"ACL disconnected for "+ device);
if (mRemoteDevice.equals(device)) {
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
}
return;
}
if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
if ((!mIsWaitingAuthorization)
|| (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS)) {
// this reply is not for us
return;
}
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
mIsWaitingAuthorization = false;
if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
BluetoothDevice.CONNECTION_ACCESS_NO)
== BluetoothDevice.CONNECTION_ACCESS_YES) {
if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
boolean result = mRemoteDevice.setPhonebookAccessPermission(
BluetoothDevice.ACCESS_ALLOWED);
if (VERBOSE) {
Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)=" + result);
}
}
try {
if (mConnSocket != null) {
startObexServerSession();
} else {
stopObexServerSession();
}
} catch (IOException ex) {
Log.e(TAG, "Caught the error: " + ex.toString());
}
} else {
if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
boolean result = mRemoteDevice.setPhonebookAccessPermission(
BluetoothDevice.ACCESS_REJECTED);
if (VERBOSE) {
Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)=" + result);
}
}
stopObexServerSession();
}
return;
}
if (action.equals(AUTH_RESPONSE_ACTION)) {
String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
notifyAuthKeyInput(sessionkey);
} else if (action.equals(AUTH_CANCELLED_ACTION)) {
notifyAuthCancelled();
} else {
Log.w(TAG, "Unrecognized intent!");
return;
}
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
}
private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
parseIntent(intent);
}
};
private void startRfcommSocketListener() {
if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener");
if (mAcceptThread == null) {
mAcceptThread = new SocketAcceptThread();
mAcceptThread.setName("BluetoothPbapAcceptThread");
mAcceptThread.start();
}
}
private final boolean initSocket() {
if (VERBOSE) Log.v(TAG, "Pbap Service initSocket");
boolean initSocketOK = false;
final int CREATE_RETRY_TIME = 10;
// 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 PSE to support initiation of bonding and
// encryption.
mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord
("OBEX Phonebook Access Server", BluetoothUuid.PBAP_PSE.getUuid());
} catch (IOException e) {
Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
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)");
break;
}
} else {
break;
}
}
if (mInterrupted) {
initSocketOK = false;
// close server socket to avoid resource leakage
closeServerSocket();
}
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 final 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 final synchronized void closeConnectionSocket() {
if (mConnSocket != null) {
try {
mConnSocket.close();
mConnSocket = null;
} catch (IOException e) {
Log.e(TAG, "Close Connection Socket error: " + e.toString());
}
}
}
private final void closeService() {
if (VERBOSE) Log.v(TAG, "Pbap 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) {
mWakeLock.release();
mWakeLock = null;
}
if (mServerSession != null) {
mServerSession.close();
mServerSession = null;
}
closeConnectionSocket();
mHasStarted = false;
if (mStartId != -1 && stopSelfResult(mStartId)) {
if (VERBOSE) Log.v(TAG, "successfully stopped pbap service");
mStartId = -1;
}
if (VERBOSE) Log.v(TAG, "Pbap Service closeService out");
}
private final void startObexServerSession() throws IOException {
if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
// acquire the wakeLock before start Obex transaction thread
if (mWakeLock == null) {
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"StartingObexPbapTransaction");
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
}
TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
sLocalPhoneNum = tm.getLine1Number();
sLocalPhoneName = tm.getLine1AlphaTag();
if (TextUtils.isEmpty(sLocalPhoneName)) {
sLocalPhoneName = this.getString(R.string.localPhoneName);
}
}
mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this);
synchronized (this) {
mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
mAuth.setChallenged(false);
mAuth.setCancelled(false);
}
BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
mServerSession = new ServerSession(transport, mPbapServer, mAuth);
setState(BluetoothPbap.STATE_CONNECTED);
mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
.obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
if (VERBOSE) {
Log.v(TAG, "startObexServerSession() success!");
}
}
private void stopObexServerSession() {
if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
// Release the wake lock if obex transaction is over
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
}
if (mServerSession != null) {
mServerSession.close();
mServerSession = null;
}
mAcceptThread = null;
closeConnectionSocket();
// Last obex transaction is finished, we start to listen for incoming
// connection again
if (mAdapter.isEnabled()) {
startRfcommSocketListener();
}
setState(BluetoothPbap.STATE_DISCONNECTED);
}
private void notifyAuthKeyInput(final String key) {
synchronized (mAuth) {
if (key != null) {
mAuth.setSessionKey(key);
}
mAuth.setChallenged(true);
mAuth.notify();
}
}
private void notifyAuthCancelled() {
synchronized (mAuth) {
mAuth.setCancelled(true);
mAuth.notify();
}
}
/**
* 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 stopped = false;
@Override
public void run() {
BluetoothServerSocket serverSocket;
if (mServerSocket == null) {
if (!initSocket()) {
return;
}
}
while (!stopped) {
try {
if (VERBOSE) Log.v(TAG, "Accepting socket connection...");
serverSocket = mServerSocket;
if (serverSocket == null) {
Log.w(TAG, "mServerSocket is null");
break;
}
mConnSocket = serverSocket.accept();
if (VERBOSE) Log.v(TAG, "Accepted socket connection...");
synchronized (BluetoothPbapService.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.getPhonebookAccessPermission();
if (VERBOSE) Log.v(TAG, "getPhonebookAccessPermission() = " + permission);
if (permission == BluetoothDevice.ACCESS_ALLOWED) {
try {
if (VERBOSE) {
Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName
+ " automatically as already allowed device");
}
startObexServerSession();
} catch (IOException ex) {
Log.e(TAG, "Caught exception starting obex server session"
+ ex.toString());
}
} else if (permission == BluetoothDevice.ACCESS_REJECTED) {
if (VERBOSE) {
Log.v(TAG, "incoming connection rejected from: " + sRemoteDeviceName
+ " automatically as already rejected device");
}
stopObexServerSession();
} else { // permission == BluetoothDevice.ACCESS_UNKNOWN
// Send an Intent to Settings app to ask user preference.
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_PHONEBOOK_ACCESS);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, getName());
mIsWaitingAuthorization = true;
sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
+ sRemoteDeviceName);
// In case car kit time out and try to use HFP for
// phonebook
// access, while UI still there waiting for user to
// confirm
mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
.obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
// We will continue the process when we receive
// BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app.
}
stopped = true; // job done ,close this thread;
} catch (IOException ex) {
stopped=true;
/*
if (stopped) {
break;
}
*/
if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
}
}
}
void shutdown() {
stopped = 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:
Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
intent.setPackage(getString(R.string.pairing_ui_package));
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
mIsWaitingAuthorization = false;
stopObexServerSession();
break;
case AUTH_TIMEOUT:
Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
sendBroadcast(i);
removePbapNotification(NOTIFICATION_ID_AUTH);
notifyAuthCancelled();
break;
case MSG_SERVERSESSION_CLOSE:
stopObexServerSession();
break;
case MSG_SESSION_ESTABLISHED:
break;
case MSG_SESSION_DISCONNECTED:
// case MSG_SERVERSESSION_CLOSE will handle ,so just skip
break;
case MSG_OBEX_AUTH_CHALL:
createPbapNotification(AUTH_CHALL_ACTION);
mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
.obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
break;
case MSG_ACQUIRE_WAKE_LOCK:
if (mWakeLock == null) {
PowerManager pm = (PowerManager)getSystemService(
Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"StartingObexPbapTransaction");
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
Log.w(TAG, "Acquire Wake Lock");
}
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 (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
Log.w(TAG, "Release Wake Lock");
}
break;
default:
break;
}
}
};
private void setState(int state) {
setState(state, BluetoothPbap.RESULT_SUCCESS);
}
private synchronized void setState(int state, int result) {
if (state != mState) {
if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
+ result);
int prevState = mState;
mState = state;
Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, prevState);
intent.putExtra(BluetoothPbap.PBAP_STATE, mState);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
sendBroadcast(intent, BLUETOOTH_PERM);
}
}
protected int getState() {
return mState;
}
protected BluetoothDevice getRemoteDevice() {
return mRemoteDevice;
}
private void createPbapNotification(String action) {
NotificationManager nm = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
// Create an intent triggered by clicking on the status icon.
Intent clickIntent = new Intent();
clickIntent.setClass(this, BluetoothPbapActivity.class);
clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
clickIntent.setAction(action);
// Create an intent triggered by clicking on the
// "Clear All Notifications" button
Intent deleteIntent = new Intent();
deleteIntent.setClass(this, BluetoothPbapService.class);
deleteIntent.setAction(AUTH_CANCELLED_ACTION);
String name = getRemoteDeviceName();
if (action.equals(AUTH_CHALL_ACTION)) {
Notification notification =
new Notification.Builder(this)
.setWhen(System.currentTimeMillis())
.setContentTitle(getString(R.string.auth_notif_title))
.setContentText(getString(R.string.auth_notif_message, name))
.setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
.setTicker(getString(R.string.auth_notif_ticker))
.setColor(getResources().getColor(
com.android.internal.R.color.system_notification_accent_color,
this.getTheme()))
.setFlag(Notification.FLAG_AUTO_CANCEL, true)
.setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
.setDefaults(Notification.DEFAULT_SOUND)
.setContentIntent(PendingIntent.getActivity(this, 0, clickIntent, 0))
.setDeleteIntent(PendingIntent.getBroadcast(this, 0, deleteIntent, 0))
.build();
nm.notify(NOTIFICATION_ID_AUTH, notification);
}
}
private void removePbapNotification(int id) {
NotificationManager nm = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(id);
}
public static String getLocalPhoneNum() {
return sLocalPhoneNum;
}
public static String getLocalPhoneName() {
return sLocalPhoneName;
}
public static String getRemoteDeviceName() {
return sRemoteDeviceName;
}
@Override
protected IProfileServiceBinder initBinder() {
return new PbapBinder(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(AUTH_RESPONSE_ACTION);
filter.addAction(AUTH_CANCELLED_ACTION);
try {
registerReceiver(mPbapReceiver, filter);
mIsRegistered = true;
} catch (Exception e) {
Log.w(TAG, "Unable to register pbap receiver", e);
}
mInterrupted = false;
BluetoothPbapConfig.init(this);
mAdapter = BluetoothAdapter.getDefaultAdapter();
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;
}
try {
mIsRegistered = false;
unregisterReceiver(mPbapReceiver);
} catch (Exception e) {
Log.w(TAG, "Unable to unregister pbap receiver", e);
}
setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
closeService();
if (mSessionStatusHandler != null) {
mSessionStatusHandler.removeCallbacksAndMessages(null);
}
return true;
}
protected void disconnect() {
synchronized (this) {
if (mState == BluetoothPbap.STATE_CONNECTED) {
if (mServerSession != null) {
mServerSession.close();
mServerSession = null;
}
closeConnectionSocket();
setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
}
}
}
// Has to be a static class or a memory leak can occur.
private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder {
private BluetoothPbapService mService;
private BluetoothPbapService getService(String perm) {
if (!Utils.checkCaller()) {
Log.w(TAG, "not allowed for non-active user");
return null;
}
if (mService != null && mService.isAvailable()) {
mService.enforceCallingOrSelfPermission(perm, "Need " + perm + " permission");
return mService;
}
return null;
}
PbapBinder(BluetoothPbapService service) {
Log.v(TAG, "PbapBinder()");
mService = service;
}
public boolean cleanup() {
mService = null;
return true;
}
public int getState() {
if (DEBUG) Log.d(TAG, "getState = " + mService.getState());
BluetoothPbapService service = getService(BLUETOOTH_PERM);
if (service == null) return BluetoothPbap.STATE_DISCONNECTED;
return service.getState();
}
public BluetoothDevice getClient() {
if (DEBUG) Log.d(TAG, "getClient = " + mService.getRemoteDevice());
BluetoothPbapService service = getService(BLUETOOTH_PERM);
if (service == null) return null;
return service.getRemoteDevice();
}
public boolean isConnected(BluetoothDevice device) {
if (DEBUG) Log.d(TAG, "isConnected " + device);
BluetoothPbapService service = getService(BLUETOOTH_PERM);
if (service == null) return false;
return service.getState() == BluetoothPbap.STATE_CONNECTED
&& service.getRemoteDevice().equals(device);
}
public boolean connect(BluetoothDevice device) {
BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
return false;
}
public void disconnect() {
if (DEBUG) Log.d(TAG, "disconnect");
BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
if (service == null) return;
service.disconnect();
}
}
}