Loading android/app/src/com/android/bluetooth/pbap/BluetoothPbapService.java +124 −79 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ import com.android.bluetooth.sdp.SdpManager; import com.android.bluetooth.util.DevicePolicyUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class BluetoothPbapService extends ProfileService implements IObexConnectionHandler { Loading Loading @@ -153,14 +154,19 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F; /* PBAP will use Bluetooth notification ID from 1000000 (included) to 2000000 (excluded). The notification ID should be unique in Bluetooth package. */ private static final int PBAP_NOTIFICATION_ID_START = 1000000; private static final int PBAP_NOTIFICATION_ID_END = 2000000; private int mSdpHandle = -1; protected Context mContext; private PbapHandler mSessionStatusHandler; private HandlerThread mHandlerThread; private PbapStateMachine mPbapStateMachine; private final HashMap<BluetoothDevice, PbapStateMachine> mPbapStateMachineMap = new HashMap<>(); private volatile int mNextNotificationId = PBAP_NOTIFICATION_ID_START; // 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"; Loading Loading @@ -190,77 +196,80 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect private BluetoothPbapContentObserver mContactChangeObserver; // process the intent from receiver private void parseIntent(final Intent intent) { String action = intent.getAction(); if (DEBUG) { Log.d(TAG, "action: " + action); } if (action == null) { return; // Nothing to do return; } int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (DEBUG) { Log.d(TAG, "state: " + state); } if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (DEBUG) { Log.d(TAG, "ACL disconnected for " + device); } if (mPbapStateMachine != null && mPbapStateMachine.getRemoteDevice().equals(device)) { mPbapStateMachine.sendMessage(PbapStateMachine.DISCONNECT); } return; } if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); if (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) { // this reply is not for us return; } BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm == null) { Log.w(TAG, "device not connected! device=" + device); return; } mSessionStatusHandler.removeMessages(USER_TIMEOUT, sm); int access = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, BluetoothDevice.CONNECTION_ACCESS_NO); boolean savePreference = intent.getBooleanExtra( BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false); mSessionStatusHandler.removeMessages(USER_TIMEOUT); if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, BluetoothDevice.CONNECTION_ACCESS_NO) == BluetoothDevice.CONNECTION_ACCESS_YES) { if (device != null && intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { if (access == BluetoothDevice.CONNECTION_ACCESS_YES) { if (savePreference) { device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); if (VERBOSE) { Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)"); } } mPbapStateMachine.sendMessage(PbapStateMachine.AUTHORIZED); sm.sendMessage(PbapStateMachine.AUTHORIZED); } else { if (device != null && intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { if (savePreference) { device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); if (VERBOSE) { Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)"); } } mPbapStateMachine.sendMessage(PbapStateMachine.REJECTED); sm.sendMessage(PbapStateMachine.REJECTED); } return; } } if (action.equals(AUTH_RESPONSE_ACTION)) { String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY); String sessionKey = intent.getStringExtra(EXTRA_SESSION_KEY); BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE); Message msg = mPbapStateMachine.obtainMessage(PbapStateMachine.AUTH_KEY_INPUT); msg.obj = sessionkey; mPbapStateMachine.sendMessage(msg); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm == null) { return; } Message msg = sm.obtainMessage(PbapStateMachine.AUTH_KEY_INPUT, sessionKey); sm.sendMessage(msg); } } else if (action.equals(AUTH_CANCELLED_ACTION)) { BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE); mPbapStateMachine.sendMessage(PbapStateMachine.AUTH_CANCELLED); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm == null) { return; } sm.sendMessage(PbapStateMachine.AUTH_CANCELLED); } } else { Log.w(TAG, "Unrecognized intent!"); } Loading @@ -283,7 +292,6 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect BluetoothPbapUtils.contactsLastUpdated, BluetoothPbapUtils.totalFields, BluetoothPbapUtils.totalSvcFields, BluetoothPbapUtils.totalContacts); // exit initSocket early if (mWakeLock != null) { mWakeLock.release(); mWakeLock = null; Loading @@ -298,8 +306,10 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect private void cleanUpServerSocket() { // Step 1, 2: clean up active server session and connection socket if (mPbapStateMachine != null) { mPbapStateMachine.sendMessage(PbapStateMachine.DISCONNECT); synchronized (mPbapStateMachineMap) { for (PbapStateMachine stateMachine : mPbapStateMachineMap.values()) { stateMachine.sendMessage(PbapStateMachine.DISCONNECT); } } // Step 3: clean up SDP record cleanUpSdpRecord(); Loading Loading @@ -407,6 +417,13 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect BluetoothPbapUtils.rolloverCounters(); break; case MSG_STATE_MACHINE_DONE: PbapStateMachine sm = (PbapStateMachine) msg.obj; BluetoothDevice remoteDevice = sm.getRemoteDevice(); sm.quitNow(); synchronized (mPbapStateMachineMap) { mPbapStateMachineMap.remove(remoteDevice); } break; default: break; } Loading @@ -415,36 +432,57 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect int getConnectionState(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if (mPbapStateMachine == null) { if (mPbapStateMachineMap == null) { return BluetoothProfile.STATE_DISCONNECTED; } return mPbapStateMachine.getConnectionState(); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm == null) { return BluetoothProfile.STATE_DISCONNECTED; } return sm.getConnectionState(); } } List<BluetoothDevice> getConnectedDevices() { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List<BluetoothDevice> devices = new ArrayList<>(); if (mPbapStateMachine != null) { devices.add(mPbapStateMachine.getRemoteDevice()); if (mPbapStateMachineMap == null) { return new ArrayList<>(); } synchronized (mPbapStateMachineMap) { return new ArrayList<>(mPbapStateMachineMap.keySet()); } return devices; } List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); if (mPbapStateMachine != null) { List<BluetoothDevice> devices = new ArrayList<>(); if (mPbapStateMachineMap == null || states == null) { return devices; } synchronized (mPbapStateMachineMap) { for (int state : states) { if (state == mPbapStateMachine.getConnectionState()) { devices.add(mPbapStateMachine.getRemoteDevice()); break; for (BluetoothDevice device : mPbapStateMachineMap.keySet()) { if (state == mPbapStateMachineMap.get(device).getConnectionState()) { devices.add(device); } } } } return devices; } void disconnect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm != null) { sm.sendMessage(PbapStateMachine.DISCONNECT); } } } static String getLocalPhoneNum() { return sLocalPhoneNum; } Loading @@ -460,14 +498,15 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect @Override protected boolean start() { if (VERBOSE) { Log.v(TAG, "start()"); } mContext = this; mHandlerThread = new HandlerThread("PbapHandlerThread"); mHandlerThread.start(); mSessionStatusHandler = new PbapHandler(mHandlerThread.getLooper()); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); filter.addAction(AUTH_RESPONSE_ACTION); filter.addAction(AUTH_CANCELLED_ACTION); BluetoothPbapConfig.init(this); Loading Loading @@ -499,7 +538,11 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect @Override protected boolean stop() { if (VERBOSE) { Log.v(TAG, "stop()"); } mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget(); mHandlerThread.quitSafely(); if (mContactChangeObserver == null) { Log.i(TAG, "Avoid unregister when receiver it is not registered"); return true; Loading @@ -511,21 +554,9 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect } catch (Exception e) { Log.w(TAG, "Unable to unregister pbap receiver", e); } mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget(); mHandlerThread.quitSafely(); return true; } void disconnect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); synchronized (this) { if (mPbapStateMachine != null) { mPbapStateMachine.sendMessage(PbapStateMachine.DISCONNECT); } } } // Has to be a static class or a memory leak can occur. private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder { private BluetoothPbapService mService; Loading @@ -541,7 +572,9 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect } PbapBinder(BluetoothPbapService service) { if (VERBOSE) { Log.v(TAG, "PbapBinder()"); } mService = service; } Loading Loading @@ -606,8 +639,17 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect + " socket=" + socket); return false; } mPbapStateMachine = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice, socket, this, mSessionStatusHandler); PbapStateMachine sm = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice, socket, this, mSessionStatusHandler, mNextNotificationId); mNextNotificationId++; if (mNextNotificationId == PBAP_NOTIFICATION_ID_END) { mNextNotificationId = PBAP_NOTIFICATION_ID_START; } synchronized (mPbapStateMachineMap) { mPbapStateMachineMap.put(remoteDevice, sm); } sm.sendMessage(PbapStateMachine.REQUEST_PERMISSION); return true; } Loading @@ -629,7 +671,6 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect } else if (permission == BluetoothDevice.ACCESS_REJECTED) { stateMachine.sendMessage(PbapStateMachine.REJECTED); } 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(BluetoothPbapService.ACCESS_AUTHORITY_PACKAGE, BluetoothPbapService.ACCESS_AUTHORITY_CLASS); Loading @@ -643,8 +684,8 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect } /* In case car kit time out and try to use HFP for phonebook * access, while UI still there waiting for user to confirm */ Message msg = mSessionStatusHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT); msg.obj = stateMachine; Message msg = mSessionStatusHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT, stateMachine); mSessionStatusHandler.sendMessageDelayed(msg, USER_CONFIRM_TIMEOUT_VALUE); /* We will continue the process when we receive * BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app. */ Loading @@ -670,6 +711,10 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect mSessionStatusHandler.removeCallbacksAndMessages(null); } synchronized (mPbapStateMachineMap) { mPbapStateMachineMap.clear(); } mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER)); } Loading android/app/src/com/android/bluetooth/pbap/PbapStateMachine.java +15 −21 Original line number Diff line number Diff line Loading @@ -60,10 +60,7 @@ class PbapStateMachine extends StateMachine { private static final String TAG = "PbapStateMachine"; private static final boolean DEBUG = true; private static final boolean VERBOSE = true; private static final String PBAP_OBEX_NOTIFICATION_CHANNEL = "pbap_obex_notification_channel"; private static final int NOTIFICATION_ID_AUTH = -1000002; // TODO: set a notification channel for each sm static final int AUTHORIZED = 1; static final int REJECTED = 2; Loading @@ -86,16 +83,18 @@ class PbapStateMachine extends StateMachine { private BluetoothPbapObexServer mPbapServer; private BluetoothPbapAuthenticator mObexAuth; private ServerSession mServerSession; private int mNotificationId; private PbapStateMachine(@NonNull BluetoothPbapService service, Looper looper, @NonNull BluetoothDevice device, @NonNull BluetoothSocket connSocket, IObexConnectionHandler obexConnectionHandler, Handler pbapHandler) { IObexConnectionHandler obexConnectionHandler, Handler pbapHandler, int notificationId) { super(TAG, looper); mService = service; mIObexConnectionHandler = obexConnectionHandler; mRemoteDevice = device; mServiceHandler = pbapHandler; mConnSocket = connSocket; mNotificationId = notificationId; addState(mFinished); addState(mWaitingForAuth); Loading @@ -105,9 +104,9 @@ class PbapStateMachine extends StateMachine { static PbapStateMachine make(BluetoothPbapService service, Looper looper, BluetoothDevice device, BluetoothSocket connSocket, IObexConnectionHandler obexConnectionHandler, Handler pbapHandler) { IObexConnectionHandler obexConnectionHandler, Handler pbapHandler, int notificationId) { PbapStateMachine stateMachine = new PbapStateMachine(service, looper, device, connSocket, obexConnectionHandler, pbapHandler); obexConnectionHandler, pbapHandler, notificationId); stateMachine.start(); return stateMachine; } Loading @@ -134,14 +133,11 @@ class PbapStateMachine extends StateMachine { return BluetoothProfile.STATE_CONNECTING; } @Override public void enter() { mService.checkOrGetPhonebookPermission(PbapStateMachine.this); } @Override public boolean processMessage(Message message) { switch (message.what) { case REQUEST_PERMISSION: mService.checkOrGetPhonebookPermission(PbapStateMachine.this); case AUTHORIZED: transitionTo(mConnected); break; Loading @@ -150,11 +146,10 @@ class PbapStateMachine extends StateMachine { transitionTo(mFinished); break; case DISCONNECT: mServiceHandler.removeMessages(BluetoothPbapService.USER_TIMEOUT); Message msg = mServiceHandler.obtainMessage( BluetoothPbapService.USER_TIMEOUT); msg.obj = PbapStateMachine.this; msg.sendToTarget(); mServiceHandler.removeMessages(BluetoothPbapService.USER_TIMEOUT, PbapStateMachine.this); mServiceHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT, PbapStateMachine.this).sendToTarget(); transitionTo(mFinished); break; } Loading Loading @@ -198,10 +193,9 @@ class PbapStateMachine extends StateMachine { Log.e(TAG, "Close Connection Socket error: " + e.toString()); } mServiceHandler.obtainMessage(BluetoothPbapService.MSG_STATE_MACHINE_DONE) .sendToTarget(); mServiceHandler.obtainMessage(BluetoothPbapService.MSG_STATE_MACHINE_DONE, PbapStateMachine.this).sendToTarget(); } } class Connected extends PbapStateBase { Loading Loading @@ -236,7 +230,7 @@ class PbapStateMachine extends StateMachine { Intent i = new Intent(BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION); mService.sendBroadcast(i); notifyAuthCancelled(); removePbapNotification(NOTIFICATION_ID_AUTH); removePbapNotification(mNotificationId); break; case AUTH_KEY_INPUT: String key = (String) message.obj; Loading Loading @@ -320,7 +314,7 @@ class PbapStateMachine extends StateMachine { 0)) .setLocalOnly(true) .build(); nm.notify(NOTIFICATION_ID_AUTH, notification); nm.notify(mNotificationId, notification); } private void removePbapNotification(int id) { Loading android/app/tests/unit/src/com/android/bluetooth/pbap/PbapTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import java.lang.reflect.Method; @MediumTest @RunWith(AndroidJUnit4.class) public class PbapTest { private static final int TEST_NOTIFICATION_ID = 1000000; private static AdapterService sAdapterService; private BluetoothAdapter mAdapter; Loading Loading @@ -84,7 +85,7 @@ public class PbapTest { mBluetoothPbapService = mock(BluetoothPbapService.class); doNothing().when(mBluetoothPbapService).checkOrGetPhonebookPermission(any()); mPbapStateMachine = PbapStateMachine.make(mBluetoothPbapService, mHandlerThread.getLooper(), mTestDevice, mSocket, mBluetoothPbapService, mHandler); mTestDevice, mSocket, mBluetoothPbapService, mHandler, TEST_NOTIFICATION_ID); } @After Loading Loading
android/app/src/com/android/bluetooth/pbap/BluetoothPbapService.java +124 −79 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ import com.android.bluetooth.sdp.SdpManager; import com.android.bluetooth.util.DevicePolicyUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class BluetoothPbapService extends ProfileService implements IObexConnectionHandler { Loading Loading @@ -153,14 +154,19 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F; /* PBAP will use Bluetooth notification ID from 1000000 (included) to 2000000 (excluded). The notification ID should be unique in Bluetooth package. */ private static final int PBAP_NOTIFICATION_ID_START = 1000000; private static final int PBAP_NOTIFICATION_ID_END = 2000000; private int mSdpHandle = -1; protected Context mContext; private PbapHandler mSessionStatusHandler; private HandlerThread mHandlerThread; private PbapStateMachine mPbapStateMachine; private final HashMap<BluetoothDevice, PbapStateMachine> mPbapStateMachineMap = new HashMap<>(); private volatile int mNextNotificationId = PBAP_NOTIFICATION_ID_START; // 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"; Loading Loading @@ -190,77 +196,80 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect private BluetoothPbapContentObserver mContactChangeObserver; // process the intent from receiver private void parseIntent(final Intent intent) { String action = intent.getAction(); if (DEBUG) { Log.d(TAG, "action: " + action); } if (action == null) { return; // Nothing to do return; } int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (DEBUG) { Log.d(TAG, "state: " + state); } if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (DEBUG) { Log.d(TAG, "ACL disconnected for " + device); } if (mPbapStateMachine != null && mPbapStateMachine.getRemoteDevice().equals(device)) { mPbapStateMachine.sendMessage(PbapStateMachine.DISCONNECT); } return; } if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); if (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) { // this reply is not for us return; } BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm == null) { Log.w(TAG, "device not connected! device=" + device); return; } mSessionStatusHandler.removeMessages(USER_TIMEOUT, sm); int access = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, BluetoothDevice.CONNECTION_ACCESS_NO); boolean savePreference = intent.getBooleanExtra( BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false); mSessionStatusHandler.removeMessages(USER_TIMEOUT); if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, BluetoothDevice.CONNECTION_ACCESS_NO) == BluetoothDevice.CONNECTION_ACCESS_YES) { if (device != null && intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { if (access == BluetoothDevice.CONNECTION_ACCESS_YES) { if (savePreference) { device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); if (VERBOSE) { Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)"); } } mPbapStateMachine.sendMessage(PbapStateMachine.AUTHORIZED); sm.sendMessage(PbapStateMachine.AUTHORIZED); } else { if (device != null && intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { if (savePreference) { device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); if (VERBOSE) { Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)"); } } mPbapStateMachine.sendMessage(PbapStateMachine.REJECTED); sm.sendMessage(PbapStateMachine.REJECTED); } return; } } if (action.equals(AUTH_RESPONSE_ACTION)) { String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY); String sessionKey = intent.getStringExtra(EXTRA_SESSION_KEY); BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE); Message msg = mPbapStateMachine.obtainMessage(PbapStateMachine.AUTH_KEY_INPUT); msg.obj = sessionkey; mPbapStateMachine.sendMessage(msg); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm == null) { return; } Message msg = sm.obtainMessage(PbapStateMachine.AUTH_KEY_INPUT, sessionKey); sm.sendMessage(msg); } } else if (action.equals(AUTH_CANCELLED_ACTION)) { BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE); mPbapStateMachine.sendMessage(PbapStateMachine.AUTH_CANCELLED); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm == null) { return; } sm.sendMessage(PbapStateMachine.AUTH_CANCELLED); } } else { Log.w(TAG, "Unrecognized intent!"); } Loading @@ -283,7 +292,6 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect BluetoothPbapUtils.contactsLastUpdated, BluetoothPbapUtils.totalFields, BluetoothPbapUtils.totalSvcFields, BluetoothPbapUtils.totalContacts); // exit initSocket early if (mWakeLock != null) { mWakeLock.release(); mWakeLock = null; Loading @@ -298,8 +306,10 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect private void cleanUpServerSocket() { // Step 1, 2: clean up active server session and connection socket if (mPbapStateMachine != null) { mPbapStateMachine.sendMessage(PbapStateMachine.DISCONNECT); synchronized (mPbapStateMachineMap) { for (PbapStateMachine stateMachine : mPbapStateMachineMap.values()) { stateMachine.sendMessage(PbapStateMachine.DISCONNECT); } } // Step 3: clean up SDP record cleanUpSdpRecord(); Loading Loading @@ -407,6 +417,13 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect BluetoothPbapUtils.rolloverCounters(); break; case MSG_STATE_MACHINE_DONE: PbapStateMachine sm = (PbapStateMachine) msg.obj; BluetoothDevice remoteDevice = sm.getRemoteDevice(); sm.quitNow(); synchronized (mPbapStateMachineMap) { mPbapStateMachineMap.remove(remoteDevice); } break; default: break; } Loading @@ -415,36 +432,57 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect int getConnectionState(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if (mPbapStateMachine == null) { if (mPbapStateMachineMap == null) { return BluetoothProfile.STATE_DISCONNECTED; } return mPbapStateMachine.getConnectionState(); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm == null) { return BluetoothProfile.STATE_DISCONNECTED; } return sm.getConnectionState(); } } List<BluetoothDevice> getConnectedDevices() { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List<BluetoothDevice> devices = new ArrayList<>(); if (mPbapStateMachine != null) { devices.add(mPbapStateMachine.getRemoteDevice()); if (mPbapStateMachineMap == null) { return new ArrayList<>(); } synchronized (mPbapStateMachineMap) { return new ArrayList<>(mPbapStateMachineMap.keySet()); } return devices; } List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); if (mPbapStateMachine != null) { List<BluetoothDevice> devices = new ArrayList<>(); if (mPbapStateMachineMap == null || states == null) { return devices; } synchronized (mPbapStateMachineMap) { for (int state : states) { if (state == mPbapStateMachine.getConnectionState()) { devices.add(mPbapStateMachine.getRemoteDevice()); break; for (BluetoothDevice device : mPbapStateMachineMap.keySet()) { if (state == mPbapStateMachineMap.get(device).getConnectionState()) { devices.add(device); } } } } return devices; } void disconnect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); synchronized (mPbapStateMachineMap) { PbapStateMachine sm = mPbapStateMachineMap.get(device); if (sm != null) { sm.sendMessage(PbapStateMachine.DISCONNECT); } } } static String getLocalPhoneNum() { return sLocalPhoneNum; } Loading @@ -460,14 +498,15 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect @Override protected boolean start() { if (VERBOSE) { Log.v(TAG, "start()"); } mContext = this; mHandlerThread = new HandlerThread("PbapHandlerThread"); mHandlerThread.start(); mSessionStatusHandler = new PbapHandler(mHandlerThread.getLooper()); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); filter.addAction(AUTH_RESPONSE_ACTION); filter.addAction(AUTH_CANCELLED_ACTION); BluetoothPbapConfig.init(this); Loading Loading @@ -499,7 +538,11 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect @Override protected boolean stop() { if (VERBOSE) { Log.v(TAG, "stop()"); } mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget(); mHandlerThread.quitSafely(); if (mContactChangeObserver == null) { Log.i(TAG, "Avoid unregister when receiver it is not registered"); return true; Loading @@ -511,21 +554,9 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect } catch (Exception e) { Log.w(TAG, "Unable to unregister pbap receiver", e); } mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget(); mHandlerThread.quitSafely(); return true; } void disconnect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); synchronized (this) { if (mPbapStateMachine != null) { mPbapStateMachine.sendMessage(PbapStateMachine.DISCONNECT); } } } // Has to be a static class or a memory leak can occur. private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder { private BluetoothPbapService mService; Loading @@ -541,7 +572,9 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect } PbapBinder(BluetoothPbapService service) { if (VERBOSE) { Log.v(TAG, "PbapBinder()"); } mService = service; } Loading Loading @@ -606,8 +639,17 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect + " socket=" + socket); return false; } mPbapStateMachine = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice, socket, this, mSessionStatusHandler); PbapStateMachine sm = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice, socket, this, mSessionStatusHandler, mNextNotificationId); mNextNotificationId++; if (mNextNotificationId == PBAP_NOTIFICATION_ID_END) { mNextNotificationId = PBAP_NOTIFICATION_ID_START; } synchronized (mPbapStateMachineMap) { mPbapStateMachineMap.put(remoteDevice, sm); } sm.sendMessage(PbapStateMachine.REQUEST_PERMISSION); return true; } Loading @@ -629,7 +671,6 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect } else if (permission == BluetoothDevice.ACCESS_REJECTED) { stateMachine.sendMessage(PbapStateMachine.REJECTED); } 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(BluetoothPbapService.ACCESS_AUTHORITY_PACKAGE, BluetoothPbapService.ACCESS_AUTHORITY_CLASS); Loading @@ -643,8 +684,8 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect } /* In case car kit time out and try to use HFP for phonebook * access, while UI still there waiting for user to confirm */ Message msg = mSessionStatusHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT); msg.obj = stateMachine; Message msg = mSessionStatusHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT, stateMachine); mSessionStatusHandler.sendMessageDelayed(msg, USER_CONFIRM_TIMEOUT_VALUE); /* We will continue the process when we receive * BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app. */ Loading @@ -670,6 +711,10 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect mSessionStatusHandler.removeCallbacksAndMessages(null); } synchronized (mPbapStateMachineMap) { mPbapStateMachineMap.clear(); } mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER)); } Loading
android/app/src/com/android/bluetooth/pbap/PbapStateMachine.java +15 −21 Original line number Diff line number Diff line Loading @@ -60,10 +60,7 @@ class PbapStateMachine extends StateMachine { private static final String TAG = "PbapStateMachine"; private static final boolean DEBUG = true; private static final boolean VERBOSE = true; private static final String PBAP_OBEX_NOTIFICATION_CHANNEL = "pbap_obex_notification_channel"; private static final int NOTIFICATION_ID_AUTH = -1000002; // TODO: set a notification channel for each sm static final int AUTHORIZED = 1; static final int REJECTED = 2; Loading @@ -86,16 +83,18 @@ class PbapStateMachine extends StateMachine { private BluetoothPbapObexServer mPbapServer; private BluetoothPbapAuthenticator mObexAuth; private ServerSession mServerSession; private int mNotificationId; private PbapStateMachine(@NonNull BluetoothPbapService service, Looper looper, @NonNull BluetoothDevice device, @NonNull BluetoothSocket connSocket, IObexConnectionHandler obexConnectionHandler, Handler pbapHandler) { IObexConnectionHandler obexConnectionHandler, Handler pbapHandler, int notificationId) { super(TAG, looper); mService = service; mIObexConnectionHandler = obexConnectionHandler; mRemoteDevice = device; mServiceHandler = pbapHandler; mConnSocket = connSocket; mNotificationId = notificationId; addState(mFinished); addState(mWaitingForAuth); Loading @@ -105,9 +104,9 @@ class PbapStateMachine extends StateMachine { static PbapStateMachine make(BluetoothPbapService service, Looper looper, BluetoothDevice device, BluetoothSocket connSocket, IObexConnectionHandler obexConnectionHandler, Handler pbapHandler) { IObexConnectionHandler obexConnectionHandler, Handler pbapHandler, int notificationId) { PbapStateMachine stateMachine = new PbapStateMachine(service, looper, device, connSocket, obexConnectionHandler, pbapHandler); obexConnectionHandler, pbapHandler, notificationId); stateMachine.start(); return stateMachine; } Loading @@ -134,14 +133,11 @@ class PbapStateMachine extends StateMachine { return BluetoothProfile.STATE_CONNECTING; } @Override public void enter() { mService.checkOrGetPhonebookPermission(PbapStateMachine.this); } @Override public boolean processMessage(Message message) { switch (message.what) { case REQUEST_PERMISSION: mService.checkOrGetPhonebookPermission(PbapStateMachine.this); case AUTHORIZED: transitionTo(mConnected); break; Loading @@ -150,11 +146,10 @@ class PbapStateMachine extends StateMachine { transitionTo(mFinished); break; case DISCONNECT: mServiceHandler.removeMessages(BluetoothPbapService.USER_TIMEOUT); Message msg = mServiceHandler.obtainMessage( BluetoothPbapService.USER_TIMEOUT); msg.obj = PbapStateMachine.this; msg.sendToTarget(); mServiceHandler.removeMessages(BluetoothPbapService.USER_TIMEOUT, PbapStateMachine.this); mServiceHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT, PbapStateMachine.this).sendToTarget(); transitionTo(mFinished); break; } Loading Loading @@ -198,10 +193,9 @@ class PbapStateMachine extends StateMachine { Log.e(TAG, "Close Connection Socket error: " + e.toString()); } mServiceHandler.obtainMessage(BluetoothPbapService.MSG_STATE_MACHINE_DONE) .sendToTarget(); mServiceHandler.obtainMessage(BluetoothPbapService.MSG_STATE_MACHINE_DONE, PbapStateMachine.this).sendToTarget(); } } class Connected extends PbapStateBase { Loading Loading @@ -236,7 +230,7 @@ class PbapStateMachine extends StateMachine { Intent i = new Intent(BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION); mService.sendBroadcast(i); notifyAuthCancelled(); removePbapNotification(NOTIFICATION_ID_AUTH); removePbapNotification(mNotificationId); break; case AUTH_KEY_INPUT: String key = (String) message.obj; Loading Loading @@ -320,7 +314,7 @@ class PbapStateMachine extends StateMachine { 0)) .setLocalOnly(true) .build(); nm.notify(NOTIFICATION_ID_AUTH, notification); nm.notify(mNotificationId, notification); } private void removePbapNotification(int id) { Loading
android/app/tests/unit/src/com/android/bluetooth/pbap/PbapTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import java.lang.reflect.Method; @MediumTest @RunWith(AndroidJUnit4.class) public class PbapTest { private static final int TEST_NOTIFICATION_ID = 1000000; private static AdapterService sAdapterService; private BluetoothAdapter mAdapter; Loading Loading @@ -84,7 +85,7 @@ public class PbapTest { mBluetoothPbapService = mock(BluetoothPbapService.class); doNothing().when(mBluetoothPbapService).checkOrGetPhonebookPermission(any()); mPbapStateMachine = PbapStateMachine.make(mBluetoothPbapService, mHandlerThread.getLooper(), mTestDevice, mSocket, mBluetoothPbapService, mHandler); mTestDevice, mSocket, mBluetoothPbapService, mHandler, TEST_NOTIFICATION_ID); } @After Loading