| /* |
| * 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.IBluetooth; |
| import android.bluetooth.IBluetoothPbap; |
| import android.bluetooth.BluetoothUuid; |
| 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.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.util.Log; |
| 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; |
| |
| 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; |
| |
| |
| // 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 IBluetooth mBluetoothService; |
| |
| private boolean isWaitingAuthorization = 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) { |
| //int retCode = super.onStartCommand(intent, flags, startId); |
| //if (retCode == START_STICKY) { |
| 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 (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); |
| sendBroadcast(timeoutIntent, BLUETOOTH_ADMIN_PERM); |
| } |
| // Release all resources |
| closeService(); |
| } else { |
| removeTimeoutMsg = false; |
| } |
| } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { |
| if (!isWaitingAuthorization) { |
| // this reply is not for us |
| return; |
| } |
| |
| isWaitingAuthorization = 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.setTrust(true); |
| if (VERBOSE) Log.v(TAG, "setTrust() result=" + result); |
| } |
| try { |
| if (mConnSocket != null) { |
| startObexServerSession(); |
| } else { |
| stopObexServerSession(); |
| } |
| } catch (IOException ex) { |
| Log.e(TAG, "Caught the error: " + ex.toString()); |
| } |
| } else { |
| 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); |
| if (mWakeLock != null) { |
| mWakeLock.release(); |
| mWakeLock = null; |
| } |
| 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 = true; |
| 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++) { |
| 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; |
| } |
| synchronized (this) { |
| try { |
| if (VERBOSE) Log.v(TAG, "wait 300 ms"); |
| Thread.sleep(300); |
| } catch (InterruptedException e) { |
| Log.e(TAG, "socketAcceptThread thread was interrupted (3)"); |
| mInterrupted = true; |
| } |
| } |
| } 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 final void closeSocket(boolean server, boolean accept) throws IOException { |
| if (server == true) { |
| // Stop the possible trying to init serverSocket |
| mInterrupted = true; |
| |
| if (mServerSocket != null) { |
| mServerSocket.close(); |
| mServerSocket = null; |
| } |
| } |
| |
| if (accept == true) { |
| if (mConnSocket != null) { |
| mConnSocket.close(); |
| mConnSocket = null; |
| } |
| } |
| } |
| |
| private final void closeService() { |
| if (VERBOSE) Log.v(TAG, "Pbap Service closeService in"); |
| |
| try { |
| closeSocket(true, true); |
| } catch (IOException ex) { |
| Log.e(TAG, "CloseSocket error: " + ex); |
| } |
| |
| if (mAcceptThread != null) { |
| try { |
| mAcceptThread.shutdown(); |
| mAcceptThread.join(); |
| mAcceptThread = null; |
| } catch (InterruptedException ex) { |
| Log.w(TAG, "mAcceptThread close error" + ex); |
| } |
| } |
| if (mServerSession != null) { |
| mServerSession.close(); |
| mServerSession = null; |
| } |
| |
| 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); |
| } |
| BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket); |
| mServerSession = new ServerSession(transport, mPbapServer, mAuth); |
| setState(BluetoothPbap.STATE_CONNECTED); |
| if (VERBOSE) { |
| Log.v(TAG, "startObexServerSession() success!"); |
| } |
| } |
| |
| private void stopObexServerSession() { |
| if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession"); |
| |
| // 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; |
| |
| try { |
| closeSocket(false, true); |
| mConnSocket = null; |
| } catch (IOException e) { |
| Log.e(TAG, "closeSocket error: " + e.toString()); |
| } |
| // 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() { |
| if (mServerSocket == null) { |
| if (!initSocket()) { |
| closeService(); |
| return; |
| } |
| } |
| |
| while (!stopped) { |
| try { |
| if (VERBOSE) Log.v(TAG, "Accepting socket connection..."); |
| mConnSocket = mServerSocket.accept(); |
| if (VERBOSE) Log.v(TAG, "Accepted socket connection..."); |
| |
| 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); |
| } |
| boolean trust = mRemoteDevice.getTrustState(); |
| if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust); |
| |
| if (trust) { |
| try { |
| if (VERBOSE) Log.v(TAG, "incoming connection accepted from: " |
| + sRemoteDeviceName + " automatically as trusted device"); |
| startObexServerSession(); |
| } catch (IOException ex) { |
| Log.e(TAG, "catch exception starting obex server session" |
| + ex.toString()); |
| } |
| } else { |
| 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()); |
| sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); |
| isWaitingAuthorization = true; |
| |
| 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); |
| } |
| 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.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); |
| sendBroadcast(intent); |
| isWaitingAuthorization = 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; |
| 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); |
| |
| Notification notification = null; |
| String name = getRemoteDeviceName(); |
| |
| if (action.equals(AUTH_CHALL_ACTION)) { |
| deleteIntent.setAction(AUTH_CANCELLED_ACTION); |
| notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, |
| getString(R.string.auth_notif_ticker), System.currentTimeMillis()); |
| notification.setLatestEventInfo(this, getString(R.string.auth_notif_title), |
| getString(R.string.auth_notif_message, name), PendingIntent |
| .getActivity(this, 0, clickIntent, 0)); |
| |
| notification.flags |= Notification.FLAG_AUTO_CANCEL; |
| notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; |
| notification.defaults = Notification.DEFAULT_SOUND; |
| notification.deleteIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, 0); |
| 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; |
| } |
| try { |
| closeSocket(false, true); |
| mConnSocket = null; |
| } catch (IOException ex) { |
| Log.e(TAG, "Caught the error: " + ex); |
| } |
| setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| }; |
| } |