/* * 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.Context; import android.content.Intent; 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.Utils; import com.android.bluetooth.R; import com.android.bluetooth.btservice.AdapterService; import java.io.IOException; import javax.obex.ServerSession; public class BluetoothPbapService extends Service { 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; // package and class name to which we send intent to check phone book access permission private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; private static final String ACCESS_AUTHORITY_CLASS = "com.android.settings.bluetooth.BluetoothPermissionRequest"; public BluetoothPbapService() { mState = BluetoothPbap.STATE_DISCONNECTED; } @Override public void onCreate() { super.onCreate(); if (VERBOSE) Log.v(TAG, "Pbap Service onCreate"); mInterrupted = false; mAdapter = BluetoothAdapter.getDefaultAdapter(); if (!mHasStarted) { mHasStarted = true; if (VERBOSE) Log.v(TAG, "Starting PBAP service"); BluetoothPbapConfig.init(this); int state = mAdapter.getState(); if (state == BluetoothAdapter.STATE_ON) { mSessionStatusHandler.sendMessage(mSessionStatusHandler .obtainMessage(START_LISTENER)); } } } @Override public int onStartCommand(Intent intent, int flags, int startId) { mStartId = startId; if (mAdapter == null) { Log.w(TAG, "Stopping BluetoothPbapService: " + "device does not have BT or device is not ready"); // Release all resources closeService(); } else { // No need to handle the null intent case, because we have // all restart work done in onCreate() if (intent != null) { parseIntent(intent); } } return START_NOT_STICKY; } // process the intent from receiver private void parseIntent(final Intent intent) { String action = intent.getStringExtra("action"); 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); boolean removeTimeoutMsg = true; 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)) { Intent timeoutIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); timeoutIntent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); timeoutIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); sendBroadcast(timeoutIntent, BLUETOOTH_ADMIN_PERM); } // Release all resources closeService(); } else { removeTimeoutMsg = false; } } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (mRemoteDevice == null || device == null) { Log.e(TAG, "Unexpected error!"); return; } if (DEBUG) Log.d(TAG,"ACL disconnected for "+ device); if (mRemoteDevice.equals(device)) { Intent cancelIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); cancelIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); cancelIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); sendBroadcast(cancelIntent); mIsWaitingAuthorization = false; stopObexServerSession(); } } else 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; } 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=" + 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=" + result); } } stopObexServerSession(); } } else if (action.equals(AUTH_RESPONSE_ACTION)) { String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY); notifyAuthKeyInput(sessionkey); } else if (action.equals(AUTH_CANCELLED_ACTION)) { notifyAuthCancelled(); } else { removeTimeoutMsg = false; } if (removeTimeoutMsg) { mSessionStatusHandler.removeMessages(USER_TIMEOUT); } } @Override public void onDestroy() { if (VERBOSE) Log.v(TAG, "Pbap Service onDestroy"); super.onDestroy(); setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); closeService(); if(mSessionStatusHandler != null) { mSessionStatusHandler.removeCallbacksAndMessages(null); } } @Override public IBinder onBind(Intent intent) { if (VERBOSE) Log.v(TAG, "Pbap Service onBind"); return mBinder; } 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.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 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, BluetoothPbapReceiver.class.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(); } else { closeService();// release all resources } break; case USER_TIMEOUT: Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); sendBroadcast(intent); 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); AdapterService s = AdapterService.getAdapterService(); if (s != null) { s.onProfileConnectionStateChanged(mRemoteDevice, BluetoothProfile.PBAP, mState, prevState); } } } 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, BluetoothPbapReceiver.class); 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.setAction(AUTH_CANCELLED_ACTION), 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; } /** * Handlers for incoming service calls */ private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() { public int getState() { if (DEBUG) Log.d(TAG, "getState " + mState); if (!Utils.checkCaller()) { Log.w(TAG,"getState(): not allowed for non-active user"); return BluetoothPbap.STATE_DISCONNECTED; } enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return mState; } public BluetoothDevice getClient() { if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice); if (!Utils.checkCaller()) { Log.w(TAG,"getClient(): not allowed for non-active user"); return null; } enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if (mState == BluetoothPbap.STATE_DISCONNECTED) { return null; } return mRemoteDevice; } public boolean isConnected(BluetoothDevice device) { if (!Utils.checkCaller()) { Log.w(TAG,"isConnected(): not allowed for non-active user"); return false; } enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device); } public boolean connect(BluetoothDevice device) { if (!Utils.checkCaller()) { Log.w(TAG,"connect(): not allowed for non-active user"); return false; } enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); return false; } public void disconnect() { if (DEBUG) Log.d(TAG, "disconnect"); if (!Utils.checkCaller()) { Log.w(TAG,"disconnect(): not allowed for non-active user"); return; } enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); synchronized (BluetoothPbapService.this) { switch (mState) { case BluetoothPbap.STATE_CONNECTED: if (mServerSession != null) { mServerSession.close(); mServerSession = null; } closeConnectionSocket(); setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); break; default: break; } } } }; }